mirror of
https://github.com/SagerNet/sing-box.git
synced 2026-04-12 01:57:18 +10:00
Compare commits
213 Commits
dev-auto-r
...
v1.12.8
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 | ||
|
|
4fb5ac292b | ||
|
|
0e23a3d7c2 | ||
|
|
76ee64ae50 | ||
|
|
e1dbcccab5 | ||
|
|
fba802effd | ||
|
|
9495b56772 | ||
|
|
a8434b176f | ||
|
|
ef0004400d | ||
|
|
0a63049845 | ||
|
|
2dcb86941f | ||
|
|
5c6eb89cfb | ||
|
|
5b92eeb3bf | ||
|
|
3518ce083b | ||
|
|
f13c54afc1 | ||
|
|
3388efe65a | ||
|
|
a11384b286 | ||
|
|
9dd9fb27cd | ||
|
|
0f2035149c | ||
|
|
cba364204a | ||
|
|
4e17788549 | ||
|
|
18a6719893 | ||
|
|
687343f6ca | ||
|
|
e061538c30 | ||
|
|
a6375c7530 | ||
|
|
45fa18a2e3 | ||
|
|
534cccce91 | ||
|
|
72dbcd3ad4 | ||
|
|
5533094984 | ||
|
|
ae2ecd6002 | ||
|
|
0098a2adc5 | ||
|
|
c0dd4a3f07 | ||
|
|
497ddb5829 | ||
|
|
811ff93549 | ||
|
|
96df69bcdc | ||
|
|
6cfa2b8b86 | ||
|
|
eea1e701b7 | ||
|
|
455e5de74d | ||
|
|
9533031891 | ||
|
|
80f8ea6849 | ||
|
|
50eadb00c7 | ||
|
|
d4012bd0b2 | ||
|
|
a902e9f9f6 | ||
|
|
da3ba573d8 | ||
|
|
bea9048cfe | ||
|
|
fc0f5ed83a | ||
|
|
c0588c30d7 | ||
|
|
24c940c51c | ||
|
|
407ee08d8a | ||
|
|
756585fb2a | ||
|
|
5662784afb | ||
|
|
3801901726 | ||
|
|
7d58174f1f | ||
|
|
d339f85087 | ||
|
|
b6a114f7f4 | ||
|
|
e586ef070e | ||
|
|
71a76e9ecb | ||
|
|
1d66474022 | ||
|
|
3934e53476 | ||
|
|
0146fbfc40 | ||
|
|
6ee3117755 | ||
|
|
e2440a569e | ||
|
|
7a1eee78df | ||
|
|
e3c8c0705f | ||
|
|
886d427337 | ||
|
|
d5432b4c27 | ||
|
|
42064fe7ec | ||
|
|
7cee76f9a6 | ||
|
|
ed5b2f2997 | ||
|
|
3b480de38a | ||
|
|
f990630ccc | ||
|
|
d33614d6a0 | ||
|
|
b3866bcea0 | ||
|
|
26ec73c71b | ||
|
|
c3403c5413 | ||
|
|
3b6ddcae37 | ||
|
|
dbdcce20a8 | ||
|
|
e7ef1b2368 | ||
|
|
ce32d1c2c3 | ||
|
|
596b66f397 | ||
|
|
d4fd43cf6f | ||
|
|
6c377f16e7 | ||
|
|
349db7baec | ||
|
|
1f3097da00 | ||
|
|
0b4b5e6f0f | ||
|
|
245273e6c1 | ||
|
|
54a0004de6 | ||
|
|
6a211f6ed6 | ||
|
|
aadb44ebd6 | ||
|
|
9b0db6ab15 | ||
|
|
5b363c347f | ||
|
|
cdea3f63d4 | ||
|
|
40a6260f6e | ||
|
|
a5e47f4e0f | ||
|
|
ac7bc587cb | ||
|
|
4e11a3585a | ||
|
|
63d3e9f6e5 | ||
|
|
d115e36ed8 | ||
|
|
af56b1a950 | ||
|
|
f9999a76fe | ||
|
|
42eb3841a1 | ||
|
|
fb622ccbdf | ||
|
|
d2dc3ddf72 | ||
|
|
e8499452f8 | ||
|
|
e0a6b31c03 | ||
|
|
7c923209ad | ||
|
|
bca2bd2fa1 | ||
|
|
fa99ca2757 | ||
|
|
7073f2a272 | ||
|
|
390e30ae7b | ||
|
|
23cf8c49e0 | ||
|
|
b17a024f6c | ||
|
|
1ed21085bb | ||
|
|
56409ff269 | ||
|
|
0c523980ff | ||
|
|
32873d06bc | ||
|
|
4accaccf77 | ||
|
|
ff416aacaf | ||
|
|
b97947e8ac | ||
|
|
dfcd9fb8c3 | ||
|
|
803811568e | ||
|
|
50b0bd5c39 | ||
|
|
2d02b2b1cf | ||
|
|
456fbecf16 | ||
|
|
668923c392 | ||
|
|
c51e9cbe06 | ||
|
|
60b451e6cf | ||
|
|
3e35390d8f | ||
|
|
f2dad289fb | ||
|
|
b4a8fa59f5 | ||
|
|
73de2a7d07 | ||
|
|
1699a7ce33 | ||
|
|
7743c6e881 | ||
|
|
9a5f69f435 | ||
|
|
5c4211e849 | ||
|
|
c1189e2a7b | ||
|
|
f18889369f | ||
|
|
91c7b638e8 | ||
|
|
6f793a0273 | ||
|
|
0f6c417c3c | ||
|
|
c830e9a634 | ||
|
|
e809623ec9 | ||
|
|
061276902b | ||
|
|
fa6f7d396e | ||
|
|
23666a9230 | ||
|
|
17576e9f66 | ||
|
|
90ec9c8bcb | ||
|
|
988ac62a1b | ||
|
|
3016338e34 | ||
|
|
bc35aca017 | ||
|
|
281d52a1ea |
@@ -16,7 +16,7 @@ release/config/sing-box.service=/usr/lib/systemd/system/sing-box.service
|
||||
release/config/sing-box@.service=/usr/lib/systemd/system/sing-box@.service
|
||||
release/config/sing-box.sysusers=/usr/lib/sysusers.d/sing-box.conf
|
||||
release/config/sing-box.rules=usr/share/polkit-1/rules.d/sing-box.rules
|
||||
release/config/sing-box-dbus.xml=/usr/share/dbus-1/system.d/sing-box-dbus.conf
|
||||
release/config/sing-box-split-dns.xml=/usr/share/dbus-1/system.d/sing-box-split-dns.conf
|
||||
|
||||
release/completions/sing-box.bash=/usr/share/bash-completion/completions/sing-box.bash
|
||||
release/completions/sing-box.fish=/usr/share/fish/vendor_completions.d/sing-box.fish
|
||||
|
||||
2
.github/setup_legacy_go.sh
vendored
2
.github/setup_legacy_go.sh
vendored
@@ -1,6 +1,6 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
VERSION="1.23.6"
|
||||
VERSION="1.23.12"
|
||||
|
||||
mkdir -p $HOME/go
|
||||
cd $HOME/go
|
||||
|
||||
67
.github/workflows/build.yml
vendored
67
.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
|
||||
go-version: ^1.25.1
|
||||
- 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_go123: true, legacy_name: "windows-7" }
|
||||
- { os: windows, arch: "386" }
|
||||
- { os: windows, arch: "386", legacy_go: true }
|
||||
- { os: windows, arch: "386", legacy_go123: 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,28 +103,33 @@ 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
|
||||
- name: Cache Legacy Go
|
||||
if: matrix.require_legacy_go
|
||||
go-version: ^1.25.1
|
||||
- name: Setup Go 1.24
|
||||
if: matrix.legacy_go124
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ~1.24.6
|
||||
- name: Cache Go 1.23
|
||||
if: matrix.legacy_go123
|
||||
id: cache-legacy-go
|
||||
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'
|
||||
key: go_legacy_12312
|
||||
- name: Setup Go 1.23
|
||||
if: matrix.legacy_go123 && steps.cache-legacy-go.outputs.cache-hit != 'true'
|
||||
run: |-
|
||||
.github/setup_legacy_go.sh
|
||||
- name: Setup Legacy Go 2
|
||||
if: matrix.legacy_go
|
||||
- name: Setup Go 1.23
|
||||
if: matrix.legacy_go123
|
||||
run: |-
|
||||
echo "PATH=$HOME/go/go_legacy/bin:$PATH" >> $GITHUB_ENV
|
||||
echo "GOROOT=$HOME/go/go_legacy" >> $GITHUB_ENV
|
||||
@@ -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
|
||||
go-version: ^1.25.1
|
||||
- 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
|
||||
go-version: ^1.25.1
|
||||
- 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
|
||||
- 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.1
|
||||
- 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-*
|
||||
|
||||
6
.github/workflows/lint.yml
vendored
6
.github/workflows/lint.yml
vendored
@@ -22,15 +22,15 @@ 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
|
||||
go-version: ~1.24.6
|
||||
- name: golangci-lint
|
||||
uses: golangci/golangci-lint-action@v6
|
||||
uses: golangci/golangci-lint-action@v8
|
||||
with:
|
||||
version: latest
|
||||
args: --timeout=30m
|
||||
|
||||
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
|
||||
go-version: ^1.25.1
|
||||
- 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
|
||||
go-version: ^1.25.1
|
||||
- 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
|
||||
|
||||
@@ -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$
|
||||
|
||||
@@ -59,8 +59,8 @@ nfpms:
|
||||
dst: /usr/lib/sysusers.d/sing-box.conf
|
||||
- src: release/config/sing-box.rules
|
||||
dst: /usr/share/polkit-1/rules.d/sing-box.rules
|
||||
- src: release/config/sing-box-dbus.xml
|
||||
dst: /usr/share/dbus-1/system.d/sing-box-dbus.conf
|
||||
- src: release/config/sing-box-split-dns.xml
|
||||
dst: /usr/share/dbus-1/system.d/sing-box-split-dns.conf
|
||||
|
||||
- src: release/completions/sing-box.bash
|
||||
dst: /usr/share/bash-completion/completions/sing-box.bash
|
||||
|
||||
@@ -140,8 +140,8 @@ nfpms:
|
||||
dst: /usr/lib/sysusers.d/sing-box.conf
|
||||
- src: release/config/sing-box.rules
|
||||
dst: /usr/share/polkit-1/rules.d/sing-box.rules
|
||||
- src: release/config/sing-box-dbus.xml
|
||||
dst: /usr/share/dbus-1/system.d/sing-box-dbus.conf
|
||||
- src: release/config/sing-box-split-dns.xml
|
||||
dst: /usr/share/dbus-1/system.d/sing-box-split-dns.conf
|
||||
|
||||
- src: release/completions/sing-box.bash
|
||||
dst: /usr/share/bash-completion/completions/sing-box.bash
|
||||
|
||||
@@ -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
|
||||
|
||||
10
Makefile
10
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) && \
|
||||
@@ -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@latest
|
||||
|
||||
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: 7f1fa971e3...cd8ac376f6
Submodule clients/apple updated: f7883b0f3e...96bee16bf0
@@ -107,8 +107,10 @@ func buildAndroid() {
|
||||
}
|
||||
|
||||
if !debugEnabled {
|
||||
sharedFlags[3] = sharedFlags[3] + " -checklinkname=0"
|
||||
args = append(args, sharedFlags...)
|
||||
} else {
|
||||
debugFlags[1] = debugFlags[1] + " -checklinkname=0"
|
||||
args = append(args, debugFlags...)
|
||||
}
|
||||
|
||||
|
||||
284
cmd/internal/tun_bench/main.go
Normal file
284
cmd/internal/tun_bench/main.go
Normal file
@@ -0,0 +1,284 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/netip"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"github.com/sagernet/sing-box/include"
|
||||
"github.com/sagernet/sing-box/log"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
"github.com/sagernet/sing/common"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
"github.com/sagernet/sing/common/json"
|
||||
"github.com/sagernet/sing/common/shell"
|
||||
)
|
||||
|
||||
var iperf3Path string
|
||||
|
||||
func main() {
|
||||
err := main0()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func main0() error {
|
||||
err := shell.Exec("sudo", "ls").Run()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
results, err := runTests()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
encoder := json.NewEncoder(os.Stdout)
|
||||
encoder.SetIndent("", " ")
|
||||
return encoder.Encode(results)
|
||||
}
|
||||
|
||||
func runTests() ([]TestResult, error) {
|
||||
boxPaths := []string{
|
||||
os.ExpandEnv("$HOME/Downloads/sing-box-1.11.15-darwin-arm64/sing-box"),
|
||||
//"/Users/sekai/Downloads/sing-box-1.11.15-linux-arm64/sing-box",
|
||||
"./sing-box",
|
||||
}
|
||||
stacks := []string{
|
||||
"gvisor",
|
||||
"system",
|
||||
}
|
||||
mtus := []int{
|
||||
1500,
|
||||
4064,
|
||||
// 16384,
|
||||
// 32768,
|
||||
// 49152,
|
||||
65535,
|
||||
}
|
||||
flagList := [][]string{
|
||||
{},
|
||||
}
|
||||
var results []TestResult
|
||||
for _, boxPath := range boxPaths {
|
||||
for _, stack := range stacks {
|
||||
for _, mtu := range mtus {
|
||||
if strings.HasPrefix(boxPath, ".") {
|
||||
for _, flags := range flagList {
|
||||
result, err := testOnce(boxPath, stack, mtu, false, flags)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
results = append(results, *result)
|
||||
}
|
||||
} else {
|
||||
result, err := testOnce(boxPath, stack, mtu, false, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
results = append(results, *result)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return results, nil
|
||||
}
|
||||
|
||||
type TestResult struct {
|
||||
BoxPath string `json:"box_path"`
|
||||
Stack string `json:"stack"`
|
||||
MTU int `json:"mtu"`
|
||||
Flags []string `json:"flags"`
|
||||
MultiThread bool `json:"multi_thread"`
|
||||
UploadSpeed string `json:"upload_speed"`
|
||||
DownloadSpeed string `json:"download_speed"`
|
||||
}
|
||||
|
||||
func testOnce(boxPath string, stackName string, mtu int, multiThread bool, flags []string) (result *TestResult, err error) {
|
||||
testAddress := netip.MustParseAddr("1.1.1.1")
|
||||
testConfig := option.Options{
|
||||
Inbounds: []option.Inbound{
|
||||
{
|
||||
Type: C.TypeTun,
|
||||
Options: &option.TunInboundOptions{
|
||||
Address: []netip.Prefix{netip.MustParsePrefix("172.18.0.1/30")},
|
||||
AutoRoute: true,
|
||||
MTU: uint32(mtu),
|
||||
Stack: stackName,
|
||||
RouteAddress: []netip.Prefix{netip.PrefixFrom(testAddress, testAddress.BitLen())},
|
||||
},
|
||||
},
|
||||
},
|
||||
Route: &option.RouteOptions{
|
||||
Rules: []option.Rule{
|
||||
{
|
||||
Type: C.RuleTypeDefault,
|
||||
DefaultOptions: option.DefaultRule{
|
||||
RawDefaultRule: option.RawDefaultRule{
|
||||
IPCIDR: []string{testAddress.String()},
|
||||
},
|
||||
RuleAction: option.RuleAction{
|
||||
Action: C.RuleActionTypeRouteOptions,
|
||||
RouteOptionsOptions: option.RouteOptionsActionOptions{
|
||||
OverrideAddress: "127.0.0.1",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
AutoDetectInterface: true,
|
||||
},
|
||||
}
|
||||
ctx := include.Context(context.Background())
|
||||
tempConfig, err := os.CreateTemp("", "tun-bench-*.json")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer os.Remove(tempConfig.Name())
|
||||
encoder := json.NewEncoderContext(ctx, tempConfig)
|
||||
encoder.SetIndent("", " ")
|
||||
err = encoder.Encode(testConfig)
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "encode test config")
|
||||
}
|
||||
tempConfig.Close()
|
||||
var sudoArgs []string
|
||||
if len(flags) > 0 {
|
||||
sudoArgs = append(sudoArgs, "env")
|
||||
sudoArgs = append(sudoArgs, flags...)
|
||||
}
|
||||
sudoArgs = append(sudoArgs, boxPath, "run", "-c", tempConfig.Name())
|
||||
boxProcess := shell.Exec("sudo", sudoArgs...)
|
||||
boxProcess.Stdout = &stderrWriter{}
|
||||
boxProcess.Stderr = io.Discard
|
||||
err = boxProcess.Start()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if C.IsDarwin {
|
||||
iperf3Path, err = exec.LookPath("iperf3-darwin")
|
||||
} else {
|
||||
iperf3Path, err = exec.LookPath("iperf3")
|
||||
}
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
serverProcess := shell.Exec(iperf3Path, "-s")
|
||||
serverProcess.Stdout = io.Discard
|
||||
serverProcess.Stderr = io.Discard
|
||||
err = serverProcess.Start()
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "start iperf3 server")
|
||||
}
|
||||
|
||||
time.Sleep(time.Second)
|
||||
|
||||
args := []string{"-c", testAddress.String()}
|
||||
if multiThread {
|
||||
args = append(args, "-P", "10")
|
||||
}
|
||||
|
||||
uploadProcess := shell.Exec(iperf3Path, args...)
|
||||
output, err := uploadProcess.Read()
|
||||
if err != nil {
|
||||
boxProcess.Process.Signal(syscall.SIGKILL)
|
||||
serverProcess.Process.Signal(syscall.SIGKILL)
|
||||
println(output)
|
||||
return
|
||||
}
|
||||
|
||||
uploadResult := common.SubstringBeforeLast(output, "iperf Done.")
|
||||
uploadResult = common.SubstringBeforeLast(uploadResult, "sender")
|
||||
uploadResult = common.SubstringBeforeLast(uploadResult, "bits/sec")
|
||||
uploadResult = common.SubstringAfterLast(uploadResult, "Bytes")
|
||||
uploadResult = strings.ReplaceAll(uploadResult, " ", "")
|
||||
|
||||
result = &TestResult{
|
||||
BoxPath: boxPath,
|
||||
Stack: stackName,
|
||||
MTU: mtu,
|
||||
Flags: flags,
|
||||
MultiThread: multiThread,
|
||||
UploadSpeed: uploadResult,
|
||||
}
|
||||
|
||||
downloadProcess := shell.Exec(iperf3Path, append(args, "-R")...)
|
||||
output, err = downloadProcess.Read()
|
||||
if err != nil {
|
||||
boxProcess.Process.Signal(syscall.SIGKILL)
|
||||
serverProcess.Process.Signal(syscall.SIGKILL)
|
||||
println(output)
|
||||
return
|
||||
}
|
||||
|
||||
downloadResult := common.SubstringBeforeLast(output, "iperf Done.")
|
||||
downloadResult = common.SubstringBeforeLast(downloadResult, "receiver")
|
||||
downloadResult = common.SubstringBeforeLast(downloadResult, "bits/sec")
|
||||
downloadResult = common.SubstringAfterLast(downloadResult, "Bytes")
|
||||
downloadResult = strings.ReplaceAll(downloadResult, " ", "")
|
||||
|
||||
result.DownloadSpeed = downloadResult
|
||||
|
||||
printArgs := []any{boxPath, stackName, mtu, "upload", uploadResult, "download", downloadResult}
|
||||
if len(flags) > 0 {
|
||||
printArgs = append(printArgs, "flags", strings.Join(flags, " "))
|
||||
}
|
||||
if multiThread {
|
||||
printArgs = append(printArgs, "(-P 10)")
|
||||
}
|
||||
fmt.Println(printArgs...)
|
||||
err = boxProcess.Process.Signal(syscall.SIGTERM)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = serverProcess.Process.Signal(syscall.SIGTERM)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
boxDone := make(chan struct{})
|
||||
go func() {
|
||||
boxProcess.Cmd.Wait()
|
||||
close(boxDone)
|
||||
}()
|
||||
|
||||
serverDone := make(chan struct{})
|
||||
go func() {
|
||||
serverProcess.Process.Wait()
|
||||
close(serverDone)
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-boxDone:
|
||||
case <-time.After(2 * time.Second):
|
||||
boxProcess.Process.Kill()
|
||||
case <-time.After(4 * time.Second):
|
||||
println("box process did not close!")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
select {
|
||||
case <-serverDone:
|
||||
case <-time.After(2 * time.Second):
|
||||
serverProcess.Process.Kill()
|
||||
case <-time.After(4 * time.Second):
|
||||
println("server process did not close!")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
type stderrWriter struct{}
|
||||
|
||||
func (w *stderrWriter) Write(p []byte) (n int, err error) {
|
||||
return os.Stderr.Write(p)
|
||||
}
|
||||
@@ -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()
|
||||
|
||||
@@ -454,5 +454,5 @@ func parseADGuardIPCIDRLine(ruleLine string) (netip.Prefix, error) {
|
||||
for len(ruleParts) < 4 {
|
||||
ruleParts = append(ruleParts, 0)
|
||||
}
|
||||
return netip.PrefixFrom(netip.AddrFrom4(*(*[4]byte)(ruleParts)), bitLen), nil
|
||||
return netip.PrefixFrom(netip.AddrFrom4([4]byte(ruleParts)), bitLen), nil
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -111,7 +111,7 @@ func NewWithOptions(options Options) (N.Dialer, error) {
|
||||
dnsQueryOptions.Transport = dnsTransport.Default()
|
||||
} else if options.NewDialer {
|
||||
return nil, E.New("missing domain resolver for domain server address")
|
||||
} else if !options.DirectOutbound {
|
||||
} else {
|
||||
deprecated.Report(options.Context, deprecated.OptionMissingDomainResolver)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -164,9 +165,8 @@ func (l *Listener) loopUDPOut() {
|
||||
if l.shutdown.Load() && E.IsClosed(err) {
|
||||
return
|
||||
}
|
||||
l.udpConn.Close()
|
||||
l.logger.Error("udp listener write back: ", destination, ": ", err)
|
||||
return
|
||||
continue
|
||||
}
|
||||
continue
|
||||
case <-l.packetOutboundClosed:
|
||||
|
||||
@@ -96,11 +96,11 @@ func findProcessName(network string, ip netip.Addr, port int) (string, error) {
|
||||
switch {
|
||||
case flag&0x1 > 0 && isIPv4:
|
||||
// ipv4
|
||||
srcIP = netip.AddrFrom4(*(*[4]byte)(buf[inp+76 : inp+80]))
|
||||
srcIP = netip.AddrFrom4([4]byte(buf[inp+76 : inp+80]))
|
||||
srcIsIPv4 = true
|
||||
case flag&0x2 > 0 && !isIPv4:
|
||||
// ipv6
|
||||
srcIP = netip.AddrFrom16(*(*[16]byte)(buf[inp+64 : inp+80]))
|
||||
srcIP = netip.AddrFrom16([16]byte(buf[inp+64 : inp+80]))
|
||||
default:
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -1,14 +1,11 @@
|
||||
package tls
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"crypto/ecdh"
|
||||
"crypto/rand"
|
||||
"encoding/pem"
|
||||
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
|
||||
"github.com/cloudflare/circl/hpke"
|
||||
"github.com/cloudflare/circl/kem"
|
||||
"golang.org/x/crypto/cryptobyte"
|
||||
)
|
||||
|
||||
type ECHCapableConfig interface {
|
||||
@@ -17,145 +14,68 @@ type ECHCapableConfig interface {
|
||||
SetECHConfigList([]byte)
|
||||
}
|
||||
|
||||
func ECHKeygenDefault(serverName string) (configPem string, keyPem string, err error) {
|
||||
cipherSuites := []echCipherSuite{
|
||||
{
|
||||
kdf: hpke.KDF_HKDF_SHA256,
|
||||
aead: hpke.AEAD_AES128GCM,
|
||||
}, {
|
||||
kdf: hpke.KDF_HKDF_SHA256,
|
||||
aead: hpke.AEAD_ChaCha20Poly1305,
|
||||
},
|
||||
}
|
||||
keyConfig := []myECHKeyConfig{
|
||||
{id: 0, kem: hpke.KEM_X25519_HKDF_SHA256},
|
||||
}
|
||||
|
||||
keyPairs, err := echKeygen(0xfe0d, serverName, keyConfig, cipherSuites)
|
||||
func ECHKeygenDefault(publicName string) (configPem string, keyPem string, err error) {
|
||||
echKey, err := ecdh.X25519().GenerateKey(rand.Reader)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var configBuffer bytes.Buffer
|
||||
var totalLen uint16
|
||||
for _, keyPair := range keyPairs {
|
||||
totalLen += uint16(len(keyPair.rawConf))
|
||||
echConfig, err := marshalECHConfig(0, echKey.PublicKey().Bytes(), publicName, 0)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
binary.Write(&configBuffer, binary.BigEndian, totalLen)
|
||||
for _, keyPair := range keyPairs {
|
||||
configBuffer.Write(keyPair.rawConf)
|
||||
configBuilder := cryptobyte.NewBuilder(nil)
|
||||
configBuilder.AddUint16LengthPrefixed(func(builder *cryptobyte.Builder) {
|
||||
builder.AddBytes(echConfig)
|
||||
})
|
||||
configBytes, err := configBuilder.Bytes()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var keyBuffer bytes.Buffer
|
||||
for _, keyPair := range keyPairs {
|
||||
keyBuffer.Write(keyPair.rawKey)
|
||||
keyBuilder := cryptobyte.NewBuilder(nil)
|
||||
keyBuilder.AddUint16LengthPrefixed(func(builder *cryptobyte.Builder) {
|
||||
builder.AddBytes(echKey.Bytes())
|
||||
})
|
||||
keyBuilder.AddUint16LengthPrefixed(func(builder *cryptobyte.Builder) {
|
||||
builder.AddBytes(echConfig)
|
||||
})
|
||||
keyBytes, err := keyBuilder.Bytes()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
configPem = string(pem.EncodeToMemory(&pem.Block{Type: "ECH CONFIGS", Bytes: configBuffer.Bytes()}))
|
||||
keyPem = string(pem.EncodeToMemory(&pem.Block{Type: "ECH KEYS", Bytes: keyBuffer.Bytes()}))
|
||||
configPem = string(pem.EncodeToMemory(&pem.Block{Type: "ECH CONFIGS", Bytes: configBytes}))
|
||||
keyPem = string(pem.EncodeToMemory(&pem.Block{Type: "ECH KEYS", Bytes: keyBytes}))
|
||||
return
|
||||
}
|
||||
|
||||
type echKeyConfigPair struct {
|
||||
id uint8
|
||||
rawKey []byte
|
||||
conf myECHKeyConfig
|
||||
rawConf []byte
|
||||
}
|
||||
func marshalECHConfig(id uint8, pubKey []byte, publicName string, maxNameLen uint8) ([]byte, error) {
|
||||
const extensionEncryptedClientHello = 0xfe0d
|
||||
const DHKEM_X25519_HKDF_SHA256 = 0x0020
|
||||
const KDF_HKDF_SHA256 = 0x0001
|
||||
builder := cryptobyte.NewBuilder(nil)
|
||||
builder.AddUint16(extensionEncryptedClientHello)
|
||||
builder.AddUint16LengthPrefixed(func(builder *cryptobyte.Builder) {
|
||||
builder.AddUint8(id)
|
||||
|
||||
type echCipherSuite struct {
|
||||
kdf hpke.KDF
|
||||
aead hpke.AEAD
|
||||
}
|
||||
|
||||
type myECHKeyConfig struct {
|
||||
id uint8
|
||||
kem hpke.KEM
|
||||
seed []byte
|
||||
}
|
||||
|
||||
func echKeygen(version uint16, serverName string, conf []myECHKeyConfig, suite []echCipherSuite) ([]echKeyConfigPair, error) {
|
||||
be := binary.BigEndian
|
||||
// prepare for future update
|
||||
if version != 0xfe0d {
|
||||
return nil, E.New("unsupported ECH version", version)
|
||||
}
|
||||
|
||||
suiteBuf := make([]byte, 0, len(suite)*4+2)
|
||||
suiteBuf = be.AppendUint16(suiteBuf, uint16(len(suite))*4)
|
||||
for _, s := range suite {
|
||||
if !s.kdf.IsValid() || !s.aead.IsValid() {
|
||||
return nil, E.New("invalid HPKE cipher suite")
|
||||
}
|
||||
suiteBuf = be.AppendUint16(suiteBuf, uint16(s.kdf))
|
||||
suiteBuf = be.AppendUint16(suiteBuf, uint16(s.aead))
|
||||
}
|
||||
|
||||
pairs := []echKeyConfigPair{}
|
||||
for _, c := range conf {
|
||||
pair := echKeyConfigPair{}
|
||||
pair.id = c.id
|
||||
pair.conf = c
|
||||
|
||||
if !c.kem.IsValid() {
|
||||
return nil, E.New("invalid HPKE KEM")
|
||||
}
|
||||
|
||||
kpGenerator := c.kem.Scheme().GenerateKeyPair
|
||||
if len(c.seed) > 0 {
|
||||
kpGenerator = func() (kem.PublicKey, kem.PrivateKey, error) {
|
||||
pub, sec := c.kem.Scheme().DeriveKeyPair(c.seed)
|
||||
return pub, sec, nil
|
||||
builder.AddUint16(DHKEM_X25519_HKDF_SHA256) // The only DHKEM we support
|
||||
builder.AddUint16LengthPrefixed(func(builder *cryptobyte.Builder) {
|
||||
builder.AddBytes(pubKey)
|
||||
})
|
||||
builder.AddUint16LengthPrefixed(func(builder *cryptobyte.Builder) {
|
||||
const (
|
||||
AEAD_AES_128_GCM = 0x0001
|
||||
AEAD_AES_256_GCM = 0x0002
|
||||
AEAD_ChaCha20Poly1305 = 0x0003
|
||||
)
|
||||
for _, aeadID := range []uint16{AEAD_AES_128_GCM, AEAD_AES_256_GCM, AEAD_ChaCha20Poly1305} {
|
||||
builder.AddUint16(KDF_HKDF_SHA256) // The only KDF we support
|
||||
builder.AddUint16(aeadID)
|
||||
}
|
||||
if len(c.seed) < c.kem.Scheme().PrivateKeySize() {
|
||||
return nil, E.New("HPKE KEM seed too short")
|
||||
}
|
||||
}
|
||||
|
||||
pub, sec, err := kpGenerator()
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "generate ECH config key pair")
|
||||
}
|
||||
b := []byte{}
|
||||
b = be.AppendUint16(b, version)
|
||||
b = be.AppendUint16(b, 0) // length field
|
||||
// contents
|
||||
// key config
|
||||
b = append(b, c.id)
|
||||
b = be.AppendUint16(b, uint16(c.kem))
|
||||
pubBuf, err := pub.MarshalBinary()
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "serialize ECH public key")
|
||||
}
|
||||
b = be.AppendUint16(b, uint16(len(pubBuf)))
|
||||
b = append(b, pubBuf...)
|
||||
|
||||
b = append(b, suiteBuf...)
|
||||
// end key config
|
||||
// max name len, not supported
|
||||
b = append(b, 0)
|
||||
// server name
|
||||
b = append(b, byte(len(serverName)))
|
||||
b = append(b, []byte(serverName)...)
|
||||
// extensions, not supported
|
||||
b = be.AppendUint16(b, 0)
|
||||
|
||||
be.PutUint16(b[2:], uint16(len(b)-4))
|
||||
|
||||
pair.rawConf = b
|
||||
|
||||
secBuf, err := sec.MarshalBinary()
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "serialize ECH private key")
|
||||
}
|
||||
sk := []byte{}
|
||||
sk = be.AppendUint16(sk, uint16(len(secBuf)))
|
||||
sk = append(sk, secBuf...)
|
||||
sk = be.AppendUint16(sk, uint16(len(b)))
|
||||
sk = append(sk, b...)
|
||||
pair.rawKey = sk
|
||||
|
||||
pairs = append(pairs, pair)
|
||||
}
|
||||
return pairs, nil
|
||||
})
|
||||
builder.AddUint8(maxNameLen)
|
||||
builder.AddUint8LengthPrefixed(func(builder *cryptobyte.Builder) {
|
||||
builder.AddBytes([]byte(publicName))
|
||||
})
|
||||
builder.AddUint16(0) // extensions
|
||||
})
|
||||
return builder.Bytes()
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -86,12 +86,16 @@ func NewSTDClient(ctx context.Context, serverAddress string, options option.Outb
|
||||
tlsConfig.InsecureSkipVerify = true
|
||||
tlsConfig.VerifyConnection = func(state tls.ConnectionState) error {
|
||||
verifyOptions := x509.VerifyOptions{
|
||||
Roots: tlsConfig.RootCAs,
|
||||
DNSName: serverName,
|
||||
Intermediates: x509.NewCertPool(),
|
||||
}
|
||||
for _, cert := range state.PeerCertificates[1:] {
|
||||
verifyOptions.Intermediates.AddCert(cert)
|
||||
}
|
||||
if tlsConfig.Time != nil {
|
||||
verifyOptions.CurrentTime = tlsConfig.Time()
|
||||
}
|
||||
_, err := state.PeerCertificates[0].Verify(verifyOptions)
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -11,10 +11,13 @@ type TimeServiceWrapper struct {
|
||||
}
|
||||
|
||||
func (w *TimeServiceWrapper) TimeFunc() func() time.Time {
|
||||
if w.TimeService == nil {
|
||||
return nil
|
||||
return func() time.Time {
|
||||
if w.TimeService != nil {
|
||||
return w.TimeService.TimeFunc()()
|
||||
} else {
|
||||
return time.Now()
|
||||
}
|
||||
}
|
||||
return w.TimeService.TimeFunc()
|
||||
}
|
||||
|
||||
func (w *TimeServiceWrapper) Upstream() any {
|
||||
|
||||
@@ -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
|
||||
@@ -145,11 +153,16 @@ func NewUTLSClient(ctx context.Context, serverAddress string, options option.Out
|
||||
var tlsConfig utls.Config
|
||||
tlsConfig.Time = ntp.TimeFuncFromContext(ctx)
|
||||
tlsConfig.RootCAs = adapter.RootPoolFromContext(ctx)
|
||||
tlsConfig.ServerName = serverName
|
||||
if !options.DisableSNI {
|
||||
tlsConfig.ServerName = serverName
|
||||
}
|
||||
if options.Insecure {
|
||||
tlsConfig.InsecureSkipVerify = options.Insecure
|
||||
} else if options.DisableSNI {
|
||||
return nil, E.New("disable_sni is unsupported in uTLS")
|
||||
if options.Reality != nil && options.Reality.Enabled {
|
||||
return nil, E.New("disable_sni is unsupported in reality")
|
||||
}
|
||||
tlsConfig.InsecureServerNameToVerify = serverName
|
||||
}
|
||||
if len(options.ALPN) > 0 {
|
||||
tlsConfig.NextProtos = options.ALPN
|
||||
|
||||
@@ -45,7 +45,7 @@ func (c *Conn) Write(b []byte) (n int, err error) {
|
||||
defer func() {
|
||||
c.firstPacketWritten = true
|
||||
}()
|
||||
serverName := indexTLSServerName(b)
|
||||
serverName := IndexTLSServerName(b)
|
||||
if serverName != nil {
|
||||
if c.splitPacket {
|
||||
if c.tcpConn != nil {
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,13 +22,13 @@ const (
|
||||
tls13 uint16 = 0x0304
|
||||
)
|
||||
|
||||
type myServerName struct {
|
||||
type MyServerName struct {
|
||||
Index int
|
||||
Length int
|
||||
ServerName string
|
||||
}
|
||||
|
||||
func indexTLSServerName(payload []byte) *myServerName {
|
||||
func IndexTLSServerName(payload []byte) *MyServerName {
|
||||
if len(payload) < recordLayerHeaderLen || payload[0] != contentType {
|
||||
return nil
|
||||
}
|
||||
@@ -36,47 +36,48 @@ func indexTLSServerName(payload []byte) *myServerName {
|
||||
if len(payload) < recordLayerHeaderLen+int(segmentLen) {
|
||||
return nil
|
||||
}
|
||||
serverName := indexTLSServerNameFromHandshake(payload[recordLayerHeaderLen : recordLayerHeaderLen+int(segmentLen)])
|
||||
serverName := indexTLSServerNameFromHandshake(payload[recordLayerHeaderLen:])
|
||||
if serverName == nil {
|
||||
return nil
|
||||
}
|
||||
serverName.Length += recordLayerHeaderLen
|
||||
serverName.Index += recordLayerHeaderLen
|
||||
return serverName
|
||||
}
|
||||
|
||||
func indexTLSServerNameFromHandshake(hs []byte) *myServerName {
|
||||
if len(hs) < handshakeHeaderLen+randomDataLen+sessionIDHeaderLen {
|
||||
func indexTLSServerNameFromHandshake(handshake []byte) *MyServerName {
|
||||
if len(handshake) < handshakeHeaderLen+randomDataLen+sessionIDHeaderLen {
|
||||
return nil
|
||||
}
|
||||
if hs[0] != handshakeType {
|
||||
if handshake[0] != handshakeType {
|
||||
return nil
|
||||
}
|
||||
handshakeLen := uint32(hs[1])<<16 | uint32(hs[2])<<8 | uint32(hs[3])
|
||||
if len(hs[4:]) != int(handshakeLen) {
|
||||
handshakeLen := uint32(handshake[1])<<16 | uint32(handshake[2])<<8 | uint32(handshake[3])
|
||||
if len(handshake[4:]) != int(handshakeLen) {
|
||||
return nil
|
||||
}
|
||||
tlsVersion := uint16(hs[4])<<8 | uint16(hs[5])
|
||||
tlsVersion := uint16(handshake[4])<<8 | uint16(handshake[5])
|
||||
if tlsVersion&tlsVersionBitmask != 0x0300 && tlsVersion != tls13 {
|
||||
return nil
|
||||
}
|
||||
sessionIDLen := hs[38]
|
||||
if len(hs) < handshakeHeaderLen+randomDataLen+sessionIDHeaderLen+int(sessionIDLen) {
|
||||
sessionIDLen := handshake[38]
|
||||
currentIndex := handshakeHeaderLen + randomDataLen + sessionIDHeaderLen + int(sessionIDLen)
|
||||
if len(handshake) < currentIndex {
|
||||
return nil
|
||||
}
|
||||
cs := hs[handshakeHeaderLen+randomDataLen+sessionIDHeaderLen+int(sessionIDLen):]
|
||||
if len(cs) < cipherSuiteHeaderLen {
|
||||
cipherSuites := handshake[currentIndex:]
|
||||
if len(cipherSuites) < cipherSuiteHeaderLen {
|
||||
return nil
|
||||
}
|
||||
csLen := uint16(cs[0])<<8 | uint16(cs[1])
|
||||
if len(cs) < cipherSuiteHeaderLen+int(csLen)+compressMethodHeaderLen {
|
||||
csLen := uint16(cipherSuites[0])<<8 | uint16(cipherSuites[1])
|
||||
if len(cipherSuites) < cipherSuiteHeaderLen+int(csLen)+compressMethodHeaderLen {
|
||||
return nil
|
||||
}
|
||||
compressMethodLen := uint16(cs[cipherSuiteHeaderLen+int(csLen)])
|
||||
if len(cs) < cipherSuiteHeaderLen+int(csLen)+compressMethodHeaderLen+int(compressMethodLen) {
|
||||
compressMethodLen := uint16(cipherSuites[cipherSuiteHeaderLen+int(csLen)])
|
||||
currentIndex += cipherSuiteHeaderLen + int(csLen) + compressMethodHeaderLen + int(compressMethodLen)
|
||||
if len(handshake) < currentIndex {
|
||||
return nil
|
||||
}
|
||||
currentIndex := cipherSuiteHeaderLen + int(csLen) + compressMethodHeaderLen + int(compressMethodLen)
|
||||
serverName := indexTLSServerNameFromExtensions(cs[currentIndex:])
|
||||
serverName := indexTLSServerNameFromExtensions(handshake[currentIndex:])
|
||||
if serverName == nil {
|
||||
return nil
|
||||
}
|
||||
@@ -84,7 +85,7 @@ func indexTLSServerNameFromHandshake(hs []byte) *myServerName {
|
||||
return serverName
|
||||
}
|
||||
|
||||
func indexTLSServerNameFromExtensions(exs []byte) *myServerName {
|
||||
func indexTLSServerNameFromExtensions(exs []byte) *MyServerName {
|
||||
if len(exs) == 0 {
|
||||
return nil
|
||||
}
|
||||
@@ -118,7 +119,8 @@ func indexTLSServerNameFromExtensions(exs []byte) *myServerName {
|
||||
}
|
||||
sniLen := uint16(sex[3])<<8 | uint16(sex[4])
|
||||
sex = sex[sniExtensionHeaderLen:]
|
||||
return &myServerName{
|
||||
|
||||
return &MyServerName{
|
||||
Index: currentIndex + extensionHeaderLen + sniExtensionHeaderLen,
|
||||
Length: int(sniLen),
|
||||
ServerName: string(sex),
|
||||
|
||||
20
common/tlsfragment/index_test.go
Normal file
20
common/tlsfragment/index_test.go
Normal file
@@ -0,0 +1,20 @@
|
||||
package tf_test
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"testing"
|
||||
|
||||
"github.com/sagernet/sing-box/common/tlsfragment"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestIndexTLSServerName(t *testing.T) {
|
||||
t.Parallel()
|
||||
payload, err := hex.DecodeString("16030105f8010005f403036e35de7389a679c54029cf452611f2211c70d9ac3897271de589ab6155f8e4ab20637d225f1ef969ad87ed78bfb9d171300bcb1703b6f314ccefb964f79b7d0961002a0a0a130213031301c02cc02bcca9c030c02fcca8c00ac009c014c013009d009c0035002fc008c012000a01000581baba00000000000f000d00000a6769746875622e636f6d00170000ff01000100000a000e000c3a3a11ec001d001700180019000b000201000010000e000c02683208687474702f312e31000500050100000000000d00160014040308040401050308050805050108060601020100120000003304ef04ed3a3a00010011ec04c0aeb2250c092a3463161cccb29d9183331a424964248579507ed23a180b0ceab2a5f5d9ce41547e497a89055471ea572867ba3a1fc3c9e45025274a20f60c6b60e62476b6afed0403af59ab83660ef4112ae20386a602010d0a5d454c0ed34c84ed4423e750213e6a2baab1bf9c4367a6007ab40a33d95220c2dcaa44f257024a5626b545db0510f4311b1a60714154909c6a61fdfca011fb2626d657aeb6070bf078508babe3b584555013e34acc56198ed4663742b3155a664a9901794c4586820a7dc162c01827291f3792e1237f801a8d1ef096013c181c4a58d2f6859ba75022d18cc4418bd4f351d5c18f83a58857d05af860c4b9ac018a5b63f17184e591532c6bc2cf2215d4a282c8a8a4f6f7aee110422c8bc9ebd3b1d609c568523aaae555db320e6c269473d87af38c256cbb9febc20aea6380c32a8916f7a373c8b1e37554e3260bf6621f6b804ee80b3c516b1d01985bf4c603b6daa9a5991de6a7a29f3a7122b8afb843a7660110fce62b43c615f5bcc2db688ba012649c0952b0a2c031e732d2b454c6b2968683cb8d244be2c9a7fa163222979eaf92722b92b862d81a3d94450c2b60c318421ebb4307c42d1f0473592a5c30e42039cc68cda9721e61aa63f49def17c15221680ed444896340133bbee67556f56b9f9d78a4df715f926a12add0cc9c862e46ea8b7316ae468282c18601b2771c9c9322f982228cf93effaacd3f80cbd12bce5fc36f56e2a3caf91e578a5fae00c9b23a8ed1a66764f4433c3628a70b8f0a6196adc60a4cb4226f07ba4c6b363fe9065563bfc1347452946386bab488686e837ab979c64f9047417fca635fe1bb4f074f256cc8af837c7b455e280426547755af90a61640169ef180aea3a77e662bb6dac1b6c3696027129b1a5edf495314e9c7f4b6110e16378ec893fa24642330a40aba1a85326101acb97c620fd8d71389e69eaed7bdb01bbe1fd428d66191150c7b2cd1ad4257391676a82ba8ce07fb2667c3b289f159003a7c7bc31d361b7b7f49a802961739d950dfcc0fa1c7abce5abdd2245101da391151490862028110465950b9e9c03d08a90998ab83267838d2e74a0593bc81f74cdf734519a05b351c0e5488c68dd810e6e9142ccc1e2f4a7f464297eb340e27acc6b9d64e12e38cce8492b3d939140b5a9e149a75597f10a23874c84323a07cdd657274378f887c85c4259b9c04cd33ba58ed630ef2a744f8e19dd34843dff331d2a6be7e2332c599289cd248a611c73d7481cd4a9bd43449a3836f14b2af18a1739e17999e4c67e85cc5bcecabb14185e5bcaff3c96098f03dc5aba819f29587758f49f940585354a2a780830528d68ccd166920dadcaa25cab5fc1907272a826aba3f08bc6b88757776812ecb6c7cec69a223ec0a13a7b62a2349a0f63ed7a27a3b15ba21d71fe6864ec6e089ae17cadd433fa3138f7ee24353c11365818f8fc34f43a05542d18efaac24bfccc1f748a0cc1a67ad379468b76fd34973dba785f5c91d618333cd810fe0700d1bbc8422029782628070a624c52c5309a4a64d625b11f8033ab28df34a1add297517fcc06b92b6817b3c5144438cf260867c57bde68c8c4b82e6a135ef676a52fbae5708002a404e6189a60e2836de565ad1b29e3819e5ed49f6810bcb28e1bd6de57306f94b79d9dae1cc4624d2a068499beef81cd5fe4b76dcbfff2a2008001d002001976128c6d5a934533f28b9914d2480aab2a8c1ab03d212529ce8b27640a716002d00020101002b000706caca03040303001b00030200015a5a000100")
|
||||
require.NoError(t, err)
|
||||
serverName := tf.IndexTLSServerName(payload)
|
||||
require.NotNil(t, serverName)
|
||||
require.Equal(t, serverName.ServerName, string(payload[serverName.Index:serverName.Index+serverName.Length]))
|
||||
require.Equal(t, "github.com", serverName.ServerName)
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
149
dns/client.go
149
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 {
|
||||
@@ -96,17 +100,15 @@ func (c *Client) Exchange(ctx context.Context, transport adapter.DNSTransport, m
|
||||
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 +116,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 +155,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 +172,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 +214,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 || len(response.Answer) == 0
|
||||
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)
|
||||
@@ -259,7 +281,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 +327,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,36 +341,36 @@ 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{
|
||||
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 response4 != nil || response6 != nil {
|
||||
return sortAddresses(MessageToAddresses(response4), MessageToAddresses(response6), strategy), true
|
||||
}
|
||||
}
|
||||
return nil, false
|
||||
@@ -390,15 +411,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 +538,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 +585,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,
|
||||
}
|
||||
|
||||
@@ -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>"))
|
||||
|
||||
@@ -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"
|
||||
@@ -178,7 +177,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) {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -20,8 +20,8 @@ import (
|
||||
)
|
||||
|
||||
func dnsReadConfig(_ context.Context, _ string) *dnsConfig {
|
||||
var state C.res_state
|
||||
if C.res_ninit(state) != 0 {
|
||||
var state C.struct___res_state
|
||||
if C.res_ninit(&state) != 0 {
|
||||
return &dnsConfig{
|
||||
servers: defaultNS,
|
||||
search: dnsDefaultSearch(),
|
||||
|
||||
13
dns/transport/local/resolv_test.go
Normal file
13
dns/transport/local/resolv_test.go
Normal file
@@ -0,0 +1,13 @@
|
||||
package local
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestDNSReadConfig(t *testing.T) {
|
||||
t.Parallel()
|
||||
require.NoError(t, dnsReadConfig(context.Background(), "/etc/resolv.conf").err)
|
||||
}
|
||||
@@ -60,7 +60,7 @@ func NewUDPRaw(logger logger.ContextLogger, adapter dns.TransportAdapter, dialer
|
||||
logger: logger,
|
||||
dialer: dialer,
|
||||
serverAddr: serverAddr,
|
||||
udpSize: 512,
|
||||
udpSize: 2048,
|
||||
tcpTransport: &TCPTransport{
|
||||
dialer: dialer,
|
||||
serverAddr: serverAddr,
|
||||
@@ -97,15 +97,19 @@ func (t *UDPTransport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.M
|
||||
}
|
||||
|
||||
func (t *UDPTransport) exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {
|
||||
conn, err := t.open(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
t.access.Lock()
|
||||
if edns0Opt := message.IsEdns0(); edns0Opt != nil {
|
||||
if udpSize := int(edns0Opt.UDPSize()); udpSize > t.udpSize {
|
||||
t.udpSize = udpSize
|
||||
close(t.done)
|
||||
t.done = make(chan struct{})
|
||||
}
|
||||
}
|
||||
t.access.Unlock()
|
||||
conn, err := t.open(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
buffer := buf.NewSize(1 + message.Len())
|
||||
defer buffer.Release()
|
||||
exMessage := *message
|
||||
|
||||
@@ -2,11 +2,181 @@
|
||||
icon: material/alert-decagram
|
||||
---
|
||||
|
||||
#### 1.12.0-beta.33
|
||||
#### 1.12.8
|
||||
|
||||
* Add firewalld compatibility for auto redirect
|
||||
* 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
|
||||
|
||||
#### 1.12.0
|
||||
|
||||
* Refactor DNS servers **1**
|
||||
* Add domain resolver options**2**
|
||||
* Add TLS fragment/record fragment support to route options and outbound TLS options **3**
|
||||
* Add certificate options **4**
|
||||
* Add Tailscale endpoint and DNS server **5**
|
||||
* Drop support for go1.22 **6**
|
||||
* Add AnyTLS protocol **7**
|
||||
* Migrate to stdlib ECH implementation **8**
|
||||
* Add NTP sniffer **9**
|
||||
* Add wildcard SNI support for ShadowTLS inbound **10**
|
||||
* Improve `auto_redirect` **11**
|
||||
* Add control options for listeners **12**
|
||||
* Add DERP service **13**
|
||||
* Add Resolved service and DNS server **14**
|
||||
* Add SSM API service **15**
|
||||
* Add loopback address support for tun **16**
|
||||
* Improve tun performance on Apple platforms **17**
|
||||
* Update quic-go to v0.52.0
|
||||
* Update gVisor to 20250319.0
|
||||
* Update the status of graphical clients in stores **18**
|
||||
|
||||
**1**:
|
||||
|
||||
DNS servers are refactored for better performance and scalability.
|
||||
|
||||
See [DNS server](/configuration/dns/server/).
|
||||
|
||||
For migration, see [Migrate to new DNS server formats](/migration/#migrate-to-new-dns-servers).
|
||||
|
||||
Compatibility for old formats will be removed in sing-box 1.14.0.
|
||||
|
||||
**2**:
|
||||
|
||||
Legacy `outbound` DNS rules are deprecated
|
||||
and can be replaced by the new `domain_resolver` option.
|
||||
|
||||
See [Dial Fields](/configuration/shared/dial/#domain_resolver) and
|
||||
[Route](/configuration/route/#default_domain_resolver).
|
||||
|
||||
For migration,
|
||||
see [Migrate outbound DNS rule items to domain resolver](/migration/#migrate-outbound-dns-rule-items-to-domain-resolver).
|
||||
|
||||
**3**:
|
||||
|
||||
See [Route Action](/configuration/route/rule_action/#tls_fragment) and [TLS](/configuration/shared/tls/).
|
||||
|
||||
**4**:
|
||||
|
||||
New certificate options allow you to manage the default list of trusted X509 CA certificates.
|
||||
|
||||
For the system certificate list, fixed Go not reading Android trusted certificates correctly.
|
||||
|
||||
You can also use the Mozilla Included List instead, or add trusted certificates yourself.
|
||||
|
||||
See [Certificate](/configuration/certificate/).
|
||||
|
||||
**5**:
|
||||
|
||||
See [Tailscale](/configuration/endpoint/tailscale/).
|
||||
|
||||
**6**:
|
||||
|
||||
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).
|
||||
|
||||
**7**:
|
||||
|
||||
The new AnyTLS protocol claims to mitigate TLS proxy traffic characteristics and comes with a new multiplexing scheme.
|
||||
|
||||
See [AnyTLS Inbound](/configuration/inbound/anytls/) and [AnyTLS Outbound](/configuration/outbound/anytls/).
|
||||
|
||||
**8**:
|
||||
|
||||
See [TLS](/configuration/shared/tls).
|
||||
|
||||
The build tag `with_ech` is no longer needed and has been removed.
|
||||
|
||||
**9**:
|
||||
|
||||
See [Protocol Sniff](/configuration/route/sniff/).
|
||||
|
||||
**10**:
|
||||
|
||||
See [ShadowTLS](/configuration/inbound/shadowtls/#wildcard_sni).
|
||||
|
||||
**11**:
|
||||
|
||||
Now `auto_redirect` fixes compatibility issues between tun and Docker bridge networks,
|
||||
see [Tun](/configuration/inbound/tun/#auto_redirect).
|
||||
|
||||
**12**:
|
||||
|
||||
You can now set `bind_interface`, `routing_mark` and `reuse_addr` in Listen Fields.
|
||||
|
||||
See [Listen Fields](/configuration/shared/listen/).
|
||||
|
||||
**13**:
|
||||
|
||||
DERP service is a Tailscale DERP server, similar to [derper](https://pkg.go.dev/tailscale.com/cmd/derper).
|
||||
|
||||
See [DERP Service](/configuration/service/derp/).
|
||||
|
||||
**14**:
|
||||
|
||||
Resolved service is a fake systemd-resolved DBUS service to receive DNS settings from other programs
|
||||
(e.g. NetworkManager) and provide DNS resolution.
|
||||
|
||||
See [Resolved Service](/configuration/service/resolved/) and [Resolved DNS Server](/configuration/dns/server/resolved/).
|
||||
|
||||
**15**:
|
||||
|
||||
SSM API service is a RESTful API server for managing Shadowsocks servers.
|
||||
|
||||
See [SSM API Service](/configuration/service/ssm-api/).
|
||||
|
||||
**16**:
|
||||
|
||||
TUN now implements SideStore's StosVPN.
|
||||
|
||||
See [Tun](/configuration/inbound/tun/#loopback_address).
|
||||
|
||||
**17**:
|
||||
|
||||
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.
|
||||
|
||||
| Version | Stack | MTU | Upload | Download |
|
||||
|-------------|--------|-------|--------|----------|
|
||||
| 1.11.15 | gvisor | 1500 | 852M | 2.57G |
|
||||
| 1.12.0-rc.4 | gvisor | 1500 | 2.90G | 4.68G |
|
||||
| 1.11.15 | gvisor | 4064 | 2.31G | 6.34G |
|
||||
| 1.12.0-rc.4 | gvisor | 4064 | 7.54G | 12.2G |
|
||||
| 1.11.15 | gvisor | 65535 | 27.6G | 18.1G |
|
||||
| 1.12.0-rc.4 | gvisor | 65535 | 39.8G | 34.7G |
|
||||
| 1.11.15 | system | 1500 | 664M | 706M |
|
||||
| 1.12.0-rc.4 | system | 1500 | 2.44G | 2.51G |
|
||||
| 1.11.15 | system | 4064 | 1.88G | 1.94G |
|
||||
| 1.12.0-rc.4 | system | 4064 | 6.45G | 6.27G |
|
||||
| 1.11.15 | system | 65535 | 26.2G | 17.4G |
|
||||
| 1.12.0-rc.4 | system | 65535 | 17.6G | 21.0G |
|
||||
|
||||
**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.
|
||||
Therefore, after this release, we will not be repeating this notice unless there is new information.
|
||||
|
||||
### 1.11.15
|
||||
|
||||
* Fixes and improvements
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -351,14 +351,15 @@ DNS servers are refactored for better performance and scalability.
|
||||
```json
|
||||
{
|
||||
"dns": {
|
||||
"servers": [
|
||||
"rules": [
|
||||
{
|
||||
"type": "predefined",
|
||||
"responses": [
|
||||
{
|
||||
"rcode": "REFUSED"
|
||||
}
|
||||
]
|
||||
"domain": [
|
||||
"example.com"
|
||||
],
|
||||
// other rules
|
||||
|
||||
"action": "predefined",
|
||||
"rcode": "REFUSED"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1187,4 +1188,4 @@ which will disrupt the existing `process_path` use cases in Windows.
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
```
|
||||
|
||||
@@ -8,7 +8,7 @@ icon: material/arrange-bring-forward
|
||||
|
||||
DNS 服务器已经重构。
|
||||
|
||||
!!! info "饮用"
|
||||
!!! info "引用"
|
||||
|
||||
[DNS 服务器](/configuration/dns/server/) /
|
||||
[旧 DNS 服务器](/configuration/dns/server/legacy/)
|
||||
@@ -351,14 +351,15 @@ DNS 服务器已经重构。
|
||||
```json
|
||||
{
|
||||
"dns": {
|
||||
"servers": [
|
||||
"rules": [
|
||||
{
|
||||
"type": "predefined",
|
||||
"responses": [
|
||||
{
|
||||
"rcode": "REFUSED"
|
||||
}
|
||||
]
|
||||
"domain": [
|
||||
"example.com"
|
||||
],
|
||||
// 其它规则
|
||||
|
||||
"action": "predefined",
|
||||
"rcode": "REFUSED"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -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 })}
|
||||
}
|
||||
|
||||
19
experimental/libbox/pidfd_android.go
Normal file
19
experimental/libbox/pidfd_android.go
Normal file
@@ -0,0 +1,19 @@
|
||||
package libbox
|
||||
|
||||
import (
|
||||
"os"
|
||||
_ "unsafe"
|
||||
)
|
||||
|
||||
// https://github.com/SagerNet/sing-box/issues/3233
|
||||
// https://github.com/golang/go/issues/70508
|
||||
// https://github.com/tailscale/tailscale/issues/13452
|
||||
|
||||
//go:linkname checkPidfdOnce os.checkPidfdOnce
|
||||
var checkPidfdOnce func() error
|
||||
|
||||
func init() {
|
||||
checkPidfdOnce = func() error {
|
||||
return os.ErrInvalid
|
||||
}
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -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,
|
||||
|
||||
55
go.mod
55
go.mod
@@ -5,39 +5,38 @@ go 1.23.1
|
||||
require (
|
||||
github.com/anytls/sing-anytls v0.0.8
|
||||
github.com/caddyserver/certmagic v0.23.0
|
||||
github.com/cloudflare/circl v1.6.1
|
||||
github.com/coder/websocket v1.8.12
|
||||
github.com/coder/websocket v1.8.13
|
||||
github.com/cretz/bine v0.2.0
|
||||
github.com/go-chi/chi/v5 v5.2.1
|
||||
github.com/go-chi/chi/v5 v5.2.2
|
||||
github.com/go-chi/render v1.0.3
|
||||
github.com/godbus/dbus/v5 v5.1.1-0.20230522191255-76236955d466
|
||||
github.com/gofrs/uuid/v5 v5.3.2
|
||||
github.com/insomniacslk/dhcp v0.0.0-20250417080101-5f8cf70e8c5f
|
||||
github.com/libdns/alidns v1.0.4-libdns.v1.beta1
|
||||
github.com/libdns/cloudflare v0.2.2-0.20250430151523-b46a2b0885f6
|
||||
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.7.0-alpha.3
|
||||
github.com/metacubex/tfo-go v0.0.0-20250516165257-e29c16ae41d4
|
||||
github.com/metacubex/utls v1.8.0
|
||||
github.com/mholt/acmez/v3 v3.1.2
|
||||
github.com/miekg/dns v1.1.66
|
||||
github.com/miekg/dns v1.1.67
|
||||
github.com/oschwald/maxminddb-golang v1.13.1
|
||||
github.com/sagernet/asc-go v0.0.0-20241217030726-d563060fe4e1
|
||||
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.6.12-0.20250704043954-da981379f151
|
||||
github.com/sagernet/sing-mux v0.3.2
|
||||
github.com/sagernet/sing-quic v0.5.0-beta.2
|
||||
github.com/sagernet/sing v0.7.10
|
||||
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.6.10-0.20250707094843-b2e2674d73e5
|
||||
github.com/sagernet/sing-vmess v0.2.4-0.20250605032146-38cc72672c88
|
||||
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
|
||||
@@ -45,13 +44,13 @@ 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.39.0
|
||||
golang.org/x/crypto v0.41.0
|
||||
golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6
|
||||
golang.org/x/mod v0.25.0
|
||||
golang.org/x/net v0.41.0
|
||||
golang.org/x/sys v0.33.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.72.0
|
||||
google.golang.org/grpc v1.73.0
|
||||
google.golang.org/protobuf v1.36.6
|
||||
howett.net/plist v1.0.1
|
||||
)
|
||||
@@ -81,7 +80,7 @@ require (
|
||||
github.com/gobwas/pool v0.2.1 // indirect
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
||||
github.com/google/btree v1.1.3 // indirect
|
||||
github.com/google/go-cmp v0.6.0 // indirect
|
||||
github.com/google/go-cmp v0.7.0 // indirect
|
||||
github.com/google/go-querystring v1.1.0 // indirect
|
||||
github.com/google/nftables v0.2.1-0.20240414091927-5e242ec57806 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
@@ -95,7 +94,7 @@ require (
|
||||
github.com/klauspost/compress v1.17.11 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.2.10 // indirect
|
||||
github.com/kortschak/wol v0.0.0-20200729010619-da482cc4850a // indirect
|
||||
github.com/libdns/libdns v1.0.0-beta.1 // indirect
|
||||
github.com/libdns/libdns v1.1.0 // indirect
|
||||
github.com/mdlayher/genetlink v1.3.2 // indirect
|
||||
github.com/mdlayher/netlink v1.7.3-0.20250113171957-fbb4dce95f42 // indirect
|
||||
github.com/mdlayher/sdnotify v1.0.0 // indirect
|
||||
@@ -107,7 +106,7 @@ require (
|
||||
github.com/quic-go/qpack v0.5.1 // indirect
|
||||
github.com/safchain/ethtool v0.3.0 // indirect
|
||||
github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a // indirect
|
||||
github.com/sagernet/nftables v0.3.0-mod.1 // indirect
|
||||
github.com/sagernet/nftables v0.3.0-beta.4 // indirect
|
||||
github.com/spf13/pflag v1.0.6 // indirect
|
||||
github.com/tailscale/certstore v0.1.1-0.20231202035212-d3fa0460f47e // indirect
|
||||
github.com/tailscale/go-winio v0.0.0-20231025203758-c4f33415bf55 // indirect
|
||||
@@ -123,14 +122,14 @@ require (
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
go.uber.org/zap/exp v0.3.0 // indirect
|
||||
go4.org/mem v0.0.0-20240501181205-ae6ca9944745 // indirect
|
||||
golang.org/x/sync v0.15.0 // indirect
|
||||
golang.org/x/term v0.32.0 // indirect
|
||||
golang.org/x/text v0.26.0 // indirect
|
||||
golang.org/x/sync v0.16.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.33.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-20250218202821-56aae31c358a // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250324211829-b45e905df463 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
lukechampine.com/blake3 v1.3.0 // indirect
|
||||
)
|
||||
|
||||
129
go.sum
129
go.sum
@@ -20,10 +20,8 @@ github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK3
|
||||
github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
|
||||
github.com/cilium/ebpf v0.15.0 h1:7NxJhNiBT3NG8pZJ3c+yfrVdHY8ScgKD27sScgjLMMk=
|
||||
github.com/cilium/ebpf v0.15.0/go.mod h1:DHp1WyrLeiBh19Cf/tfiSMhqheEiK8fXFZ4No0P1Hso=
|
||||
github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0=
|
||||
github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs=
|
||||
github.com/coder/websocket v1.8.12 h1:5bUXkEPPIbewrnkU8LTCLVaxi4N4J8ahufH2vlo4NAo=
|
||||
github.com/coder/websocket v1.8.12/go.mod h1:LNVeNrXQZfe5qhS9ALED3uA+l5pPqvwXg3CKoDBB2gs=
|
||||
github.com/coder/websocket v1.8.13 h1:f3QZdXy7uGVz+4uCJy2nTZyM0yTBj8yANEHhqlXZ9FE=
|
||||
github.com/coder/websocket v1.8.13/go.mod h1:LNVeNrXQZfe5qhS9ALED3uA+l5pPqvwXg3CKoDBB2gs=
|
||||
github.com/coreos/go-iptables v0.7.1-0.20240112124308-65c67c9f46e6 h1:8h5+bWd7R6AYUslN6c6iuZWTKsKxUFDlpnmilO6R2n0=
|
||||
github.com/coreos/go-iptables v0.7.1-0.20240112124308-65c67c9f46e6/go.mod h1:Qe8Bv2Xik5FyTXwgIbLAnv2sWSBmvWdFETJConOQ//Q=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
|
||||
@@ -47,8 +45,8 @@ github.com/gaissmai/bart v0.11.1 h1:5Uv5XwsaFBRo4E5VBcb9TzY8B7zxFf+U7isDxqOrRfc=
|
||||
github.com/gaissmai/bart v0.11.1/go.mod h1:KHeYECXQiBjTzQz/om2tqn3sZF1J7hw9m6z41ftj3fg=
|
||||
github.com/github/fakeca v0.1.0 h1:Km/MVOFvclqxPM9dZBC4+QE564nU4gz4iZ0D9pMw28I=
|
||||
github.com/github/fakeca v0.1.0/go.mod h1:+bormgoGMMuamOscx7N91aOuUST7wdaJ2rNjeohylyo=
|
||||
github.com/go-chi/chi/v5 v5.2.1 h1:KOIHODQj58PmL80G2Eak4WdvUzjSJSm0vG72crDCqb8=
|
||||
github.com/go-chi/chi/v5 v5.2.1/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops=
|
||||
github.com/go-chi/chi/v5 v5.2.2 h1:CMwsvRVTbXVytCk1Wd72Zy1LAsAh9GxMmSNWLHCG618=
|
||||
github.com/go-chi/chi/v5 v5.2.2/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops=
|
||||
github.com/go-chi/render v1.0.3 h1:AsXqd2a1/INaIfUSKq3G5uA8weYx20FOsM7uSoCyyt4=
|
||||
github.com/go-chi/render v1.0.3/go.mod h1:/gr3hVkmYR0YlEy3LxCuVRFzEu9Ruok+gFqbIofjao0=
|
||||
github.com/go-json-experiment/json v0.0.0-20250103232110-6a9a0fde9288 h1:KbX3Z3CgiYlbaavUq3Cj9/MjpO+88S7/AGXzynVDv84=
|
||||
@@ -74,8 +72,8 @@ github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6
|
||||
github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg=
|
||||
github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
|
||||
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
|
||||
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
|
||||
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
|
||||
@@ -107,12 +105,13 @@ github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2
|
||||
github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
||||
github.com/kortschak/wol v0.0.0-20200729010619-da482cc4850a h1:+RR6SqnTkDLWyICxS1xpjCi/3dhyV+TgZwA6Ww3KncQ=
|
||||
github.com/kortschak/wol v0.0.0-20200729010619-da482cc4850a/go.mod h1:YTtCCM3ryyfiu4F7t8HQ1mxvp1UBdWM2r6Xa+nGWvDk=
|
||||
github.com/libdns/alidns v1.0.4-libdns.v1.beta1 h1:ods22gD4PcT0g4qRX77ucykjz7Rppnkz3vQoxDbbKTM=
|
||||
github.com/libdns/alidns v1.0.4-libdns.v1.beta1/go.mod h1:ystHmPwcGoWjPrGpensQSMY9VoCx4cpR2hXNlwk9H/g=
|
||||
github.com/libdns/cloudflare v0.2.2-0.20250430151523-b46a2b0885f6 h1:0dlpPjNr8TaYZbkpwCiee4udBNrYrWG8EZPYEbjHEn8=
|
||||
github.com/libdns/cloudflare v0.2.2-0.20250430151523-b46a2b0885f6/go.mod h1:Aq4IXdjalB6mD0ELvKqJiIGim8zSC6mlIshRPMOAb5w=
|
||||
github.com/libdns/libdns v1.0.0-beta.1 h1:KIf4wLfsrEpXpZ3vmc/poM8zCATXT2klbdPe6hyOBjQ=
|
||||
github.com/libdns/alidns v1.0.5-libdns.v1.beta1 h1:txHK7UxDed3WFBDjrTZPuMn8X+WmhjBTTAMW5xdy5pQ=
|
||||
github.com/libdns/alidns v1.0.5-libdns.v1.beta1/go.mod h1:ystHmPwcGoWjPrGpensQSMY9VoCx4cpR2hXNlwk9H/g=
|
||||
github.com/libdns/cloudflare v0.2.2-0.20250708034226-c574dccb31a6 h1:3MGrVWs2COjMkQR17oUw1zMIPbm2YAzxDC3oGVZvQs8=
|
||||
github.com/libdns/cloudflare v0.2.2-0.20250708034226-c574dccb31a6/go.mod h1:w9uTmRCDlAoafAsTPnn2nJ0XHK/eaUMh86DUk8BWi60=
|
||||
github.com/libdns/libdns v1.0.0-beta.1/go.mod h1:4Bj9+5CQiNMVGf87wjX4CY3HQJypUHRuLvlsfsZqLWQ=
|
||||
github.com/libdns/libdns v1.1.0 h1:9ze/tWvt7Df6sbhOJRB8jT33GHEHpEQXdtkE3hPthbU=
|
||||
github.com/libdns/libdns v1.1.0/go.mod h1:4Bj9+5CQiNMVGf87wjX4CY3HQJypUHRuLvlsfsZqLWQ=
|
||||
github.com/logrusorgru/aurora v2.0.3+incompatible h1:tOpm7WcpBTn4fjmVfgpQq0EfczGlG91VSDkswnjF5A8=
|
||||
github.com/logrusorgru/aurora v2.0.3+incompatible/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4=
|
||||
github.com/mdlayher/genetlink v1.3.2 h1:KdrNKe+CTu+IbZnm/GVUMXSqBBLqcGpRDa0xkQy56gw=
|
||||
@@ -123,14 +122,14 @@ 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/utls v1.7.0-alpha.3 h1:cp1cEMUnoifiWrGHRzo+nCwPRveN9yPD8QaRFmfcYxA=
|
||||
github.com/metacubex/utls v1.7.0-alpha.3/go.mod h1:oknYT0qTOwE4hjPmZOEpzVdefnW7bAdGLvZcqmk4TLU=
|
||||
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/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.66 h1:FeZXOS3VCVsKnEAd+wBkjMC3D2K+ww66Cq3VnCINuJE=
|
||||
github.com/miekg/dns v1.1.66/go.mod h1:jGFzBsSNbJw6z1HYut1RKBKHA9PBdxeHrZG8J+gC2WE=
|
||||
github.com/miekg/dns v1.1.67 h1:kg0EHj0G4bfT5/oOys6HhZw4vmMlnoZ+gDu8tJ/AlI0=
|
||||
github.com/miekg/dns v1.1.67/go.mod h1:fujopn7TB3Pu3JM69XaawiU0wqjpL9/8xGop5UrTPps=
|
||||
github.com/mitchellh/go-ps v1.0.0 h1:i6ampVEEF4wQFF+bkYfwYgY+F/uYJDktmvLPf7qIgjc=
|
||||
github.com/mitchellh/go-ps v1.0.0/go.mod h1:J4lOc8z8yJs6vUwklHw2XEIiT4z4C40KtWVN3nvg8Pg=
|
||||
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ=
|
||||
@@ -157,37 +156,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-mod.1 h1:OMe+qoEAx8EipYAQbD2FI5erVvKmTS9+cYhdpg+vezY=
|
||||
github.com/sagernet/nftables v0.3.0-mod.1/go.mod h1:8kslHG4VvYNihcco+i6uxIX7qbT8A56T0y5q7U44ZaQ=
|
||||
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/sing v0.6.9/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
|
||||
github.com/sagernet/sing v0.6.12-0.20250704043954-da981379f151 h1:UCiQ1d/t5Y9uKAL9ir3i06+ClqS93OGGG8oqB82RMCE=
|
||||
github.com/sagernet/sing v0.6.12-0.20250704043954-da981379f151/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.2 h1:j7KAbBuGmsKwSxVAQL5soJ+wDqxim4/llK2kxB0hSKk=
|
||||
github.com/sagernet/sing-quic v0.5.0-beta.2/go.mod h1:SAv/qdeDN+75msGG5U5ZIwG+3Ua50jVIKNrRSY8pkx0=
|
||||
github.com/sagernet/sing v0.7.10 h1:2yPhZFx+EkyHPH8hXNezgyRSHyGY12CboId7CtwLROw=
|
||||
github.com/sagernet/sing v0.7.10/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.6.10-0.20250707094843-b2e2674d73e5 h1:JHa9vyTie1FbWGofPt4TEpysl7tBeEoiQDtwVK0Scqg=
|
||||
github.com/sagernet/sing-tun v0.6.10-0.20250707094843-b2e2674d73e5/go.mod h1:c/7Blmaw8GRL4JPvoajBfwUfdzoa2KCMtAnq5Q9AjA0=
|
||||
github.com/sagernet/sing-vmess v0.2.4-0.20250605032146-38cc72672c88 h1:0pVm8sPOel+BoiCddW3pV3cKDKEaSioVTYDdTSKjyFI=
|
||||
github.com/sagernet/sing-vmess v0.2.4-0.20250605032146-38cc72672c88/go.mod h1:IL8Rr+EGwuqijszZkNrEFTQDKhilEpkqFqOlvdpS6/w=
|
||||
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=
|
||||
@@ -240,16 +239,16 @@ github.com/zeebo/pcg v1.0.1 h1:lyqfGeWiv4ahac6ttHs+I5hwtH/+1mrhlCtVNQM2kHo=
|
||||
github.com/zeebo/pcg v1.0.1/go.mod h1:09F0S9iiKrwn9rlI5yjLkmrug154/YRW6KnnXVDM/l4=
|
||||
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
|
||||
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
|
||||
go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY=
|
||||
go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI=
|
||||
go.opentelemetry.io/otel/metric v1.34.0 h1:+eTR3U0MyfWjRDhmFMxe2SsW64QrZ84AOhvqS7Y+PoQ=
|
||||
go.opentelemetry.io/otel/metric v1.34.0/go.mod h1:CEDrp0fy2D0MvkXE+dPV7cMi8tWZwX3dmaIhwPOaqHE=
|
||||
go.opentelemetry.io/otel/sdk v1.34.0 h1:95zS4k/2GOy069d321O8jWgYsW3MzVV+KuSPKp7Wr1A=
|
||||
go.opentelemetry.io/otel/sdk v1.34.0/go.mod h1:0e/pNiaMAqaykJGKbi+tSjWfNNHMTxoC9qANsCzbyxU=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.34.0 h1:5CeK9ujjbFVL5c1PhLuStg1wxA7vQv7ce1EK0Gyvahk=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.34.0/go.mod h1:jQ/r8Ze28zRKoNRdkjCZxfs6YvBTG1+YIqyFVFYec5w=
|
||||
go.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC8mh/k=
|
||||
go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE=
|
||||
go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ=
|
||||
go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y=
|
||||
go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M=
|
||||
go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE=
|
||||
go.opentelemetry.io/otel/sdk v1.35.0 h1:iPctf8iprVySXSKJffSS79eOjl9pvxV9ZqOWT0QejKY=
|
||||
go.opentelemetry.io/otel/sdk v1.35.0/go.mod h1:+ga1bZliga3DxJ3CQGg3updiaAJoNECOgJREo9KHGQg=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.35.0 h1:1RriWBmCKgkeHEhM7a2uMjMUfP7MsOF5JpUCaEqEI9o=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.35.0/go.mod h1:is6XYCUMpcKi+ZsOvfluY5YstFnhW0BidkR+gL+qN+w=
|
||||
go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs=
|
||||
go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc=
|
||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
||||
@@ -263,21 +262,21 @@ 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.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM=
|
||||
golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U=
|
||||
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.25.0 h1:n7a+ZbQKQA/Ysbyb0/6IbB1H/X41mKgbhfv7AfG/44w=
|
||||
golang.org/x/mod v0.25.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
|
||||
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.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw=
|
||||
golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA=
|
||||
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.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8=
|
||||
golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
|
||||
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||
golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
@@ -286,20 +285,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.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
|
||||
golang.org/x/sys v0.33.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.32.0 h1:DR4lr0TjUs3epypdhTOkMmuF5CDFJ/8pOnbzMZPQ7bg=
|
||||
golang.org/x/term v0.32.0/go.mod h1:uZG1FhGx848Sqfsq4/DlJr3xGGsYMu/L5GW4abiaEPQ=
|
||||
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.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M=
|
||||
golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA=
|
||||
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.33.0 h1:4qz2S3zmRxbGIhDIAgjxvFutSvH5EfnsYrRBj0UI0bc=
|
||||
golang.org/x/tools v0.33.0/go.mod h1:CIJMaWEY88juyUfo7UbgPqbC8rU2OqfAV1h2Qp0oMYI=
|
||||
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=
|
||||
@@ -309,10 +308,10 @@ golang.zx2c4.com/wireguard/wgctrl v0.0.0-20241231184526-a9ab2273dd10 h1:3GDAcqdI
|
||||
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20241231184526-a9ab2273dd10/go.mod h1:T97yPqesLiNrOYxkwmhMI0ZIlJDm+p0PMR8eRVeR5tQ=
|
||||
golang.zx2c4.com/wireguard/windows v0.5.3 h1:On6j2Rpn3OEMXqBq00QEDC7bWSZrPIHKIus8eIuExIE=
|
||||
golang.zx2c4.com/wireguard/windows v0.5.3/go.mod h1:9TEe8TJmtwyQebdFwAkEWOPr3prrtqm+REGFifP60hI=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a h1:51aaUVRocpvUOSQKM6Q7VuoaktNIaMCLuhZB6DKksq4=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a/go.mod h1:uRxBH1mhmO8PGhU89cMcHaXKZqO+OfakD8QQO0oYwlQ=
|
||||
google.golang.org/grpc v1.72.0 h1:S7UkcVa60b5AAQTaO6ZKamFp1zMZSU0fGDK2WZLbBnM=
|
||||
google.golang.org/grpc v1.72.0/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250324211829-b45e905df463 h1:e0AIkUUhxyBKh6ssZNrAMeqhA7RKUj42346d1y02i2g=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250324211829-b45e905df463/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
|
||||
google.golang.org/grpc v1.73.0 h1:VIWSmpI2MegBtTuFt5/JWy2oXxtjJ/e89Z70ImfD2ok=
|
||||
google.golang.org/grpc v1.73.0/go.mod h1:50sbHOUqWoCQGI8V2HQLJM0B+LMlIUjNSZmow7EVBQc=
|
||||
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
|
||||
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
|
||||
@@ -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 {
|
||||
@@ -180,7 +179,7 @@ func (o *DNSServerOptions) Upgrade(ctx context.Context) error {
|
||||
options := o.Options.(*LegacyDNSServerOptions)
|
||||
serverURL, _ := url.Parse(options.Address)
|
||||
var serverType string
|
||||
if serverURL.Scheme != "" {
|
||||
if serverURL != nil && serverURL.Scheme != "" {
|
||||
serverType = serverURL.Scheme
|
||||
} else {
|
||||
switch options.Address {
|
||||
@@ -217,7 +216,7 @@ func (o *DNSServerOptions) Upgrade(ctx context.Context) error {
|
||||
o.Type = C.DNSTypeUDP
|
||||
o.Options = &remoteOptions
|
||||
var serverAddr M.Socksaddr
|
||||
if serverURL.Scheme == "" {
|
||||
if serverURL == nil || serverURL.Scheme == "" {
|
||||
serverAddr = M.ParseSocksaddr(options.Address)
|
||||
} else {
|
||||
serverAddr = M.ParseSocksaddr(serverURL.Host)
|
||||
@@ -232,6 +231,9 @@ func (o *DNSServerOptions) Upgrade(ctx context.Context) error {
|
||||
case C.DNSTypeTCP:
|
||||
o.Type = C.DNSTypeTCP
|
||||
o.Options = &remoteOptions
|
||||
if serverURL == nil {
|
||||
return E.New("invalid server address")
|
||||
}
|
||||
serverAddr := M.ParseSocksaddr(serverURL.Host)
|
||||
if !serverAddr.IsValid() {
|
||||
return E.New("invalid server address")
|
||||
@@ -242,6 +244,9 @@ func (o *DNSServerOptions) Upgrade(ctx context.Context) error {
|
||||
}
|
||||
case C.DNSTypeTLS, C.DNSTypeQUIC:
|
||||
o.Type = serverType
|
||||
if serverURL == nil {
|
||||
return E.New("invalid server address")
|
||||
}
|
||||
serverAddr := M.ParseSocksaddr(serverURL.Host)
|
||||
if !serverAddr.IsValid() {
|
||||
return E.New("invalid server address")
|
||||
@@ -261,6 +266,9 @@ func (o *DNSServerOptions) Upgrade(ctx context.Context) error {
|
||||
},
|
||||
}
|
||||
o.Options = &httpsOptions
|
||||
if serverURL == nil {
|
||||
return E.New("invalid server address")
|
||||
}
|
||||
serverAddr := M.ParseSocksaddr(serverURL.Host)
|
||||
if !serverAddr.IsValid() {
|
||||
return E.New("invalid server address")
|
||||
@@ -274,6 +282,9 @@ func (o *DNSServerOptions) Upgrade(ctx context.Context) error {
|
||||
}
|
||||
case "rcode":
|
||||
var rcode int
|
||||
if serverURL == nil {
|
||||
return E.New("invalid server address")
|
||||
}
|
||||
switch serverURL.Host {
|
||||
case "success":
|
||||
rcode = dns.RcodeSuccess
|
||||
@@ -295,6 +306,9 @@ func (o *DNSServerOptions) Upgrade(ctx context.Context) error {
|
||||
case C.DNSTypeDHCP:
|
||||
o.Type = C.DNSTypeDHCP
|
||||
dhcpOptions := DHCPDNSServerOptions{}
|
||||
if serverURL == nil {
|
||||
return E.New("invalid server address")
|
||||
}
|
||||
if serverURL.Host != "" && serverURL.Host != "auto" {
|
||||
dhcpOptions.Interface = serverURL.Host
|
||||
}
|
||||
|
||||
@@ -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,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 {
|
||||
|
||||
@@ -132,12 +132,16 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo
|
||||
|
||||
platformInterface := service.FromContext[platform.Interface](ctx)
|
||||
tunMTU := options.MTU
|
||||
enableGSO := C.IsLinux && options.Stack == "gvisor" && platformInterface == nil && tunMTU > 0 && tunMTU < 49152
|
||||
if tunMTU == 0 {
|
||||
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 {
|
||||
} else if C.IsAndroid {
|
||||
// Some Android devices report ENOBUFS when using MTU 65535
|
||||
tunMTU = 9000
|
||||
} else {
|
||||
tunMTU = 65535
|
||||
}
|
||||
}
|
||||
var udpTimeout time.Duration
|
||||
@@ -179,6 +183,7 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo
|
||||
outputMark = tun.DefaultAutoRedirectOutputMark
|
||||
}
|
||||
networkManager := service.FromContext[adapter.NetworkManager](ctx)
|
||||
multiPendingPackets := C.IsDarwin && ((options.Stack == "gvisor" && tunMTU < 32768) || (options.Stack != "gvisor" && options.MTU <= 9000))
|
||||
inbound := &Inbound{
|
||||
tag: tag,
|
||||
ctx: ctx,
|
||||
@@ -189,6 +194,7 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo
|
||||
tunOptions: tun.Options{
|
||||
Name: options.InterfaceName,
|
||||
MTU: tunMTU,
|
||||
GSO: enableGSO,
|
||||
Inet4Address: inet4Address,
|
||||
Inet6Address: inet6Address,
|
||||
AutoRoute: options.AutoRoute,
|
||||
@@ -211,6 +217,7 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo
|
||||
IncludePackage: options.IncludePackage,
|
||||
ExcludePackage: options.ExcludePackage,
|
||||
InterfaceMonitor: networkManager.InterfaceMonitor(),
|
||||
EXP_MultiPendingPackets: multiPendingPackets,
|
||||
},
|
||||
udpTimeout: udpTimeout,
|
||||
stack: options.Stack,
|
||||
|
||||
@@ -15,11 +15,11 @@ import (
|
||||
"github.com/sagernet/sing-box/log"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
"github.com/sagernet/sing-box/transport/v2ray"
|
||||
"github.com/sagernet/sing-vmess"
|
||||
"github.com/sagernet/sing-vmess/packetaddr"
|
||||
"github.com/sagernet/sing-vmess/vless"
|
||||
"github.com/sagernet/sing/common"
|
||||
"github.com/sagernet/sing/common/auth"
|
||||
"github.com/sagernet/sing/common/bufio"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
F "github.com/sagernet/sing/common/format"
|
||||
"github.com/sagernet/sing/common/logger"
|
||||
@@ -189,7 +189,7 @@ func (h *Inbound) newPacketConnectionEx(ctx context.Context, conn N.PacketConn,
|
||||
}
|
||||
if metadata.Destination.Fqdn == packetaddr.SeqPacketMagicAddress {
|
||||
metadata.Destination = M.Socksaddr{}
|
||||
conn = packetaddr.NewConn(conn.(vmess.PacketConn), metadata.Destination)
|
||||
conn = packetaddr.NewConn(bufio.NewNetPacketConn(conn), metadata.Destination)
|
||||
h.logger.InfoContext(ctx, "[", user, "] inbound packet addr connection")
|
||||
} else {
|
||||
h.logger.InfoContext(ctx, "[", user, "] inbound packet connection to ", metadata.Destination)
|
||||
|
||||
@@ -124,7 +124,6 @@ func (h *Outbound) InterfaceUpdated() {
|
||||
if h.multiplexDialer != nil {
|
||||
h.multiplexDialer.Reset()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (h *Outbound) Close() error {
|
||||
|
||||
@@ -19,6 +19,7 @@ import (
|
||||
"github.com/sagernet/sing-vmess/packetaddr"
|
||||
"github.com/sagernet/sing/common"
|
||||
"github.com/sagernet/sing/common/auth"
|
||||
"github.com/sagernet/sing/common/bufio"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
F "github.com/sagernet/sing/common/format"
|
||||
"github.com/sagernet/sing/common/logger"
|
||||
@@ -203,7 +204,7 @@ func (h *Inbound) newPacketConnectionEx(ctx context.Context, conn N.PacketConn,
|
||||
}
|
||||
if metadata.Destination.Fqdn == packetaddr.SeqPacketMagicAddress {
|
||||
metadata.Destination = M.Socksaddr{}
|
||||
conn = packetaddr.NewConn(conn.(vmess.PacketConn), metadata.Destination)
|
||||
conn = packetaddr.NewConn(bufio.NewNetPacketConn(conn), metadata.Destination)
|
||||
h.logger.InfoContext(ctx, "[", user, "] inbound packet addr connection")
|
||||
} else {
|
||||
h.logger.InfoContext(ctx, "[", user, "] inbound packet connection to ", metadata.Destination)
|
||||
|
||||
@@ -108,7 +108,6 @@ func (h *Outbound) InterfaceUpdated() {
|
||||
if h.multiplexDialer != nil {
|
||||
h.multiplexDialer.Reset()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (h *Outbound) Close() error {
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
polkit.addRule(function(action, subject) {
|
||||
if ((action.id == "org.freedesktop.resolve1.set-domains" ||
|
||||
action.id == "org.freedesktop.resolve1.set-default-route" ||
|
||||
action.id == "org.freedesktop.resolve1.set-dns-servers" ||
|
||||
action.id == "org.fedoraproject.FirewallD1.all" ||
|
||||
action.id == "org.fedoraproject.FirewallD1.config") &&
|
||||
action.id == "org.freedesktop.resolve1.set-dns-servers") &&
|
||||
subject.user == "sing-box") {
|
||||
return polkit.Result.YES;
|
||||
}
|
||||
|
||||
@@ -277,7 +277,7 @@ func (m *ConnectionManager) connectionCopy(ctx context.Context, source net.Conn,
|
||||
return
|
||||
}
|
||||
}
|
||||
_, err := bufio.CopyWithCounters(destinationWriter, sourceReader, source, readCounters, writeCounters, bufio.DefaultIncreaseBufferAfter)
|
||||
_, err := bufio.CopyWithCounters(destinationWriter, sourceReader, source, readCounters, writeCounters, bufio.DefaultIncreaseBufferAfter, bufio.DefaultBatchSize)
|
||||
if err != nil {
|
||||
common.Close(source, destination)
|
||||
} else if duplexDst, isDuplex := destination.(N.WriteCloser); isDuplex {
|
||||
|
||||
@@ -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
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user