mirror of
https://github.com/SagerNet/sing-box.git
synced 2026-04-12 01:57:18 +10:00
Compare commits
83 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
444f454810 | ||
|
|
d0e1fd6c7e | ||
|
|
17b4d1e010 | ||
|
|
06791470c9 | ||
|
|
ef14c8ca0e | ||
|
|
36dc883c7c | ||
|
|
6557bd7029 | ||
|
|
41b30c91d9 | ||
|
|
0f767d5ce1 | ||
|
|
328a6de797 | ||
|
|
886be6414d | ||
|
|
9362d3cab3 | ||
|
|
ced2e39dbf | ||
|
|
2159d8877b | ||
|
|
cb7dba3eff | ||
|
|
d9d7f7880d | ||
|
|
a031aaf2c0 | ||
|
|
4bca951773 | ||
|
|
140735dbde | ||
|
|
714a68bba1 | ||
|
|
573c6179ab | ||
|
|
510bf05e36 | ||
|
|
ae852e0be4 | ||
|
|
1955002ed8 | ||
|
|
44559fb7b9 | ||
|
|
0977c5cf73 | ||
|
|
07697bf931 | ||
|
|
5d1d1a1456 | ||
|
|
146383499e | ||
|
|
e81a76fdf9 | ||
|
|
de13137418 | ||
|
|
e42b818c2a | ||
|
|
fcde0c94e0 | ||
|
|
1af83e997d | ||
|
|
59ee7be72a | ||
|
|
c331ee3d5c | ||
|
|
36babe4bef | ||
|
|
c5f2cea802 | ||
|
|
8a200bf913 | ||
|
|
f16468e74f | ||
|
|
79c0b9f51d | ||
|
|
f98a3a4f65 | ||
|
|
b14cecaeb2 | ||
|
|
2594745ef8 | ||
|
|
cc3041322e | ||
|
|
f352f84483 | ||
|
|
cbf48e9b8c | ||
|
|
0ef7e8eca2 | ||
|
|
1a18e43a88 | ||
|
|
6849288d6d | ||
|
|
2edfed7d91 | ||
|
|
30c069f5b7 | ||
|
|
649163cb7b | ||
|
|
980e96250b | ||
|
|
963bc4b647 | ||
|
|
031f25c1c1 | ||
|
|
b40f642fa4 | ||
|
|
22782ca6fc | ||
|
|
1468d83895 | ||
|
|
97f0dc8a60 | ||
|
|
ee02532ab5 | ||
|
|
f1dd0dba78 | ||
|
|
f4ed684146 | ||
|
|
83f02d0bfb | ||
|
|
52fa5f20a3 | ||
|
|
f462ce5615 | ||
|
|
cef3e538ba | ||
|
|
acda4ce985 | ||
|
|
354ece2bdf | ||
|
|
de10bb00a9 | ||
|
|
fdc181106d | ||
|
|
8752b631bd | ||
|
|
378e39f70c | ||
|
|
043a2e7a07 | ||
|
|
7e190e92ca | ||
|
|
5eb318ba06 | ||
|
|
4a209f1afb | ||
|
|
c0ac3c748c | ||
|
|
a65d3e040a | ||
|
|
2358efe44a | ||
|
|
09d3b8f2c2 | ||
|
|
531de77124 | ||
|
|
44981fd803 |
@@ -1,25 +1,27 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
VERSION="1.23.6"
|
||||
VERSION="1.25.3"
|
||||
|
||||
mkdir -p $HOME/go
|
||||
cd $HOME/go
|
||||
wget "https://dl.google.com/go/go${VERSION}.linux-amd64.tar.gz"
|
||||
tar -xzf "go${VERSION}.linux-amd64.tar.gz"
|
||||
mv go go_legacy
|
||||
cd go_legacy
|
||||
mv go go_win7
|
||||
cd go_win7
|
||||
|
||||
# modify from https://github.com/restic/restic/issues/4636#issuecomment-1896455557
|
||||
# this patch file only works on golang1.23.x
|
||||
# that means after golang1.24 release it must be changed
|
||||
# see: https://github.com/MetaCubeX/go/commits/release-branch.go1.23/
|
||||
# this patch file only works on golang1.25.x
|
||||
# that means after golang1.26 release it must be changed
|
||||
# see: https://github.com/MetaCubeX/go/commits/release-branch.go1.25/
|
||||
# revert:
|
||||
# 693def151adff1af707d82d28f55dba81ceb08e1: "crypto/rand,runtime: switch RtlGenRandom for ProcessPrng"
|
||||
# 7c1157f9544922e96945196b47b95664b1e39108: "net: remove sysSocket fallback for Windows 7"
|
||||
# 48042aa09c2f878c4faa576948b07fe625c4707a: "syscall: remove Windows 7 console handle workaround"
|
||||
# a17d959debdb04cd550016a3501dd09d50cd62e7: "runtime: always use LoadLibraryEx to load system libraries"
|
||||
|
||||
curl https://github.com/MetaCubeX/go/commit/9ac42137ef6730e8b7daca016ece831297a1d75b.diff | patch --verbose -p 1
|
||||
curl https://github.com/MetaCubeX/go/commit/21290de8a4c91408de7c2b5b68757b1e90af49dd.diff | patch --verbose -p 1
|
||||
curl https://github.com/MetaCubeX/go/commit/6a31d3fa8e47ddabc10bd97bff10d9a85f4cfb76.diff | patch --verbose -p 1
|
||||
curl https://github.com/MetaCubeX/go/commit/69e2eed6dd0f6d815ebf15797761c13f31213dd6.diff | patch --verbose -p 1
|
||||
alias curl='curl -H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}"'
|
||||
|
||||
curl https://github.com/MetaCubeX/go/commit/8cb5472d94c34b88733a81091bd328e70ee565a4.diff | patch --verbose -p 1
|
||||
curl https://github.com/MetaCubeX/go/commit/6788c4c6f9fafb56729bad6b660f7ee2272d699f.diff | patch --verbose -p 1
|
||||
curl https://github.com/MetaCubeX/go/commit/a5b2168bb836ed9d6601c626f95e56c07923f906.diff | patch --verbose -p 1
|
||||
curl https://github.com/MetaCubeX/go/commit/f56f1e23507e646c85243a71bde7b9629b2f970c.diff | patch --verbose -p 1
|
||||
77
.github/workflows/build.yml
vendored
77
.github/workflows/build.yml
vendored
@@ -40,13 +40,13 @@ jobs:
|
||||
version: ${{ steps.outputs.outputs.version }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ^1.24.6
|
||||
go-version: ^1.25.3
|
||||
- name: Check input version
|
||||
if: github.event_name == 'workflow_dispatch'
|
||||
run: |-
|
||||
@@ -88,13 +88,14 @@ jobs:
|
||||
- { os: linux, arch: loong64, debian: loongarch64, rpm: loongarch64, openwrt: "loongarch64_generic" }
|
||||
|
||||
- { os: windows, arch: amd64 }
|
||||
- { os: windows, arch: amd64, legacy_go: true }
|
||||
- { os: windows, arch: amd64, legacy_win7: true, legacy_name: "windows-7" }
|
||||
- { os: windows, arch: "386" }
|
||||
- { os: windows, arch: "386", legacy_go: true }
|
||||
- { os: windows, arch: "386", legacy_win7: true, legacy_name: "windows-7" }
|
||||
- { os: windows, arch: arm64 }
|
||||
|
||||
- { os: darwin, arch: amd64 }
|
||||
- { os: darwin, arch: arm64 }
|
||||
- { os: darwin, arch: amd64, legacy_go124: true, legacy_name: "macos-11" }
|
||||
|
||||
- { os: android, arch: arm64, ndk: "aarch64-linux-android21" }
|
||||
- { os: android, arch: arm, ndk: "armv7a-linux-androideabi21" }
|
||||
@@ -102,31 +103,36 @@ jobs:
|
||||
- { os: android, arch: "386", ndk: "i686-linux-android21" }
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Setup Go
|
||||
if: ${{ ! matrix.legacy_go }}
|
||||
if: ${{ ! (matrix.legacy_go123 || matrix.legacy_go124) }}
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ^1.24.6
|
||||
- name: Cache Legacy Go
|
||||
if: matrix.require_legacy_go
|
||||
id: cache-legacy-go
|
||||
go-version: ^1.25.3
|
||||
- name: Setup Go 1.24
|
||||
if: matrix.legacy_go124
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ~1.24.6
|
||||
- name: Cache Go for Windows 7
|
||||
if: matrix.legacy_win7
|
||||
id: cache-go-for-windows7
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/go/go_legacy
|
||||
key: go_legacy_1236
|
||||
- name: Setup Legacy Go
|
||||
if: matrix.legacy_go && steps.cache-legacy-go.outputs.cache-hit != 'true'
|
||||
~/go/go_win7
|
||||
key: go_win7_1253
|
||||
- name: Setup Go for Windows 7
|
||||
if: matrix.legacy_win7 && steps.cache-go-for-windows7.outputs.cache-hit != 'true'
|
||||
run: |-
|
||||
.github/setup_legacy_go.sh
|
||||
- name: Setup Legacy Go 2
|
||||
if: matrix.legacy_go
|
||||
.github/setup_go_for_windows7.sh
|
||||
- name: Setup Go for Windows 7
|
||||
if: matrix.legacy_win7
|
||||
run: |-
|
||||
echo "PATH=$HOME/go/go_legacy/bin:$PATH" >> $GITHUB_ENV
|
||||
echo "GOROOT=$HOME/go/go_legacy" >> $GITHUB_ENV
|
||||
echo "PATH=$HOME/go/go_win7/bin:$PATH" >> $GITHUB_ENV
|
||||
echo "GOROOT=$HOME/go/go_win7" >> $GITHUB_ENV
|
||||
- name: Setup Android NDK
|
||||
if: matrix.os == 'android'
|
||||
uses: nttld/setup-ndk@v1
|
||||
@@ -184,8 +190,8 @@ jobs:
|
||||
DIR_NAME="${DIR_NAME}-${{ matrix.go386 }}"
|
||||
elif [[ -n "${{ matrix.gomips }}" && "${{ matrix.gomips }}" != 'hardfloat' ]]; then
|
||||
DIR_NAME="${DIR_NAME}-${{ matrix.gomips }}"
|
||||
elif [[ "${{ matrix.legacy_go }}" == 'true' ]]; then
|
||||
DIR_NAME="${DIR_NAME}-legacy"
|
||||
elif [[ -n "${{ matrix.legacy_name }}" ]]; then
|
||||
DIR_NAME="${DIR_NAME}-legacy-${{ matrix.legacy_name }}"
|
||||
fi
|
||||
echo "DIR_NAME=${DIR_NAME}" >> "${GITHUB_ENV}"
|
||||
PKG_VERSION="${{ needs.calculate_version.outputs.version }}"
|
||||
@@ -277,7 +283,7 @@ jobs:
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: binary-${{ matrix.os }}_${{ matrix.arch }}${{ matrix.goarm && format('v{0}', matrix.goarm) }}${{ matrix.go386 && format('_{0}', matrix.go386) }}${{ matrix.gomips && format('_{0}', matrix.gomips) }}${{ matrix.legacy_go && '-legacy' || '' }}
|
||||
name: binary-${{ matrix.os }}_${{ matrix.arch }}${{ matrix.goarm && format('v{0}', matrix.goarm) }}${{ matrix.go386 && format('_{0}', matrix.go386) }}${{ matrix.gomips && format('_{0}', matrix.gomips) }}${{ matrix.legacy_name && format('-legacy-{0}', matrix.legacy_name) }}
|
||||
path: "dist"
|
||||
build_android:
|
||||
name: Build Android
|
||||
@@ -287,14 +293,14 @@ jobs:
|
||||
- calculate_version
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
||||
with:
|
||||
fetch-depth: 0
|
||||
submodules: 'recursive'
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ^1.24.6
|
||||
go-version: ^1.25.3
|
||||
- name: Setup Android NDK
|
||||
id: setup-ndk
|
||||
uses: nttld/setup-ndk@v1
|
||||
@@ -367,14 +373,14 @@ jobs:
|
||||
- calculate_version
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
||||
with:
|
||||
fetch-depth: 0
|
||||
submodules: 'recursive'
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ^1.24.6
|
||||
go-version: ^1.25.3
|
||||
- name: Setup Android NDK
|
||||
id: setup-ndk
|
||||
uses: nttld/setup-ndk@v1
|
||||
@@ -426,7 +432,8 @@ jobs:
|
||||
SERVICE_ACCOUNT_CREDENTIALS: ${{ secrets.SERVICE_ACCOUNT_CREDENTIALS }}
|
||||
build_apple:
|
||||
name: Build Apple clients
|
||||
runs-on: macos-15
|
||||
runs-on: macos-26
|
||||
if: false
|
||||
needs:
|
||||
- calculate_version
|
||||
strategy:
|
||||
@@ -464,7 +471,7 @@ jobs:
|
||||
steps:
|
||||
- name: Checkout
|
||||
if: matrix.if
|
||||
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
||||
with:
|
||||
fetch-depth: 0
|
||||
submodules: 'recursive'
|
||||
@@ -472,15 +479,7 @@ jobs:
|
||||
if: matrix.if
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ^1.24.6
|
||||
- name: Setup Xcode stable
|
||||
if: matrix.if && github.ref == 'refs/heads/main-next'
|
||||
run: |-
|
||||
sudo xcode-select -s /Applications/Xcode_16.4.app
|
||||
- name: Setup Xcode beta
|
||||
if: matrix.if && github.ref == 'refs/heads/dev-next'
|
||||
run: |-
|
||||
sudo xcode-select -s /Applications/Xcode_16.4.app
|
||||
go-version: ^1.25.3
|
||||
- name: Set tag
|
||||
if: matrix.if
|
||||
run: |-
|
||||
@@ -624,7 +623,7 @@ jobs:
|
||||
- build_apple
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Cache ghr
|
||||
@@ -647,7 +646,7 @@ jobs:
|
||||
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
|
||||
uses: actions/download-artifact@v5
|
||||
with:
|
||||
path: dist
|
||||
merge-multiple: true
|
||||
|
||||
4
.github/workflows/docker.yml
vendored
4
.github/workflows/docker.yml
vendored
@@ -39,7 +39,7 @@ jobs:
|
||||
echo "ref=$ref"
|
||||
echo "ref=$ref" >> $GITHUB_OUTPUT
|
||||
- name: Checkout
|
||||
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
||||
with:
|
||||
ref: ${{ steps.ref.outputs.ref }}
|
||||
fetch-depth: 0
|
||||
@@ -107,7 +107,7 @@ jobs:
|
||||
echo "latest=$latest"
|
||||
echo "latest=$latest" >> $GITHUB_OUTPUT
|
||||
- name: Download digests
|
||||
uses: actions/download-artifact@v4
|
||||
uses: actions/download-artifact@v5
|
||||
with:
|
||||
path: /tmp/digests
|
||||
pattern: digests-*
|
||||
|
||||
8
.github/workflows/lint.yml
vendored
8
.github/workflows/lint.yml
vendored
@@ -22,17 +22,17 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ^1.24.6
|
||||
go-version: ~1.24.6
|
||||
- name: golangci-lint
|
||||
uses: golangci/golangci-lint-action@v6
|
||||
uses: golangci/golangci-lint-action@v8
|
||||
with:
|
||||
version: latest
|
||||
version: v2.4.0
|
||||
args: --timeout=30m
|
||||
install-mode: binary
|
||||
verify: false
|
||||
|
||||
21
.github/workflows/linux.yml
vendored
21
.github/workflows/linux.yml
vendored
@@ -7,6 +7,11 @@ on:
|
||||
description: "Version name"
|
||||
required: true
|
||||
type: string
|
||||
forceBeta:
|
||||
description: "Force beta"
|
||||
required: false
|
||||
type: boolean
|
||||
default: false
|
||||
release:
|
||||
types:
|
||||
- published
|
||||
@@ -19,13 +24,13 @@ jobs:
|
||||
version: ${{ steps.outputs.outputs.version }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ^1.24.6
|
||||
go-version: ^1.25.3
|
||||
- name: Check input version
|
||||
if: github.event_name == 'workflow_dispatch'
|
||||
run: |-
|
||||
@@ -60,13 +65,13 @@ jobs:
|
||||
- { os: linux, arch: loong64, debian: loongarch64, rpm: loongarch64 }
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ^1.24.6
|
||||
go-version: ^1.25.3
|
||||
- name: Setup Android NDK
|
||||
if: matrix.os == 'android'
|
||||
uses: nttld/setup-ndk@v1
|
||||
@@ -99,11 +104,11 @@ jobs:
|
||||
run: |-
|
||||
TZ=UTC touch -t '197001010000' dist/sing-box
|
||||
- name: Set name
|
||||
if: ${{ ! contains(needs.calculate_version.outputs.version, '-') }}
|
||||
if: (! contains(needs.calculate_version.outputs.version, '-')) && !inputs.forceBeta
|
||||
run: |-
|
||||
echo "NAME=sing-box" >> "$GITHUB_ENV"
|
||||
- name: Set beta name
|
||||
if: contains(needs.calculate_version.outputs.version, '-')
|
||||
if: contains(needs.calculate_version.outputs.version, '-') || inputs.forceBeta
|
||||
run: |-
|
||||
echo "NAME=sing-box-beta" >> "$GITHUB_ENV"
|
||||
- name: Set version
|
||||
@@ -166,7 +171,7 @@ jobs:
|
||||
- build
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Set tag
|
||||
@@ -175,7 +180,7 @@ jobs:
|
||||
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
|
||||
uses: actions/download-artifact@v5
|
||||
with:
|
||||
path: dist
|
||||
merge-multiple: true
|
||||
|
||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -15,4 +15,6 @@
|
||||
.DS_Store
|
||||
/config.d/
|
||||
/venv/
|
||||
|
||||
CLAUDE.md
|
||||
AGENTS.md
|
||||
/.claude/
|
||||
|
||||
@@ -1,27 +1,6 @@
|
||||
linters:
|
||||
disable-all: true
|
||||
enable:
|
||||
- gofumpt
|
||||
- govet
|
||||
- gci
|
||||
- staticcheck
|
||||
- paralleltest
|
||||
- ineffassign
|
||||
|
||||
linters-settings:
|
||||
gci:
|
||||
custom-order: true
|
||||
sections:
|
||||
- standard
|
||||
- prefix(github.com/sagernet/)
|
||||
- default
|
||||
staticcheck:
|
||||
checks:
|
||||
- all
|
||||
- -SA1003
|
||||
|
||||
version: "2"
|
||||
run:
|
||||
go: "1.23"
|
||||
go: "1.24"
|
||||
build-tags:
|
||||
- with_gvisor
|
||||
- with_quic
|
||||
@@ -30,7 +9,51 @@ run:
|
||||
- with_utls
|
||||
- with_acme
|
||||
- with_clash_api
|
||||
|
||||
issues:
|
||||
exclude-dirs:
|
||||
- transport/simple-obfs
|
||||
linters:
|
||||
default: none
|
||||
enable:
|
||||
- govet
|
||||
- ineffassign
|
||||
- paralleltest
|
||||
- staticcheck
|
||||
settings:
|
||||
staticcheck:
|
||||
checks:
|
||||
- all
|
||||
- -S1000
|
||||
- -S1008
|
||||
- -S1017
|
||||
- -ST1003
|
||||
- -QF1001
|
||||
- -QF1003
|
||||
- -QF1008
|
||||
exclusions:
|
||||
generated: lax
|
||||
presets:
|
||||
- comments
|
||||
- common-false-positives
|
||||
- legacy
|
||||
- std-error-handling
|
||||
paths:
|
||||
- transport/simple-obfs
|
||||
- third_party$
|
||||
- builtin$
|
||||
- examples$
|
||||
formatters:
|
||||
enable:
|
||||
- gci
|
||||
- gofumpt
|
||||
settings:
|
||||
gci:
|
||||
sections:
|
||||
- standard
|
||||
- prefix(github.com/sagernet/)
|
||||
- default
|
||||
custom-order: true
|
||||
exclusions:
|
||||
generated: lax
|
||||
paths:
|
||||
- transport/simple-obfs
|
||||
- third_party$
|
||||
- builtin$
|
||||
- examples$
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM --platform=$BUILDPLATFORM golang:1.24-alpine AS builder
|
||||
FROM --platform=$BUILDPLATFORM golang:1.25-alpine AS builder
|
||||
LABEL maintainer="nekohasekai <contact-git@sekai.icu>"
|
||||
COPY . /go/src/github.com/sagernet/sing-box
|
||||
WORKDIR /go/src/github.com/sagernet/sing-box
|
||||
|
||||
12
Makefile
12
Makefile
@@ -17,6 +17,10 @@ build:
|
||||
export GOTOOLCHAIN=local && \
|
||||
go build $(MAIN_PARAMS) $(MAIN)
|
||||
|
||||
race:
|
||||
export GOTOOLCHAIN=local && \
|
||||
go build -race $(MAIN_PARAMS) $(MAIN)
|
||||
|
||||
ci_build:
|
||||
export GOTOOLCHAIN=local && \
|
||||
go build $(PARAMS) $(MAIN) && \
|
||||
@@ -34,7 +38,7 @@ fmt:
|
||||
@gci write --custom-order -s standard -s "prefix(github.com/sagernet/)" -s "default" .
|
||||
|
||||
fmt_install:
|
||||
go install -v mvdan.cc/gofumpt@latest
|
||||
go install -v mvdan.cc/gofumpt@v0.8.0
|
||||
go install -v github.com/daixiang0/gci@latest
|
||||
|
||||
lint:
|
||||
@@ -45,7 +49,7 @@ lint:
|
||||
GOOS=freebsd golangci-lint run ./...
|
||||
|
||||
lint_install:
|
||||
go install -v github.com/golangci/golangci-lint/cmd/golangci-lint@latest
|
||||
go install -v github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.4.0
|
||||
|
||||
proto:
|
||||
@go run ./cmd/internal/protogen
|
||||
@@ -245,8 +249,8 @@ lib:
|
||||
go run ./cmd/internal/build_libbox -target ios
|
||||
|
||||
lib_install:
|
||||
go install -v github.com/sagernet/gomobile/cmd/gomobile@v0.1.7
|
||||
go install -v github.com/sagernet/gomobile/cmd/gobind@v0.1.7
|
||||
go install -v github.com/sagernet/gomobile/cmd/gomobile@v0.1.8
|
||||
go install -v github.com/sagernet/gomobile/cmd/gobind@v0.1.8
|
||||
|
||||
docs:
|
||||
venv/bin/mkdocs serve
|
||||
|
||||
@@ -57,6 +57,7 @@ type InboundContext struct {
|
||||
Domain string
|
||||
Client string
|
||||
SniffContext any
|
||||
SnifferNames []string
|
||||
SniffError error
|
||||
|
||||
// cache
|
||||
@@ -135,8 +136,7 @@ func ExtendContext(ctx context.Context) (context.Context, *InboundContext) {
|
||||
|
||||
func OverrideContext(ctx context.Context) context.Context {
|
||||
if metadata := ContextFrom(ctx); metadata != nil {
|
||||
var newMetadata InboundContext
|
||||
newMetadata = *metadata
|
||||
newMetadata := *metadata
|
||||
return WithContext(ctx, &newMetadata)
|
||||
}
|
||||
return ctx
|
||||
|
||||
@@ -20,6 +20,7 @@ type NetworkManager interface {
|
||||
DefaultOptions() NetworkOptions
|
||||
RegisterAutoRedirectOutputMark(mark uint32) error
|
||||
AutoRedirectOutputMark() uint32
|
||||
AutoRedirectOutputMarkFunc() control.Func
|
||||
NetworkMonitor() tun.NetworkUpdateMonitor
|
||||
InterfaceMonitor() tun.DefaultInterfaceMonitor
|
||||
PackageManager() tun.PackageManager
|
||||
|
||||
@@ -30,7 +30,7 @@ type Manager struct {
|
||||
outboundByTag map[string]adapter.Outbound
|
||||
dependByTag map[string][]string
|
||||
defaultOutbound adapter.Outbound
|
||||
defaultOutboundFallback adapter.Outbound
|
||||
defaultOutboundFallback func() (adapter.Outbound, error)
|
||||
}
|
||||
|
||||
func NewManager(logger logger.ContextLogger, registry adapter.OutboundRegistry, endpoint adapter.EndpointManager, defaultTag string) *Manager {
|
||||
@@ -44,7 +44,7 @@ func NewManager(logger logger.ContextLogger, registry adapter.OutboundRegistry,
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Manager) Initialize(defaultOutboundFallback adapter.Outbound) {
|
||||
func (m *Manager) Initialize(defaultOutboundFallback func() (adapter.Outbound, error)) {
|
||||
m.defaultOutboundFallback = defaultOutboundFallback
|
||||
}
|
||||
|
||||
@@ -55,18 +55,31 @@ func (m *Manager) Start(stage adapter.StartStage) error {
|
||||
}
|
||||
m.started = true
|
||||
m.stage = stage
|
||||
outbounds := m.outbounds
|
||||
m.access.Unlock()
|
||||
if stage == adapter.StartStateStart {
|
||||
if m.defaultTag != "" && m.defaultOutbound == nil {
|
||||
defaultEndpoint, loaded := m.endpoint.Get(m.defaultTag)
|
||||
if !loaded {
|
||||
m.access.Unlock()
|
||||
return E.New("default outbound not found: ", m.defaultTag)
|
||||
}
|
||||
m.defaultOutbound = defaultEndpoint
|
||||
}
|
||||
if m.defaultOutbound == nil {
|
||||
directOutbound, err := m.defaultOutboundFallback()
|
||||
if err != nil {
|
||||
m.access.Unlock()
|
||||
return E.Cause(err, "create direct outbound for fallback")
|
||||
}
|
||||
m.outbounds = append(m.outbounds, directOutbound)
|
||||
m.outboundByTag[directOutbound.Tag()] = directOutbound
|
||||
m.defaultOutbound = directOutbound
|
||||
}
|
||||
outbounds := m.outbounds
|
||||
m.access.Unlock()
|
||||
return m.startOutbounds(append(outbounds, common.Map(m.endpoint.Endpoints(), func(it adapter.Endpoint) adapter.Outbound { return it })...))
|
||||
} else {
|
||||
outbounds := m.outbounds
|
||||
m.access.Unlock()
|
||||
for _, outbound := range outbounds {
|
||||
err := adapter.LegacyStart(outbound, stage)
|
||||
if err != nil {
|
||||
@@ -187,11 +200,7 @@ func (m *Manager) Outbound(tag string) (adapter.Outbound, bool) {
|
||||
func (m *Manager) Default() adapter.Outbound {
|
||||
m.access.RLock()
|
||||
defer m.access.RUnlock()
|
||||
if m.defaultOutbound != nil {
|
||||
return m.defaultOutbound
|
||||
} else {
|
||||
return m.defaultOutboundFallback
|
||||
}
|
||||
return m.defaultOutbound
|
||||
}
|
||||
|
||||
func (m *Manager) Remove(tag string) error {
|
||||
|
||||
@@ -78,8 +78,8 @@ func (w *myUpstreamHandlerWrapper) NewError(ctx context.Context, err error) {
|
||||
// Deprecated: removed
|
||||
func UpstreamMetadata(metadata InboundContext) M.Metadata {
|
||||
return M.Metadata{
|
||||
Source: metadata.Source,
|
||||
Destination: metadata.Destination,
|
||||
Source: metadata.Source.Unwrap(),
|
||||
Destination: metadata.Destination.Unwrap(),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
8
box.go
8
box.go
@@ -314,15 +314,15 @@ func New(options Options) (*Box, error) {
|
||||
return nil, E.Cause(err, "initialize service[", i, "]")
|
||||
}
|
||||
}
|
||||
outboundManager.Initialize(common.Must1(
|
||||
direct.NewOutbound(
|
||||
outboundManager.Initialize(func() (adapter.Outbound, error) {
|
||||
return direct.NewOutbound(
|
||||
ctx,
|
||||
router,
|
||||
logFactory.NewLogger("outbound/direct"),
|
||||
"direct",
|
||||
option.DirectOutboundOptions{},
|
||||
),
|
||||
))
|
||||
)
|
||||
})
|
||||
dnsTransportManager.Initialize(common.Must1(
|
||||
local.NewTransport(
|
||||
ctx,
|
||||
|
||||
Submodule clients/android updated: 6db8e06e8d...2eeb9d5366
Submodule clients/apple updated: c5734677bd...84d8cf1757
@@ -134,6 +134,7 @@ func publishTestflight(ctx context.Context) error {
|
||||
asc.PlatformTVOS,
|
||||
}
|
||||
}
|
||||
waitingForProcess := false
|
||||
for _, platform := range platforms {
|
||||
log.Info(string(platform), " list builds")
|
||||
for {
|
||||
@@ -145,12 +146,13 @@ func publishTestflight(ctx context.Context) error {
|
||||
return err
|
||||
}
|
||||
build := builds.Data[0]
|
||||
if common.Contains(buildIDs, build.ID) || time.Since(build.Attributes.UploadedDate.Time) > 30*time.Minute {
|
||||
if !waitingForProcess && (common.Contains(buildIDs, build.ID) || time.Since(build.Attributes.UploadedDate.Time) > 30*time.Minute) {
|
||||
log.Info(string(platform), " ", tag, " waiting for process")
|
||||
time.Sleep(15 * time.Second)
|
||||
continue
|
||||
}
|
||||
if *build.Attributes.ProcessingState != "VALID" {
|
||||
waitingForProcess = true
|
||||
log.Info(string(platform), " ", tag, " waiting for process: ", *build.Attributes.ProcessingState)
|
||||
time.Sleep(15 * time.Second)
|
||||
continue
|
||||
|
||||
@@ -107,13 +107,13 @@ func buildAndroid() {
|
||||
}
|
||||
|
||||
if !debugEnabled {
|
||||
sharedFlags[3] = sharedFlags[3] + " -checklinkname=0"
|
||||
args = append(args, sharedFlags...)
|
||||
} else {
|
||||
debugFlags[1] = debugFlags[1] + " -checklinkname=0"
|
||||
args = append(args, debugFlags...)
|
||||
}
|
||||
|
||||
args = append(args, "-ldflags", "-checklinkname=0")
|
||||
|
||||
tags := append(sharedTags, memcTags...)
|
||||
if debugEnabled {
|
||||
tags = append(tags, debugTags...)
|
||||
|
||||
@@ -151,9 +151,7 @@ func testOnce(boxPath string, stackName string, mtu int, multiThread bool, flags
|
||||
var sudoArgs []string
|
||||
if len(flags) > 0 {
|
||||
sudoArgs = append(sudoArgs, "env")
|
||||
for _, flag := range flags {
|
||||
sudoArgs = append(sudoArgs, flag)
|
||||
}
|
||||
sudoArgs = append(sudoArgs, flags...)
|
||||
}
|
||||
sudoArgs = append(sudoArgs, boxPath, "run", "-c", tempConfig.Name())
|
||||
boxProcess := shell.Exec("sudo", sudoArgs...)
|
||||
|
||||
@@ -128,6 +128,10 @@ func (c *ReadWaitConn) Upstream() any {
|
||||
return c.Conn
|
||||
}
|
||||
|
||||
func (c *ReadWaitConn) ReaderReplaceable() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
var tlsRegistry []func(conn net.Conn) (loaded bool, tlsReadRecord func() error, tlsHandlePostHandshakeMessage func() error)
|
||||
|
||||
func init() {
|
||||
|
||||
@@ -6,22 +6,26 @@ import (
|
||||
"net"
|
||||
_ "unsafe"
|
||||
|
||||
"github.com/sagernet/sing/common"
|
||||
|
||||
"github.com/metacubex/utls"
|
||||
)
|
||||
|
||||
func init() {
|
||||
tlsRegistry = append(tlsRegistry, func(conn net.Conn) (loaded bool, tlsReadRecord func() error, tlsHandlePostHandshakeMessage func() error) {
|
||||
tlsConn, loaded := common.Cast[*tls.UConn](conn)
|
||||
if !loaded {
|
||||
return
|
||||
switch tlsConn := conn.(type) {
|
||||
case *tls.UConn:
|
||||
return true, func() error {
|
||||
return utlsReadRecord(tlsConn.Conn)
|
||||
}, func() error {
|
||||
return utlsHandlePostHandshakeMessage(tlsConn.Conn)
|
||||
}
|
||||
case *tls.Conn:
|
||||
return true, func() error {
|
||||
return utlsReadRecord(tlsConn)
|
||||
}, func() error {
|
||||
return utlsHandlePostHandshakeMessage(tlsConn)
|
||||
}
|
||||
}
|
||||
return true, func() error {
|
||||
return utlsReadRecord(tlsConn.Conn)
|
||||
}, func() error {
|
||||
return utlsHandlePostHandshakeMessage(tlsConn.Conn)
|
||||
}
|
||||
return
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/sagernet/fswatch"
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
@@ -21,6 +22,7 @@ import (
|
||||
var _ adapter.CertificateStore = (*Store)(nil)
|
||||
|
||||
type Store struct {
|
||||
access sync.RWMutex
|
||||
systemPool *x509.CertPool
|
||||
currentPool *x509.CertPool
|
||||
certificate string
|
||||
@@ -115,10 +117,14 @@ func (s *Store) Close() error {
|
||||
}
|
||||
|
||||
func (s *Store) Pool() *x509.CertPool {
|
||||
s.access.RLock()
|
||||
defer s.access.RUnlock()
|
||||
return s.currentPool
|
||||
}
|
||||
|
||||
func (s *Store) update() error {
|
||||
s.access.Lock()
|
||||
defer s.access.Unlock()
|
||||
var currentPool *x509.CertPool
|
||||
if s.systemPool == nil {
|
||||
currentPool = x509.NewCertPool()
|
||||
|
||||
@@ -15,7 +15,6 @@ import (
|
||||
"github.com/sagernet/sing-box/experimental/libbox/platform"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
"github.com/sagernet/sing/common"
|
||||
"github.com/sagernet/sing/common/atomic"
|
||||
"github.com/sagernet/sing/common/control"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
@@ -43,7 +42,7 @@ type DefaultDialer struct {
|
||||
networkType []C.InterfaceType
|
||||
fallbackNetworkType []C.InterfaceType
|
||||
networkFallbackDelay time.Duration
|
||||
networkLastFallback atomic.TypedValue[time.Time]
|
||||
networkLastFallback common.TypedValue[time.Time]
|
||||
}
|
||||
|
||||
func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDialer, error) {
|
||||
@@ -89,37 +88,35 @@ func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDial
|
||||
|
||||
if networkManager != nil {
|
||||
defaultOptions := networkManager.DefaultOptions()
|
||||
if !disableDefaultBind {
|
||||
if defaultOptions.BindInterface != "" {
|
||||
bindFunc := control.BindToInterface(networkManager.InterfaceFinder(), defaultOptions.BindInterface, -1)
|
||||
if defaultOptions.BindInterface != "" {
|
||||
bindFunc := control.BindToInterface(networkManager.InterfaceFinder(), defaultOptions.BindInterface, -1)
|
||||
dialer.Control = control.Append(dialer.Control, bindFunc)
|
||||
listener.Control = control.Append(listener.Control, bindFunc)
|
||||
} else if networkManager.AutoDetectInterface() && !disableDefaultBind {
|
||||
if platformInterface != nil {
|
||||
networkStrategy = (*C.NetworkStrategy)(options.NetworkStrategy)
|
||||
networkType = common.Map(options.NetworkType, option.InterfaceType.Build)
|
||||
fallbackNetworkType = common.Map(options.FallbackNetworkType, option.InterfaceType.Build)
|
||||
if networkStrategy == nil && len(networkType) == 0 && len(fallbackNetworkType) == 0 {
|
||||
networkStrategy = defaultOptions.NetworkStrategy
|
||||
networkType = defaultOptions.NetworkType
|
||||
fallbackNetworkType = defaultOptions.FallbackNetworkType
|
||||
}
|
||||
networkFallbackDelay = time.Duration(options.FallbackDelay)
|
||||
if networkFallbackDelay == 0 && defaultOptions.FallbackDelay != 0 {
|
||||
networkFallbackDelay = defaultOptions.FallbackDelay
|
||||
}
|
||||
if networkStrategy == nil {
|
||||
networkStrategy = common.Ptr(C.NetworkStrategyDefault)
|
||||
defaultNetworkStrategy = true
|
||||
}
|
||||
bindFunc := networkManager.ProtectFunc()
|
||||
dialer.Control = control.Append(dialer.Control, bindFunc)
|
||||
listener.Control = control.Append(listener.Control, bindFunc)
|
||||
} else {
|
||||
bindFunc := networkManager.AutoDetectInterfaceFunc()
|
||||
dialer.Control = control.Append(dialer.Control, bindFunc)
|
||||
listener.Control = control.Append(listener.Control, bindFunc)
|
||||
} else if networkManager.AutoDetectInterface() {
|
||||
if platformInterface != nil {
|
||||
networkStrategy = (*C.NetworkStrategy)(options.NetworkStrategy)
|
||||
networkType = common.Map(options.NetworkType, option.InterfaceType.Build)
|
||||
fallbackNetworkType = common.Map(options.FallbackNetworkType, option.InterfaceType.Build)
|
||||
if networkStrategy == nil && len(networkType) == 0 && len(fallbackNetworkType) == 0 {
|
||||
networkStrategy = defaultOptions.NetworkStrategy
|
||||
networkType = defaultOptions.NetworkType
|
||||
fallbackNetworkType = defaultOptions.FallbackNetworkType
|
||||
}
|
||||
networkFallbackDelay = time.Duration(options.FallbackDelay)
|
||||
if networkFallbackDelay == 0 && defaultOptions.FallbackDelay != 0 {
|
||||
networkFallbackDelay = defaultOptions.FallbackDelay
|
||||
}
|
||||
if networkStrategy == nil {
|
||||
networkStrategy = common.Ptr(C.NetworkStrategyDefault)
|
||||
defaultNetworkStrategy = true
|
||||
}
|
||||
bindFunc := networkManager.ProtectFunc()
|
||||
dialer.Control = control.Append(dialer.Control, bindFunc)
|
||||
listener.Control = control.Append(listener.Control, bindFunc)
|
||||
} else {
|
||||
bindFunc := networkManager.AutoDetectInterfaceFunc()
|
||||
dialer.Control = control.Append(dialer.Control, bindFunc)
|
||||
listener.Control = control.Append(listener.Control, bindFunc)
|
||||
}
|
||||
}
|
||||
}
|
||||
if options.RoutingMark == 0 && defaultOptions.RoutingMark != 0 {
|
||||
@@ -127,6 +124,11 @@ func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDial
|
||||
listener.Control = control.Append(listener.Control, setMarkWrapper(networkManager, defaultOptions.RoutingMark, true))
|
||||
}
|
||||
}
|
||||
if networkManager != nil {
|
||||
markFunc := networkManager.AutoRedirectOutputMarkFunc()
|
||||
dialer.Control = control.Append(dialer.Control, markFunc)
|
||||
listener.Control = control.Append(listener.Control, markFunc)
|
||||
}
|
||||
if options.ReuseAddr {
|
||||
listener.Control = control.Append(listener.Control, control.ReuseAddr())
|
||||
}
|
||||
@@ -271,7 +273,7 @@ func (d *DefaultDialer) DialParallelInterface(ctx context.Context, network strin
|
||||
} else {
|
||||
dialer = d.udpDialer4
|
||||
}
|
||||
fastFallback := time.Now().Sub(d.networkLastFallback.Load()) < C.TCPTimeout
|
||||
fastFallback := time.Since(d.networkLastFallback.Load()) < C.TCPTimeout
|
||||
var (
|
||||
conn net.Conn
|
||||
isPrimary bool
|
||||
|
||||
@@ -8,8 +8,10 @@ import (
|
||||
"net"
|
||||
"os"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/sing/common"
|
||||
"github.com/sagernet/sing/common/bufio"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
@@ -22,7 +24,7 @@ type slowOpenConn struct {
|
||||
ctx context.Context
|
||||
network string
|
||||
destination M.Socksaddr
|
||||
conn net.Conn
|
||||
conn atomic.Pointer[net.TCPConn]
|
||||
create chan struct{}
|
||||
done chan struct{}
|
||||
access sync.Mutex
|
||||
@@ -50,22 +52,25 @@ func DialSlowContext(dialer *tcpDialer, ctx context.Context, network string, des
|
||||
}
|
||||
|
||||
func (c *slowOpenConn) Read(b []byte) (n int, err error) {
|
||||
if c.conn == nil {
|
||||
select {
|
||||
case <-c.create:
|
||||
if c.err != nil {
|
||||
return 0, c.err
|
||||
}
|
||||
case <-c.done:
|
||||
return 0, os.ErrClosed
|
||||
}
|
||||
conn := c.conn.Load()
|
||||
if conn != nil {
|
||||
return conn.Read(b)
|
||||
}
|
||||
select {
|
||||
case <-c.create:
|
||||
if c.err != nil {
|
||||
return 0, c.err
|
||||
}
|
||||
return c.conn.Load().Read(b)
|
||||
case <-c.done:
|
||||
return 0, os.ErrClosed
|
||||
}
|
||||
return c.conn.Read(b)
|
||||
}
|
||||
|
||||
func (c *slowOpenConn) Write(b []byte) (n int, err error) {
|
||||
if c.conn != nil {
|
||||
return c.conn.Write(b)
|
||||
tcpConn := c.conn.Load()
|
||||
if tcpConn != nil {
|
||||
return tcpConn.Write(b)
|
||||
}
|
||||
c.access.Lock()
|
||||
defer c.access.Unlock()
|
||||
@@ -74,7 +79,7 @@ func (c *slowOpenConn) Write(b []byte) (n int, err error) {
|
||||
if c.err != nil {
|
||||
return 0, c.err
|
||||
}
|
||||
return c.conn.Write(b)
|
||||
return c.conn.Load().Write(b)
|
||||
case <-c.done:
|
||||
return 0, os.ErrClosed
|
||||
default:
|
||||
@@ -83,7 +88,7 @@ func (c *slowOpenConn) Write(b []byte) (n int, err error) {
|
||||
if err != nil {
|
||||
c.err = err
|
||||
} else {
|
||||
c.conn = conn
|
||||
c.conn.Store(conn.(*net.TCPConn))
|
||||
}
|
||||
n = len(b)
|
||||
close(c.create)
|
||||
@@ -93,70 +98,77 @@ func (c *slowOpenConn) Write(b []byte) (n int, err error) {
|
||||
func (c *slowOpenConn) Close() error {
|
||||
c.closeOnce.Do(func() {
|
||||
close(c.done)
|
||||
if c.conn != nil {
|
||||
c.conn.Close()
|
||||
conn := c.conn.Load()
|
||||
if conn != nil {
|
||||
conn.Close()
|
||||
}
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *slowOpenConn) LocalAddr() net.Addr {
|
||||
if c.conn == nil {
|
||||
conn := c.conn.Load()
|
||||
if conn == nil {
|
||||
return M.Socksaddr{}
|
||||
}
|
||||
return c.conn.LocalAddr()
|
||||
return conn.LocalAddr()
|
||||
}
|
||||
|
||||
func (c *slowOpenConn) RemoteAddr() net.Addr {
|
||||
if c.conn == nil {
|
||||
conn := c.conn.Load()
|
||||
if conn == nil {
|
||||
return M.Socksaddr{}
|
||||
}
|
||||
return c.conn.RemoteAddr()
|
||||
return conn.RemoteAddr()
|
||||
}
|
||||
|
||||
func (c *slowOpenConn) SetDeadline(t time.Time) error {
|
||||
if c.conn == nil {
|
||||
conn := c.conn.Load()
|
||||
if conn == nil {
|
||||
return os.ErrInvalid
|
||||
}
|
||||
return c.conn.SetDeadline(t)
|
||||
return conn.SetDeadline(t)
|
||||
}
|
||||
|
||||
func (c *slowOpenConn) SetReadDeadline(t time.Time) error {
|
||||
if c.conn == nil {
|
||||
conn := c.conn.Load()
|
||||
if conn == nil {
|
||||
return os.ErrInvalid
|
||||
}
|
||||
return c.conn.SetReadDeadline(t)
|
||||
return conn.SetReadDeadline(t)
|
||||
}
|
||||
|
||||
func (c *slowOpenConn) SetWriteDeadline(t time.Time) error {
|
||||
if c.conn == nil {
|
||||
conn := c.conn.Load()
|
||||
if conn == nil {
|
||||
return os.ErrInvalid
|
||||
}
|
||||
return c.conn.SetWriteDeadline(t)
|
||||
return conn.SetWriteDeadline(t)
|
||||
}
|
||||
|
||||
func (c *slowOpenConn) Upstream() any {
|
||||
return c.conn
|
||||
return common.PtrOrNil(c.conn.Load())
|
||||
}
|
||||
|
||||
func (c *slowOpenConn) ReaderReplaceable() bool {
|
||||
return c.conn != nil
|
||||
return c.conn.Load() != nil
|
||||
}
|
||||
|
||||
func (c *slowOpenConn) WriterReplaceable() bool {
|
||||
return c.conn != nil
|
||||
return c.conn.Load() != nil
|
||||
}
|
||||
|
||||
func (c *slowOpenConn) LazyHeadroom() bool {
|
||||
return c.conn == nil
|
||||
return c.conn.Load() == nil
|
||||
}
|
||||
|
||||
func (c *slowOpenConn) NeedHandshake() bool {
|
||||
return c.conn == nil
|
||||
return c.conn.Load() == nil
|
||||
}
|
||||
|
||||
func (c *slowOpenConn) WriteTo(w io.Writer) (n int64, err error) {
|
||||
if c.conn == nil {
|
||||
conn := c.conn.Load()
|
||||
if conn == nil {
|
||||
select {
|
||||
case <-c.create:
|
||||
if c.err != nil {
|
||||
@@ -166,5 +178,5 @@ func (c *slowOpenConn) WriteTo(w io.Writer) (n int64, err error) {
|
||||
return 0, c.err
|
||||
}
|
||||
}
|
||||
return bufio.Copy(w, c.conn)
|
||||
return bufio.Copy(w, c.conn.Load())
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package listener
|
||||
import (
|
||||
"net"
|
||||
"net/netip"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
@@ -56,7 +57,7 @@ func (l *Listener) ListenTCP() (net.Listener, error) {
|
||||
if l.tproxy {
|
||||
listenConfig.Control = control.Append(listenConfig.Control, func(network, address string, conn syscall.RawConn) error {
|
||||
return control.Raw(conn, func(fd uintptr) error {
|
||||
return redir.TProxy(fd, !M.ParseSocksaddr(address).IsIPv4(), false)
|
||||
return redir.TProxy(fd, !strings.HasSuffix(network, "4"), false)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"net"
|
||||
"net/netip"
|
||||
"os"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
@@ -41,7 +42,7 @@ func (l *Listener) ListenUDP() (net.PacketConn, error) {
|
||||
if l.tproxy {
|
||||
listenConfig.Control = control.Append(listenConfig.Control, func(network, address string, conn syscall.RawConn) error {
|
||||
return control.Raw(conn, func(fd uintptr) error {
|
||||
return redir.TProxy(fd, !M.ParseSocksaddr(address).IsIPv4(), true)
|
||||
return redir.TProxy(fd, !strings.HasSuffix(network, "4"), true)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@@ -17,8 +17,5 @@ var uQUICChrome115 = &ja3.ClientHello{
|
||||
}
|
||||
|
||||
func maybeUQUIC(fingerprint *ja3.ClientHello) bool {
|
||||
if uQUICChrome115.Equals(fingerprint, true) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
return !uQUICChrome115.Equals(fingerprint, true)
|
||||
}
|
||||
|
||||
@@ -56,7 +56,7 @@ func TestSniffUQUICChrome115(t *testing.T) {
|
||||
err = sniff.QUICClientHello(context.Background(), &metadata, pkt)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, metadata.Protocol, C.ProtocolQUIC)
|
||||
require.Equal(t, metadata.Client, C.ClientQUICGo)
|
||||
require.Equal(t, metadata.Client, C.ClientChromium)
|
||||
require.Equal(t, metadata.Domain, "www.google.com")
|
||||
}
|
||||
|
||||
|
||||
@@ -53,26 +53,48 @@ func ClientHandshake(ctx context.Context, conn net.Conn, config Config) (Conn, e
|
||||
return tlsConn, nil
|
||||
}
|
||||
|
||||
type Dialer struct {
|
||||
type Dialer interface {
|
||||
N.Dialer
|
||||
DialTLSContext(ctx context.Context, destination M.Socksaddr) (Conn, error)
|
||||
}
|
||||
|
||||
type defaultDialer struct {
|
||||
dialer N.Dialer
|
||||
config Config
|
||||
}
|
||||
|
||||
func NewDialer(dialer N.Dialer, config Config) N.Dialer {
|
||||
return &Dialer{dialer, config}
|
||||
func NewDialer(dialer N.Dialer, config Config) Dialer {
|
||||
return &defaultDialer{dialer, config}
|
||||
}
|
||||
|
||||
func (d *Dialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
|
||||
if network != N.NetworkTCP {
|
||||
func (d *defaultDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
|
||||
if N.NetworkName(network) != N.NetworkTCP {
|
||||
return nil, os.ErrInvalid
|
||||
}
|
||||
conn, err := d.dialer.DialContext(ctx, network, destination)
|
||||
return d.DialTLSContext(ctx, destination)
|
||||
}
|
||||
|
||||
func (d *defaultDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
|
||||
return nil, os.ErrInvalid
|
||||
}
|
||||
|
||||
func (d *defaultDialer) DialTLSContext(ctx context.Context, destination M.Socksaddr) (Conn, error) {
|
||||
return d.dialContext(ctx, destination)
|
||||
}
|
||||
|
||||
func (d *defaultDialer) dialContext(ctx context.Context, destination M.Socksaddr) (Conn, error) {
|
||||
conn, err := d.dialer.DialContext(ctx, N.NetworkTCP, destination)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ClientHandshake(ctx, conn, d.config)
|
||||
tlsConn, err := aTLS.ClientHandshake(ctx, conn, d.config)
|
||||
if err != nil {
|
||||
conn.Close()
|
||||
return nil, err
|
||||
}
|
||||
return tlsConn, nil
|
||||
}
|
||||
|
||||
func (d *Dialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
|
||||
return nil, os.ErrInvalid
|
||||
func (d *defaultDialer) Upstream() any {
|
||||
return d.dialer
|
||||
}
|
||||
|
||||
@@ -69,11 +69,7 @@ func parseECHServerConfig(ctx context.Context, options option.InboundTLSOptions,
|
||||
} else {
|
||||
return E.New("missing ECH keys")
|
||||
}
|
||||
block, rest := pem.Decode(echKey)
|
||||
if block == nil || block.Type != "ECH KEYS" || len(rest) > 0 {
|
||||
return E.New("invalid ECH keys pem")
|
||||
}
|
||||
echKeys, err := UnmarshalECHKeys(block.Bytes)
|
||||
echKeys, err := parseECHKeys(echKey)
|
||||
if err != nil {
|
||||
return E.Cause(err, "parse ECH keys")
|
||||
}
|
||||
@@ -85,21 +81,29 @@ func parseECHServerConfig(ctx context.Context, options option.InboundTLSOptions,
|
||||
return nil
|
||||
}
|
||||
|
||||
func reloadECHKeys(echKeyPath string, tlsConfig *tls.Config) error {
|
||||
echKey, err := os.ReadFile(echKeyPath)
|
||||
func (c *STDServerConfig) setECHServerConfig(echKey []byte) error {
|
||||
echKeys, err := parseECHKeys(echKey)
|
||||
if err != nil {
|
||||
return E.Cause(err, "reload ECH keys from ", echKeyPath)
|
||||
return err
|
||||
}
|
||||
c.access.Lock()
|
||||
config := c.config.Clone()
|
||||
config.EncryptedClientHelloKeys = echKeys
|
||||
c.config = config
|
||||
c.access.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
func parseECHKeys(echKey []byte) ([]tls.EncryptedClientHelloKey, error) {
|
||||
block, _ := pem.Decode(echKey)
|
||||
if block == nil || block.Type != "ECH KEYS" {
|
||||
return E.New("invalid ECH keys pem")
|
||||
return nil, E.New("invalid ECH keys pem")
|
||||
}
|
||||
echKeys, err := UnmarshalECHKeys(block.Bytes)
|
||||
if err != nil {
|
||||
return E.Cause(err, "parse ECH keys")
|
||||
return nil, E.Cause(err, "parse ECH keys")
|
||||
}
|
||||
tlsConfig.EncryptedClientHelloKeys = echKeys
|
||||
return nil
|
||||
return echKeys, nil
|
||||
}
|
||||
|
||||
type ECHClientConfig struct {
|
||||
@@ -125,7 +129,7 @@ func (s *ECHClientConfig) ClientHandshake(ctx context.Context, conn net.Conn) (a
|
||||
func (s *ECHClientConfig) fetchAndHandshake(ctx context.Context, conn net.Conn) (aTLS.Conn, error) {
|
||||
s.access.Lock()
|
||||
defer s.access.Unlock()
|
||||
if len(s.ECHConfigList()) == 0 || s.lastTTL == 0 || time.Now().Sub(s.lastUpdate) > s.lastTTL {
|
||||
if len(s.ECHConfigList()) == 0 || s.lastTTL == 0 || time.Since(s.lastUpdate) > s.lastTTL {
|
||||
message := &mDNS.Msg{
|
||||
MsgHdr: mDNS.MsgHdr{
|
||||
RecursionDesired: true,
|
||||
|
||||
@@ -18,6 +18,6 @@ func parseECHServerConfig(ctx context.Context, options option.InboundTLSOptions,
|
||||
return E.New("ECH requires go1.24, please recompile your binary.")
|
||||
}
|
||||
|
||||
func reloadECHKeys(echKeyPath string, tlsConfig *tls.Config) error {
|
||||
return E.New("ECH requires go1.24, please recompile your binary.")
|
||||
func (c *STDServerConfig) setECHServerConfig(echKey []byte) error {
|
||||
panic("unreachable")
|
||||
}
|
||||
|
||||
@@ -307,3 +307,11 @@ func (c *realityClientConnWrapper) Upstream() any {
|
||||
func (c *realityClientConnWrapper) CloseWrite() error {
|
||||
return c.Close()
|
||||
}
|
||||
|
||||
func (c *realityClientConnWrapper) ReaderReplaceable() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (c *realityClientConnWrapper) WriterReplaceable() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -206,3 +206,11 @@ func (c *realityConnWrapper) Upstream() any {
|
||||
func (c *realityConnWrapper) CloseWrite() error {
|
||||
return c.Close()
|
||||
}
|
||||
|
||||
func (c *realityConnWrapper) ReaderReplaceable() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (c *realityConnWrapper) WriterReplaceable() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"net"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/fswatch"
|
||||
@@ -20,6 +21,7 @@ import (
|
||||
var errInsecureUnused = E.New("tls: insecure unused")
|
||||
|
||||
type STDServerConfig struct {
|
||||
access sync.RWMutex
|
||||
config *tls.Config
|
||||
logger log.Logger
|
||||
acmeService adapter.SimpleLifecycle
|
||||
@@ -32,14 +34,22 @@ type STDServerConfig struct {
|
||||
}
|
||||
|
||||
func (c *STDServerConfig) ServerName() string {
|
||||
c.access.RLock()
|
||||
defer c.access.RUnlock()
|
||||
return c.config.ServerName
|
||||
}
|
||||
|
||||
func (c *STDServerConfig) SetServerName(serverName string) {
|
||||
c.config.ServerName = serverName
|
||||
c.access.Lock()
|
||||
defer c.access.Unlock()
|
||||
config := c.config.Clone()
|
||||
config.ServerName = serverName
|
||||
c.config = config
|
||||
}
|
||||
|
||||
func (c *STDServerConfig) NextProtos() []string {
|
||||
c.access.RLock()
|
||||
defer c.access.RUnlock()
|
||||
if c.acmeService != nil && len(c.config.NextProtos) > 1 && c.config.NextProtos[0] == ACMETLS1Protocol {
|
||||
return c.config.NextProtos[1:]
|
||||
} else {
|
||||
@@ -48,11 +58,15 @@ func (c *STDServerConfig) NextProtos() []string {
|
||||
}
|
||||
|
||||
func (c *STDServerConfig) SetNextProtos(nextProto []string) {
|
||||
c.access.Lock()
|
||||
defer c.access.Unlock()
|
||||
config := c.config.Clone()
|
||||
if c.acmeService != nil && len(c.config.NextProtos) > 1 && c.config.NextProtos[0] == ACMETLS1Protocol {
|
||||
c.config.NextProtos = append(c.config.NextProtos[:1], nextProto...)
|
||||
config.NextProtos = append(c.config.NextProtos[:1], nextProto...)
|
||||
} else {
|
||||
c.config.NextProtos = nextProto
|
||||
config.NextProtos = nextProto
|
||||
}
|
||||
c.config = config
|
||||
}
|
||||
|
||||
func (c *STDServerConfig) Config() (*STDConfig, error) {
|
||||
@@ -77,9 +91,6 @@ func (c *STDServerConfig) Start() error {
|
||||
if c.acmeService != nil {
|
||||
return c.acmeService.Start()
|
||||
} else {
|
||||
if c.certificatePath == "" && c.keyPath == "" {
|
||||
return nil
|
||||
}
|
||||
err := c.startWatcher()
|
||||
if err != nil {
|
||||
c.logger.Warn("create fsnotify watcher: ", err)
|
||||
@@ -99,6 +110,9 @@ func (c *STDServerConfig) startWatcher() error {
|
||||
if c.echKeyPath != "" {
|
||||
watchPath = append(watchPath, c.echKeyPath)
|
||||
}
|
||||
if len(watchPath) == 0 {
|
||||
return nil
|
||||
}
|
||||
watcher, err := fswatch.NewWatcher(fswatch.Options{
|
||||
Path: watchPath,
|
||||
Callback: func(path string) {
|
||||
@@ -138,10 +152,18 @@ func (c *STDServerConfig) certificateUpdated(path string) error {
|
||||
if err != nil {
|
||||
return E.Cause(err, "reload key pair")
|
||||
}
|
||||
c.config.Certificates = []tls.Certificate{keyPair}
|
||||
c.access.Lock()
|
||||
config := c.config.Clone()
|
||||
config.Certificates = []tls.Certificate{keyPair}
|
||||
c.config = config
|
||||
c.access.Unlock()
|
||||
c.logger.Info("reloaded TLS certificate")
|
||||
} else if path == c.echKeyPath {
|
||||
err := reloadECHKeys(c.echKeyPath, c.config)
|
||||
echKey, err := os.ReadFile(c.echKeyPath)
|
||||
if err != nil {
|
||||
return E.Cause(err, "reload ECH keys from ", c.echKeyPath)
|
||||
}
|
||||
err = c.setECHServerConfig(echKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -262,7 +284,7 @@ func NewSTDServer(ctx context.Context, logger log.Logger, options option.Inbound
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return &STDServerConfig{
|
||||
serverConfig := &STDServerConfig{
|
||||
config: tlsConfig,
|
||||
logger: logger,
|
||||
acmeService: acmeService,
|
||||
@@ -271,5 +293,11 @@ func NewSTDServer(ctx context.Context, logger log.Logger, options option.Inbound
|
||||
certificatePath: options.CertificatePath,
|
||||
keyPath: options.KeyPath,
|
||||
echKeyPath: echKeyPath,
|
||||
}, nil
|
||||
}
|
||||
serverConfig.config.GetConfigForClient = func(info *tls.ClientHelloInfo) (*tls.Config, error) {
|
||||
serverConfig.access.Lock()
|
||||
defer serverConfig.access.Unlock()
|
||||
return serverConfig.config, nil
|
||||
}
|
||||
return serverConfig, nil
|
||||
}
|
||||
|
||||
@@ -106,6 +106,14 @@ func (c *utlsConnWrapper) Upstream() any {
|
||||
return c.UConn
|
||||
}
|
||||
|
||||
func (c *utlsConnWrapper) ReaderReplaceable() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (c *utlsConnWrapper) WriterReplaceable() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
type utlsALPNWrapper struct {
|
||||
utlsConnWrapper
|
||||
nextProtocols []string
|
||||
|
||||
@@ -109,6 +109,9 @@ func (c *Conn) Write(b []byte) (n int, err error) {
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if i != len(splitIndexes) {
|
||||
time.Sleep(c.fallbackDelay)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,10 @@ import (
|
||||
)
|
||||
|
||||
func writeAndWaitAck(ctx context.Context, conn *net.TCPConn, payload []byte, fallbackDelay time.Duration) error {
|
||||
_, err := conn.Write(payload)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
time.Sleep(fallbackDelay)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -16,6 +16,9 @@ func writeAndWaitAck(ctx context.Context, conn *net.TCPConn, payload []byte, fal
|
||||
err := winiphlpapi.WriteAndWaitAck(ctx, conn, payload)
|
||||
if err != nil {
|
||||
if errors.Is(err, windows.ERROR_ACCESS_DENIED) {
|
||||
if _, err := conn.Write(payload); err != nil {
|
||||
return err
|
||||
}
|
||||
time.Sleep(fallbackDelay)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -47,15 +47,15 @@ func (s *HistoryStorage) LoadURLTestHistory(tag string) *adapter.URLTestHistory
|
||||
func (s *HistoryStorage) DeleteURLTestHistory(tag string) {
|
||||
s.access.Lock()
|
||||
delete(s.delayHistory, tag)
|
||||
s.access.Unlock()
|
||||
s.notifyUpdated()
|
||||
s.access.Unlock()
|
||||
}
|
||||
|
||||
func (s *HistoryStorage) StoreURLTestHistory(tag string, history *adapter.URLTestHistory) {
|
||||
s.access.Lock()
|
||||
s.delayHistory[tag] = history
|
||||
s.access.Unlock()
|
||||
s.notifyUpdated()
|
||||
s.access.Unlock()
|
||||
}
|
||||
|
||||
func (s *HistoryStorage) notifyUpdated() {
|
||||
@@ -69,6 +69,8 @@ func (s *HistoryStorage) notifyUpdated() {
|
||||
}
|
||||
|
||||
func (s *HistoryStorage) Close() error {
|
||||
s.access.Lock()
|
||||
defer s.access.Unlock()
|
||||
s.updateHook = nil
|
||||
return nil
|
||||
}
|
||||
|
||||
182
dns/client.go
182
dns/client.go
@@ -2,12 +2,14 @@ package dns
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net"
|
||||
"net/netip"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing-box/common/compatible"
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"github.com/sagernet/sing/common"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
@@ -17,7 +19,7 @@ import (
|
||||
"github.com/sagernet/sing/contrab/freelru"
|
||||
"github.com/sagernet/sing/contrab/maphash"
|
||||
|
||||
dns "github.com/miekg/dns"
|
||||
"github.com/miekg/dns"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -30,16 +32,18 @@ var (
|
||||
var _ adapter.DNSClient = (*Client)(nil)
|
||||
|
||||
type Client struct {
|
||||
timeout time.Duration
|
||||
disableCache bool
|
||||
disableExpire bool
|
||||
independentCache bool
|
||||
clientSubnet netip.Prefix
|
||||
rdrc adapter.RDRCStore
|
||||
initRDRCFunc func() adapter.RDRCStore
|
||||
logger logger.ContextLogger
|
||||
cache freelru.Cache[dns.Question, *dns.Msg]
|
||||
transportCache freelru.Cache[transportCacheKey, *dns.Msg]
|
||||
timeout time.Duration
|
||||
disableCache bool
|
||||
disableExpire bool
|
||||
independentCache bool
|
||||
clientSubnet netip.Prefix
|
||||
rdrc adapter.RDRCStore
|
||||
initRDRCFunc func() adapter.RDRCStore
|
||||
logger logger.ContextLogger
|
||||
cache freelru.Cache[dns.Question, *dns.Msg]
|
||||
cacheLock compatible.Map[dns.Question, chan struct{}]
|
||||
transportCache freelru.Cache[transportCacheKey, *dns.Msg]
|
||||
transportCacheLock compatible.Map[dns.Question, chan struct{}]
|
||||
}
|
||||
|
||||
type ClientOptions struct {
|
||||
@@ -91,22 +95,34 @@ func (c *Client) Start() {
|
||||
}
|
||||
}
|
||||
|
||||
func extractNegativeTTL(response *dns.Msg) (uint32, bool) {
|
||||
for _, record := range response.Ns {
|
||||
if soa, isSOA := record.(*dns.SOA); isSOA {
|
||||
soaTTL := soa.Header().Ttl
|
||||
soaMinimum := soa.Minttl
|
||||
if soaTTL < soaMinimum {
|
||||
return soaTTL, true
|
||||
}
|
||||
return soaMinimum, true
|
||||
}
|
||||
}
|
||||
return 0, false
|
||||
}
|
||||
|
||||
func (c *Client) Exchange(ctx context.Context, transport adapter.DNSTransport, message *dns.Msg, options adapter.DNSQueryOptions, responseChecker func(responseAddrs []netip.Addr) bool) (*dns.Msg, error) {
|
||||
if len(message.Question) == 0 {
|
||||
if c.logger != nil {
|
||||
c.logger.WarnContext(ctx, "bad question size: ", len(message.Question))
|
||||
}
|
||||
responseMessage := dns.Msg{
|
||||
MsgHdr: dns.MsgHdr{
|
||||
Id: message.Id,
|
||||
Response: true,
|
||||
Rcode: dns.RcodeFormatError,
|
||||
},
|
||||
Question: message.Question,
|
||||
}
|
||||
return &responseMessage, nil
|
||||
return FixedResponseStatus(message, dns.RcodeFormatError), nil
|
||||
}
|
||||
question := message.Question[0]
|
||||
if question.Qtype == dns.TypeA && options.Strategy == C.DomainStrategyIPv6Only || question.Qtype == dns.TypeAAAA && options.Strategy == C.DomainStrategyIPv4Only {
|
||||
if c.logger != nil {
|
||||
c.logger.DebugContext(ctx, "strategy rejected")
|
||||
}
|
||||
return FixedResponseStatus(message, dns.RcodeSuccess), nil
|
||||
}
|
||||
clientSubnet := options.ClientSubnet
|
||||
if !clientSubnet.IsValid() {
|
||||
clientSubnet = c.clientSubnet
|
||||
@@ -114,12 +130,38 @@ func (c *Client) Exchange(ctx context.Context, transport adapter.DNSTransport, m
|
||||
if clientSubnet.IsValid() {
|
||||
message = SetClientSubnet(message, clientSubnet)
|
||||
}
|
||||
|
||||
isSimpleRequest := len(message.Question) == 1 &&
|
||||
len(message.Ns) == 0 &&
|
||||
len(message.Extra) == 0 &&
|
||||
(len(message.Extra) == 0 || len(message.Extra) == 1 &&
|
||||
message.Extra[0].Header().Rrtype == dns.TypeOPT &&
|
||||
message.Extra[0].Header().Class > 0 &&
|
||||
message.Extra[0].Header().Ttl == 0 &&
|
||||
len(message.Extra[0].(*dns.OPT).Option) == 0) &&
|
||||
!options.ClientSubnet.IsValid()
|
||||
disableCache := !isSimpleRequest || c.disableCache || options.DisableCache
|
||||
if !disableCache {
|
||||
if c.cache != nil {
|
||||
cond, loaded := c.cacheLock.LoadOrStore(question, make(chan struct{}))
|
||||
if loaded {
|
||||
<-cond
|
||||
} else {
|
||||
defer func() {
|
||||
c.cacheLock.Delete(question)
|
||||
close(cond)
|
||||
}()
|
||||
}
|
||||
} else if c.transportCache != nil {
|
||||
cond, loaded := c.transportCacheLock.LoadOrStore(question, make(chan struct{}))
|
||||
if loaded {
|
||||
<-cond
|
||||
} else {
|
||||
defer func() {
|
||||
c.transportCacheLock.Delete(question)
|
||||
close(cond)
|
||||
}()
|
||||
}
|
||||
}
|
||||
response, ttl := c.loadResponse(question, transport)
|
||||
if response != nil {
|
||||
logCachedResponse(c.logger, ctx, response, ttl)
|
||||
@@ -127,27 +169,14 @@ func (c *Client) Exchange(ctx context.Context, transport adapter.DNSTransport, m
|
||||
return response, nil
|
||||
}
|
||||
}
|
||||
if question.Qtype == dns.TypeA && options.Strategy == C.DomainStrategyIPv6Only || question.Qtype == dns.TypeAAAA && options.Strategy == C.DomainStrategyIPv4Only {
|
||||
responseMessage := dns.Msg{
|
||||
MsgHdr: dns.MsgHdr{
|
||||
Id: message.Id,
|
||||
Response: true,
|
||||
Rcode: dns.RcodeSuccess,
|
||||
},
|
||||
Question: []dns.Question{question},
|
||||
}
|
||||
if c.logger != nil {
|
||||
c.logger.DebugContext(ctx, "strategy rejected")
|
||||
}
|
||||
return &responseMessage, nil
|
||||
}
|
||||
|
||||
messageId := message.Id
|
||||
contextTransport, clientSubnetLoaded := transportTagFromContext(ctx)
|
||||
if clientSubnetLoaded && transport.Tag() == contextTransport {
|
||||
return nil, E.New("DNS query loopback in transport[", contextTransport, "]")
|
||||
}
|
||||
ctx = contextWithTransportTag(ctx, transport.Tag())
|
||||
if responseChecker != nil && c.rdrc != nil {
|
||||
if !disableCache && responseChecker != nil && c.rdrc != nil {
|
||||
rejected := c.rdrc.LoadRDRC(transport.Tag(), question.Name, question.Qtype)
|
||||
if rejected {
|
||||
return nil, ErrResponseRejectedCached
|
||||
@@ -157,7 +186,12 @@ func (c *Client) Exchange(ctx context.Context, transport adapter.DNSTransport, m
|
||||
response, err := transport.Exchange(ctx, message)
|
||||
cancel()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
var rcodeError RcodeError
|
||||
if errors.As(err, &rcodeError) {
|
||||
response = FixedResponseStatus(message, int(rcodeError))
|
||||
} else {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
/*if question.Qtype == dns.TypeA || question.Qtype == dns.TypeAAAA {
|
||||
validResponse := response
|
||||
@@ -194,15 +228,17 @@ func (c *Client) Exchange(ctx context.Context, transport adapter.DNSTransport, m
|
||||
response.Answer = append(response.Answer, validResponse.Answer...)
|
||||
}
|
||||
}*/
|
||||
disableCache = disableCache || (response.Rcode != dns.RcodeSuccess && response.Rcode != dns.RcodeNameError)
|
||||
if responseChecker != nil {
|
||||
var rejected bool
|
||||
if !(response.Rcode == dns.RcodeSuccess || response.Rcode == dns.RcodeNameError) {
|
||||
// TODO: add accept_any rule and support to check response instead of addresses
|
||||
if response.Rcode != dns.RcodeSuccess || len(response.Answer) == 0 {
|
||||
rejected = true
|
||||
} else {
|
||||
rejected = !responseChecker(MessageToAddresses(response))
|
||||
}
|
||||
if rejected {
|
||||
if c.rdrc != nil {
|
||||
if !disableCache && c.rdrc != nil {
|
||||
c.rdrc.SaveRDRCAsync(transport.Tag(), question.Name, question.Qtype, c.logger)
|
||||
}
|
||||
logRejectedResponse(c.logger, ctx, response)
|
||||
@@ -229,10 +265,17 @@ func (c *Client) Exchange(ctx context.Context, transport adapter.DNSTransport, m
|
||||
}
|
||||
}
|
||||
var timeToLive uint32
|
||||
for _, recordList := range [][]dns.RR{response.Answer, response.Ns, response.Extra} {
|
||||
for _, record := range recordList {
|
||||
if timeToLive == 0 || record.Header().Ttl > 0 && record.Header().Ttl < timeToLive {
|
||||
timeToLive = record.Header().Ttl
|
||||
if len(response.Answer) == 0 {
|
||||
if soaTTL, hasSOA := extractNegativeTTL(response); hasSOA {
|
||||
timeToLive = soaTTL
|
||||
}
|
||||
}
|
||||
if timeToLive == 0 {
|
||||
for _, recordList := range [][]dns.RR{response.Answer, response.Ns, response.Extra} {
|
||||
for _, record := range recordList {
|
||||
if timeToLive == 0 || record.Header().Ttl > 0 && record.Header().Ttl < timeToLive {
|
||||
timeToLive = record.Header().Ttl
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -259,7 +302,7 @@ func (c *Client) Exchange(ctx context.Context, transport adapter.DNSTransport, m
|
||||
}
|
||||
}
|
||||
logExchangedResponse(c.logger, ctx, response, timeToLive)
|
||||
return response, err
|
||||
return response, nil
|
||||
}
|
||||
|
||||
func (c *Client) Lookup(ctx context.Context, transport adapter.DNSTransport, domain string, options adapter.DNSQueryOptions, responseChecker func(responseAddrs []netip.Addr) bool) ([]netip.Addr, error) {
|
||||
@@ -305,8 +348,7 @@ func (c *Client) Lookup(ctx context.Context, transport adapter.DNSTransport, dom
|
||||
func (c *Client) ClearCache() {
|
||||
if c.cache != nil {
|
||||
c.cache.Purge()
|
||||
}
|
||||
if c.transportCache != nil {
|
||||
} else if c.transportCache != nil {
|
||||
c.transportCache.Purge()
|
||||
}
|
||||
}
|
||||
@@ -320,37 +362,41 @@ func (c *Client) LookupCache(domain string, strategy C.DomainStrategy) ([]netip.
|
||||
}
|
||||
dnsName := dns.Fqdn(domain)
|
||||
if strategy == C.DomainStrategyIPv4Only {
|
||||
response, err := c.questionCache(dns.Question{
|
||||
addresses, err := c.questionCache(dns.Question{
|
||||
Name: dnsName,
|
||||
Qtype: dns.TypeA,
|
||||
Qclass: dns.ClassINET,
|
||||
}, nil)
|
||||
if err != ErrNotCached {
|
||||
return response, true
|
||||
return addresses, true
|
||||
}
|
||||
} else if strategy == C.DomainStrategyIPv6Only {
|
||||
response, err := c.questionCache(dns.Question{
|
||||
addresses, err := c.questionCache(dns.Question{
|
||||
Name: dnsName,
|
||||
Qtype: dns.TypeAAAA,
|
||||
Qclass: dns.ClassINET,
|
||||
}, nil)
|
||||
if err != ErrNotCached {
|
||||
return response, true
|
||||
return addresses, true
|
||||
}
|
||||
} else {
|
||||
response4, _ := c.questionCache(dns.Question{
|
||||
response4, _ := c.loadResponse(dns.Question{
|
||||
Name: dnsName,
|
||||
Qtype: dns.TypeA,
|
||||
Qclass: dns.ClassINET,
|
||||
}, nil)
|
||||
response6, _ := c.questionCache(dns.Question{
|
||||
if response4 == nil {
|
||||
return nil, false
|
||||
}
|
||||
response6, _ := c.loadResponse(dns.Question{
|
||||
Name: dnsName,
|
||||
Qtype: dns.TypeAAAA,
|
||||
Qclass: dns.ClassINET,
|
||||
}, nil)
|
||||
if len(response4) > 0 || len(response6) > 0 {
|
||||
return sortAddresses(response4, response6, strategy), true
|
||||
if response6 == nil {
|
||||
return nil, false
|
||||
}
|
||||
return sortAddresses(MessageToAddresses(response4), MessageToAddresses(response6), strategy), true
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
@@ -390,15 +436,15 @@ func (c *Client) storeCache(transport adapter.DNSTransport, question dns.Questio
|
||||
transportTag: transport.Tag(),
|
||||
}, message)
|
||||
}
|
||||
return
|
||||
}
|
||||
if !c.independentCache {
|
||||
c.cache.AddWithLifetime(question, message, time.Second*time.Duration(timeToLive))
|
||||
} else {
|
||||
c.transportCache.AddWithLifetime(transportCacheKey{
|
||||
Question: question,
|
||||
transportTag: transport.Tag(),
|
||||
}, message, time.Second*time.Duration(timeToLive))
|
||||
if !c.independentCache {
|
||||
c.cache.AddWithLifetime(question, message, time.Second*time.Duration(timeToLive))
|
||||
} else {
|
||||
c.transportCache.AddWithLifetime(transportCacheKey{
|
||||
Question: question,
|
||||
transportTag: transport.Tag(),
|
||||
}, message, time.Second*time.Duration(timeToLive))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -517,6 +563,9 @@ func (c *Client) loadResponse(question dns.Question, transport adapter.DNSTransp
|
||||
}
|
||||
|
||||
func MessageToAddresses(response *dns.Msg) []netip.Addr {
|
||||
if response == nil || response.Rcode != dns.RcodeSuccess {
|
||||
return nil
|
||||
}
|
||||
addresses := make([]netip.Addr, 0, len(response.Answer))
|
||||
for _, rawAnswer := range response.Answer {
|
||||
switch answer := rawAnswer.(type) {
|
||||
@@ -561,9 +610,12 @@ func transportTagFromContext(ctx context.Context) (string, bool) {
|
||||
func FixedResponseStatus(message *dns.Msg, rcode int) *dns.Msg {
|
||||
return &dns.Msg{
|
||||
MsgHdr: dns.MsgHdr{
|
||||
Id: message.Id,
|
||||
Rcode: rcode,
|
||||
Response: true,
|
||||
Id: message.Id,
|
||||
Response: true,
|
||||
Authoritative: true,
|
||||
RecursionDesired: true,
|
||||
RecursionAvailable: true,
|
||||
Rcode: rcode,
|
||||
},
|
||||
Question: message.Question,
|
||||
}
|
||||
|
||||
@@ -15,8 +15,7 @@ func TruncateDNSMessage(request *dns.Msg, response *dns.Msg, headroom int) (*buf
|
||||
}
|
||||
responseLen := response.Len()
|
||||
if responseLen > maxLen {
|
||||
copyResponse := *response
|
||||
response = ©Response
|
||||
response = response.Copy()
|
||||
response.Truncate(maxLen)
|
||||
}
|
||||
buffer := buf.NewSize(headroom*2 + 1 + responseLen)
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
RcodeSuccess RcodeError = mDNS.RcodeSuccess
|
||||
RcodeFormatError RcodeError = mDNS.RcodeFormatError
|
||||
RcodeNameError RcodeError = mDNS.RcodeNameError
|
||||
RcodeRefused RcodeError = mDNS.RcodeRefused
|
||||
|
||||
@@ -293,12 +293,7 @@ func (r *Router) Exchange(ctx context.Context, message *mDNS.Msg, options adapte
|
||||
} else if errors.Is(err, ErrResponseRejected) {
|
||||
rejected = true
|
||||
r.logger.DebugContext(ctx, E.Cause(err, "response rejected for ", FormatQuestion(message.Question[0].String())))
|
||||
/*} else if responseCheck!= nil && errors.Is(err, RcodeError(mDNS.RcodeNameError)) {
|
||||
rejected = true
|
||||
r.logger.DebugContext(ctx, E.Cause(err, "response rejected for ", FormatQuestion(message.Question[0].String())))
|
||||
*/
|
||||
} else if len(message.Question) > 0 {
|
||||
rejected = true
|
||||
r.logger.ErrorContext(ctx, E.Cause(err, "exchange failed for ", FormatQuestion(message.Question[0].String())))
|
||||
} else {
|
||||
r.logger.ErrorContext(ctx, E.Cause(err, "exchange failed for <empty query>"))
|
||||
@@ -391,12 +386,7 @@ func (r *Router) Lookup(ctx context.Context, domain string, options adapter.DNSQ
|
||||
if rule != nil {
|
||||
switch action := rule.Action().(type) {
|
||||
case *R.RuleActionReject:
|
||||
switch action.Method {
|
||||
case C.RuleActionRejectMethodDefault:
|
||||
return nil, nil
|
||||
case C.RuleActionRejectMethodDrop:
|
||||
return nil, tun.ErrDrop
|
||||
}
|
||||
return nil, &R.RejectedError{Cause: action.Error(ctx)}
|
||||
case *R.RuleActionPredefined:
|
||||
if action.Rcode != mDNS.RcodeSuccess {
|
||||
err = RcodeError(action.Rcode)
|
||||
|
||||
@@ -2,17 +2,18 @@ package dhcp
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"io"
|
||||
"net"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing-box/common/dialer"
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"github.com/sagernet/sing-box/dns"
|
||||
"github.com/sagernet/sing-box/dns/transport"
|
||||
"github.com/sagernet/sing-box/log"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
"github.com/sagernet/sing-tun"
|
||||
@@ -29,6 +30,7 @@ import (
|
||||
|
||||
"github.com/insomniacslk/dhcp/dhcpv4"
|
||||
mDNS "github.com/miekg/dns"
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
func RegisterTransport(registry *dns.TransportRegistry) {
|
||||
@@ -45,9 +47,12 @@ type Transport struct {
|
||||
networkManager adapter.NetworkManager
|
||||
interfaceName string
|
||||
interfaceCallback *list.Element[tun.DefaultInterfaceUpdateCallback]
|
||||
transports []adapter.DNSTransport
|
||||
updateAccess sync.Mutex
|
||||
transportLock sync.RWMutex
|
||||
updatedAt time.Time
|
||||
servers []M.Socksaddr
|
||||
search []string
|
||||
ndots int
|
||||
attempts int
|
||||
}
|
||||
|
||||
func NewTransport(ctx context.Context, logger log.ContextLogger, tag string, options option.DHCPDNSServerOptions) (adapter.DNSTransport, error) {
|
||||
@@ -62,27 +67,40 @@ func NewTransport(ctx context.Context, logger log.ContextLogger, tag string, opt
|
||||
logger: logger,
|
||||
networkManager: service.FromContext[adapter.NetworkManager](ctx),
|
||||
interfaceName: options.Interface,
|
||||
ndots: 1,
|
||||
attempts: 2,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func NewRawTransport(transportAdapter dns.TransportAdapter, ctx context.Context, dialer N.Dialer, logger log.ContextLogger) *Transport {
|
||||
return &Transport{
|
||||
TransportAdapter: transportAdapter,
|
||||
ctx: ctx,
|
||||
dialer: dialer,
|
||||
logger: logger,
|
||||
networkManager: service.FromContext[adapter.NetworkManager](ctx),
|
||||
ndots: 1,
|
||||
attempts: 2,
|
||||
}
|
||||
}
|
||||
|
||||
func (t *Transport) Start(stage adapter.StartStage) error {
|
||||
if stage != adapter.StartStateStart {
|
||||
return nil
|
||||
}
|
||||
err := t.fetchServers()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if t.interfaceName == "" {
|
||||
t.interfaceCallback = t.networkManager.InterfaceMonitor().RegisterCallback(t.interfaceUpdated)
|
||||
}
|
||||
go func() {
|
||||
_, err := t.Fetch()
|
||||
if err != nil {
|
||||
t.logger.Error(E.Cause(err, "fetch DNS servers"))
|
||||
}
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *Transport) Close() error {
|
||||
for _, transport := range t.transports {
|
||||
transport.Close()
|
||||
}
|
||||
if t.interfaceCallback != nil {
|
||||
t.networkManager.InterfaceMonitor().UnregisterCallback(t.interfaceCallback)
|
||||
}
|
||||
@@ -90,23 +108,44 @@ func (t *Transport) Close() error {
|
||||
}
|
||||
|
||||
func (t *Transport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {
|
||||
err := t.fetchServers()
|
||||
servers, err := t.Fetch()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(t.transports) == 0 {
|
||||
if len(servers) == 0 {
|
||||
return nil, E.New("dhcp: empty DNS servers from response")
|
||||
}
|
||||
return t.Exchange0(ctx, message, servers)
|
||||
}
|
||||
|
||||
var response *mDNS.Msg
|
||||
for _, transport := range t.transports {
|
||||
response, err = transport.Exchange(ctx, message)
|
||||
if err == nil {
|
||||
return response, nil
|
||||
}
|
||||
func (t *Transport) Exchange0(ctx context.Context, message *mDNS.Msg, servers []M.Socksaddr) (*mDNS.Msg, error) {
|
||||
question := message.Question[0]
|
||||
domain := dns.FqdnToDomain(question.Name)
|
||||
if len(servers) == 1 || !(message.Question[0].Qtype == mDNS.TypeA || message.Question[0].Qtype == mDNS.TypeAAAA) {
|
||||
return t.exchangeSingleRequest(ctx, servers, message, domain)
|
||||
} else {
|
||||
return t.exchangeParallel(ctx, servers, message, domain)
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
func (t *Transport) Fetch() ([]M.Socksaddr, error) {
|
||||
t.transportLock.RLock()
|
||||
updatedAt := t.updatedAt
|
||||
servers := t.servers
|
||||
t.transportLock.RUnlock()
|
||||
if time.Since(updatedAt) < C.DHCPTTL {
|
||||
return servers, nil
|
||||
}
|
||||
t.transportLock.Lock()
|
||||
defer t.transportLock.Unlock()
|
||||
if time.Since(t.updatedAt) < C.DHCPTTL {
|
||||
return t.servers, nil
|
||||
}
|
||||
err := t.updateServers()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return t.servers, nil
|
||||
}
|
||||
|
||||
func (t *Transport) fetchInterface() (*control.Interface, error) {
|
||||
@@ -124,18 +163,6 @@ func (t *Transport) fetchInterface() (*control.Interface, error) {
|
||||
}
|
||||
}
|
||||
|
||||
func (t *Transport) fetchServers() error {
|
||||
if time.Since(t.updatedAt) < C.DHCPTTL {
|
||||
return nil
|
||||
}
|
||||
t.updateAccess.Lock()
|
||||
defer t.updateAccess.Unlock()
|
||||
if time.Since(t.updatedAt) < C.DHCPTTL {
|
||||
return nil
|
||||
}
|
||||
return t.updateServers()
|
||||
}
|
||||
|
||||
func (t *Transport) updateServers() error {
|
||||
iface, err := t.fetchInterface()
|
||||
if err != nil {
|
||||
@@ -148,7 +175,7 @@ func (t *Transport) updateServers() error {
|
||||
cancel()
|
||||
if err != nil {
|
||||
return err
|
||||
} else if len(t.transports) == 0 {
|
||||
} else if len(t.servers) == 0 {
|
||||
return E.New("dhcp: empty DNS servers response")
|
||||
} else {
|
||||
t.updatedAt = time.Now()
|
||||
@@ -171,13 +198,27 @@ func (t *Transport) fetchServers0(ctx context.Context, iface *control.Interface)
|
||||
if runtime.GOOS == "linux" || runtime.GOOS == "android" {
|
||||
listenAddr = "255.255.255.255:68"
|
||||
}
|
||||
packetConn, err := listener.ListenPacket(t.ctx, "udp4", listenAddr)
|
||||
var (
|
||||
packetConn net.PacketConn
|
||||
err error
|
||||
)
|
||||
for i := 0; i < 5; i++ {
|
||||
packetConn, err = listener.ListenPacket(t.ctx, "udp4", listenAddr)
|
||||
if err == nil || !errors.Is(err, syscall.EADDRINUSE) {
|
||||
break
|
||||
}
|
||||
time.Sleep(time.Second)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer packetConn.Close()
|
||||
|
||||
discovery, err := dhcpv4.NewDiscovery(iface.HardwareAddr, dhcpv4.WithBroadcast(true), dhcpv4.WithRequestedOptions(dhcpv4.OptionDomainNameServer))
|
||||
discovery, err := dhcpv4.NewDiscovery(iface.HardwareAddr, dhcpv4.WithBroadcast(true), dhcpv4.WithRequestedOptions(
|
||||
dhcpv4.OptionDomainName,
|
||||
dhcpv4.OptionDomainNameServer,
|
||||
dhcpv4.OptionDNSDomainSearchList,
|
||||
))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -204,6 +245,9 @@ func (t *Transport) fetchServersResponse(iface *control.Interface, packetConn ne
|
||||
for {
|
||||
_, _, err := buffer.ReadPacketFrom(packetConn)
|
||||
if err != nil {
|
||||
if errors.Is(err, io.ErrShortBuffer) {
|
||||
continue
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -223,31 +267,23 @@ func (t *Transport) fetchServersResponse(iface *control.Interface, packetConn ne
|
||||
continue
|
||||
}
|
||||
|
||||
dns := dhcpPacket.DNS()
|
||||
if len(dns) == 0 {
|
||||
return nil
|
||||
}
|
||||
return t.recreateServers(iface, common.Map(dns, func(it net.IP) M.Socksaddr {
|
||||
return M.SocksaddrFrom(M.AddrFromIP(it), 53)
|
||||
}))
|
||||
return t.recreateServers(iface, dhcpPacket)
|
||||
}
|
||||
}
|
||||
|
||||
func (t *Transport) recreateServers(iface *control.Interface, serverAddrs []M.Socksaddr) error {
|
||||
if len(serverAddrs) > 0 {
|
||||
t.logger.Info("dhcp: updated DNS servers from ", iface.Name, ": [", strings.Join(common.Map(serverAddrs, M.Socksaddr.String), ","), "]")
|
||||
func (t *Transport) recreateServers(iface *control.Interface, dhcpPacket *dhcpv4.DHCPv4) error {
|
||||
searchList := dhcpPacket.DomainSearch()
|
||||
if searchList != nil && len(searchList.Labels) > 0 {
|
||||
t.search = searchList.Labels
|
||||
} else if dhcpPacket.DomainName() != "" {
|
||||
t.search = []string{dhcpPacket.DomainName()}
|
||||
}
|
||||
serverDialer := common.Must1(dialer.NewDefault(t.ctx, option.DialerOptions{
|
||||
BindInterface: iface.Name,
|
||||
UDPFragmentDefault: true,
|
||||
}))
|
||||
var transports []adapter.DNSTransport
|
||||
for _, serverAddr := range serverAddrs {
|
||||
transports = append(transports, transport.NewUDPRaw(t.logger, t.TransportAdapter, serverDialer, serverAddr))
|
||||
serverAddrs := common.Map(dhcpPacket.DNS(), func(it net.IP) M.Socksaddr {
|
||||
return M.SocksaddrFrom(M.AddrFromIP(it), 53)
|
||||
})
|
||||
if len(serverAddrs) > 0 && !slices.Equal(t.servers, serverAddrs) {
|
||||
t.logger.Info("dhcp: updated DNS servers from ", iface.Name, ": [", strings.Join(common.Map(serverAddrs, M.Socksaddr.String), ","), "], search: [", strings.Join(t.search, ","), "]")
|
||||
}
|
||||
for _, transport := range t.transports {
|
||||
transport.Close()
|
||||
}
|
||||
t.transports = transports
|
||||
t.servers = serverAddrs
|
||||
return nil
|
||||
}
|
||||
|
||||
213
dns/transport/dhcp/dhcp_shared.go
Normal file
213
dns/transport/dhcp/dhcp_shared.go
Normal file
@@ -0,0 +1,213 @@
|
||||
package dhcp
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"math/rand"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"github.com/sagernet/sing-box/dns"
|
||||
"github.com/sagernet/sing-box/dns/transport"
|
||||
"github.com/sagernet/sing/common/buf"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
|
||||
mDNS "github.com/miekg/dns"
|
||||
)
|
||||
|
||||
func (t *Transport) exchangeSingleRequest(ctx context.Context, servers []M.Socksaddr, message *mDNS.Msg, domain string) (*mDNS.Msg, error) {
|
||||
var lastErr error
|
||||
for _, fqdn := range t.nameList(domain) {
|
||||
response, err := t.tryOneName(ctx, servers, fqdn, message)
|
||||
if err != nil {
|
||||
lastErr = err
|
||||
continue
|
||||
}
|
||||
return response, nil
|
||||
}
|
||||
return nil, lastErr
|
||||
}
|
||||
|
||||
func (t *Transport) exchangeParallel(ctx context.Context, servers []M.Socksaddr, message *mDNS.Msg, domain string) (*mDNS.Msg, error) {
|
||||
returned := make(chan struct{})
|
||||
defer close(returned)
|
||||
type queryResult struct {
|
||||
response *mDNS.Msg
|
||||
err error
|
||||
}
|
||||
results := make(chan queryResult)
|
||||
startRacer := func(ctx context.Context, fqdn string) {
|
||||
response, err := t.tryOneName(ctx, servers, fqdn, message)
|
||||
if err == nil {
|
||||
if response.Rcode != mDNS.RcodeSuccess {
|
||||
err = dns.RcodeError(response.Rcode)
|
||||
} else if len(dns.MessageToAddresses(response)) == 0 {
|
||||
err = dns.RcodeSuccess
|
||||
}
|
||||
}
|
||||
select {
|
||||
case results <- queryResult{response, err}:
|
||||
case <-returned:
|
||||
}
|
||||
}
|
||||
queryCtx, queryCancel := context.WithCancel(ctx)
|
||||
defer queryCancel()
|
||||
var nameCount int
|
||||
for _, fqdn := range t.nameList(domain) {
|
||||
nameCount++
|
||||
go startRacer(queryCtx, fqdn)
|
||||
}
|
||||
var errors []error
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return nil, ctx.Err()
|
||||
case result := <-results:
|
||||
if result.err == nil {
|
||||
return result.response, nil
|
||||
}
|
||||
errors = append(errors, result.err)
|
||||
if len(errors) == nameCount {
|
||||
return nil, E.Errors(errors...)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (t *Transport) tryOneName(ctx context.Context, servers []M.Socksaddr, fqdn string, message *mDNS.Msg) (*mDNS.Msg, error) {
|
||||
sLen := len(servers)
|
||||
var lastErr error
|
||||
for i := 0; i < t.attempts; i++ {
|
||||
for j := 0; j < sLen; j++ {
|
||||
server := servers[j]
|
||||
question := message.Question[0]
|
||||
question.Name = fqdn
|
||||
response, err := t.exchangeOne(ctx, server, question)
|
||||
if err != nil {
|
||||
lastErr = err
|
||||
continue
|
||||
}
|
||||
return response, nil
|
||||
}
|
||||
}
|
||||
return nil, E.Cause(lastErr, fqdn)
|
||||
}
|
||||
|
||||
func (t *Transport) exchangeOne(ctx context.Context, server M.Socksaddr, question mDNS.Question) (*mDNS.Msg, error) {
|
||||
if server.Port == 0 {
|
||||
server.Port = 53
|
||||
}
|
||||
request := &mDNS.Msg{
|
||||
MsgHdr: mDNS.MsgHdr{
|
||||
Id: uint16(rand.Uint32()),
|
||||
RecursionDesired: true,
|
||||
AuthenticatedData: true,
|
||||
},
|
||||
Question: []mDNS.Question{question},
|
||||
Compress: true,
|
||||
}
|
||||
request.SetEdns0(buf.UDPBufferSize, false)
|
||||
return t.exchangeUDP(ctx, server, request)
|
||||
}
|
||||
|
||||
func (t *Transport) exchangeUDP(ctx context.Context, server M.Socksaddr, request *mDNS.Msg) (*mDNS.Msg, error) {
|
||||
conn, err := t.dialer.DialContext(ctx, N.NetworkUDP, server)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer conn.Close()
|
||||
if deadline, loaded := ctx.Deadline(); loaded && !deadline.IsZero() {
|
||||
conn.SetDeadline(deadline)
|
||||
}
|
||||
buffer := buf.Get(buf.UDPBufferSize)
|
||||
defer buf.Put(buffer)
|
||||
rawMessage, err := request.PackBuffer(buffer)
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "pack request")
|
||||
}
|
||||
_, err = conn.Write(rawMessage)
|
||||
if err != nil {
|
||||
if errors.Is(err, syscall.EMSGSIZE) {
|
||||
return t.exchangeTCP(ctx, server, request)
|
||||
}
|
||||
return nil, E.Cause(err, "write request")
|
||||
}
|
||||
n, err := conn.Read(buffer)
|
||||
if err != nil {
|
||||
if errors.Is(err, syscall.EMSGSIZE) {
|
||||
return t.exchangeTCP(ctx, server, request)
|
||||
}
|
||||
return nil, E.Cause(err, "read response")
|
||||
}
|
||||
var response mDNS.Msg
|
||||
err = response.Unpack(buffer[:n])
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "unpack response")
|
||||
}
|
||||
if response.Truncated {
|
||||
return t.exchangeTCP(ctx, server, request)
|
||||
}
|
||||
return &response, nil
|
||||
}
|
||||
|
||||
func (t *Transport) exchangeTCP(ctx context.Context, server M.Socksaddr, request *mDNS.Msg) (*mDNS.Msg, error) {
|
||||
conn, err := t.dialer.DialContext(ctx, N.NetworkTCP, server)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer conn.Close()
|
||||
if deadline, loaded := ctx.Deadline(); loaded && !deadline.IsZero() {
|
||||
conn.SetDeadline(deadline)
|
||||
}
|
||||
err = transport.WriteMessage(conn, 0, request)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return transport.ReadMessage(conn)
|
||||
}
|
||||
|
||||
func (t *Transport) nameList(name string) []string {
|
||||
l := len(name)
|
||||
rooted := l > 0 && name[l-1] == '.'
|
||||
if l > 254 || l == 254 && !rooted {
|
||||
return nil
|
||||
}
|
||||
|
||||
if rooted {
|
||||
if avoidDNS(name) {
|
||||
return nil
|
||||
}
|
||||
return []string{name}
|
||||
}
|
||||
|
||||
hasNdots := strings.Count(name, ".") >= t.ndots
|
||||
name += "."
|
||||
// l++
|
||||
|
||||
names := make([]string, 0, 1+len(t.search))
|
||||
if hasNdots && !avoidDNS(name) {
|
||||
names = append(names, name)
|
||||
}
|
||||
for _, suffix := range t.search {
|
||||
fqdn := name + suffix
|
||||
if !avoidDNS(fqdn) && len(fqdn) <= 254 {
|
||||
names = append(names, fqdn)
|
||||
}
|
||||
}
|
||||
if !hasNdots && !avoidDNS(name) {
|
||||
names = append(names, name)
|
||||
}
|
||||
return names
|
||||
}
|
||||
|
||||
func avoidDNS(name string) bool {
|
||||
if name == "" {
|
||||
return true
|
||||
}
|
||||
if name[len(name)-1] == '.' {
|
||||
name = name[:len(name)-1]
|
||||
}
|
||||
return strings.HasSuffix(name, ".onion")
|
||||
}
|
||||
@@ -8,7 +8,6 @@ import (
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
@@ -26,7 +25,6 @@ import (
|
||||
"github.com/sagernet/sing/common/logger"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
aTLS "github.com/sagernet/sing/common/tls"
|
||||
sHTTP "github.com/sagernet/sing/protocol/http"
|
||||
|
||||
mDNS "github.com/miekg/dns"
|
||||
@@ -48,7 +46,7 @@ type HTTPSTransport struct {
|
||||
destination *url.URL
|
||||
headers http.Header
|
||||
transportAccess sync.Mutex
|
||||
transport *http.Transport
|
||||
transport *HTTPSTransportWrapper
|
||||
transportResetAt time.Time
|
||||
}
|
||||
|
||||
@@ -63,11 +61,8 @@ func NewHTTPS(ctx context.Context, logger log.ContextLogger, tag string, options
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if common.Error(tlsConfig.Config()) == nil && !common.Contains(tlsConfig.NextProtos(), http2.NextProtoTLS) {
|
||||
tlsConfig.SetNextProtos(append(tlsConfig.NextProtos(), http2.NextProtoTLS))
|
||||
}
|
||||
if !common.Contains(tlsConfig.NextProtos(), "http/1.1") {
|
||||
tlsConfig.SetNextProtos(append(tlsConfig.NextProtos(), "http/1.1"))
|
||||
if len(tlsConfig.NextProtos()) == 0 {
|
||||
tlsConfig.SetNextProtos([]string{http2.NextProtoTLS, "http/1.1"})
|
||||
}
|
||||
headers := options.Headers.Build()
|
||||
host := headers.Get("Host")
|
||||
@@ -125,37 +120,13 @@ func NewHTTPSRaw(
|
||||
serverAddr M.Socksaddr,
|
||||
tlsConfig tls.Config,
|
||||
) *HTTPSTransport {
|
||||
var transport *http.Transport
|
||||
if tlsConfig != nil {
|
||||
transport = &http.Transport{
|
||||
ForceAttemptHTTP2: true,
|
||||
DialTLSContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||
tcpConn, hErr := dialer.DialContext(ctx, network, serverAddr)
|
||||
if hErr != nil {
|
||||
return nil, hErr
|
||||
}
|
||||
tlsConn, hErr := aTLS.ClientHandshake(ctx, tcpConn, tlsConfig)
|
||||
if hErr != nil {
|
||||
tcpConn.Close()
|
||||
return nil, hErr
|
||||
}
|
||||
return tlsConn, nil
|
||||
},
|
||||
}
|
||||
} else {
|
||||
transport = &http.Transport{
|
||||
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||
return dialer.DialContext(ctx, network, serverAddr)
|
||||
},
|
||||
}
|
||||
}
|
||||
return &HTTPSTransport{
|
||||
TransportAdapter: adapter,
|
||||
logger: logger,
|
||||
dialer: dialer,
|
||||
destination: destination,
|
||||
headers: headers,
|
||||
transport: transport,
|
||||
transport: NewHTTPSTransportWrapper(tls.NewDialer(dialer, tlsConfig), serverAddr),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -178,7 +149,7 @@ func (t *HTTPSTransport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS
|
||||
startAt := time.Now()
|
||||
response, err := t.exchange(ctx, message)
|
||||
if err != nil {
|
||||
if errors.Is(err, os.ErrDeadlineExceeded) {
|
||||
if errors.Is(err, context.DeadlineExceeded) {
|
||||
t.transportAccess.Lock()
|
||||
defer t.transportAccess.Unlock()
|
||||
if t.transportResetAt.After(startAt) {
|
||||
|
||||
80
dns/transport/https_transport.go
Normal file
80
dns/transport/https_transport.go
Normal file
@@ -0,0 +1,80 @@
|
||||
package transport
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net"
|
||||
"net/http"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/sagernet/sing-box/common/tls"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
|
||||
"golang.org/x/net/http2"
|
||||
)
|
||||
|
||||
var errFallback = E.New("fallback to HTTP/1.1")
|
||||
|
||||
type HTTPSTransportWrapper struct {
|
||||
http2Transport *http2.Transport
|
||||
httpTransport *http.Transport
|
||||
fallback *atomic.Bool
|
||||
}
|
||||
|
||||
func NewHTTPSTransportWrapper(dialer tls.Dialer, serverAddr M.Socksaddr) *HTTPSTransportWrapper {
|
||||
var fallback atomic.Bool
|
||||
return &HTTPSTransportWrapper{
|
||||
http2Transport: &http2.Transport{
|
||||
DialTLSContext: func(ctx context.Context, _, _ string, _ *tls.STDConfig) (net.Conn, error) {
|
||||
tlsConn, err := dialer.DialTLSContext(ctx, serverAddr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
state := tlsConn.ConnectionState()
|
||||
if state.NegotiatedProtocol == http2.NextProtoTLS {
|
||||
return tlsConn, nil
|
||||
}
|
||||
tlsConn.Close()
|
||||
fallback.Store(true)
|
||||
return nil, errFallback
|
||||
},
|
||||
},
|
||||
httpTransport: &http.Transport{
|
||||
DialTLSContext: func(ctx context.Context, _, _ string) (net.Conn, error) {
|
||||
return dialer.DialTLSContext(ctx, serverAddr)
|
||||
},
|
||||
},
|
||||
fallback: &fallback,
|
||||
}
|
||||
}
|
||||
|
||||
func (h *HTTPSTransportWrapper) RoundTrip(request *http.Request) (*http.Response, error) {
|
||||
if h.fallback.Load() {
|
||||
return h.httpTransport.RoundTrip(request)
|
||||
} else {
|
||||
response, err := h.http2Transport.RoundTrip(request)
|
||||
if err != nil {
|
||||
if errors.Is(err, errFallback) {
|
||||
return h.httpTransport.RoundTrip(request)
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return response, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (h *HTTPSTransportWrapper) CloseIdleConnections() {
|
||||
h.http2Transport.CloseIdleConnections()
|
||||
h.httpTransport.CloseIdleConnections()
|
||||
}
|
||||
|
||||
func (h *HTTPSTransportWrapper) Clone() *HTTPSTransportWrapper {
|
||||
return &HTTPSTransportWrapper{
|
||||
httpTransport: h.httpTransport,
|
||||
http2Transport: &http2.Transport{
|
||||
DialTLSContext: h.http2Transport.DialTLSContext,
|
||||
},
|
||||
fallback: h.fallback,
|
||||
}
|
||||
}
|
||||
@@ -2,12 +2,15 @@ package local
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"math/rand"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"github.com/sagernet/sing-box/dns"
|
||||
"github.com/sagernet/sing-box/dns/transport"
|
||||
"github.com/sagernet/sing-box/dns/transport/hosts"
|
||||
"github.com/sagernet/sing-box/log"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
@@ -93,7 +96,7 @@ func (t *Transport) exchangeParallel(ctx context.Context, systemConfig *dnsConfi
|
||||
if response.Rcode != mDNS.RcodeSuccess {
|
||||
err = dns.RcodeError(response.Rcode)
|
||||
} else if len(dns.MessageToAddresses(response)) == 0 {
|
||||
err = E.New(fqdn, ": empty result")
|
||||
err = dns.RcodeSuccess
|
||||
}
|
||||
}
|
||||
select {
|
||||
@@ -149,12 +152,6 @@ func (t *Transport) exchangeOne(ctx context.Context, server M.Socksaddr, questio
|
||||
if server.Port == 0 {
|
||||
server.Port = 53
|
||||
}
|
||||
var networks []string
|
||||
if useTCP {
|
||||
networks = []string{N.NetworkTCP}
|
||||
} else {
|
||||
networks = []string{N.NetworkUDP, N.NetworkTCP}
|
||||
}
|
||||
request := &mDNS.Msg{
|
||||
MsgHdr: mDNS.MsgHdr{
|
||||
Id: uint16(rand.Uint32()),
|
||||
@@ -164,41 +161,74 @@ func (t *Transport) exchangeOne(ctx context.Context, server M.Socksaddr, questio
|
||||
Question: []mDNS.Question{question},
|
||||
Compress: true,
|
||||
}
|
||||
request.SetEdns0(maxDNSPacketSize, false)
|
||||
request.SetEdns0(buf.UDPBufferSize, false)
|
||||
if !useTCP {
|
||||
return t.exchangeUDP(ctx, server, request, timeout)
|
||||
} else {
|
||||
return t.exchangeTCP(ctx, server, request, timeout)
|
||||
}
|
||||
}
|
||||
|
||||
func (t *Transport) exchangeUDP(ctx context.Context, server M.Socksaddr, request *mDNS.Msg, timeout time.Duration) (*mDNS.Msg, error) {
|
||||
conn, err := t.dialer.DialContext(ctx, N.NetworkUDP, server)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer conn.Close()
|
||||
if deadline, loaded := ctx.Deadline(); loaded && !deadline.IsZero() {
|
||||
newDeadline := time.Now().Add(timeout)
|
||||
if deadline.After(newDeadline) {
|
||||
deadline = newDeadline
|
||||
}
|
||||
conn.SetDeadline(deadline)
|
||||
}
|
||||
buffer := buf.Get(buf.UDPBufferSize)
|
||||
defer buf.Put(buffer)
|
||||
for _, network := range networks {
|
||||
ctx, cancel := context.WithDeadline(ctx, time.Now().Add(timeout))
|
||||
defer cancel()
|
||||
conn, err := t.dialer.DialContext(ctx, network, server)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer conn.Close()
|
||||
if deadline, loaded := ctx.Deadline(); loaded && !deadline.IsZero() {
|
||||
conn.SetDeadline(deadline)
|
||||
}
|
||||
rawMessage, err := request.PackBuffer(buffer)
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "pack request")
|
||||
}
|
||||
_, err = conn.Write(rawMessage)
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "write request")
|
||||
}
|
||||
n, err := conn.Read(buffer)
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "read response")
|
||||
}
|
||||
var response mDNS.Msg
|
||||
err = response.Unpack(buffer[:n])
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "unpack response")
|
||||
}
|
||||
if response.Truncated && network == N.NetworkUDP {
|
||||
continue
|
||||
}
|
||||
return &response, nil
|
||||
rawMessage, err := request.PackBuffer(buffer)
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "pack request")
|
||||
}
|
||||
panic("unexpected")
|
||||
_, err = conn.Write(rawMessage)
|
||||
if err != nil {
|
||||
if errors.Is(err, syscall.EMSGSIZE) {
|
||||
return t.exchangeTCP(ctx, server, request, timeout)
|
||||
}
|
||||
return nil, E.Cause(err, "write request")
|
||||
}
|
||||
n, err := conn.Read(buffer)
|
||||
if err != nil {
|
||||
if errors.Is(err, syscall.EMSGSIZE) {
|
||||
return t.exchangeTCP(ctx, server, request, timeout)
|
||||
}
|
||||
return nil, E.Cause(err, "read response")
|
||||
}
|
||||
var response mDNS.Msg
|
||||
err = response.Unpack(buffer[:n])
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "unpack response")
|
||||
}
|
||||
if response.Truncated {
|
||||
return t.exchangeTCP(ctx, server, request, timeout)
|
||||
}
|
||||
return &response, nil
|
||||
}
|
||||
|
||||
func (t *Transport) exchangeTCP(ctx context.Context, server M.Socksaddr, request *mDNS.Msg, timeout time.Duration) (*mDNS.Msg, error) {
|
||||
conn, err := t.dialer.DialContext(ctx, N.NetworkTCP, server)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer conn.Close()
|
||||
if deadline, loaded := ctx.Deadline(); loaded && !deadline.IsZero() {
|
||||
newDeadline := time.Now().Add(timeout)
|
||||
if deadline.After(newDeadline) {
|
||||
deadline = newDeadline
|
||||
}
|
||||
conn.SetDeadline(deadline)
|
||||
}
|
||||
err = transport.WriteMessage(conn, 0, request)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return transport.ReadMessage(conn)
|
||||
}
|
||||
|
||||
@@ -10,11 +10,6 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
// net.maxDNSPacketSize
|
||||
maxDNSPacketSize = 1232
|
||||
)
|
||||
|
||||
type resolverConfig struct {
|
||||
initOnce sync.Once
|
||||
ch chan struct{}
|
||||
@@ -104,7 +99,7 @@ func (c *dnsConfig) serverOffset() uint32 {
|
||||
return 0
|
||||
}
|
||||
|
||||
func (conf *dnsConfig) nameList(name string) []string {
|
||||
func (c *dnsConfig) nameList(name string) []string {
|
||||
l := len(name)
|
||||
rooted := l > 0 && name[l-1] == '.'
|
||||
if l > 254 || l == 254 && !rooted {
|
||||
@@ -118,15 +113,15 @@ func (conf *dnsConfig) nameList(name string) []string {
|
||||
return []string{name}
|
||||
}
|
||||
|
||||
hasNdots := strings.Count(name, ".") >= conf.ndots
|
||||
hasNdots := strings.Count(name, ".") >= c.ndots
|
||||
name += "."
|
||||
// l++
|
||||
|
||||
names := make([]string, 0, 1+len(conf.search))
|
||||
names := make([]string, 0, 1+len(c.search))
|
||||
if hasNdots && !avoidDNS(name) {
|
||||
names = append(names, name)
|
||||
}
|
||||
for _, suffix := range conf.search {
|
||||
for _, suffix := range c.search {
|
||||
fqdn := name + suffix
|
||||
if !avoidDNS(fqdn) && len(fqdn) <= 254 {
|
||||
names = append(names, fqdn)
|
||||
|
||||
@@ -2,6 +2,40 @@
|
||||
icon: material/alert-decagram
|
||||
---
|
||||
|
||||
#### 1.12.10
|
||||
|
||||
* Update uTLS to v1.8.1 **1**
|
||||
* Fixes and improvements
|
||||
|
||||
**1**:
|
||||
|
||||
This update fixes an critical issue that could cause simulated Chrome fingerprints to be detected,
|
||||
see https://github.com/refraction-networking/utls/pull/375.
|
||||
|
||||
#### 1.12.9
|
||||
|
||||
* Fixes and improvements
|
||||
|
||||
#### 1.12.8
|
||||
|
||||
* Fixes and improvements
|
||||
|
||||
#### 1.12.5
|
||||
|
||||
* Fixes and improvements
|
||||
|
||||
#### 1.12.4
|
||||
|
||||
* Fixes and improvements
|
||||
|
||||
#### 1.12.3
|
||||
|
||||
* Fixes and improvements
|
||||
|
||||
#### 1.12.2
|
||||
|
||||
* Fixes and improvements
|
||||
|
||||
#### 1.12.1
|
||||
|
||||
* Fixes and improvements
|
||||
@@ -72,7 +106,8 @@ See [Tailscale](/configuration/endpoint/tailscale/).
|
||||
|
||||
Due to maintenance difficulties, sing-box 1.12.0 requires at least Go 1.23 to compile.
|
||||
|
||||
For Windows 7 users, legacy binaries now continue to compile with Go 1.23 and patches from [MetaCubeX/go](https://github.com/MetaCubeX/go).
|
||||
For Windows 7 users, legacy binaries now continue to compile with Go 1.23 and patches
|
||||
from [MetaCubeX/go](https://github.com/MetaCubeX/go).
|
||||
|
||||
**7**:
|
||||
|
||||
@@ -134,7 +169,8 @@ See [Tun](/configuration/inbound/tun/#loopback_address).
|
||||
|
||||
We have significantly improved the performance of tun inbound on Apple platforms, especially in the gVisor stack.
|
||||
|
||||
The following data was tested using [tun_bench](https://github.com/SagerNet/sing-box/blob/dev-next/cmd/internal/tun_bench/main.go) on M4 MacBook pro.
|
||||
The following data was tested
|
||||
using [tun_bench](https://github.com/SagerNet/sing-box/blob/dev-next/cmd/internal/tun_bench/main.go) on M4 MacBook pro.
|
||||
|
||||
| Version | Stack | MTU | Upload | Download |
|
||||
|-------------|--------|-------|--------|----------|
|
||||
@@ -153,8 +189,8 @@ The following data was tested using [tun_bench](https://github.com/SagerNet/sing
|
||||
|
||||
**18**:
|
||||
|
||||
We continue to experience issues updating our sing-box apps on the App Store and Play Store.
|
||||
Until we rewrite and resubmit the apps, they are considered irrecoverable.
|
||||
We continue to experience issues updating our sing-box apps on the App Store and Play Store.
|
||||
Until we rewrite and resubmit the apps, they are considered irrecoverable.
|
||||
Therefore, after this release, we will not be repeating this notice unless there is new information.
|
||||
|
||||
### 1.11.15
|
||||
@@ -435,7 +471,8 @@ See [AnyTLS Inbound](/configuration/inbound/anytls/) and [AnyTLS Outbound](/conf
|
||||
|
||||
**2**:
|
||||
|
||||
`resolve` route action now accepts `disable_cache` and other options like in DNS route actions, see [Route Action](/configuration/route/rule_action).
|
||||
`resolve` route action now accepts `disable_cache` and other options like in DNS route actions,
|
||||
see [Route Action](/configuration/route/rule_action).
|
||||
|
||||
**3**:
|
||||
|
||||
@@ -466,7 +503,8 @@ See [Tailscale](/configuration/endpoint/tailscale/).
|
||||
|
||||
Due to maintenance difficulties, sing-box 1.12.0 requires at least Go 1.23 to compile.
|
||||
|
||||
For Windows 7 users, legacy binaries now continue to compile with Go 1.23 and patches from [MetaCubeX/go](https://github.com/MetaCubeX/go).
|
||||
For Windows 7 users, legacy binaries now continue to compile with Go 1.23 and patches
|
||||
from [MetaCubeX/go](https://github.com/MetaCubeX/go).
|
||||
|
||||
### 1.11.3
|
||||
|
||||
|
||||
@@ -61,7 +61,7 @@ WireGuard MTU。
|
||||
|
||||
==必填==
|
||||
|
||||
接口的 IPv4/IPv6 地址或地址段的列表您。
|
||||
接口的 IPv4/IPv6 地址或地址段的列表。
|
||||
|
||||
要分配给接口的 IP(v4 或 v6)地址段列表。
|
||||
|
||||
|
||||
@@ -88,7 +88,7 @@ icon: material/delete-clock
|
||||
|
||||
==必填==
|
||||
|
||||
接口的 IPv4/IPv6 地址或地址段的列表您。
|
||||
接口的 IPv4/IPv6 地址或地址段的列表。
|
||||
|
||||
要分配给接口的 IP(v4 或 v6)地址段列表。
|
||||
|
||||
|
||||
@@ -108,7 +108,7 @@ flowchart TB
|
||||
"inbounds": [
|
||||
{
|
||||
"type": "tun",
|
||||
"inet4_address": "172.19.0.1/30",
|
||||
"address": ["172.19.0.1/30"],
|
||||
"auto_route": true,
|
||||
// "auto_redirect": true, // On linux
|
||||
"strict_route": true
|
||||
@@ -162,8 +162,7 @@ flowchart TB
|
||||
"inbounds": [
|
||||
{
|
||||
"type": "tun",
|
||||
"inet4_address": "172.19.0.1/30",
|
||||
"inet6_address": "fdfe:dcba:9876::1/126",
|
||||
"address": ["172.19.0.1/30", "fdfe:dcba:9876::1/126"],
|
||||
"auto_route": true,
|
||||
// "auto_redirect": true, // On linux
|
||||
"strict_route": true
|
||||
@@ -233,8 +232,7 @@ flowchart TB
|
||||
"inbounds": [
|
||||
{
|
||||
"type": "tun",
|
||||
"inet4_address": "172.19.0.1/30",
|
||||
"inet6_address": "fdfe:dcba:9876::1/126",
|
||||
"address": ["172.19.0.1/30","fdfe:dcba:9876::1/126"],
|
||||
"auto_route": true,
|
||||
// "auto_redirect": true, // On linux
|
||||
"strict_route": true
|
||||
|
||||
@@ -3,12 +3,12 @@ package trafficontrol
|
||||
import (
|
||||
"runtime"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/sing-box/common/compatible"
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"github.com/sagernet/sing-box/experimental/clashapi/compatible"
|
||||
"github.com/sagernet/sing/common"
|
||||
"github.com/sagernet/sing/common/atomic"
|
||||
"github.com/sagernet/sing/common/json"
|
||||
"github.com/sagernet/sing/common/x/list"
|
||||
|
||||
|
||||
@@ -2,11 +2,11 @@ package trafficontrol
|
||||
|
||||
import (
|
||||
"net"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing/common"
|
||||
"github.com/sagernet/sing/common/atomic"
|
||||
"github.com/sagernet/sing/common/bufio"
|
||||
F "github.com/sagernet/sing/common/format"
|
||||
"github.com/sagernet/sing/common/json"
|
||||
|
||||
@@ -62,10 +62,7 @@ func (s *CommandServer) handleURLTest(conn net.Conn) error {
|
||||
return false
|
||||
}
|
||||
_, isGroup := it.(adapter.OutboundGroup)
|
||||
if isGroup {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
return !isGroup
|
||||
})
|
||||
b, _ := batch.New(serviceNow.ctx, batch.WithConcurrencyNum[any](10))
|
||||
for _, detour := range outbounds {
|
||||
|
||||
@@ -22,6 +22,7 @@ import (
|
||||
"github.com/sagernet/sing/common/logger"
|
||||
"github.com/sagernet/sing/common/x/list"
|
||||
"github.com/sagernet/sing/service"
|
||||
"github.com/sagernet/sing/service/filemanager"
|
||||
)
|
||||
|
||||
func BaseContext(platformInterface PlatformInterface) context.Context {
|
||||
@@ -33,7 +34,9 @@ func BaseContext(platformInterface PlatformInterface) context.Context {
|
||||
})
|
||||
}
|
||||
}
|
||||
return box.Context(context.Background(), include.InboundRegistry(), include.OutboundRegistry(), include.EndpointRegistry(), dnsRegistry, include.ServiceRegistry())
|
||||
ctx := context.Background()
|
||||
ctx = filemanager.WithDefault(ctx, sWorkingPath, sTempPath, sUserID, sGroupID)
|
||||
return box.Context(ctx, include.InboundRegistry(), include.OutboundRegistry(), include.EndpointRegistry(), dnsRegistry, include.ServiceRegistry())
|
||||
}
|
||||
|
||||
func parseConfig(ctx context.Context, configContent string) (option.Options, error) {
|
||||
|
||||
@@ -18,6 +18,7 @@ func newIterator[T any](values []T) *iterator[T] {
|
||||
return &iterator[T]{values}
|
||||
}
|
||||
|
||||
//go:noinline
|
||||
func newPtrIterator[T any](values []T) *iterator[*T] {
|
||||
return &iterator[*T]{common.Map(values, func(value T) *T { return &value })}
|
||||
}
|
||||
|
||||
@@ -27,7 +27,6 @@ import (
|
||||
"github.com/sagernet/sing/common/logger"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
"github.com/sagernet/sing/service"
|
||||
"github.com/sagernet/sing/service/filemanager"
|
||||
"github.com/sagernet/sing/service/pause"
|
||||
)
|
||||
|
||||
@@ -44,7 +43,6 @@ type BoxService struct {
|
||||
|
||||
func NewService(configContent string, platformInterface PlatformInterface) (*BoxService, error) {
|
||||
ctx := BaseContext(platformInterface)
|
||||
ctx = filemanager.WithDefault(ctx, sWorkingPath, sTempPath, sUserID, sGroupID)
|
||||
service.MustRegister[deprecated.Manager](ctx, new(deprecatedManager))
|
||||
options, err := parseConfig(ctx, configContent)
|
||||
if err != nil {
|
||||
|
||||
@@ -12,9 +12,7 @@ type iOSPauseFields struct {
|
||||
|
||||
func (s *BoxService) Pause() {
|
||||
s.pauseManager.DevicePause()
|
||||
if !C.IsIos {
|
||||
s.instance.Router().ResetNetwork()
|
||||
} else {
|
||||
if C.IsIos {
|
||||
if s.endPauseTimer == nil {
|
||||
s.endPauseTimer = time.AfterFunc(time.Minute, s.pauseManager.DeviceWake)
|
||||
} else {
|
||||
@@ -26,7 +24,6 @@ func (s *BoxService) Pause() {
|
||||
func (s *BoxService) Wake() {
|
||||
if !C.IsIos {
|
||||
s.pauseManager.DeviceWake()
|
||||
s.instance.Router().ResetNetwork()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,11 +7,11 @@ import (
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
"github.com/sagernet/sing/common/atomic"
|
||||
"github.com/sagernet/sing/common/bufio"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
@@ -115,7 +115,7 @@ func (s *StatsService) RoutedPacketConnection(ctx context.Context, conn N.Packet
|
||||
writeCounter = append(writeCounter, s.loadOrCreateCounter("user>>>"+user+">>>traffic>>>downlink"))
|
||||
}
|
||||
s.access.Unlock()
|
||||
return bufio.NewInt64CounterPacketConn(conn, readCounter, writeCounter)
|
||||
return bufio.NewInt64CounterPacketConn(conn, readCounter, nil, writeCounter, nil)
|
||||
}
|
||||
|
||||
func (s *StatsService) GetStats(ctx context.Context, request *GetStatsRequest) (*GetStatsResponse, error) {
|
||||
@@ -192,7 +192,7 @@ func (s *StatsService) GetSysStats(ctx context.Context, request *SysStatsRequest
|
||||
var rtm runtime.MemStats
|
||||
runtime.ReadMemStats(&rtm)
|
||||
response := &SysStatsResponse{
|
||||
Uptime: uint32(time.Now().Sub(s.createdAt).Seconds()),
|
||||
Uptime: uint32(time.Since(s.createdAt).Seconds()),
|
||||
NumGoroutine: uint32(runtime.NumGoroutine()),
|
||||
Alloc: rtm.Alloc,
|
||||
TotalAlloc: rtm.TotalAlloc,
|
||||
|
||||
36
go.mod
36
go.mod
@@ -3,7 +3,7 @@ module github.com/sagernet/sing-box
|
||||
go 1.23.1
|
||||
|
||||
require (
|
||||
github.com/anytls/sing-anytls v0.0.8
|
||||
github.com/anytls/sing-anytls v0.0.11
|
||||
github.com/caddyserver/certmagic v0.23.0
|
||||
github.com/coder/websocket v1.8.13
|
||||
github.com/cretz/bine v0.2.0
|
||||
@@ -15,8 +15,8 @@ require (
|
||||
github.com/libdns/alidns v1.0.5-libdns.v1.beta1
|
||||
github.com/libdns/cloudflare v0.2.2-0.20250708034226-c574dccb31a6
|
||||
github.com/logrusorgru/aurora v2.0.3+incompatible
|
||||
github.com/metacubex/tfo-go v0.0.0-20241231083714-66613d49c422
|
||||
github.com/metacubex/utls v1.8.0
|
||||
github.com/metacubex/tfo-go v0.0.0-20250516165257-e29c16ae41d4
|
||||
github.com/metacubex/utls v1.8.2
|
||||
github.com/mholt/acmez/v3 v3.1.2
|
||||
github.com/miekg/dns v1.1.67
|
||||
github.com/oschwald/maxminddb-golang v1.13.1
|
||||
@@ -24,19 +24,19 @@ require (
|
||||
github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a
|
||||
github.com/sagernet/cors v1.2.1
|
||||
github.com/sagernet/fswatch v0.1.1
|
||||
github.com/sagernet/gomobile v0.1.7
|
||||
github.com/sagernet/gomobile v0.1.8
|
||||
github.com/sagernet/gvisor v0.0.0-20250325023245-7a9c0f5725fb
|
||||
github.com/sagernet/quic-go v0.52.0-beta.1
|
||||
github.com/sagernet/sing v0.7.5
|
||||
github.com/sagernet/sing-mux v0.3.2
|
||||
github.com/sagernet/sing-quic v0.5.0-beta.3
|
||||
github.com/sagernet/quic-go v0.52.0-sing-box-mod.2
|
||||
github.com/sagernet/sing v0.7.12
|
||||
github.com/sagernet/sing-mux v0.3.3
|
||||
github.com/sagernet/sing-quic v0.5.2-0.20250909083218-00a55617c0fb
|
||||
github.com/sagernet/sing-shadowsocks v0.2.8
|
||||
github.com/sagernet/sing-shadowsocks2 v0.2.1
|
||||
github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11
|
||||
github.com/sagernet/sing-tun v0.7.0-beta.1
|
||||
github.com/sagernet/sing-vmess v0.2.6
|
||||
github.com/sagernet/sing-tun v0.7.2
|
||||
github.com/sagernet/sing-vmess v0.2.7
|
||||
github.com/sagernet/smux v1.5.34-mod.2
|
||||
github.com/sagernet/tailscale v1.80.3-mod.5
|
||||
github.com/sagernet/tailscale v1.80.3-sing-box-1.12-mod.1
|
||||
github.com/sagernet/wireguard-go v0.0.1-beta.7
|
||||
github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854
|
||||
github.com/spf13/cobra v1.9.1
|
||||
@@ -44,11 +44,11 @@ require (
|
||||
github.com/vishvananda/netns v0.0.5
|
||||
go.uber.org/zap v1.27.0
|
||||
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba
|
||||
golang.org/x/crypto v0.40.0
|
||||
golang.org/x/crypto v0.41.0
|
||||
golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6
|
||||
golang.org/x/mod v0.26.0
|
||||
golang.org/x/net v0.42.0
|
||||
golang.org/x/sys v0.34.0
|
||||
golang.org/x/mod v0.27.0
|
||||
golang.org/x/net v0.43.0
|
||||
golang.org/x/sys v0.35.0
|
||||
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20241231184526-a9ab2273dd10
|
||||
google.golang.org/grpc v1.73.0
|
||||
google.golang.org/protobuf v1.36.6
|
||||
@@ -123,10 +123,10 @@ require (
|
||||
go.uber.org/zap/exp v0.3.0 // indirect
|
||||
go4.org/mem v0.0.0-20240501181205-ae6ca9944745 // indirect
|
||||
golang.org/x/sync v0.16.0 // indirect
|
||||
golang.org/x/term v0.33.0 // indirect
|
||||
golang.org/x/text v0.27.0 // indirect
|
||||
golang.org/x/term v0.34.0 // indirect
|
||||
golang.org/x/text v0.28.0 // indirect
|
||||
golang.org/x/time v0.9.0 // indirect
|
||||
golang.org/x/tools v0.34.0 // indirect
|
||||
golang.org/x/tools v0.36.0 // indirect
|
||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
|
||||
golang.zx2c4.com/wireguard/windows v0.5.3 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250324211829-b45e905df463 // indirect
|
||||
|
||||
70
go.sum
70
go.sum
@@ -8,8 +8,8 @@ github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa h1:LHTHcTQiSGT7V
|
||||
github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa/go.mod h1:cEWa1LVoE5KvSD9ONXsZrj0z6KqySlCCNKHlLzbqAt4=
|
||||
github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M=
|
||||
github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY=
|
||||
github.com/anytls/sing-anytls v0.0.8 h1:1u/fnH1HoeeMV5mX7/eUOjLBvPdkd1UJRmXiRi6Vymc=
|
||||
github.com/anytls/sing-anytls v0.0.8/go.mod h1:7rjN6IukwysmdusYsrV51Fgu1uW6vsrdd6ctjnEAln8=
|
||||
github.com/anytls/sing-anytls v0.0.11 h1:w8e9Uj1oP3m4zxkyZDewPk0EcQbvVxb7Nn+rapEx4fc=
|
||||
github.com/anytls/sing-anytls v0.0.11/go.mod h1:7rjN6IukwysmdusYsrV51Fgu1uW6vsrdd6ctjnEAln8=
|
||||
github.com/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/caddyserver/certmagic v0.23.0 h1:CfpZ/50jMfG4+1J/u2LV6piJq4HOfO6ppOnOf7DkFEU=
|
||||
@@ -122,10 +122,12 @@ github.com/mdlayher/sdnotify v1.0.0 h1:Ma9XeLVN/l0qpyx1tNeMSeTjCPH6NtuD6/N9XdTlQ
|
||||
github.com/mdlayher/sdnotify v1.0.0/go.mod h1:HQUmpM4XgYkhDLtd+Uad8ZFK1T9D5+pNxnXQjCeJlGE=
|
||||
github.com/mdlayher/socket v0.5.1 h1:VZaqt6RkGkt2OE9l3GcC6nZkqD3xKeQLyfleW/uBcos=
|
||||
github.com/mdlayher/socket v0.5.1/go.mod h1:TjPLHI1UgwEv5J1B5q0zTZq12A/6H7nKmtTanQE37IQ=
|
||||
github.com/metacubex/tfo-go v0.0.0-20241231083714-66613d49c422 h1:zGeQt3UyNydIVrMRB97AA5WsYEau/TyCnRtTf1yUmJY=
|
||||
github.com/metacubex/tfo-go v0.0.0-20241231083714-66613d49c422/go.mod h1:l9oLnLoEXyGZ5RVLsh7QCC5XsouTUyKk4F2nLm2DHLw=
|
||||
github.com/metacubex/tfo-go v0.0.0-20250516165257-e29c16ae41d4 h1:j1VRTiC9JLR4nUbSikx9OGdu/3AgFDqgcLj4GoqyQkc=
|
||||
github.com/metacubex/tfo-go v0.0.0-20250516165257-e29c16ae41d4/go.mod h1:l9oLnLoEXyGZ5RVLsh7QCC5XsouTUyKk4F2nLm2DHLw=
|
||||
github.com/metacubex/utls v1.8.0 h1:mSYi6FMnmc5riARl5UZDmWVy710z+P5b7xuGW0lV9ac=
|
||||
github.com/metacubex/utls v1.8.0/go.mod h1:FdjYzVfCtgtna19hX0ER1Xsa5uJInwdQ4IcaaI98lEQ=
|
||||
github.com/metacubex/utls v1.8.2 h1:d7KalMZ5hnOJ6lThMz8Ykd+5dvmXH3Eoeyfv2jUuG3w=
|
||||
github.com/metacubex/utls v1.8.2/go.mod h1:kncGGVhFaoGn5M3pFe3SXhZCzsbCJayNOH4UEqTKTko=
|
||||
github.com/mholt/acmez/v3 v3.1.2 h1:auob8J/0FhmdClQicvJvuDavgd5ezwLBfKuYmynhYzc=
|
||||
github.com/mholt/acmez/v3 v3.1.2/go.mod h1:L1wOU06KKvq7tswuMDwKdcHeKpFFgkppZy/y0DFxagQ=
|
||||
github.com/miekg/dns v1.1.67 h1:kg0EHj0G4bfT5/oOys6HhZw4vmMlnoZ+gDu8tJ/AlI0=
|
||||
@@ -156,37 +158,37 @@ github.com/sagernet/cors v1.2.1 h1:Cv5Z8y9YSD6Gm+qSpNrL3LO4lD3eQVvbFYJSG7JCMHQ=
|
||||
github.com/sagernet/cors v1.2.1/go.mod h1:O64VyOjjhrkLmQIjF4KGRrJO/5dVXFdpEmCW/eISRAI=
|
||||
github.com/sagernet/fswatch v0.1.1 h1:YqID+93B7VRfqIH3PArW/XpJv5H4OLEVWDfProGoRQs=
|
||||
github.com/sagernet/fswatch v0.1.1/go.mod h1:nz85laH0mkQqJfaOrqPpkwtU1znMFNVTpT/5oRsVz/o=
|
||||
github.com/sagernet/gomobile v0.1.7 h1:I9jCJZTH0weP5MsuydvYHX5QfN/r6Fe8ptAIj1+SJVg=
|
||||
github.com/sagernet/gomobile v0.1.7/go.mod h1:Pqq2+ZVvs10U7xK+UwJgwYWUykewi8H6vlslAO73n9E=
|
||||
github.com/sagernet/gomobile v0.1.8 h1:vXgoN0pjsMONAaYCTdsKBX2T1kxuS7sbT/mZ7PElGoo=
|
||||
github.com/sagernet/gomobile v0.1.8/go.mod h1:A8l3FlHi2D/+mfcd4HHvk5DGFPW/ShFb9jHP5VmSiDY=
|
||||
github.com/sagernet/gvisor v0.0.0-20250325023245-7a9c0f5725fb h1:pprQtDqNgqXkRsXn+0E8ikKOemzmum8bODjSfDene38=
|
||||
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/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/go.mod h1:OQXAjvjNGGFxaTgVCSTRIhYB5/llyVDeapVoENYBDS8=
|
||||
github.com/sagernet/quic-go v0.52.0-beta.1 h1:hWkojLg64zjV+MJOvJU/kOeWndm3tiEfBLx5foisszs=
|
||||
github.com/sagernet/quic-go v0.52.0-beta.1/go.mod h1:OV+V5kEBb8kJS7k29MzDu6oj9GyMc7HA07sE1tedxz4=
|
||||
github.com/sagernet/quic-go v0.52.0-sing-box-mod.2 h1:QTPr/ptUPsgregVfFXReBFrhv/8U83deZG8urQ7pWYI=
|
||||
github.com/sagernet/quic-go v0.52.0-sing-box-mod.2/go.mod h1:OV+V5kEBb8kJS7k29MzDu6oj9GyMc7HA07sE1tedxz4=
|
||||
github.com/sagernet/sing v0.6.9/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
|
||||
github.com/sagernet/sing v0.7.5 h1:gNMwZCLPqR+4e0g6dwi0sSsrvOmoMjpZgqxKsuJZatc=
|
||||
github.com/sagernet/sing v0.7.5/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
|
||||
github.com/sagernet/sing-mux v0.3.2 h1:meZVFiiStvHThb/trcpAkCrmtJOuItG5Dzl1RRP5/NE=
|
||||
github.com/sagernet/sing-mux v0.3.2/go.mod h1:pht8iFY4c9Xltj7rhVd208npkNaeCxzyXCgulDPLUDA=
|
||||
github.com/sagernet/sing-quic v0.5.0-beta.3 h1:X/acRNsqQNfDlmwE7SorHfaZiny5e67hqIzM/592ric=
|
||||
github.com/sagernet/sing-quic v0.5.0-beta.3/go.mod h1:SAv/qdeDN+75msGG5U5ZIwG+3Ua50jVIKNrRSY8pkx0=
|
||||
github.com/sagernet/sing v0.7.12 h1:MpMbO56crPRZTbltoj1wGk4Xj9+GiwH1wTO4s3fz1EA=
|
||||
github.com/sagernet/sing v0.7.12/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
|
||||
github.com/sagernet/sing-mux v0.3.3 h1:YFgt9plMWzH994BMZLmyKL37PdIVaIilwP0Jg+EcLfw=
|
||||
github.com/sagernet/sing-mux v0.3.3/go.mod h1:pht8iFY4c9Xltj7rhVd208npkNaeCxzyXCgulDPLUDA=
|
||||
github.com/sagernet/sing-quic v0.5.2-0.20250909083218-00a55617c0fb h1:5Wx3XeTiKrrrcrAky7Hc1bO3CGxrvho2Vu5b/adlEIM=
|
||||
github.com/sagernet/sing-quic v0.5.2-0.20250909083218-00a55617c0fb/go.mod h1:evP1e++ZG8TJHVV5HudXV4vWeYzGfCdF4HwSJZcdqkI=
|
||||
github.com/sagernet/sing-shadowsocks v0.2.8 h1:PURj5PRoAkqeHh2ZW205RWzN9E9RtKCVCzByXruQWfE=
|
||||
github.com/sagernet/sing-shadowsocks v0.2.8/go.mod h1:lo7TWEMDcN5/h5B8S0ew+r78ZODn6SwVaFhvB6H+PTI=
|
||||
github.com/sagernet/sing-shadowsocks2 v0.2.1 h1:dWV9OXCeFPuYGHb6IRqlSptVnSzOelnqqs2gQ2/Qioo=
|
||||
github.com/sagernet/sing-shadowsocks2 v0.2.1/go.mod h1:RnXS0lExcDAovvDeniJ4IKa2IuChrdipolPYWBv9hWQ=
|
||||
github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11 h1:tK+75l64tm9WvEFrYRE1t0YxoFdWQqw/h7Uhzj0vJ+w=
|
||||
github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11/go.mod h1:sWqKnGlMipCHaGsw1sTTlimyUpgzP4WP3pjhCsYt9oA=
|
||||
github.com/sagernet/sing-tun v0.7.0-beta.1 h1:mBIFXYAnGO5ey/HcCYanqnBx61E7yF8zTFGRZonGYmY=
|
||||
github.com/sagernet/sing-tun v0.7.0-beta.1/go.mod h1:AHJuRrLbNRJuivuFZ2VhXwDj4ViYp14szG5EkkKAqRQ=
|
||||
github.com/sagernet/sing-vmess v0.2.6 h1:1c4dGzeGy0kpBXXrT1sgiMZtHhdJylIT8eWrGhJYZec=
|
||||
github.com/sagernet/sing-vmess v0.2.6/go.mod h1:5aYoOtYksAyS0NXDm0qKeTYW1yoE1bJVcv+XLcVoyJs=
|
||||
github.com/sagernet/sing-tun v0.7.2 h1:uJkAZM0KBqIYzrq077QGqdvj/+4i/pMOx6Pnx0jYqAs=
|
||||
github.com/sagernet/sing-tun v0.7.2/go.mod h1:pUEjh9YHQ2gJT6Lk0TYDklh3WJy7lz+848vleGM3JPM=
|
||||
github.com/sagernet/sing-vmess v0.2.7 h1:2ee+9kO0xW5P4mfe6TYVWf9VtY8k1JhNysBqsiYj0sk=
|
||||
github.com/sagernet/sing-vmess v0.2.7/go.mod h1:5aYoOtYksAyS0NXDm0qKeTYW1yoE1bJVcv+XLcVoyJs=
|
||||
github.com/sagernet/smux v1.5.34-mod.2 h1:gkmBjIjlJ2zQKpLigOkFur5kBKdV6bNRoFu2WkltRQ4=
|
||||
github.com/sagernet/smux v1.5.34-mod.2/go.mod h1:0KW0+R+ycvA2INW4gbsd7BNyg+HEfLIAxa5N02/28Zc=
|
||||
github.com/sagernet/tailscale v1.80.3-mod.5 h1:7V7z+p2C//TGtff20pPnDCt3qP6uFyY62peJoKF9z/A=
|
||||
github.com/sagernet/tailscale v1.80.3-mod.5/go.mod h1:EBxXsWu4OH2ELbQLq32WoBeIubG8KgDrg4/Oaxjs6lI=
|
||||
github.com/sagernet/tailscale v1.80.3-sing-box-1.12-mod.1 h1:gMC0q+0VvZBotZMZ9G0R8ZMEIT/Q6KnXbw0/OgMjmdk=
|
||||
github.com/sagernet/tailscale v1.80.3-sing-box-1.12-mod.1/go.mod h1:EBxXsWu4OH2ELbQLq32WoBeIubG8KgDrg4/Oaxjs6lI=
|
||||
github.com/sagernet/wireguard-go v0.0.1-beta.7 h1:ltgBwYHfr+9Wz1eG59NiWnHrYEkDKHG7otNZvu85DXI=
|
||||
github.com/sagernet/wireguard-go v0.0.1-beta.7/go.mod h1:jGXij2Gn2wbrWuYNUmmNhf1dwcZtvyAvQoe8Xd8MbUo=
|
||||
github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854 h1:6uUiZcDRnZSAegryaUGwPC/Fj13JSHwiTftrXhMmYOc=
|
||||
@@ -262,18 +264,18 @@ go4.org/mem v0.0.0-20240501181205-ae6ca9944745/go.mod h1:reUoABIJ9ikfM5sgtSF3Wus
|
||||
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=
|
||||
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
|
||||
golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM=
|
||||
golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY=
|
||||
golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4=
|
||||
golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc=
|
||||
golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6 h1:y5zboxd6LQAqYIhHnB48p0ByQ/GnQx2BE33L8BOHQkI=
|
||||
golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6/go.mod h1:U6Lno4MTRCDY+Ba7aCcauB9T60gsv5s4ralQzP72ZoQ=
|
||||
golang.org/x/image v0.23.0 h1:HseQ7c2OpPKTPVzNjG5fwJsOTCiiwS4QdsYi5XU6H68=
|
||||
golang.org/x/image v0.23.0/go.mod h1:wJJBTdLfCCf3tiHa1fNxpZmUI4mmoZvwMCPP0ddoNKY=
|
||||
golang.org/x/mod v0.26.0 h1:EGMPT//Ezu+ylkCijjPc+f4Aih7sZvaAr+O3EHBxvZg=
|
||||
golang.org/x/mod v0.26.0/go.mod h1:/j6NAhSk8iQ723BGAUyoAcn7SlD7s15Dp9Nd/SfeaFQ=
|
||||
golang.org/x/mod v0.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ=
|
||||
golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs=
|
||||
golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8=
|
||||
golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE=
|
||||
golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
|
||||
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||
@@ -285,20 +287,20 @@ golang.org/x/sys v0.0.0-20220817070843-5a390386f1f2/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA=
|
||||
golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
|
||||
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.33.0 h1:NuFncQrRcaRvVmgRkvM3j/F00gWIAlcmlB8ACEKmGIg=
|
||||
golang.org/x/term v0.33.0/go.mod h1:s18+ql9tYWp1IfpV9DmCtQDDSRBUjKaw9M1eAv5UeF0=
|
||||
golang.org/x/term v0.34.0 h1:O/2T7POpk0ZZ7MAzMeWFSg6S5IpWd/RXDlM9hgM3DR4=
|
||||
golang.org/x/term v0.34.0/go.mod h1:5jC53AEywhIVebHgPVeg0mj8OD3VO9OzclacVrqpaAw=
|
||||
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.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4=
|
||||
golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU=
|
||||
golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng=
|
||||
golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU=
|
||||
golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY=
|
||||
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.34.0 h1:qIpSLOxeCYGg9TrcJokLBG4KFA6d795g0xkBkiESGlo=
|
||||
golang.org/x/tools v0.34.0/go.mod h1:pAP9OwEaY1CAW3HOmg3hLZC5Z0CCmzjAF2UQMSqNARg=
|
||||
golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg=
|
||||
golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s=
|
||||
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/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
|
||||
@@ -100,7 +100,6 @@ func rewriteRcodeAction(rcodeMap map[string]int, ruleAction *DNSRuleAction) {
|
||||
}
|
||||
ruleAction.Action = C.RuleActionTypePredefined
|
||||
ruleAction.PredefinedOptions.Rcode = common.Ptr(DNSRCode(rcode))
|
||||
return
|
||||
}
|
||||
|
||||
type DNSClientOptions struct {
|
||||
|
||||
@@ -124,7 +124,7 @@ func (h *inboundHandler) NewConnectionEx(ctx context.Context, conn net.Conn, sou
|
||||
//nolint:staticcheck
|
||||
metadata.InboundOptions = h.listener.ListenOptions().InboundOptions
|
||||
metadata.Source = source
|
||||
metadata.Destination = destination
|
||||
metadata.Destination = destination.Unwrap()
|
||||
if userName, _ := auth.UserFromContext[string](ctx); userName != "" {
|
||||
metadata.User = userName
|
||||
h.logger.InfoContext(ctx, "[", userName, "] inbound connection to ", metadata.Destination)
|
||||
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
"github.com/sagernet/sing-box/log"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
"github.com/sagernet/sing/common"
|
||||
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/uot"
|
||||
@@ -43,6 +44,13 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL
|
||||
if options.TLS == nil || !options.TLS.Enabled {
|
||||
return nil, C.ErrTLSRequired
|
||||
}
|
||||
// TCP Fast Open is incompatible with anytls because TFO creates a lazy connection
|
||||
// that only establishes on first write. The lazy connection returns an empty address
|
||||
// before establishment, but anytls SOCKS wrapper tries to access the remote address
|
||||
// during handshake, causing a null pointer dereference crash.
|
||||
if options.DialerOptions.TCPFastOpen {
|
||||
return nil, E.New("tcp_fast_open is not supported with anytls outbound")
|
||||
}
|
||||
|
||||
tlsConfig, err := tls.NewClient(ctx, options.Server, common.PtrValueOrDefault(options.TLS))
|
||||
if err != nil {
|
||||
|
||||
@@ -13,7 +13,6 @@ import (
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"github.com/sagernet/sing-box/log"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
"github.com/sagernet/sing/common"
|
||||
"github.com/sagernet/sing/common/bufio"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
"github.com/sagernet/sing/common/logger"
|
||||
@@ -164,26 +163,7 @@ func (h *Outbound) DialParallel(ctx context.Context, network string, destination
|
||||
case N.NetworkUDP:
|
||||
h.logger.InfoContext(ctx, "outbound packet connection to ", destination)
|
||||
}
|
||||
var domainStrategy C.DomainStrategy
|
||||
if h.domainStrategy != C.DomainStrategyAsIS {
|
||||
domainStrategy = h.domainStrategy
|
||||
} else {
|
||||
//nolint:staticcheck
|
||||
domainStrategy = C.DomainStrategy(metadata.InboundOptions.DomainStrategy)
|
||||
}
|
||||
switch domainStrategy {
|
||||
case C.DomainStrategyIPv4Only:
|
||||
destinationAddresses = common.Filter(destinationAddresses, netip.Addr.Is4)
|
||||
if len(destinationAddresses) == 0 {
|
||||
return nil, E.New("no IPv4 address available for ", destination)
|
||||
}
|
||||
case C.DomainStrategyIPv6Only:
|
||||
destinationAddresses = common.Filter(destinationAddresses, netip.Addr.Is6)
|
||||
if len(destinationAddresses) == 0 {
|
||||
return nil, E.New("no IPv6 address available for ", destination)
|
||||
}
|
||||
}
|
||||
return dialer.DialParallelNetwork(ctx, h.dialer, network, destination, destinationAddresses, domainStrategy == C.DomainStrategyPreferIPv6, nil, nil, nil, h.fallbackDelay)
|
||||
return dialer.DialParallelNetwork(ctx, h.dialer, network, destination, destinationAddresses, len(destinationAddresses) > 0 && destinationAddresses[0].Is6(), nil, nil, nil, h.fallbackDelay)
|
||||
}
|
||||
|
||||
func (h *Outbound) DialParallelNetwork(ctx context.Context, network string, destination M.Socksaddr, destinationAddresses []netip.Addr, networkStrategy *C.NetworkStrategy, networkType []C.InterfaceType, fallbackNetworkType []C.InterfaceType, fallbackDelay time.Duration) (net.Conn, error) {
|
||||
@@ -204,26 +184,7 @@ func (h *Outbound) DialParallelNetwork(ctx context.Context, network string, dest
|
||||
case N.NetworkUDP:
|
||||
h.logger.InfoContext(ctx, "outbound packet connection to ", destination)
|
||||
}
|
||||
var domainStrategy C.DomainStrategy
|
||||
if h.domainStrategy != C.DomainStrategyAsIS {
|
||||
domainStrategy = h.domainStrategy
|
||||
} else {
|
||||
//nolint:staticcheck
|
||||
domainStrategy = C.DomainStrategy(metadata.InboundOptions.DomainStrategy)
|
||||
}
|
||||
switch domainStrategy {
|
||||
case C.DomainStrategyIPv4Only:
|
||||
destinationAddresses = common.Filter(destinationAddresses, netip.Addr.Is4)
|
||||
if len(destinationAddresses) == 0 {
|
||||
return nil, E.New("no IPv4 address available for ", destination)
|
||||
}
|
||||
case C.DomainStrategyIPv6Only:
|
||||
destinationAddresses = common.Filter(destinationAddresses, netip.Addr.Is6)
|
||||
if len(destinationAddresses) == 0 {
|
||||
return nil, E.New("no IPv6 address available for ", destination)
|
||||
}
|
||||
}
|
||||
return dialer.DialParallelNetwork(ctx, h.dialer, network, destination, destinationAddresses, domainStrategy == C.DomainStrategyPreferIPv6, networkStrategy, networkType, fallbackNetworkType, fallbackDelay)
|
||||
return dialer.DialParallelNetwork(ctx, h.dialer, network, destination, destinationAddresses, len(destinationAddresses) > 0 && destinationAddresses[0].Is6(), networkStrategy, networkType, fallbackNetworkType, fallbackDelay)
|
||||
}
|
||||
|
||||
func (h *Outbound) ListenSerialNetworkPacket(ctx context.Context, destination M.Socksaddr, destinationAddresses []netip.Addr, networkStrategy *C.NetworkStrategy, networkType []C.InterfaceType, fallbackNetworkType []C.InterfaceType, fallbackDelay time.Duration) (net.PacketConn, netip.Addr, error) {
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"github.com/sagernet/sing-box/log"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
"github.com/sagernet/sing/common/atomic"
|
||||
"github.com/sagernet/sing/common"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
"github.com/sagernet/sing/common/logger"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
@@ -37,7 +37,7 @@ type Selector struct {
|
||||
tags []string
|
||||
defaultTag string
|
||||
outbounds map[string]adapter.Outbound
|
||||
selected atomic.TypedValue[adapter.Outbound]
|
||||
selected common.TypedValue[adapter.Outbound]
|
||||
interruptGroup *interrupt.Group
|
||||
interruptExternalConnections bool
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"net"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
@@ -14,7 +15,6 @@ import (
|
||||
"github.com/sagernet/sing-box/log"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
"github.com/sagernet/sing/common"
|
||||
"github.com/sagernet/sing/common/atomic"
|
||||
"github.com/sagernet/sing/common/batch"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
@@ -192,7 +192,7 @@ type URLTestGroup struct {
|
||||
ticker *time.Ticker
|
||||
close chan struct{}
|
||||
started bool
|
||||
lastActive atomic.TypedValue[time.Time]
|
||||
lastActive common.TypedValue[time.Time]
|
||||
}
|
||||
|
||||
func NewURLTestGroup(ctx context.Context, outboundManager adapter.OutboundManager, logger log.Logger, outbounds []adapter.Outbound, link string, interval time.Duration, tolerance uint16, idleTimeout time.Duration, interruptExternalConnections bool) (*URLTestGroup, error) {
|
||||
@@ -313,7 +313,7 @@ func (g *URLTestGroup) Select(network string) (adapter.Outbound, bool) {
|
||||
}
|
||||
|
||||
func (g *URLTestGroup) loopCheck() {
|
||||
if time.Now().Sub(g.lastActive.Load()) > g.interval {
|
||||
if time.Since(g.lastActive.Load()) > g.interval {
|
||||
g.lastActive.Store(time.Now())
|
||||
g.CheckOutbounds(false)
|
||||
}
|
||||
@@ -323,7 +323,7 @@ func (g *URLTestGroup) loopCheck() {
|
||||
return
|
||||
case <-g.ticker.C:
|
||||
}
|
||||
if time.Now().Sub(g.lastActive.Load()) > g.idleTimeout {
|
||||
if time.Since(g.lastActive.Load()) > g.idleTimeout {
|
||||
g.access.Lock()
|
||||
g.ticker.Stop()
|
||||
g.ticker = nil
|
||||
@@ -360,7 +360,7 @@ func (g *URLTestGroup) urlTest(ctx context.Context, force bool) (map[string]uint
|
||||
continue
|
||||
}
|
||||
history := g.history.LoadURLTestHistory(realTag)
|
||||
if !force && history != nil && time.Now().Sub(history.Time) < g.interval {
|
||||
if !force && history != nil && time.Since(history.Time) < g.interval {
|
||||
continue
|
||||
}
|
||||
checked[realTag] = true
|
||||
|
||||
@@ -8,10 +8,12 @@ import (
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing-box/adapter/inbound"
|
||||
"github.com/sagernet/sing-box/common/listener"
|
||||
"github.com/sagernet/sing-box/common/tls"
|
||||
"github.com/sagernet/sing-box/common/uot"
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"github.com/sagernet/sing-box/log"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
"github.com/sagernet/sing/common"
|
||||
"github.com/sagernet/sing/common/auth"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
@@ -33,6 +35,7 @@ type Inbound struct {
|
||||
logger log.ContextLogger
|
||||
listener *listener.Listener
|
||||
authenticator *auth.Authenticator
|
||||
tlsConfig tls.ServerConfig
|
||||
}
|
||||
|
||||
func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.HTTPMixedInboundOptions) (adapter.Inbound, error) {
|
||||
@@ -42,6 +45,13 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo
|
||||
logger: logger,
|
||||
authenticator: auth.NewAuthenticator(options.Users),
|
||||
}
|
||||
if options.TLS != nil {
|
||||
tlsConfig, err := tls.NewServer(ctx, logger, common.PtrValueOrDefault(options.TLS))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
inbound.tlsConfig = tlsConfig
|
||||
}
|
||||
inbound.listener = listener.New(listener.Options{
|
||||
Context: ctx,
|
||||
Logger: logger,
|
||||
@@ -58,11 +68,20 @@ func (h *Inbound) Start(stage adapter.StartStage) error {
|
||||
if stage != adapter.StartStateStart {
|
||||
return nil
|
||||
}
|
||||
if h.tlsConfig != nil {
|
||||
err := h.tlsConfig.Start()
|
||||
if err != nil {
|
||||
return E.Cause(err, "create TLS config")
|
||||
}
|
||||
}
|
||||
return h.listener.Start()
|
||||
}
|
||||
|
||||
func (h *Inbound) Close() error {
|
||||
return h.listener.Close()
|
||||
return common.Close(
|
||||
h.listener,
|
||||
h.tlsConfig,
|
||||
)
|
||||
}
|
||||
|
||||
func (h *Inbound) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) {
|
||||
@@ -78,6 +97,13 @@ func (h *Inbound) NewConnectionEx(ctx context.Context, conn net.Conn, metadata a
|
||||
}
|
||||
|
||||
func (h *Inbound) newConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) error {
|
||||
if h.tlsConfig != nil {
|
||||
tlsConn, err := tls.ServerHandshake(ctx, conn, h.tlsConfig)
|
||||
if err != nil {
|
||||
return E.Cause(err, "TLS handshake")
|
||||
}
|
||||
conn = tlsConn
|
||||
}
|
||||
reader := std_bufio.NewReader(conn)
|
||||
headerBytes, err := reader.Peek(1)
|
||||
if err != nil {
|
||||
|
||||
@@ -170,7 +170,7 @@ func (n *Inbound) ServeHTTP(writer http.ResponseWriter, request *http.Request) {
|
||||
hostPort = request.Host
|
||||
}
|
||||
source := sHttp.SourceAddress(request)
|
||||
destination := M.ParseSocksaddr(hostPort)
|
||||
destination := M.ParseSocksaddr(hostPort).Unwrap()
|
||||
|
||||
if hijacker, isHijacker := writer.(http.Hijacker); isHijacker {
|
||||
conn, _, err := hijacker.Hijack()
|
||||
|
||||
@@ -128,7 +128,6 @@ func (h *Outbound) InterfaceUpdated() {
|
||||
if h.multiplexDialer != nil {
|
||||
h.multiplexDialer.Reset()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (h *Outbound) Close() error {
|
||||
|
||||
@@ -180,7 +180,6 @@ func (s *Outbound) connect() (*ssh.Client, error) {
|
||||
|
||||
func (s *Outbound) InterfaceUpdated() {
|
||||
common.Close(s.clientConn)
|
||||
return
|
||||
}
|
||||
|
||||
func (s *Outbound) Close() error {
|
||||
|
||||
@@ -105,7 +105,6 @@ func (h *Outbound) InterfaceUpdated() {
|
||||
if h.multiplexDialer != nil {
|
||||
h.multiplexDialer.Reset()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (h *Outbound) Close() error {
|
||||
|
||||
@@ -137,6 +137,9 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo
|
||||
if platformInterface != nil && platformInterface.UnderNetworkExtension() {
|
||||
// In Network Extension, when MTU exceeds 4064 (4096-UTUN_IF_HEADROOM_SIZE), the performance of tun will drop significantly, which may be a system bug.
|
||||
tunMTU = 4064
|
||||
} else if C.IsAndroid {
|
||||
// Some Android devices report ENOBUFS when using MTU 65535
|
||||
tunMTU = 9000
|
||||
} else {
|
||||
tunMTU = 65535
|
||||
}
|
||||
|
||||
@@ -124,7 +124,6 @@ func (h *Outbound) InterfaceUpdated() {
|
||||
if h.multiplexDialer != nil {
|
||||
h.multiplexDialer.Reset()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (h *Outbound) Close() error {
|
||||
|
||||
@@ -108,7 +108,6 @@ func (h *Outbound) InterfaceUpdated() {
|
||||
if h.multiplexDialer != nil {
|
||||
h.multiplexDialer.Reset()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (h *Outbound) Close() error {
|
||||
|
||||
@@ -19,7 +19,6 @@ import (
|
||||
"github.com/sagernet/sing-box/option"
|
||||
"github.com/sagernet/sing-tun"
|
||||
"github.com/sagernet/sing/common"
|
||||
"github.com/sagernet/sing/common/atomic"
|
||||
"github.com/sagernet/sing/common/control"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
F "github.com/sagernet/sing/common/format"
|
||||
@@ -37,7 +36,7 @@ var _ adapter.NetworkManager = (*NetworkManager)(nil)
|
||||
type NetworkManager struct {
|
||||
logger logger.ContextLogger
|
||||
interfaceFinder *control.DefaultInterfaceFinder
|
||||
networkInterfaces atomic.TypedValue[[]adapter.NetworkInterface]
|
||||
networkInterfaces common.TypedValue[[]adapter.NetworkInterface]
|
||||
|
||||
autoDetectInterface bool
|
||||
defaultOptions adapter.NetworkOptions
|
||||
@@ -312,7 +311,7 @@ func (r *NetworkManager) AutoDetectInterfaceFunc() control.Func {
|
||||
if r.interfaceMonitor == nil {
|
||||
return nil
|
||||
}
|
||||
bindFunc := control.BindToInterfaceFunc(r.interfaceFinder, func(network string, address string) (interfaceName string, interfaceIndex int, err error) {
|
||||
return control.BindToInterfaceFunc(r.interfaceFinder, func(network string, address string) (interfaceName string, interfaceIndex int, err error) {
|
||||
remoteAddr := M.ParseSocksaddr(address).Addr
|
||||
if remoteAddr.IsValid() {
|
||||
iif, err := r.interfaceFinder.ByAddr(remoteAddr)
|
||||
@@ -326,16 +325,6 @@ func (r *NetworkManager) AutoDetectInterfaceFunc() control.Func {
|
||||
}
|
||||
return defaultInterface.Name, defaultInterface.Index, nil
|
||||
})
|
||||
return func(network, address string, conn syscall.RawConn) error {
|
||||
err := bindFunc(network, address, conn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if r.autoRedirectOutputMark > 0 {
|
||||
return control.RoutingMark(r.autoRedirectOutputMark)(network, address, conn)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -366,6 +355,15 @@ func (r *NetworkManager) AutoRedirectOutputMark() uint32 {
|
||||
return r.autoRedirectOutputMark
|
||||
}
|
||||
|
||||
func (r *NetworkManager) AutoRedirectOutputMarkFunc() control.Func {
|
||||
return func(network, address string, conn syscall.RawConn) error {
|
||||
if r.autoRedirectOutputMark == 0 {
|
||||
return nil
|
||||
}
|
||||
return control.RoutingMark(r.autoRedirectOutputMark)(network, address, conn)
|
||||
}
|
||||
}
|
||||
|
||||
func (r *NetworkManager) NetworkMonitor() tun.NetworkUpdateMonitor {
|
||||
return r.networkMonitor
|
||||
}
|
||||
|
||||
107
route/route.go
107
route/route.go
@@ -5,7 +5,6 @@ import (
|
||||
"errors"
|
||||
"net"
|
||||
"net/netip"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -27,6 +26,8 @@ import (
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
"github.com/sagernet/sing/common/uot"
|
||||
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
// Deprecated: use RouteConnectionEx instead.
|
||||
@@ -345,16 +346,16 @@ func (r *Router) matchRule(
|
||||
newBuffer, newPackerBuffers, newErr := r.actionSniff(ctx, metadata, &R.RuleActionSniff{
|
||||
OverrideDestination: metadata.InboundOptions.SniffOverrideDestination,
|
||||
Timeout: time.Duration(metadata.InboundOptions.SniffTimeout),
|
||||
}, inputConn, inputPacketConn, nil)
|
||||
if newErr != nil {
|
||||
fatalErr = newErr
|
||||
return
|
||||
}
|
||||
}, inputConn, inputPacketConn, nil, nil)
|
||||
if newBuffer != nil {
|
||||
buffers = []*buf.Buffer{newBuffer}
|
||||
} else if len(newPackerBuffers) > 0 {
|
||||
packetBuffers = newPackerBuffers
|
||||
}
|
||||
if newErr != nil {
|
||||
fatalErr = newErr
|
||||
return
|
||||
}
|
||||
}
|
||||
if C.DomainStrategy(metadata.InboundOptions.DomainStrategy) != C.DomainStrategyAsIS {
|
||||
fatalErr = r.actionResolve(ctx, metadata, &R.RuleActionResolve{
|
||||
@@ -453,16 +454,16 @@ match:
|
||||
switch action := currentRule.Action().(type) {
|
||||
case *R.RuleActionSniff:
|
||||
if !preMatch {
|
||||
newBuffer, newPacketBuffers, newErr := r.actionSniff(ctx, metadata, action, inputConn, inputPacketConn, buffers)
|
||||
if newErr != nil {
|
||||
fatalErr = newErr
|
||||
return
|
||||
}
|
||||
newBuffer, newPacketBuffers, newErr := r.actionSniff(ctx, metadata, action, inputConn, inputPacketConn, buffers, packetBuffers)
|
||||
if newBuffer != nil {
|
||||
buffers = append(buffers, newBuffer)
|
||||
} else if len(newPacketBuffers) > 0 {
|
||||
packetBuffers = append(packetBuffers, newPacketBuffers...)
|
||||
}
|
||||
if newErr != nil {
|
||||
fatalErr = newErr
|
||||
return
|
||||
}
|
||||
} else {
|
||||
selectedRule = currentRule
|
||||
selectedRuleIndex = currentRuleIndex
|
||||
@@ -489,7 +490,7 @@ match:
|
||||
|
||||
func (r *Router) actionSniff(
|
||||
ctx context.Context, metadata *adapter.InboundContext, action *R.RuleActionSniff,
|
||||
inputConn net.Conn, inputPacketConn N.PacketConn, inputBuffers []*buf.Buffer,
|
||||
inputConn net.Conn, inputPacketConn N.PacketConn, inputBuffers []*buf.Buffer, inputPacketBuffers []*N.PacketBuffer,
|
||||
) (buffer *buf.Buffer, packetBuffers []*N.PacketBuffer, fatalErr error) {
|
||||
if sniff.Skip(metadata) {
|
||||
r.logger.DebugContext(ctx, "sniff skipped due to port considered as server-first")
|
||||
@@ -501,7 +502,7 @@ func (r *Router) actionSniff(
|
||||
if inputConn != nil {
|
||||
if len(action.StreamSniffers) == 0 && len(action.PacketSniffers) > 0 {
|
||||
return
|
||||
} else if metadata.SniffError != nil && !errors.Is(metadata.SniffError, sniff.ErrNeedMoreData) {
|
||||
} else if slices.Equal(metadata.SnifferNames, action.SnifferNames) && metadata.SniffError != nil && !errors.Is(metadata.SniffError, sniff.ErrNeedMoreData) {
|
||||
r.logger.DebugContext(ctx, "packet sniff skipped due to previous error: ", metadata.SniffError)
|
||||
return
|
||||
}
|
||||
@@ -528,6 +529,7 @@ func (r *Router) actionSniff(
|
||||
action.Timeout,
|
||||
streamSniffers...,
|
||||
)
|
||||
metadata.SnifferNames = action.SnifferNames
|
||||
metadata.SniffError = err
|
||||
if err == nil {
|
||||
//goland:noinspection GoDeprecation
|
||||
@@ -553,10 +555,13 @@ func (r *Router) actionSniff(
|
||||
} else if inputPacketConn != nil {
|
||||
if len(action.PacketSniffers) == 0 && len(action.StreamSniffers) > 0 {
|
||||
return
|
||||
} else if metadata.SniffError != nil && !errors.Is(metadata.SniffError, sniff.ErrNeedMoreData) {
|
||||
} else if slices.Equal(metadata.SnifferNames, action.SnifferNames) && metadata.SniffError != nil && !errors.Is(metadata.SniffError, sniff.ErrNeedMoreData) {
|
||||
r.logger.DebugContext(ctx, "packet sniff skipped due to previous error: ", metadata.SniffError)
|
||||
return
|
||||
}
|
||||
quicMoreData := func() bool {
|
||||
return slices.Equal(metadata.SnifferNames, action.SnifferNames) && errors.Is(metadata.SniffError, sniff.ErrNeedMoreData)
|
||||
}
|
||||
var packetSniffers []sniff.PacketSniffer
|
||||
if len(action.PacketSniffers) > 0 {
|
||||
packetSniffers = action.PacketSniffers
|
||||
@@ -571,12 +576,37 @@ func (r *Router) actionSniff(
|
||||
sniff.NTP,
|
||||
}
|
||||
}
|
||||
var err error
|
||||
for _, packetBuffer := range inputPacketBuffers {
|
||||
if quicMoreData() {
|
||||
err = sniff.PeekPacket(
|
||||
ctx,
|
||||
metadata,
|
||||
packetBuffer.Buffer.Bytes(),
|
||||
sniff.QUICClientHello,
|
||||
)
|
||||
} else {
|
||||
err = sniff.PeekPacket(
|
||||
ctx, metadata,
|
||||
packetBuffer.Buffer.Bytes(),
|
||||
packetSniffers...,
|
||||
)
|
||||
}
|
||||
metadata.SnifferNames = action.SnifferNames
|
||||
metadata.SniffError = err
|
||||
if errors.Is(err, sniff.ErrNeedMoreData) {
|
||||
// TODO: replace with generic message when there are more multi-packet protocols
|
||||
r.logger.DebugContext(ctx, "attempt to sniff fragmented QUIC client hello")
|
||||
continue
|
||||
}
|
||||
goto finally
|
||||
}
|
||||
packetBuffers = inputPacketBuffers
|
||||
for {
|
||||
var (
|
||||
sniffBuffer = buf.NewPacket()
|
||||
destination M.Socksaddr
|
||||
done = make(chan struct{})
|
||||
err error
|
||||
)
|
||||
go func() {
|
||||
sniffTimeout := C.ReadPayloadTimeout
|
||||
@@ -597,12 +627,12 @@ func (r *Router) actionSniff(
|
||||
}
|
||||
if err != nil {
|
||||
sniffBuffer.Release()
|
||||
if !errors.Is(err, os.ErrDeadlineExceeded) {
|
||||
if !errors.Is(err, context.DeadlineExceeded) {
|
||||
fatalErr = err
|
||||
return
|
||||
}
|
||||
} else {
|
||||
if len(packetBuffers) > 0 || metadata.SniffError != nil {
|
||||
if quicMoreData() {
|
||||
err = sniff.PeekPacket(
|
||||
ctx,
|
||||
metadata,
|
||||
@@ -622,32 +652,34 @@ func (r *Router) actionSniff(
|
||||
Destination: destination,
|
||||
}
|
||||
packetBuffers = append(packetBuffers, packetBuffer)
|
||||
metadata.SnifferNames = action.SnifferNames
|
||||
metadata.SniffError = err
|
||||
if errors.Is(err, sniff.ErrNeedMoreData) {
|
||||
// TODO: replace with generic message when there are more multi-packet protocols
|
||||
r.logger.DebugContext(ctx, "attempt to sniff fragmented QUIC client hello")
|
||||
continue
|
||||
}
|
||||
if metadata.Protocol != "" {
|
||||
//goland:noinspection GoDeprecation
|
||||
if action.OverrideDestination && M.IsDomainName(metadata.Domain) {
|
||||
metadata.Destination = M.Socksaddr{
|
||||
Fqdn: metadata.Domain,
|
||||
Port: metadata.Destination.Port,
|
||||
}
|
||||
}
|
||||
if metadata.Domain != "" && metadata.Client != "" {
|
||||
r.logger.DebugContext(ctx, "sniffed packet protocol: ", metadata.Protocol, ", domain: ", metadata.Domain, ", client: ", metadata.Client)
|
||||
} else if metadata.Domain != "" {
|
||||
r.logger.DebugContext(ctx, "sniffed packet protocol: ", metadata.Protocol, ", domain: ", metadata.Domain)
|
||||
} else if metadata.Client != "" {
|
||||
r.logger.DebugContext(ctx, "sniffed packet protocol: ", metadata.Protocol, ", client: ", metadata.Client)
|
||||
} else {
|
||||
r.logger.DebugContext(ctx, "sniffed packet protocol: ", metadata.Protocol)
|
||||
}
|
||||
}
|
||||
goto finally
|
||||
}
|
||||
finally:
|
||||
if err == nil {
|
||||
//goland:noinspection GoDeprecation
|
||||
if action.OverrideDestination && M.IsDomainName(metadata.Domain) {
|
||||
metadata.Destination = M.Socksaddr{
|
||||
Fqdn: metadata.Domain,
|
||||
Port: metadata.Destination.Port,
|
||||
}
|
||||
}
|
||||
break
|
||||
if metadata.Domain != "" && metadata.Client != "" {
|
||||
r.logger.DebugContext(ctx, "sniffed packet protocol: ", metadata.Protocol, ", domain: ", metadata.Domain, ", client: ", metadata.Client)
|
||||
} else if metadata.Domain != "" {
|
||||
r.logger.DebugContext(ctx, "sniffed packet protocol: ", metadata.Protocol, ", domain: ", metadata.Domain)
|
||||
} else if metadata.Client != "" {
|
||||
r.logger.DebugContext(ctx, "sniffed packet protocol: ", metadata.Protocol, ", client: ", metadata.Client)
|
||||
} else {
|
||||
r.logger.DebugContext(ctx, "sniffed packet protocol: ", metadata.Protocol)
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
@@ -675,11 +707,6 @@ func (r *Router) actionResolve(ctx context.Context, metadata *adapter.InboundCon
|
||||
}
|
||||
metadata.DestinationAddresses = addresses
|
||||
r.logger.DebugContext(ctx, "resolved [", strings.Join(F.MapToString(metadata.DestinationAddresses), " "), "]")
|
||||
if metadata.Destination.IsIPv4() {
|
||||
metadata.IPVersion = 4
|
||||
} else if metadata.Destination.IsIPv6() {
|
||||
metadata.IPVersion = 6
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -87,7 +87,7 @@ func NewRuleAction(ctx context.Context, logger logger.ContextLogger, action opti
|
||||
return &RuleActionHijackDNS{}, nil
|
||||
case C.RuleActionTypeSniff:
|
||||
sniffAction := &RuleActionSniff{
|
||||
snifferNames: action.SniffOptions.Sniffer,
|
||||
SnifferNames: action.SniffOptions.Sniffer,
|
||||
Timeout: time.Duration(action.SniffOptions.Timeout),
|
||||
}
|
||||
return sniffAction, sniffAction.build()
|
||||
@@ -361,7 +361,7 @@ func (r *RuleActionHijackDNS) String() string {
|
||||
}
|
||||
|
||||
type RuleActionSniff struct {
|
||||
snifferNames []string
|
||||
SnifferNames []string
|
||||
StreamSniffers []sniff.StreamSniffer
|
||||
PacketSniffers []sniff.PacketSniffer
|
||||
Timeout time.Duration
|
||||
@@ -374,7 +374,7 @@ func (r *RuleActionSniff) Type() string {
|
||||
}
|
||||
|
||||
func (r *RuleActionSniff) build() error {
|
||||
for _, name := range r.snifferNames {
|
||||
for _, name := range r.SnifferNames {
|
||||
switch name {
|
||||
case C.ProtocolTLS:
|
||||
r.StreamSniffers = append(r.StreamSniffers, sniff.TLSClientHello)
|
||||
@@ -407,14 +407,14 @@ func (r *RuleActionSniff) build() error {
|
||||
}
|
||||
|
||||
func (r *RuleActionSniff) String() string {
|
||||
if len(r.snifferNames) == 0 && r.Timeout == 0 {
|
||||
if len(r.SnifferNames) == 0 && r.Timeout == 0 {
|
||||
return "sniff"
|
||||
} else if len(r.snifferNames) > 0 && r.Timeout == 0 {
|
||||
return F.ToString("sniff(", strings.Join(r.snifferNames, ","), ")")
|
||||
} else if len(r.snifferNames) == 0 && r.Timeout > 0 {
|
||||
} else if len(r.SnifferNames) > 0 && r.Timeout == 0 {
|
||||
return F.ToString("sniff(", strings.Join(r.SnifferNames, ","), ")")
|
||||
} else if len(r.SnifferNames) == 0 && r.Timeout > 0 {
|
||||
return F.ToString("sniff(", r.Timeout.String(), ")")
|
||||
} else {
|
||||
return F.ToString("sniff(", strings.Join(r.snifferNames, ","), ",", r.Timeout.String(), ")")
|
||||
return F.ToString("sniff(", strings.Join(r.SnifferNames, ","), ",", r.Timeout.String(), ")")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/sagernet/fswatch"
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
@@ -13,7 +14,6 @@ import (
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"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/json"
|
||||
@@ -27,16 +27,16 @@ import (
|
||||
var _ adapter.RuleSet = (*LocalRuleSet)(nil)
|
||||
|
||||
type LocalRuleSet struct {
|
||||
ctx context.Context
|
||||
logger logger.Logger
|
||||
tag string
|
||||
rules []adapter.HeadlessRule
|
||||
metadata adapter.RuleSetMetadata
|
||||
fileFormat string
|
||||
watcher *fswatch.Watcher
|
||||
callbackAccess sync.Mutex
|
||||
callbacks list.List[adapter.RuleSetUpdateCallback]
|
||||
refs atomic.Int32
|
||||
ctx context.Context
|
||||
logger logger.Logger
|
||||
tag string
|
||||
access sync.RWMutex
|
||||
rules []adapter.HeadlessRule
|
||||
metadata adapter.RuleSetMetadata
|
||||
fileFormat string
|
||||
watcher *fswatch.Watcher
|
||||
callbacks list.List[adapter.RuleSetUpdateCallback]
|
||||
refs atomic.Int32
|
||||
}
|
||||
|
||||
func NewLocalRuleSet(ctx context.Context, logger logger.Logger, options option.RuleSet) (*LocalRuleSet, error) {
|
||||
@@ -141,11 +141,11 @@ func (s *LocalRuleSet) reloadRules(headlessRules []option.HeadlessRule) error {
|
||||
metadata.ContainsProcessRule = hasHeadlessRule(headlessRules, isProcessHeadlessRule)
|
||||
metadata.ContainsWIFIRule = hasHeadlessRule(headlessRules, isWIFIHeadlessRule)
|
||||
metadata.ContainsIPCIDRRule = hasHeadlessRule(headlessRules, isIPCIDRHeadlessRule)
|
||||
s.access.Lock()
|
||||
s.rules = rules
|
||||
s.metadata = metadata
|
||||
s.callbackAccess.Lock()
|
||||
callbacks := s.callbacks.Array()
|
||||
s.callbackAccess.Unlock()
|
||||
s.access.Unlock()
|
||||
for _, callback := range callbacks {
|
||||
callback(s)
|
||||
}
|
||||
@@ -157,10 +157,14 @@ func (s *LocalRuleSet) PostStart() error {
|
||||
}
|
||||
|
||||
func (s *LocalRuleSet) Metadata() adapter.RuleSetMetadata {
|
||||
s.access.RLock()
|
||||
defer s.access.RUnlock()
|
||||
return s.metadata
|
||||
}
|
||||
|
||||
func (s *LocalRuleSet) ExtractIPSet() []*netipx.IPSet {
|
||||
s.access.RLock()
|
||||
defer s.access.RUnlock()
|
||||
return common.FlatMap(s.rules, extractIPSetFromRule)
|
||||
}
|
||||
|
||||
@@ -181,14 +185,14 @@ func (s *LocalRuleSet) Cleanup() {
|
||||
}
|
||||
|
||||
func (s *LocalRuleSet) RegisterCallback(callback adapter.RuleSetUpdateCallback) *list.Element[adapter.RuleSetUpdateCallback] {
|
||||
s.callbackAccess.Lock()
|
||||
defer s.callbackAccess.Unlock()
|
||||
s.access.Lock()
|
||||
defer s.access.Unlock()
|
||||
return s.callbacks.PushBack(callback)
|
||||
}
|
||||
|
||||
func (s *LocalRuleSet) UnregisterCallback(element *list.Element[adapter.RuleSetUpdateCallback]) {
|
||||
s.callbackAccess.Lock()
|
||||
defer s.callbackAccess.Unlock()
|
||||
s.access.Lock()
|
||||
defer s.access.Unlock()
|
||||
s.callbacks.Remove(element)
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
@@ -17,7 +18,6 @@ import (
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"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/json"
|
||||
@@ -40,16 +40,16 @@ type RemoteRuleSet struct {
|
||||
logger logger.ContextLogger
|
||||
outbound adapter.OutboundManager
|
||||
options option.RuleSet
|
||||
metadata adapter.RuleSetMetadata
|
||||
updateInterval time.Duration
|
||||
dialer N.Dialer
|
||||
access sync.RWMutex
|
||||
rules []adapter.HeadlessRule
|
||||
metadata adapter.RuleSetMetadata
|
||||
lastUpdated time.Time
|
||||
lastEtag string
|
||||
updateTicker *time.Ticker
|
||||
cacheFile adapter.CacheFile
|
||||
pauseManager pause.Manager
|
||||
callbackAccess sync.Mutex
|
||||
callbacks list.List[adapter.RuleSetUpdateCallback]
|
||||
refs atomic.Int32
|
||||
}
|
||||
@@ -120,10 +120,14 @@ func (s *RemoteRuleSet) PostStart() error {
|
||||
}
|
||||
|
||||
func (s *RemoteRuleSet) Metadata() adapter.RuleSetMetadata {
|
||||
s.access.RLock()
|
||||
defer s.access.RUnlock()
|
||||
return s.metadata
|
||||
}
|
||||
|
||||
func (s *RemoteRuleSet) ExtractIPSet() []*netipx.IPSet {
|
||||
s.access.RLock()
|
||||
defer s.access.RUnlock()
|
||||
return common.FlatMap(s.rules, extractIPSetFromRule)
|
||||
}
|
||||
|
||||
@@ -144,14 +148,14 @@ func (s *RemoteRuleSet) Cleanup() {
|
||||
}
|
||||
|
||||
func (s *RemoteRuleSet) RegisterCallback(callback adapter.RuleSetUpdateCallback) *list.Element[adapter.RuleSetUpdateCallback] {
|
||||
s.callbackAccess.Lock()
|
||||
defer s.callbackAccess.Unlock()
|
||||
s.access.Lock()
|
||||
defer s.access.Unlock()
|
||||
return s.callbacks.PushBack(callback)
|
||||
}
|
||||
|
||||
func (s *RemoteRuleSet) UnregisterCallback(element *list.Element[adapter.RuleSetUpdateCallback]) {
|
||||
s.callbackAccess.Lock()
|
||||
defer s.callbackAccess.Unlock()
|
||||
s.access.Lock()
|
||||
defer s.access.Unlock()
|
||||
s.callbacks.Remove(element)
|
||||
}
|
||||
|
||||
@@ -185,13 +189,13 @@ func (s *RemoteRuleSet) loadBytes(content []byte) error {
|
||||
return E.Cause(err, "parse rule_set.rules.[", i, "]")
|
||||
}
|
||||
}
|
||||
s.access.Lock()
|
||||
s.metadata.ContainsProcessRule = hasHeadlessRule(plainRuleSet.Rules, isProcessHeadlessRule)
|
||||
s.metadata.ContainsWIFIRule = hasHeadlessRule(plainRuleSet.Rules, isWIFIHeadlessRule)
|
||||
s.metadata.ContainsIPCIDRRule = hasHeadlessRule(plainRuleSet.Rules, isIPCIDRHeadlessRule)
|
||||
s.rules = rules
|
||||
s.callbackAccess.Lock()
|
||||
callbacks := s.callbacks.Array()
|
||||
s.callbackAccess.Unlock()
|
||||
s.access.Unlock()
|
||||
for _, callback := range callbacks {
|
||||
callback(s)
|
||||
}
|
||||
|
||||
@@ -182,9 +182,9 @@ func (t *resolve1Manager) logRequest(sender dbus.Sender, message ...any) context
|
||||
} else if metadata.ProcessInfo.UserId != 0 {
|
||||
prefix = F.ToString("uid:", metadata.ProcessInfo.UserId)
|
||||
}
|
||||
t.logger.InfoContext(ctx, "(", prefix, ") ", F.ToString(message...))
|
||||
t.logger.InfoContext(ctx, "(", prefix, ") ", strings.Join(F.MapToString(message), " "))
|
||||
} else {
|
||||
t.logger.InfoContext(ctx, F.ToString(message...))
|
||||
t.logger.InfoContext(ctx, strings.Join(F.MapToString(message), " "))
|
||||
}
|
||||
return adapter.WithContext(ctx, &metadata)
|
||||
}
|
||||
@@ -280,7 +280,10 @@ func (t *resolve1Manager) ResolveAddress(sender dbus.Sender, ifIndex int32, fami
|
||||
},
|
||||
}
|
||||
ctx := t.logRequest(sender, "ResolveAddress ", link.iif.Name, familyToString(family), addr, flags)
|
||||
response, lookupErr := t.dnsRouter.Exchange(ctx, request, adapter.DNSQueryOptions{})
|
||||
var metadata adapter.InboundContext
|
||||
metadata.InboundType = t.Type()
|
||||
metadata.Inbound = t.Tag()
|
||||
response, lookupErr := t.dnsRouter.Exchange(adapter.WithContext(ctx, &metadata), request, adapter.DNSQueryOptions{})
|
||||
if lookupErr != nil {
|
||||
err = wrapError(err)
|
||||
return
|
||||
@@ -301,7 +304,7 @@ func (t *resolve1Manager) ResolveAddress(sender dbus.Sender, ifIndex int32, fami
|
||||
return
|
||||
}
|
||||
|
||||
func (t *resolve1Manager) ResolveRecord(sender dbus.Sender, ifIndex int32, family int32, hostname string, qClass uint16, qType uint16, flags uint64) (records []ResourceRecord, outflags uint64, err *dbus.Error) {
|
||||
func (t *resolve1Manager) ResolveRecord(sender dbus.Sender, ifIndex int32, hostname string, qClass uint16, qType uint16, flags uint64) (records []ResourceRecord, outflags uint64, err *dbus.Error) {
|
||||
t.linkAccess.Lock()
|
||||
link, err := t.getLink(ifIndex)
|
||||
if err != nil {
|
||||
@@ -320,8 +323,11 @@ func (t *resolve1Manager) ResolveRecord(sender dbus.Sender, ifIndex int32, famil
|
||||
},
|
||||
},
|
||||
}
|
||||
ctx := t.logRequest(sender, "ResolveRecord ", link.iif.Name, familyToString(family), hostname, mDNS.Class(qClass), mDNS.Type(qType), flags)
|
||||
response, exchangeErr := t.dnsRouter.Exchange(ctx, request, adapter.DNSQueryOptions{})
|
||||
ctx := t.logRequest(sender, "ResolveRecord", link.iif.Name, hostname, mDNS.Class(qClass), mDNS.Type(qType), flags)
|
||||
var metadata adapter.InboundContext
|
||||
metadata.InboundType = t.Type()
|
||||
metadata.Inbound = t.Tag()
|
||||
response, exchangeErr := t.dnsRouter.Exchange(adapter.WithContext(ctx, &metadata), request, adapter.DNSQueryOptions{})
|
||||
if exchangeErr != nil {
|
||||
err = wrapError(exchangeErr)
|
||||
return
|
||||
@@ -341,6 +347,7 @@ func (t *resolve1Manager) ResolveRecord(sender dbus.Sender, ifIndex int32, famil
|
||||
err = wrapError(unpackErr)
|
||||
}
|
||||
record.Data = data
|
||||
records = append(records, record)
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -380,8 +387,10 @@ func (t *resolve1Manager) ResolveService(sender dbus.Sender, ifIndex int32, host
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
srvResponse, exchangeErr := t.dnsRouter.Exchange(ctx, srvRequest, adapter.DNSQueryOptions{})
|
||||
var metadata adapter.InboundContext
|
||||
metadata.InboundType = t.Type()
|
||||
metadata.Inbound = t.Tag()
|
||||
srvResponse, exchangeErr := t.dnsRouter.Exchange(adapter.WithContext(ctx, &metadata), srvRequest, adapter.DNSQueryOptions{})
|
||||
if exchangeErr != nil {
|
||||
err = wrapError(exchangeErr)
|
||||
return
|
||||
|
||||
@@ -91,11 +91,6 @@ func (i *Service) Start(stage adapter.StartStage) error {
|
||||
return E.New("multiple resolved service are not supported")
|
||||
}
|
||||
}
|
||||
case adapter.StartStateStart:
|
||||
err := i.listener.Start()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
systemBus, err := dbus.SystemBus()
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -117,6 +112,11 @@ func (i *Service) Start(stage adapter.StartStage) error {
|
||||
return E.New("unknown request name reply: ", reply)
|
||||
}
|
||||
i.networkUpdateCallback = i.network.NetworkMonitor().RegisterCallback(i.onNetworkUpdate)
|
||||
case adapter.StartStateStart:
|
||||
err := i.listener.Start()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -167,6 +167,8 @@ func (i *Service) exchangePacket0(ctx context.Context, buffer *buf.Buffer, oob [
|
||||
}
|
||||
var metadata adapter.InboundContext
|
||||
metadata.Source = source
|
||||
metadata.InboundType = i.Type()
|
||||
metadata.Inbound = i.Tag()
|
||||
response, err := i.dnsRouter.Exchange(adapter.WithContext(ctx, &metadata), &message, adapter.DNSQueryOptions{})
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -156,18 +156,14 @@ func (s *APIServer) deleteUser(writer http.ResponseWriter, request *http.Request
|
||||
}
|
||||
|
||||
func (s *APIServer) getStats(writer http.ResponseWriter, request *http.Request) {
|
||||
requireClear := chi.URLParam(request, "clear") == "true"
|
||||
requireClear := request.URL.Query().Get("clear") == "true"
|
||||
|
||||
users := s.user.List()
|
||||
s.traffic.ReadUsers(users)
|
||||
s.traffic.ReadUsers(users, requireClear)
|
||||
for i := range users {
|
||||
users[i].Password = ""
|
||||
}
|
||||
uplinkBytes, downlinkBytes, uplinkPackets, downlinkPackets, tcpSessions, udpSessions := s.traffic.ReadGlobal()
|
||||
|
||||
if requireClear {
|
||||
s.traffic.Clear()
|
||||
}
|
||||
uplinkBytes, downlinkBytes, uplinkPackets, downlinkPackets, tcpSessions, udpSessions := s.traffic.ReadGlobal(requireClear)
|
||||
|
||||
render.JSON(writer, request, render.M{
|
||||
"uplinkBytes": uplinkBytes,
|
||||
|
||||
@@ -5,8 +5,8 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/sagernet/sing/common/atomic"
|
||||
"github.com/sagernet/sing/common/json"
|
||||
"github.com/sagernet/sing/common/json/badjson"
|
||||
"github.com/sagernet/sing/service/filemanager"
|
||||
|
||||
@@ -3,9 +3,9 @@ package ssmapi
|
||||
import (
|
||||
"net"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing/common/atomic"
|
||||
"github.com/sagernet/sing/common/bufio"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
)
|
||||
@@ -142,86 +142,82 @@ func (s *TrafficManager) TrackPacketConnection(conn N.PacketConn, metadata adapt
|
||||
readPacketCounter = append(readPacketCounter, upPacketsCounter)
|
||||
writePacketCounter = append(writePacketCounter, downPacketsCounter)
|
||||
udpSessionCounter.Add(1)
|
||||
return bufio.NewInt64CounterPacketConn(conn, append(readCounter, readPacketCounter...), append(writeCounter, writePacketCounter...))
|
||||
return bufio.NewInt64CounterPacketConn(conn, readCounter, readPacketCounter, writeCounter, writePacketCounter)
|
||||
}
|
||||
|
||||
func (s *TrafficManager) ReadUser(user *UserObject) {
|
||||
s.userAccess.Lock()
|
||||
defer s.userAccess.Unlock()
|
||||
s.readUser(user)
|
||||
s.readUser(user, false)
|
||||
}
|
||||
|
||||
func (s *TrafficManager) readUser(user *UserObject) {
|
||||
func (s *TrafficManager) readUser(user *UserObject, swap bool) {
|
||||
if counter, loaded := s.userUplink[user.UserName]; loaded {
|
||||
user.UplinkBytes = counter.Load()
|
||||
if swap {
|
||||
user.UplinkBytes = counter.Swap(0)
|
||||
} else {
|
||||
user.UplinkBytes = counter.Load()
|
||||
}
|
||||
}
|
||||
if counter, loaded := s.userDownlink[user.UserName]; loaded {
|
||||
user.DownlinkBytes = counter.Load()
|
||||
if swap {
|
||||
user.DownlinkBytes = counter.Swap(0)
|
||||
} else {
|
||||
user.DownlinkBytes = counter.Load()
|
||||
}
|
||||
}
|
||||
if counter, loaded := s.userUplinkPackets[user.UserName]; loaded {
|
||||
user.UplinkPackets = counter.Load()
|
||||
if swap {
|
||||
user.UplinkPackets = counter.Swap(0)
|
||||
} else {
|
||||
user.UplinkPackets = counter.Load()
|
||||
}
|
||||
}
|
||||
if counter, loaded := s.userDownlinkPackets[user.UserName]; loaded {
|
||||
user.DownlinkPackets = counter.Load()
|
||||
if swap {
|
||||
user.DownlinkPackets = counter.Swap(0)
|
||||
} else {
|
||||
user.DownlinkPackets = counter.Load()
|
||||
}
|
||||
}
|
||||
if counter, loaded := s.userTCPSessions[user.UserName]; loaded {
|
||||
user.TCPSessions = counter.Load()
|
||||
if swap {
|
||||
user.TCPSessions = counter.Swap(0)
|
||||
} else {
|
||||
user.TCPSessions = counter.Load()
|
||||
}
|
||||
}
|
||||
if counter, loaded := s.userUDPSessions[user.UserName]; loaded {
|
||||
user.UDPSessions = counter.Load()
|
||||
if swap {
|
||||
user.UDPSessions = counter.Swap(0)
|
||||
} else {
|
||||
user.UDPSessions = counter.Load()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *TrafficManager) ReadUsers(users []*UserObject) {
|
||||
func (s *TrafficManager) ReadUsers(users []*UserObject, swap bool) {
|
||||
s.userAccess.Lock()
|
||||
defer s.userAccess.Unlock()
|
||||
for _, user := range users {
|
||||
s.readUser(user)
|
||||
s.readUser(user, swap)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (s *TrafficManager) ReadGlobal() (
|
||||
uplinkBytes int64,
|
||||
downlinkBytes int64,
|
||||
uplinkPackets int64,
|
||||
downlinkPackets int64,
|
||||
tcpSessions int64,
|
||||
udpSessions int64,
|
||||
) {
|
||||
return s.globalUplink.Load(),
|
||||
s.globalDownlink.Load(),
|
||||
s.globalUplinkPackets.Load(),
|
||||
s.globalDownlinkPackets.Load(),
|
||||
s.globalTCPSessions.Load(),
|
||||
s.globalUDPSessions.Load()
|
||||
}
|
||||
|
||||
func (s *TrafficManager) Clear() {
|
||||
s.globalUplink.Store(0)
|
||||
s.globalDownlink.Store(0)
|
||||
s.globalUplinkPackets.Store(0)
|
||||
s.globalDownlinkPackets.Store(0)
|
||||
s.globalTCPSessions.Store(0)
|
||||
s.globalUDPSessions.Store(0)
|
||||
s.userAccess.Lock()
|
||||
defer s.userAccess.Unlock()
|
||||
for _, counter := range s.userUplink {
|
||||
counter.Store(0)
|
||||
}
|
||||
for _, counter := range s.userDownlink {
|
||||
counter.Store(0)
|
||||
}
|
||||
for _, counter := range s.userUplinkPackets {
|
||||
counter.Store(0)
|
||||
}
|
||||
for _, counter := range s.userDownlinkPackets {
|
||||
counter.Store(0)
|
||||
}
|
||||
for _, counter := range s.userTCPSessions {
|
||||
counter.Store(0)
|
||||
}
|
||||
for _, counter := range s.userUDPSessions {
|
||||
counter.Store(0)
|
||||
func (s *TrafficManager) ReadGlobal(swap bool) (uplinkBytes int64, downlinkBytes int64, uplinkPackets int64, downlinkPackets int64, tcpSessions int64, udpSessions int64) {
|
||||
if swap {
|
||||
return s.globalUplink.Swap(0),
|
||||
s.globalDownlink.Swap(0),
|
||||
s.globalUplinkPackets.Swap(0),
|
||||
s.globalDownlinkPackets.Swap(0),
|
||||
s.globalTCPSessions.Swap(0),
|
||||
s.globalUDPSessions.Swap(0)
|
||||
} else {
|
||||
return s.globalUplink.Load(),
|
||||
s.globalDownlink.Load(),
|
||||
s.globalUplinkPackets.Load(),
|
||||
s.globalDownlinkPackets.Load(),
|
||||
s.globalTCPSessions.Load(),
|
||||
s.globalUDPSessions.Load()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -292,7 +292,7 @@ func ReadPacket(conn net.Conn, buffer *buf.Buffer) (M.Socksaddr, error) {
|
||||
}
|
||||
|
||||
_, err = buffer.ReadFullFrom(conn, int(length))
|
||||
return destination.Unwrap(), err
|
||||
return destination, err
|
||||
}
|
||||
|
||||
func WritePacket(conn net.Conn, buffer *buf.Buffer, destination M.Socksaddr) error {
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"net"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
@@ -29,7 +30,7 @@ type Client struct {
|
||||
serverAddr string
|
||||
serviceName string
|
||||
dialOptions []grpc.DialOption
|
||||
conn *grpc.ClientConn
|
||||
conn atomic.Pointer[grpc.ClientConn]
|
||||
connAccess sync.Mutex
|
||||
}
|
||||
|
||||
@@ -74,13 +75,13 @@ func NewClient(ctx context.Context, dialer N.Dialer, serverAddr M.Socksaddr, opt
|
||||
}
|
||||
|
||||
func (c *Client) connect() (*grpc.ClientConn, error) {
|
||||
conn := c.conn
|
||||
conn := c.conn.Load()
|
||||
if conn != nil && conn.GetState() != connectivity.Shutdown {
|
||||
return conn, nil
|
||||
}
|
||||
c.connAccess.Lock()
|
||||
defer c.connAccess.Unlock()
|
||||
conn = c.conn
|
||||
conn = c.conn.Load()
|
||||
if conn != nil && conn.GetState() != connectivity.Shutdown {
|
||||
return conn, nil
|
||||
}
|
||||
@@ -89,7 +90,7 @@ func (c *Client) connect() (*grpc.ClientConn, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c.conn = conn
|
||||
c.conn.Store(conn)
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
@@ -109,11 +110,9 @@ func (c *Client) DialContext(ctx context.Context) (net.Conn, error) {
|
||||
}
|
||||
|
||||
func (c *Client) Close() error {
|
||||
c.connAccess.Lock()
|
||||
defer c.connAccess.Unlock()
|
||||
if c.conn != nil {
|
||||
c.conn.Close()
|
||||
c.conn = nil
|
||||
conn := c.conn.Swap(nil)
|
||||
if conn != nil {
|
||||
conn.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ type Client struct {
|
||||
tlsConfig tls.Config
|
||||
quicConfig *quic.Config
|
||||
connAccess sync.Mutex
|
||||
conn quic.Connection
|
||||
conn common.TypedValue[quic.Connection]
|
||||
rawConn net.Conn
|
||||
}
|
||||
|
||||
@@ -50,13 +50,13 @@ func NewClient(ctx context.Context, dialer N.Dialer, serverAddr M.Socksaddr, opt
|
||||
}
|
||||
|
||||
func (c *Client) offer() (quic.Connection, error) {
|
||||
conn := c.conn
|
||||
conn := c.conn.Load()
|
||||
if conn != nil && !common.Done(conn.Context()) {
|
||||
return conn, nil
|
||||
}
|
||||
c.connAccess.Lock()
|
||||
defer c.connAccess.Unlock()
|
||||
conn = c.conn
|
||||
conn = c.conn.Load()
|
||||
if conn != nil && !common.Done(conn.Context()) {
|
||||
return conn, nil
|
||||
}
|
||||
@@ -72,14 +72,13 @@ func (c *Client) offerNew() (quic.Connection, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var packetConn net.PacketConn
|
||||
packetConn = bufio.NewUnbindPacketConn(udpConn)
|
||||
packetConn := bufio.NewUnbindPacketConn(udpConn)
|
||||
quicConn, err := qtls.Dial(c.ctx, packetConn, udpConn.RemoteAddr(), c.tlsConfig, c.quicConfig)
|
||||
if err != nil {
|
||||
packetConn.Close()
|
||||
return nil, err
|
||||
}
|
||||
c.conn = quicConn
|
||||
c.conn.Store(quicConn)
|
||||
c.rawConn = udpConn
|
||||
return quicConn, nil
|
||||
}
|
||||
@@ -99,13 +98,13 @@ func (c *Client) DialContext(ctx context.Context) (net.Conn, error) {
|
||||
func (c *Client) Close() error {
|
||||
c.connAccess.Lock()
|
||||
defer c.connAccess.Unlock()
|
||||
if c.conn != nil {
|
||||
c.conn.CloseWithError(0, "")
|
||||
conn := c.conn.Swap(nil)
|
||||
if conn != nil {
|
||||
conn.CloseWithError(0, "")
|
||||
}
|
||||
if c.rawConn != nil {
|
||||
c.rawConn.Close()
|
||||
}
|
||||
c.conn = nil
|
||||
c.rawConn = nil
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"net"
|
||||
"os"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
@@ -135,20 +136,22 @@ func (c *WebsocketConn) Upstream() any {
|
||||
type EarlyWebsocketConn struct {
|
||||
*Client
|
||||
ctx context.Context
|
||||
conn *WebsocketConn
|
||||
conn atomic.Pointer[WebsocketConn]
|
||||
access sync.Mutex
|
||||
create chan struct{}
|
||||
err error
|
||||
}
|
||||
|
||||
func (c *EarlyWebsocketConn) Read(b []byte) (n int, err error) {
|
||||
if c.conn == nil {
|
||||
conn := c.conn.Load()
|
||||
if conn == nil {
|
||||
<-c.create
|
||||
if c.err != nil {
|
||||
return 0, c.err
|
||||
}
|
||||
conn = c.conn.Load()
|
||||
}
|
||||
return wrapWsError0(c.conn.Read(b))
|
||||
return wrapWsError0(conn.Read(b))
|
||||
}
|
||||
|
||||
func (c *EarlyWebsocketConn) writeRequest(content []byte) error {
|
||||
@@ -187,21 +190,23 @@ func (c *EarlyWebsocketConn) writeRequest(content []byte) error {
|
||||
return err
|
||||
}
|
||||
}
|
||||
c.conn = conn
|
||||
c.conn.Store(conn)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *EarlyWebsocketConn) Write(b []byte) (n int, err error) {
|
||||
if c.conn != nil {
|
||||
return wrapWsError0(c.conn.Write(b))
|
||||
conn := c.conn.Load()
|
||||
if conn != nil {
|
||||
return wrapWsError0(conn.Write(b))
|
||||
}
|
||||
c.access.Lock()
|
||||
defer c.access.Unlock()
|
||||
conn = c.conn.Load()
|
||||
if c.err != nil {
|
||||
return 0, c.err
|
||||
}
|
||||
if c.conn != nil {
|
||||
return wrapWsError0(c.conn.Write(b))
|
||||
if conn != nil {
|
||||
return wrapWsError0(conn.Write(b))
|
||||
}
|
||||
err = c.writeRequest(b)
|
||||
c.err = err
|
||||
@@ -213,17 +218,19 @@ func (c *EarlyWebsocketConn) Write(b []byte) (n int, err error) {
|
||||
}
|
||||
|
||||
func (c *EarlyWebsocketConn) WriteBuffer(buffer *buf.Buffer) error {
|
||||
if c.conn != nil {
|
||||
return wrapWsError(c.conn.WriteBuffer(buffer))
|
||||
conn := c.conn.Load()
|
||||
if conn != nil {
|
||||
return wrapWsError(conn.WriteBuffer(buffer))
|
||||
}
|
||||
c.access.Lock()
|
||||
defer c.access.Unlock()
|
||||
if c.conn != nil {
|
||||
return wrapWsError(c.conn.WriteBuffer(buffer))
|
||||
}
|
||||
if c.err != nil {
|
||||
return c.err
|
||||
}
|
||||
conn = c.conn.Load()
|
||||
if conn != nil {
|
||||
return wrapWsError(conn.WriteBuffer(buffer))
|
||||
}
|
||||
err := c.writeRequest(buffer.Bytes())
|
||||
c.err = err
|
||||
close(c.create)
|
||||
@@ -231,24 +238,27 @@ func (c *EarlyWebsocketConn) WriteBuffer(buffer *buf.Buffer) error {
|
||||
}
|
||||
|
||||
func (c *EarlyWebsocketConn) Close() error {
|
||||
if c.conn == nil {
|
||||
conn := c.conn.Load()
|
||||
if conn == nil {
|
||||
return nil
|
||||
}
|
||||
return c.conn.Close()
|
||||
return conn.Close()
|
||||
}
|
||||
|
||||
func (c *EarlyWebsocketConn) LocalAddr() net.Addr {
|
||||
if c.conn == nil {
|
||||
conn := c.conn.Load()
|
||||
if conn == nil {
|
||||
return M.Socksaddr{}
|
||||
}
|
||||
return c.conn.LocalAddr()
|
||||
return conn.LocalAddr()
|
||||
}
|
||||
|
||||
func (c *EarlyWebsocketConn) RemoteAddr() net.Addr {
|
||||
if c.conn == nil {
|
||||
conn := c.conn.Load()
|
||||
if conn == nil {
|
||||
return M.Socksaddr{}
|
||||
}
|
||||
return c.conn.RemoteAddr()
|
||||
return conn.RemoteAddr()
|
||||
}
|
||||
|
||||
func (c *EarlyWebsocketConn) SetDeadline(t time.Time) error {
|
||||
@@ -268,11 +278,11 @@ func (c *EarlyWebsocketConn) NeedAdditionalReadDeadline() bool {
|
||||
}
|
||||
|
||||
func (c *EarlyWebsocketConn) Upstream() any {
|
||||
return common.PtrOrNil(c.conn)
|
||||
return common.PtrOrNil(c.conn.Load())
|
||||
}
|
||||
|
||||
func (c *EarlyWebsocketConn) LazyHeadroom() bool {
|
||||
return c.conn == nil
|
||||
return c.conn.Load() == nil
|
||||
}
|
||||
|
||||
func wrapWsError(err error) error {
|
||||
@@ -281,7 +291,7 @@ func wrapWsError(err error) error {
|
||||
}
|
||||
var closedErr wsutil.ClosedError
|
||||
if errors.As(err, &closedErr) {
|
||||
if closedErr.Code == ws.StatusNormalClosure {
|
||||
if closedErr.Code == ws.StatusNormalClosure || closedErr.Code == ws.StatusNoStatusRcvd {
|
||||
err = io.EOF
|
||||
}
|
||||
}
|
||||
|
||||
@@ -138,7 +138,7 @@ func (c *ClientBind) receive(packets [][]byte, sizes []int, eps []conn.Endpoint)
|
||||
b := packets[0]
|
||||
common.ClearArray(b[1:4])
|
||||
}
|
||||
eps[0] = remoteEndpoint(M.AddrPortFromNet(addr))
|
||||
eps[0] = remoteEndpoint(M.SocksaddrFromNet(addr).Unwrap().AddrPort())
|
||||
count = 1
|
||||
return
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user