mirror of
https://github.com/shtorm-7/sing-box-extended.git
synced 2026-07-07 17:01:36 +03:00
Compare commits
101 Commits
v1.12.8-ex
...
dev-next
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c3cc010880 | ||
|
|
1920c191be | ||
|
|
e0ac459204 | ||
|
|
09fb897805 | ||
|
|
a1b3d891a3 | ||
|
|
d866a40469 | ||
|
|
45cd04b07e | ||
|
|
2cf0528c4d | ||
|
|
905a2ded93 | ||
|
|
cb3c0829c5 | ||
|
|
1a8f6e053d | ||
|
|
99a09a6ce5 | ||
|
|
01b4c7fcdd | ||
|
|
fe89f946c1 | ||
|
|
6c17c7a8f5 | ||
|
|
ea067e5478 | ||
|
|
75af9a824e | ||
|
|
a5d4a42119 | ||
|
|
9821fbc3e3 | ||
|
|
c0408ad1de | ||
|
|
6b0e861afa | ||
|
|
e32d686d6c | ||
|
|
844308e128 | ||
|
|
93c14db281 | ||
|
|
b893a27dfc | ||
|
|
d39960fa23 | ||
|
|
ba0badd4bf | ||
|
|
cfbb5d63d5 | ||
|
|
8447a3edfe | ||
|
|
1a9747a531 | ||
|
|
583ecbea3b | ||
|
|
bb6c8535a5 | ||
|
|
10d90e4acc | ||
|
|
e625012219 | ||
|
|
670863fd5b | ||
|
|
f7cf87142f | ||
|
|
2597a68a01 | ||
|
|
7354332daa | ||
|
|
a0d382fc4e | ||
|
|
a6da8b6654 | ||
|
|
7385616cca | ||
|
|
4b6784b446 | ||
|
|
68579bb93b | ||
|
|
6aace7b1b7 | ||
|
|
148234b742 | ||
|
|
97b7a451be | ||
|
|
73b67e0b48 | ||
|
|
88b4d04d59 | ||
|
|
d1ec6c6dd2 | ||
|
|
523825336a | ||
|
|
032565a026 | ||
|
|
aeea24ae30 | ||
|
|
af22549f1a | ||
|
|
57b17ceb4b | ||
|
|
3dd308e7c3 | ||
|
|
7f75195d86 | ||
|
|
2fe4cad905 | ||
|
|
f55eb75a53 | ||
|
|
5ffb5b6ad2 | ||
|
|
a1d5931759 | ||
|
|
9e68e909cb | ||
|
|
117e8b76cc | ||
|
|
d2f83bfd50 | ||
|
|
eaef13febe | ||
|
|
0110c69dc9 | ||
|
|
fb2f5af1fb | ||
|
|
1553923118 | ||
|
|
0ada49489d | ||
|
|
95d5ca9393 | ||
|
|
6cebbb4590 | ||
|
|
0ef81bb5ef | ||
|
|
0d30a1df9d | ||
|
|
563499d2f9 | ||
|
|
f10c0c1c8d | ||
|
|
428074d88b | ||
|
|
fa18832ad2 | ||
|
|
87bce2de29 | ||
|
|
f5020554e4 | ||
|
|
31f3623b8a | ||
|
|
bb42657177 | ||
|
|
f19ff7eca7 | ||
|
|
8e45133f2e | ||
|
|
63df88675f | ||
|
|
0423244298 | ||
|
|
a5f1af9587 | ||
|
|
112817c1a4 | ||
|
|
6e91de51f1 | ||
|
|
efc5c542fb | ||
|
|
f1b569c7d1 | ||
|
|
a752197d5e | ||
|
|
65517d4513 | ||
|
|
ccf4fa4d3a | ||
|
|
18dbb823a1 | ||
|
|
4ec058e91a | ||
|
|
6eed06b2c2 | ||
|
|
dd209cc9d5 | ||
|
|
b0c0a6b07d | ||
|
|
951a8fabbf | ||
|
|
928298b528 | ||
|
|
5b84fa0137 | ||
|
|
2bb85ac8a1 |
2
.github/setup_legacy_go.sh
vendored
2
.github/setup_legacy_go.sh
vendored
@@ -1,6 +1,6 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
VERSION="1.23.12"
|
VERSION="1.23.6"
|
||||||
|
|
||||||
mkdir -p $HOME/go
|
mkdir -p $HOME/go
|
||||||
cd $HOME/go
|
cd $HOME/go
|
||||||
|
|||||||
69
.github/workflows/build.yml
vendored
69
.github/workflows/build.yml
vendored
@@ -40,13 +40,13 @@ jobs:
|
|||||||
version: ${{ steps.outputs.outputs.version }}
|
version: ${{ steps.outputs.outputs.version }}
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: ^1.25.1
|
go-version: ^1.24.3
|
||||||
- name: Check input version
|
- name: Check input version
|
||||||
if: github.event_name == 'workflow_dispatch'
|
if: github.event_name == 'workflow_dispatch'
|
||||||
run: |-
|
run: |-
|
||||||
@@ -88,14 +88,13 @@ jobs:
|
|||||||
- { os: linux, arch: loong64, debian: loongarch64, rpm: loongarch64, openwrt: "loongarch64_generic" }
|
- { os: linux, arch: loong64, debian: loongarch64, rpm: loongarch64, openwrt: "loongarch64_generic" }
|
||||||
|
|
||||||
- { os: windows, arch: amd64 }
|
- { os: windows, arch: amd64 }
|
||||||
- { os: windows, arch: amd64, legacy_go123: true, legacy_name: "windows-7" }
|
- { os: windows, arch: amd64, legacy_go: true }
|
||||||
- { os: windows, arch: "386" }
|
- { os: windows, arch: "386" }
|
||||||
- { os: windows, arch: "386", legacy_go123: true, legacy_name: "windows-7" }
|
- { os: windows, arch: "386", legacy_go: true }
|
||||||
- { os: windows, arch: arm64 }
|
- { os: windows, arch: arm64 }
|
||||||
|
|
||||||
- { os: darwin, arch: amd64 }
|
- { os: darwin, arch: amd64 }
|
||||||
- { os: darwin, arch: arm64 }
|
- { os: darwin, arch: arm64 }
|
||||||
- { os: darwin, arch: amd64, legacy_go124: true, legacy_name: "macos-11" }
|
|
||||||
|
|
||||||
- { os: android, arch: arm64, ndk: "aarch64-linux-android21" }
|
- { os: android, arch: arm64, ndk: "aarch64-linux-android21" }
|
||||||
- { os: android, arch: arm, ndk: "armv7a-linux-androideabi21" }
|
- { os: android, arch: arm, ndk: "armv7a-linux-androideabi21" }
|
||||||
@@ -103,33 +102,28 @@ jobs:
|
|||||||
- { os: android, arch: "386", ndk: "i686-linux-android21" }
|
- { os: android, arch: "386", ndk: "i686-linux-android21" }
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
if: ${{ ! (matrix.legacy_go123 || matrix.legacy_go124) }}
|
if: ${{ ! matrix.legacy_go }}
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: ^1.25.1
|
go-version: ^1.24.3
|
||||||
- name: Setup Go 1.24
|
- name: Cache Legacy Go
|
||||||
if: matrix.legacy_go124
|
if: matrix.require_legacy_go
|
||||||
uses: actions/setup-go@v5
|
|
||||||
with:
|
|
||||||
go-version: ~1.24.6
|
|
||||||
- name: Cache Go 1.23
|
|
||||||
if: matrix.legacy_go123
|
|
||||||
id: cache-legacy-go
|
id: cache-legacy-go
|
||||||
uses: actions/cache@v4
|
uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
~/go/go_legacy
|
~/go/go_legacy
|
||||||
key: go_legacy_12312
|
key: go_legacy_1236
|
||||||
- name: Setup Go 1.23
|
- name: Setup Legacy Go
|
||||||
if: matrix.legacy_go123 && steps.cache-legacy-go.outputs.cache-hit != 'true'
|
if: matrix.legacy_go && steps.cache-legacy-go.outputs.cache-hit != 'true'
|
||||||
run: |-
|
run: |-
|
||||||
.github/setup_legacy_go.sh
|
.github/setup_legacy_go.sh
|
||||||
- name: Setup Go 1.23
|
- name: Setup Legacy Go 2
|
||||||
if: matrix.legacy_go123
|
if: matrix.legacy_go
|
||||||
run: |-
|
run: |-
|
||||||
echo "PATH=$HOME/go/go_legacy/bin:$PATH" >> $GITHUB_ENV
|
echo "PATH=$HOME/go/go_legacy/bin:$PATH" >> $GITHUB_ENV
|
||||||
echo "GOROOT=$HOME/go/go_legacy" >> $GITHUB_ENV
|
echo "GOROOT=$HOME/go/go_legacy" >> $GITHUB_ENV
|
||||||
@@ -190,8 +184,8 @@ jobs:
|
|||||||
DIR_NAME="${DIR_NAME}-${{ matrix.go386 }}"
|
DIR_NAME="${DIR_NAME}-${{ matrix.go386 }}"
|
||||||
elif [[ -n "${{ matrix.gomips }}" && "${{ matrix.gomips }}" != 'hardfloat' ]]; then
|
elif [[ -n "${{ matrix.gomips }}" && "${{ matrix.gomips }}" != 'hardfloat' ]]; then
|
||||||
DIR_NAME="${DIR_NAME}-${{ matrix.gomips }}"
|
DIR_NAME="${DIR_NAME}-${{ matrix.gomips }}"
|
||||||
elif [[ -n "${{ matrix.legacy_name }}" ]]; then
|
elif [[ "${{ matrix.legacy_go }}" == 'true' ]]; then
|
||||||
DIR_NAME="${DIR_NAME}-legacy-${{ matrix.legacy_name }}"
|
DIR_NAME="${DIR_NAME}-legacy"
|
||||||
fi
|
fi
|
||||||
echo "DIR_NAME=${DIR_NAME}" >> "${GITHUB_ENV}"
|
echo "DIR_NAME=${DIR_NAME}" >> "${GITHUB_ENV}"
|
||||||
PKG_VERSION="${{ needs.calculate_version.outputs.version }}"
|
PKG_VERSION="${{ needs.calculate_version.outputs.version }}"
|
||||||
@@ -283,7 +277,7 @@ jobs:
|
|||||||
- name: Upload artifact
|
- name: Upload artifact
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: binary-${{ matrix.os }}_${{ matrix.arch }}${{ matrix.goarm && format('v{0}', matrix.goarm) }}${{ matrix.go386 && format('_{0}', matrix.go386) }}${{ matrix.gomips && format('_{0}', matrix.gomips) }}${{ matrix.legacy_name && format('-legacy-{0}', matrix.legacy_name) }}
|
name: binary-${{ matrix.os }}_${{ matrix.arch }}${{ matrix.goarm && format('v{0}', matrix.goarm) }}${{ matrix.go386 && format('_{0}', matrix.go386) }}${{ matrix.gomips && format('_{0}', matrix.gomips) }}${{ matrix.legacy_go && '-legacy' || '' }}
|
||||||
path: "dist"
|
path: "dist"
|
||||||
build_android:
|
build_android:
|
||||||
name: Build Android
|
name: Build Android
|
||||||
@@ -293,14 +287,14 @@ jobs:
|
|||||||
- calculate_version
|
- calculate_version
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
submodules: 'recursive'
|
submodules: 'recursive'
|
||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: ^1.25.1
|
go-version: ^1.24.3
|
||||||
- name: Setup Android NDK
|
- name: Setup Android NDK
|
||||||
id: setup-ndk
|
id: setup-ndk
|
||||||
uses: nttld/setup-ndk@v1
|
uses: nttld/setup-ndk@v1
|
||||||
@@ -373,14 +367,14 @@ jobs:
|
|||||||
- calculate_version
|
- calculate_version
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
submodules: 'recursive'
|
submodules: 'recursive'
|
||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: ^1.25.1
|
go-version: ^1.24.3
|
||||||
- name: Setup Android NDK
|
- name: Setup Android NDK
|
||||||
id: setup-ndk
|
id: setup-ndk
|
||||||
uses: nttld/setup-ndk@v1
|
uses: nttld/setup-ndk@v1
|
||||||
@@ -432,8 +426,7 @@ jobs:
|
|||||||
SERVICE_ACCOUNT_CREDENTIALS: ${{ secrets.SERVICE_ACCOUNT_CREDENTIALS }}
|
SERVICE_ACCOUNT_CREDENTIALS: ${{ secrets.SERVICE_ACCOUNT_CREDENTIALS }}
|
||||||
build_apple:
|
build_apple:
|
||||||
name: Build Apple clients
|
name: Build Apple clients
|
||||||
runs-on: macos-26
|
runs-on: macos-15
|
||||||
if: false
|
|
||||||
needs:
|
needs:
|
||||||
- calculate_version
|
- calculate_version
|
||||||
strategy:
|
strategy:
|
||||||
@@ -471,7 +464,7 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
if: matrix.if
|
if: matrix.if
|
||||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
submodules: 'recursive'
|
submodules: 'recursive'
|
||||||
@@ -479,7 +472,15 @@ jobs:
|
|||||||
if: matrix.if
|
if: matrix.if
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: ^1.25.1
|
go-version: ^1.24.3
|
||||||
|
- name: Setup Xcode stable
|
||||||
|
if: matrix.if && github.ref == 'refs/heads/main-next'
|
||||||
|
run: |-
|
||||||
|
sudo xcode-select -s /Applications/Xcode_16.2.app
|
||||||
|
- name: Setup Xcode beta
|
||||||
|
if: matrix.if && github.ref == 'refs/heads/dev-next'
|
||||||
|
run: |-
|
||||||
|
sudo xcode-select -s /Applications/Xcode_16.2.app
|
||||||
- name: Set tag
|
- name: Set tag
|
||||||
if: matrix.if
|
if: matrix.if
|
||||||
run: |-
|
run: |-
|
||||||
@@ -614,7 +615,7 @@ jobs:
|
|||||||
path: 'dist'
|
path: 'dist'
|
||||||
upload:
|
upload:
|
||||||
name: Upload builds
|
name: Upload builds
|
||||||
if: "!failure() && github.event_name == 'workflow_dispatch' && (inputs.build == 'All' || inputs.build == 'Binary' || inputs.build == 'Android' || inputs.build == 'Apple' || inputs.build == 'macOS-standalone')"
|
if: always() && github.event_name == 'workflow_dispatch' && (inputs.build == 'All' || inputs.build == 'Binary' || inputs.build == 'Android' || inputs.build == 'Apple' || inputs.build == 'macOS-standalone')
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs:
|
needs:
|
||||||
- calculate_version
|
- calculate_version
|
||||||
@@ -623,7 +624,7 @@ jobs:
|
|||||||
- build_apple
|
- build_apple
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- name: Cache ghr
|
- name: Cache ghr
|
||||||
@@ -646,7 +647,7 @@ jobs:
|
|||||||
git tag v${{ needs.calculate_version.outputs.version }} -f
|
git tag v${{ needs.calculate_version.outputs.version }} -f
|
||||||
echo "VERSION=${{ needs.calculate_version.outputs.version }}" >> "$GITHUB_ENV"
|
echo "VERSION=${{ needs.calculate_version.outputs.version }}" >> "$GITHUB_ENV"
|
||||||
- name: Download builds
|
- name: Download builds
|
||||||
uses: actions/download-artifact@v5
|
uses: actions/download-artifact@v4
|
||||||
with:
|
with:
|
||||||
path: dist
|
path: dist
|
||||||
merge-multiple: true
|
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"
|
||||||
echo "ref=$ref" >> $GITHUB_OUTPUT
|
echo "ref=$ref" >> $GITHUB_OUTPUT
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
|
||||||
with:
|
with:
|
||||||
ref: ${{ steps.ref.outputs.ref }}
|
ref: ${{ steps.ref.outputs.ref }}
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
@@ -107,7 +107,7 @@ jobs:
|
|||||||
echo "latest=$latest"
|
echo "latest=$latest"
|
||||||
echo "latest=$latest" >> $GITHUB_OUTPUT
|
echo "latest=$latest" >> $GITHUB_OUTPUT
|
||||||
- name: Download digests
|
- name: Download digests
|
||||||
uses: actions/download-artifact@v5
|
uses: actions/download-artifact@v4
|
||||||
with:
|
with:
|
||||||
path: /tmp/digests
|
path: /tmp/digests
|
||||||
pattern: digests-*
|
pattern: digests-*
|
||||||
|
|||||||
6
.github/workflows/lint.yml
vendored
6
.github/workflows/lint.yml
vendored
@@ -22,15 +22,15 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: ~1.24.6
|
go-version: ^1.24.3
|
||||||
- name: golangci-lint
|
- name: golangci-lint
|
||||||
uses: golangci/golangci-lint-action@v8
|
uses: golangci/golangci-lint-action@v6
|
||||||
with:
|
with:
|
||||||
version: latest
|
version: latest
|
||||||
args: --timeout=30m
|
args: --timeout=30m
|
||||||
|
|||||||
21
.github/workflows/linux.yml
vendored
21
.github/workflows/linux.yml
vendored
@@ -7,11 +7,6 @@ on:
|
|||||||
description: "Version name"
|
description: "Version name"
|
||||||
required: true
|
required: true
|
||||||
type: string
|
type: string
|
||||||
forceBeta:
|
|
||||||
description: "Force beta"
|
|
||||||
required: false
|
|
||||||
type: boolean
|
|
||||||
default: false
|
|
||||||
release:
|
release:
|
||||||
types:
|
types:
|
||||||
- published
|
- published
|
||||||
@@ -24,13 +19,13 @@ jobs:
|
|||||||
version: ${{ steps.outputs.outputs.version }}
|
version: ${{ steps.outputs.outputs.version }}
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: ^1.25.1
|
go-version: ^1.24.3
|
||||||
- name: Check input version
|
- name: Check input version
|
||||||
if: github.event_name == 'workflow_dispatch'
|
if: github.event_name == 'workflow_dispatch'
|
||||||
run: |-
|
run: |-
|
||||||
@@ -65,13 +60,13 @@ jobs:
|
|||||||
- { os: linux, arch: loong64, debian: loongarch64, rpm: loongarch64 }
|
- { os: linux, arch: loong64, debian: loongarch64, rpm: loongarch64 }
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: ^1.25.1
|
go-version: ^1.24.3
|
||||||
- name: Setup Android NDK
|
- name: Setup Android NDK
|
||||||
if: matrix.os == 'android'
|
if: matrix.os == 'android'
|
||||||
uses: nttld/setup-ndk@v1
|
uses: nttld/setup-ndk@v1
|
||||||
@@ -104,11 +99,11 @@ jobs:
|
|||||||
run: |-
|
run: |-
|
||||||
TZ=UTC touch -t '197001010000' dist/sing-box
|
TZ=UTC touch -t '197001010000' dist/sing-box
|
||||||
- name: Set name
|
- name: Set name
|
||||||
if: (! contains(needs.calculate_version.outputs.version, '-')) && !inputs.forceBeta
|
if: ${{ ! contains(needs.calculate_version.outputs.version, '-') }}
|
||||||
run: |-
|
run: |-
|
||||||
echo "NAME=sing-box" >> "$GITHUB_ENV"
|
echo "NAME=sing-box" >> "$GITHUB_ENV"
|
||||||
- name: Set beta name
|
- name: Set beta name
|
||||||
if: contains(needs.calculate_version.outputs.version, '-') || inputs.forceBeta
|
if: contains(needs.calculate_version.outputs.version, '-')
|
||||||
run: |-
|
run: |-
|
||||||
echo "NAME=sing-box-beta" >> "$GITHUB_ENV"
|
echo "NAME=sing-box-beta" >> "$GITHUB_ENV"
|
||||||
- name: Set version
|
- name: Set version
|
||||||
@@ -171,7 +166,7 @@ jobs:
|
|||||||
- build
|
- build
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- name: Set tag
|
- name: Set tag
|
||||||
@@ -180,7 +175,7 @@ jobs:
|
|||||||
git tag v${{ needs.calculate_version.outputs.version }} -f
|
git tag v${{ needs.calculate_version.outputs.version }} -f
|
||||||
echo "VERSION=${{ needs.calculate_version.outputs.version }}" >> "$GITHUB_ENV"
|
echo "VERSION=${{ needs.calculate_version.outputs.version }}" >> "$GITHUB_ENV"
|
||||||
- name: Download builds
|
- name: Download builds
|
||||||
uses: actions/download-artifact@v5
|
uses: actions/download-artifact@v4
|
||||||
with:
|
with:
|
||||||
path: dist
|
path: dist
|
||||||
merge-multiple: true
|
merge-multiple: true
|
||||||
|
|||||||
@@ -1,6 +1,27 @@
|
|||||||
version: "2"
|
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
|
||||||
|
|
||||||
run:
|
run:
|
||||||
go: "1.24"
|
go: "1.23"
|
||||||
build-tags:
|
build-tags:
|
||||||
- with_gvisor
|
- with_gvisor
|
||||||
- with_quic
|
- with_quic
|
||||||
@@ -9,51 +30,7 @@ run:
|
|||||||
- with_utls
|
- with_utls
|
||||||
- with_acme
|
- with_acme
|
||||||
- with_clash_api
|
- with_clash_api
|
||||||
linters:
|
|
||||||
default: none
|
issues:
|
||||||
enable:
|
exclude-dirs:
|
||||||
- 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
|
- transport/simple-obfs
|
||||||
- third_party$
|
|
||||||
- builtin$
|
|
||||||
- examples$
|
|
||||||
formatters:
|
|
||||||
enable:
|
|
||||||
- gci
|
|
||||||
- gofumpt
|
|
||||||
settings:
|
|
||||||
gci:
|
|
||||||
sections:
|
|
||||||
- standard
|
|
||||||
- prefix(github.com/sagernet/)
|
|
||||||
- default
|
|
||||||
custom-order: true
|
|
||||||
exclusions:
|
|
||||||
generated: lax
|
|
||||||
paths:
|
|
||||||
- transport/simple-obfs
|
|
||||||
- third_party$
|
|
||||||
- builtin$
|
|
||||||
- examples$
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
FROM --platform=$BUILDPLATFORM golang:1.25-alpine AS builder
|
FROM --platform=$BUILDPLATFORM golang:1.24-alpine AS builder
|
||||||
LABEL maintainer="nekohasekai <contact-git@sekai.icu>"
|
LABEL maintainer="nekohasekai <contact-git@sekai.icu>"
|
||||||
COPY . /go/src/github.com/sagernet/sing-box
|
COPY . /go/src/github.com/sagernet/sing-box
|
||||||
WORKDIR /go/src/github.com/sagernet/sing-box
|
WORKDIR /go/src/github.com/sagernet/sing-box
|
||||||
|
|||||||
30
Makefile
30
Makefile
@@ -17,10 +17,6 @@ build:
|
|||||||
export GOTOOLCHAIN=local && \
|
export GOTOOLCHAIN=local && \
|
||||||
go build $(MAIN_PARAMS) $(MAIN)
|
go build $(MAIN_PARAMS) $(MAIN)
|
||||||
|
|
||||||
race:
|
|
||||||
export GOTOOLCHAIN=local && \
|
|
||||||
go build -race $(MAIN_PARAMS) $(MAIN)
|
|
||||||
|
|
||||||
ci_build:
|
ci_build:
|
||||||
export GOTOOLCHAIN=local && \
|
export GOTOOLCHAIN=local && \
|
||||||
go build $(PARAMS) $(MAIN) && \
|
go build $(PARAMS) $(MAIN) && \
|
||||||
@@ -49,7 +45,7 @@ lint:
|
|||||||
GOOS=freebsd golangci-lint run ./...
|
GOOS=freebsd golangci-lint run ./...
|
||||||
|
|
||||||
lint_install:
|
lint_install:
|
||||||
go install -v github.com/golangci/golangci-lint/v2/cmd/golangci-lint@latest
|
go install -v github.com/golangci/golangci-lint/cmd/golangci-lint@latest
|
||||||
|
|
||||||
proto:
|
proto:
|
||||||
@go run ./cmd/internal/protogen
|
@go run ./cmd/internal/protogen
|
||||||
@@ -112,16 +108,6 @@ upload_ios_app_store:
|
|||||||
cd ../sing-box-for-apple && \
|
cd ../sing-box-for-apple && \
|
||||||
xcodebuild -exportArchive -archivePath build/SFI.xcarchive -exportOptionsPlist SFI/Upload.plist -allowProvisioningUpdates
|
xcodebuild -exportArchive -archivePath build/SFI.xcarchive -exportOptionsPlist SFI/Upload.plist -allowProvisioningUpdates
|
||||||
|
|
||||||
export_ios_ipa:
|
|
||||||
cd ../sing-box-for-apple && \
|
|
||||||
xcodebuild -exportArchive -archivePath build/SFI.xcarchive -exportOptionsPlist SFI/Export.plist -allowProvisioningUpdates -exportPath build/SFI && \
|
|
||||||
cp build/SFI/sing-box.ipa dist/SFI.ipa
|
|
||||||
|
|
||||||
upload_ios_ipa:
|
|
||||||
cd dist && \
|
|
||||||
cp SFI.ipa "SFI-${VERSION}.ipa" && \
|
|
||||||
ghr --replace --draft --prerelease "v${VERSION}" "SFI-${VERSION}.ipa"
|
|
||||||
|
|
||||||
release_ios: build_ios upload_ios_app_store
|
release_ios: build_ios upload_ios_app_store
|
||||||
|
|
||||||
build_macos:
|
build_macos:
|
||||||
@@ -189,16 +175,6 @@ upload_tvos_app_store:
|
|||||||
cd ../sing-box-for-apple && \
|
cd ../sing-box-for-apple && \
|
||||||
xcodebuild -exportArchive -archivePath "build/SFT.xcarchive" -exportOptionsPlist SFI/Upload.plist -allowProvisioningUpdates
|
xcodebuild -exportArchive -archivePath "build/SFT.xcarchive" -exportOptionsPlist SFI/Upload.plist -allowProvisioningUpdates
|
||||||
|
|
||||||
export_tvos_ipa:
|
|
||||||
cd ../sing-box-for-apple && \
|
|
||||||
xcodebuild -exportArchive -archivePath "build/SFT.xcarchive" -exportOptionsPlist SFI/Export.plist -allowProvisioningUpdates -exportPath build/SFT && \
|
|
||||||
cp build/SFT/sing-box.ipa dist/SFT.ipa
|
|
||||||
|
|
||||||
upload_tvos_ipa:
|
|
||||||
cd dist && \
|
|
||||||
cp SFT.ipa "SFT-${VERSION}.ipa" && \
|
|
||||||
ghr --replace --draft --prerelease "v${VERSION}" "SFT-${VERSION}.ipa"
|
|
||||||
|
|
||||||
release_tvos: build_tvos upload_tvos_app_store
|
release_tvos: build_tvos upload_tvos_app_store
|
||||||
|
|
||||||
update_apple_version:
|
update_apple_version:
|
||||||
@@ -249,8 +225,8 @@ lib:
|
|||||||
go run ./cmd/internal/build_libbox -target ios
|
go run ./cmd/internal/build_libbox -target ios
|
||||||
|
|
||||||
lib_install:
|
lib_install:
|
||||||
go install -v github.com/sagernet/gomobile/cmd/gomobile@v0.1.8
|
go install -v github.com/sagernet/gomobile/cmd/gomobile@v0.1.6
|
||||||
go install -v github.com/sagernet/gomobile/cmd/gobind@v0.1.8
|
go install -v github.com/sagernet/gomobile/cmd/gobind@v0.1.6
|
||||||
|
|
||||||
docs:
|
docs:
|
||||||
venv/bin/mkdocs serve
|
venv/bin/mkdocs serve
|
||||||
|
|||||||
18
README.md
18
README.md
@@ -1,20 +1,12 @@
|
|||||||
# sing-box-extended
|
# sing-box
|
||||||
|
|
||||||
Sing-box with extended features.
|
The universal proxy platform.
|
||||||
|
|
||||||
## Features
|
[](https://repology.org/project/sing-box/versions)
|
||||||
|
|
||||||
* Amnezia 1.5
|
## Documentation
|
||||||
* WARP
|
|
||||||
* Tunneling
|
|
||||||
* Mieru
|
|
||||||
* XHTTP
|
|
||||||
* SDNS (DNSCrypt)
|
|
||||||
* Unified delay
|
|
||||||
|
|
||||||
## Examples
|
https://sing-box.sagernet.org
|
||||||
|
|
||||||
https://github.com/shtorm-7/sing-box-extended/tree/extended/examples
|
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ func NewManager(logger log.ContextLogger, registry adapter.EndpointRegistry) *Ma
|
|||||||
|
|
||||||
func (m *Manager) Start(stage adapter.StartStage) error {
|
func (m *Manager) Start(stage adapter.StartStage) error {
|
||||||
m.access.Lock()
|
m.access.Lock()
|
||||||
|
defer m.access.Unlock()
|
||||||
if m.started && m.stage >= stage {
|
if m.started && m.stage >= stage {
|
||||||
panic("already started")
|
panic("already started")
|
||||||
}
|
}
|
||||||
@@ -42,12 +43,9 @@ func (m *Manager) Start(stage adapter.StartStage) error {
|
|||||||
m.stage = stage
|
m.stage = stage
|
||||||
if stage == adapter.StartStateStart {
|
if stage == adapter.StartStateStart {
|
||||||
// started with outbound manager
|
// started with outbound manager
|
||||||
m.access.Unlock()
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
endpoints := m.endpoints
|
for _, endpoint := range m.endpoints {
|
||||||
m.access.Unlock()
|
|
||||||
for _, endpoint := range endpoints {
|
|
||||||
err := adapter.LegacyStart(endpoint, stage)
|
err := adapter.LegacyStart(endpoint, stage)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return E.Cause(err, stage, " endpoint/", endpoint.Type(), "[", endpoint.Tag(), "]")
|
return E.Cause(err, stage, " endpoint/", endpoint.Type(), "[", endpoint.Tag(), "]")
|
||||||
|
|||||||
@@ -44,8 +44,6 @@ type CacheFile interface {
|
|||||||
StoreRDRC() bool
|
StoreRDRC() bool
|
||||||
RDRCStore
|
RDRCStore
|
||||||
|
|
||||||
StoreWARPConfig() bool
|
|
||||||
|
|
||||||
LoadMode() string
|
LoadMode() string
|
||||||
StoreMode(mode string) error
|
StoreMode(mode string) error
|
||||||
LoadSelected(group string) string
|
LoadSelected(group string) string
|
||||||
@@ -54,8 +52,6 @@ type CacheFile interface {
|
|||||||
StoreGroupExpand(group string, expand bool) error
|
StoreGroupExpand(group string, expand bool) error
|
||||||
LoadRuleSet(tag string) *SavedBinary
|
LoadRuleSet(tag string) *SavedBinary
|
||||||
SaveRuleSet(tag string, set *SavedBinary) error
|
SaveRuleSet(tag string, set *SavedBinary) error
|
||||||
LoadWARPConfig(tag string) *SavedBinary
|
|
||||||
SaveWARPConfig(tag string, set *SavedBinary) error
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type SavedBinary struct {
|
type SavedBinary struct {
|
||||||
|
|||||||
@@ -48,8 +48,6 @@ type InboundContext struct {
|
|||||||
Network string
|
Network string
|
||||||
Source M.Socksaddr
|
Source M.Socksaddr
|
||||||
Destination M.Socksaddr
|
Destination M.Socksaddr
|
||||||
TunnelSource string
|
|
||||||
TunnelDestination string
|
|
||||||
User string
|
User string
|
||||||
Outbound string
|
Outbound string
|
||||||
|
|
||||||
@@ -59,8 +57,7 @@ type InboundContext struct {
|
|||||||
Domain string
|
Domain string
|
||||||
Client string
|
Client string
|
||||||
SniffContext any
|
SniffContext any
|
||||||
SnifferNames []string
|
PacketSniffError error
|
||||||
SniffError error
|
|
||||||
|
|
||||||
// cache
|
// cache
|
||||||
|
|
||||||
@@ -138,7 +135,8 @@ func ExtendContext(ctx context.Context) (context.Context, *InboundContext) {
|
|||||||
|
|
||||||
func OverrideContext(ctx context.Context) context.Context {
|
func OverrideContext(ctx context.Context) context.Context {
|
||||||
if metadata := ContextFrom(ctx); metadata != nil {
|
if metadata := ContextFrom(ctx); metadata != nil {
|
||||||
newMetadata := *metadata
|
var newMetadata InboundContext
|
||||||
|
newMetadata = *metadata
|
||||||
return WithContext(ctx, &newMetadata)
|
return WithContext(ctx, &newMetadata)
|
||||||
}
|
}
|
||||||
return ctx
|
return ctx
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ type NetworkManager interface {
|
|||||||
DefaultOptions() NetworkOptions
|
DefaultOptions() NetworkOptions
|
||||||
RegisterAutoRedirectOutputMark(mark uint32) error
|
RegisterAutoRedirectOutputMark(mark uint32) error
|
||||||
AutoRedirectOutputMark() uint32
|
AutoRedirectOutputMark() uint32
|
||||||
AutoRedirectOutputMarkFunc() control.Func
|
|
||||||
NetworkMonitor() tun.NetworkUpdateMonitor
|
NetworkMonitor() tun.NetworkUpdateMonitor
|
||||||
InterfaceMonitor() tun.DefaultInterfaceMonitor
|
InterfaceMonitor() tun.DefaultInterfaceMonitor
|
||||||
PackageManager() tun.PackageManager
|
PackageManager() tun.PackageManager
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ type Manager struct {
|
|||||||
outboundByTag map[string]adapter.Outbound
|
outboundByTag map[string]adapter.Outbound
|
||||||
dependByTag map[string][]string
|
dependByTag map[string][]string
|
||||||
defaultOutbound adapter.Outbound
|
defaultOutbound adapter.Outbound
|
||||||
defaultOutboundFallback func() (adapter.Outbound, error)
|
defaultOutboundFallback adapter.Outbound
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewManager(logger logger.ContextLogger, registry adapter.OutboundRegistry, endpoint adapter.EndpointManager, defaultTag string) *Manager {
|
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 func() (adapter.Outbound, error)) {
|
func (m *Manager) Initialize(defaultOutboundFallback adapter.Outbound) {
|
||||||
m.defaultOutboundFallback = defaultOutboundFallback
|
m.defaultOutboundFallback = defaultOutboundFallback
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -55,31 +55,18 @@ func (m *Manager) Start(stage adapter.StartStage) error {
|
|||||||
}
|
}
|
||||||
m.started = true
|
m.started = true
|
||||||
m.stage = stage
|
m.stage = stage
|
||||||
|
outbounds := m.outbounds
|
||||||
|
m.access.Unlock()
|
||||||
if stage == adapter.StartStateStart {
|
if stage == adapter.StartStateStart {
|
||||||
if m.defaultTag != "" && m.defaultOutbound == nil {
|
if m.defaultTag != "" && m.defaultOutbound == nil {
|
||||||
defaultEndpoint, loaded := m.endpoint.Get(m.defaultTag)
|
defaultEndpoint, loaded := m.endpoint.Get(m.defaultTag)
|
||||||
if !loaded {
|
if !loaded {
|
||||||
m.access.Unlock()
|
|
||||||
return E.New("default outbound not found: ", m.defaultTag)
|
return E.New("default outbound not found: ", m.defaultTag)
|
||||||
}
|
}
|
||||||
m.defaultOutbound = defaultEndpoint
|
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 })...))
|
return m.startOutbounds(append(outbounds, common.Map(m.endpoint.Endpoints(), func(it adapter.Endpoint) adapter.Outbound { return it })...))
|
||||||
} else {
|
} else {
|
||||||
outbounds := m.outbounds
|
|
||||||
m.access.Unlock()
|
|
||||||
for _, outbound := range outbounds {
|
for _, outbound := range outbounds {
|
||||||
err := adapter.LegacyStart(outbound, stage)
|
err := adapter.LegacyStart(outbound, stage)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -200,7 +187,11 @@ func (m *Manager) Outbound(tag string) (adapter.Outbound, bool) {
|
|||||||
func (m *Manager) Default() adapter.Outbound {
|
func (m *Manager) Default() adapter.Outbound {
|
||||||
m.access.RLock()
|
m.access.RLock()
|
||||||
defer m.access.RUnlock()
|
defer m.access.RUnlock()
|
||||||
|
if m.defaultOutbound != nil {
|
||||||
return m.defaultOutbound
|
return m.defaultOutbound
|
||||||
|
} else {
|
||||||
|
return m.defaultOutboundFallback
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) Remove(tag string) error {
|
func (m *Manager) Remove(tag string) error {
|
||||||
|
|||||||
@@ -78,8 +78,8 @@ func (w *myUpstreamHandlerWrapper) NewError(ctx context.Context, err error) {
|
|||||||
// Deprecated: removed
|
// Deprecated: removed
|
||||||
func UpstreamMetadata(metadata InboundContext) M.Metadata {
|
func UpstreamMetadata(metadata InboundContext) M.Metadata {
|
||||||
return M.Metadata{
|
return M.Metadata{
|
||||||
Source: metadata.Source.Unwrap(),
|
Source: metadata.Source,
|
||||||
Destination: metadata.Destination.Unwrap(),
|
Destination: metadata.Destination,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
14
box.go
14
box.go
@@ -17,7 +17,6 @@ import (
|
|||||||
"github.com/sagernet/sing-box/common/dialer"
|
"github.com/sagernet/sing-box/common/dialer"
|
||||||
"github.com/sagernet/sing-box/common/taskmonitor"
|
"github.com/sagernet/sing-box/common/taskmonitor"
|
||||||
"github.com/sagernet/sing-box/common/tls"
|
"github.com/sagernet/sing-box/common/tls"
|
||||||
"github.com/sagernet/sing-box/common/urltest"
|
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing-box/dns"
|
"github.com/sagernet/sing-box/dns"
|
||||||
"github.com/sagernet/sing-box/dns/transport/local"
|
"github.com/sagernet/sing-box/dns/transport/local"
|
||||||
@@ -140,9 +139,6 @@ func New(options Options) (*Box, error) {
|
|||||||
if experimentalOptions.V2RayAPI != nil && experimentalOptions.V2RayAPI.Listen != "" {
|
if experimentalOptions.V2RayAPI != nil && experimentalOptions.V2RayAPI.Listen != "" {
|
||||||
needV2RayAPI = true
|
needV2RayAPI = true
|
||||||
}
|
}
|
||||||
if experimentalOptions.UnifiedDelay != nil && experimentalOptions.UnifiedDelay.Enabled {
|
|
||||||
ctx = urltest.ContextWithIsUnifiedDelay(ctx)
|
|
||||||
}
|
|
||||||
platformInterface := service.FromContext[platform.Interface](ctx)
|
platformInterface := service.FromContext[platform.Interface](ctx)
|
||||||
var defaultLogWriter io.Writer
|
var defaultLogWriter io.Writer
|
||||||
if platformInterface != nil {
|
if platformInterface != nil {
|
||||||
@@ -318,15 +314,15 @@ func New(options Options) (*Box, error) {
|
|||||||
return nil, E.Cause(err, "initialize service[", i, "]")
|
return nil, E.Cause(err, "initialize service[", i, "]")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
outboundManager.Initialize(func() (adapter.Outbound, error) {
|
outboundManager.Initialize(common.Must1(
|
||||||
return direct.NewOutbound(
|
direct.NewOutbound(
|
||||||
ctx,
|
ctx,
|
||||||
router,
|
router,
|
||||||
logFactory.NewLogger("outbound/direct"),
|
logFactory.NewLogger("outbound/direct"),
|
||||||
"direct",
|
"direct",
|
||||||
option.DirectOutboundOptions{},
|
option.DirectOutboundOptions{},
|
||||||
)
|
),
|
||||||
})
|
))
|
||||||
dnsTransportManager.Initialize(common.Must1(
|
dnsTransportManager.Initialize(common.Must1(
|
||||||
local.NewTransport(
|
local.NewTransport(
|
||||||
ctx,
|
ctx,
|
||||||
@@ -502,7 +498,7 @@ func (s *Box) Close() error {
|
|||||||
close(s.done)
|
close(s.done)
|
||||||
}
|
}
|
||||||
err := common.Close(
|
err := common.Close(
|
||||||
s.service, s.endpoint, s.inbound, s.outbound, s.router, s.connection, s.dnsRouter, s.dnsTransport, s.network,
|
s.inbound, s.outbound, s.endpoint, s.router, s.connection, s.dnsRouter, s.dnsTransport, s.network,
|
||||||
)
|
)
|
||||||
for _, lifecycleService := range s.internalService {
|
for _, lifecycleService := range s.internalService {
|
||||||
err = E.Append(err, lifecycleService.Close(), func(err error) error {
|
err = E.Append(err, lifecycleService.Close(), func(err error) error {
|
||||||
|
|||||||
Submodule clients/android updated: cd8ac376f6...320170a107
Submodule clients/apple updated: 96bee16bf0...ae5818ee5a
@@ -177,7 +177,7 @@ func publishTestflight(ctx context.Context) error {
|
|||||||
}
|
}
|
||||||
log.Info(string(platform), " ", tag, " publish")
|
log.Info(string(platform), " ", tag, " publish")
|
||||||
response, err := client.TestFlight.AddBuildsToBetaGroup(ctx, groupID, []string{build.ID})
|
response, err := client.TestFlight.AddBuildsToBetaGroup(ctx, groupID, []string{build.ID})
|
||||||
if response != nil && (response.StatusCode == http.StatusUnprocessableEntity || response.StatusCode == http.StatusNotFound) {
|
if response != nil && response.StatusCode == http.StatusUnprocessableEntity {
|
||||||
log.Info("waiting for process")
|
log.Info("waiting for process")
|
||||||
time.Sleep(15 * time.Second)
|
time.Sleep(15 * time.Second)
|
||||||
continue
|
continue
|
||||||
|
|||||||
@@ -19,14 +19,12 @@ var (
|
|||||||
debugEnabled bool
|
debugEnabled bool
|
||||||
target string
|
target string
|
||||||
platform string
|
platform string
|
||||||
withTailscale bool
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
flag.BoolVar(&debugEnabled, "debug", false, "enable debug")
|
flag.BoolVar(&debugEnabled, "debug", false, "enable debug")
|
||||||
flag.StringVar(&target, "target", "android", "target platform")
|
flag.StringVar(&target, "target", "android", "target platform")
|
||||||
flag.StringVar(&platform, "platform", "", "specify platform")
|
flag.StringVar(&platform, "platform", "", "specify platform")
|
||||||
flag.BoolVar(&withTailscale, "with-tailscale", false, "build tailscale for iOS and tvOS")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
@@ -46,9 +44,8 @@ var (
|
|||||||
sharedFlags []string
|
sharedFlags []string
|
||||||
debugFlags []string
|
debugFlags []string
|
||||||
sharedTags []string
|
sharedTags []string
|
||||||
darwinTags []string
|
iosTags []string
|
||||||
memcTags []string
|
memcTags []string
|
||||||
notMemcTags []string
|
|
||||||
debugTags []string
|
debugTags []string
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -63,9 +60,8 @@ func init() {
|
|||||||
debugFlags = append(debugFlags, "-ldflags", "-X github.com/sagernet/sing-box/constant.Version="+currentTag)
|
debugFlags = append(debugFlags, "-ldflags", "-X github.com/sagernet/sing-box/constant.Version="+currentTag)
|
||||||
|
|
||||||
sharedTags = append(sharedTags, "with_gvisor", "with_quic", "with_wireguard", "with_utls", "with_clash_api", "with_conntrack")
|
sharedTags = append(sharedTags, "with_gvisor", "with_quic", "with_wireguard", "with_utls", "with_clash_api", "with_conntrack")
|
||||||
darwinTags = append(darwinTags, "with_dhcp")
|
iosTags = append(iosTags, "with_dhcp", "with_low_memory")
|
||||||
memcTags = append(memcTags, "with_tailscale")
|
memcTags = append(memcTags, "with_tailscale")
|
||||||
notMemcTags = append(notMemcTags, "with_low_memory")
|
|
||||||
debugTags = append(debugTags, "debug")
|
debugTags = append(debugTags, "debug")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -107,10 +103,8 @@ func buildAndroid() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !debugEnabled {
|
if !debugEnabled {
|
||||||
sharedFlags[3] = sharedFlags[3] + " -checklinkname=0"
|
|
||||||
args = append(args, sharedFlags...)
|
args = append(args, sharedFlags...)
|
||||||
} else {
|
} else {
|
||||||
debugFlags[1] = debugFlags[1] + " -checklinkname=0"
|
|
||||||
args = append(args, debugFlags...)
|
args = append(args, debugFlags...)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -157,10 +151,7 @@ func buildApple() {
|
|||||||
"-v",
|
"-v",
|
||||||
"-target", bindTarget,
|
"-target", bindTarget,
|
||||||
"-libname=box",
|
"-libname=box",
|
||||||
"-tags-not-macos=with_low_memory",
|
"-tags-macos=" + strings.Join(memcTags, ","),
|
||||||
}
|
|
||||||
if !withTailscale {
|
|
||||||
args = append(args, "-tags-macos="+strings.Join(memcTags, ","))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if !debugEnabled {
|
if !debugEnabled {
|
||||||
@@ -169,10 +160,7 @@ func buildApple() {
|
|||||||
args = append(args, debugFlags...)
|
args = append(args, debugFlags...)
|
||||||
}
|
}
|
||||||
|
|
||||||
tags := append(sharedTags, darwinTags...)
|
tags := append(sharedTags, iosTags...)
|
||||||
if withTailscale {
|
|
||||||
tags = append(tags, memcTags...)
|
|
||||||
}
|
|
||||||
if debugEnabled {
|
if debugEnabled {
|
||||||
tags = append(tags, debugTags...)
|
tags = append(tags, debugTags...)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,284 +0,0 @@
|
|||||||
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)
|
|
||||||
}
|
|
||||||
@@ -5,7 +5,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/common/convertor/adguard"
|
"github.com/sagernet/sing-box/cmd/sing-box/internal/convertor/adguard"
|
||||||
"github.com/sagernet/sing-box/common/srs"
|
"github.com/sagernet/sing-box/common/srs"
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
@@ -54,7 +54,7 @@ func convertRuleSet(sourcePath string) error {
|
|||||||
var rules []option.HeadlessRule
|
var rules []option.HeadlessRule
|
||||||
switch flagRuleSetConvertType {
|
switch flagRuleSetConvertType {
|
||||||
case "adguard":
|
case "adguard":
|
||||||
rules, err = adguard.ToOptions(reader, log.StdLogger())
|
rules, err = adguard.Convert(reader)
|
||||||
case "":
|
case "":
|
||||||
return E.New("source type is required")
|
return E.New("source type is required")
|
||||||
default:
|
default:
|
||||||
|
|||||||
@@ -6,10 +6,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/common/srs"
|
"github.com/sagernet/sing-box/common/srs"
|
||||||
C "github.com/sagernet/sing-box/constant"
|
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
"github.com/sagernet/sing-box/option"
|
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
|
||||||
"github.com/sagernet/sing/common/json"
|
"github.com/sagernet/sing/common/json"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
@@ -53,11 +50,6 @@ func decompileRuleSet(sourcePath string) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if hasRule(ruleSet.Options.Rules, func(rule option.DefaultHeadlessRule) bool {
|
|
||||||
return len(rule.AdGuardDomain) > 0
|
|
||||||
}) {
|
|
||||||
return E.New("unable to decompile binary AdGuard rules to rule-set.")
|
|
||||||
}
|
|
||||||
var outputPath string
|
var outputPath string
|
||||||
if flagRuleSetDecompileOutput == flagRuleSetDecompileDefaultOutput {
|
if flagRuleSetDecompileOutput == flagRuleSetDecompileDefaultOutput {
|
||||||
if strings.HasSuffix(sourcePath, ".srs") {
|
if strings.HasSuffix(sourcePath, ".srs") {
|
||||||
@@ -83,19 +75,3 @@ func decompileRuleSet(sourcePath string) error {
|
|||||||
outputFile.Close()
|
outputFile.Close()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func hasRule(rules []option.HeadlessRule, cond func(rule option.DefaultHeadlessRule) bool) bool {
|
|
||||||
for _, rule := range rules {
|
|
||||||
switch rule.Type {
|
|
||||||
case C.RuleTypeDefault:
|
|
||||||
if cond(rule.DefaultOptions) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
case C.RuleTypeLogical:
|
|
||||||
if hasRule(rule.LogicalOptions.Rules, cond) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package adguard
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"bytes"
|
|
||||||
"io"
|
"io"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"os"
|
"os"
|
||||||
@@ -10,10 +9,10 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
"github.com/sagernet/sing-box/log"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
"github.com/sagernet/sing/common"
|
"github.com/sagernet/sing/common"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
"github.com/sagernet/sing/common/logger"
|
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -28,7 +27,7 @@ type agdguardRuleLine struct {
|
|||||||
isImportant bool
|
isImportant bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func ToOptions(reader io.Reader, logger logger.Logger) ([]option.HeadlessRule, error) {
|
func Convert(reader io.Reader) ([]option.HeadlessRule, error) {
|
||||||
scanner := bufio.NewScanner(reader)
|
scanner := bufio.NewScanner(reader)
|
||||||
var (
|
var (
|
||||||
ruleLines []agdguardRuleLine
|
ruleLines []agdguardRuleLine
|
||||||
@@ -37,10 +36,7 @@ func ToOptions(reader io.Reader, logger logger.Logger) ([]option.HeadlessRule, e
|
|||||||
parseLine:
|
parseLine:
|
||||||
for scanner.Scan() {
|
for scanner.Scan() {
|
||||||
ruleLine := scanner.Text()
|
ruleLine := scanner.Text()
|
||||||
if ruleLine == "" {
|
if ruleLine == "" || ruleLine[0] == '!' || ruleLine[0] == '#' {
|
||||||
continue
|
|
||||||
}
|
|
||||||
if strings.HasPrefix(ruleLine, "!") || strings.HasPrefix(ruleLine, "#") {
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
originRuleLine := ruleLine
|
originRuleLine := ruleLine
|
||||||
@@ -96,7 +92,7 @@ parseLine:
|
|||||||
}
|
}
|
||||||
if !ignored {
|
if !ignored {
|
||||||
ignoredLines++
|
ignoredLines++
|
||||||
logger.Debug("ignored unsupported rule with modifier: ", paramParts[0], ": ", originRuleLine)
|
log.Debug("ignored unsupported rule with modifier: ", paramParts[0], ": ", ruleLine)
|
||||||
continue parseLine
|
continue parseLine
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -124,35 +120,27 @@ parseLine:
|
|||||||
ruleLine = ruleLine[1 : len(ruleLine)-1]
|
ruleLine = ruleLine[1 : len(ruleLine)-1]
|
||||||
if ignoreIPCIDRRegexp(ruleLine) {
|
if ignoreIPCIDRRegexp(ruleLine) {
|
||||||
ignoredLines++
|
ignoredLines++
|
||||||
logger.Debug("ignored unsupported rule with IPCIDR regexp: ", originRuleLine)
|
log.Debug("ignored unsupported rule with IPCIDR regexp: ", ruleLine)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
isRegexp = true
|
isRegexp = true
|
||||||
} else {
|
} else {
|
||||||
if strings.Contains(ruleLine, "://") {
|
if strings.Contains(ruleLine, "://") {
|
||||||
ruleLine = common.SubstringAfter(ruleLine, "://")
|
ruleLine = common.SubstringAfter(ruleLine, "://")
|
||||||
isSuffix = true
|
|
||||||
}
|
}
|
||||||
if strings.Contains(ruleLine, "/") {
|
if strings.Contains(ruleLine, "/") {
|
||||||
ignoredLines++
|
ignoredLines++
|
||||||
logger.Debug("ignored unsupported rule with path: ", originRuleLine)
|
log.Debug("ignored unsupported rule with path: ", ruleLine)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if strings.Contains(ruleLine, "?") || strings.Contains(ruleLine, "&") {
|
if strings.Contains(ruleLine, "##") {
|
||||||
ignoredLines++
|
ignoredLines++
|
||||||
logger.Debug("ignored unsupported rule with query: ", originRuleLine)
|
log.Debug("ignored unsupported rule with element hiding: ", ruleLine)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if strings.Contains(ruleLine, "[") || strings.Contains(ruleLine, "]") ||
|
if strings.Contains(ruleLine, "#$#") {
|
||||||
strings.Contains(ruleLine, "(") || strings.Contains(ruleLine, ")") ||
|
|
||||||
strings.Contains(ruleLine, "!") || strings.Contains(ruleLine, "#") {
|
|
||||||
ignoredLines++
|
ignoredLines++
|
||||||
logger.Debug("ignored unsupported cosmetic filter: ", originRuleLine)
|
log.Debug("ignored unsupported rule with element hiding: ", ruleLine)
|
||||||
continue
|
|
||||||
}
|
|
||||||
if strings.Contains(ruleLine, "~") {
|
|
||||||
ignoredLines++
|
|
||||||
logger.Debug("ignored unsupported rule modifier: ", originRuleLine)
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
var domainCheck string
|
var domainCheck string
|
||||||
@@ -163,7 +151,7 @@ parseLine:
|
|||||||
}
|
}
|
||||||
if ruleLine == "" {
|
if ruleLine == "" {
|
||||||
ignoredLines++
|
ignoredLines++
|
||||||
logger.Debug("ignored unsupported rule with empty domain", originRuleLine)
|
log.Debug("ignored unsupported rule with empty domain", originRuleLine)
|
||||||
continue
|
continue
|
||||||
} else {
|
} else {
|
||||||
domainCheck = strings.ReplaceAll(domainCheck, "*", "x")
|
domainCheck = strings.ReplaceAll(domainCheck, "*", "x")
|
||||||
@@ -171,13 +159,13 @@ parseLine:
|
|||||||
_, ipErr := parseADGuardIPCIDRLine(ruleLine)
|
_, ipErr := parseADGuardIPCIDRLine(ruleLine)
|
||||||
if ipErr == nil {
|
if ipErr == nil {
|
||||||
ignoredLines++
|
ignoredLines++
|
||||||
logger.Debug("ignored unsupported rule with IPCIDR: ", originRuleLine)
|
log.Debug("ignored unsupported rule with IPCIDR: ", ruleLine)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if M.ParseSocksaddr(domainCheck).Port != 0 {
|
if M.ParseSocksaddr(domainCheck).Port != 0 {
|
||||||
logger.Debug("ignored unsupported rule with port: ", originRuleLine)
|
log.Debug("ignored unsupported rule with port: ", ruleLine)
|
||||||
} else {
|
} else {
|
||||||
logger.Debug("ignored unsupported rule with invalid domain: ", originRuleLine)
|
log.Debug("ignored unsupported rule with invalid domain: ", ruleLine)
|
||||||
}
|
}
|
||||||
ignoredLines++
|
ignoredLines++
|
||||||
continue
|
continue
|
||||||
@@ -295,112 +283,10 @@ parseLine:
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ignoredLines > 0 {
|
log.Info("parsed rules: ", len(ruleLines), "/", len(ruleLines)+ignoredLines)
|
||||||
logger.Info("parsed rules: ", len(ruleLines), "/", len(ruleLines)+ignoredLines)
|
|
||||||
}
|
|
||||||
return []option.HeadlessRule{currentRule}, nil
|
return []option.HeadlessRule{currentRule}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var ErrInvalid = E.New("invalid binary AdGuard rule-set")
|
|
||||||
|
|
||||||
func FromOptions(rules []option.HeadlessRule) ([]byte, error) {
|
|
||||||
if len(rules) != 1 {
|
|
||||||
return nil, ErrInvalid
|
|
||||||
}
|
|
||||||
rule := rules[0]
|
|
||||||
var (
|
|
||||||
importantDomain []string
|
|
||||||
importantDomainRegex []string
|
|
||||||
importantExcludeDomain []string
|
|
||||||
importantExcludeDomainRegex []string
|
|
||||||
domain []string
|
|
||||||
domainRegex []string
|
|
||||||
excludeDomain []string
|
|
||||||
excludeDomainRegex []string
|
|
||||||
)
|
|
||||||
parse:
|
|
||||||
for {
|
|
||||||
switch rule.Type {
|
|
||||||
case C.RuleTypeLogical:
|
|
||||||
if !(len(rule.LogicalOptions.Rules) == 2 && rule.LogicalOptions.Rules[0].Type == C.RuleTypeDefault) {
|
|
||||||
return nil, ErrInvalid
|
|
||||||
}
|
|
||||||
if rule.LogicalOptions.Mode == C.LogicalTypeAnd && rule.LogicalOptions.Rules[0].DefaultOptions.Invert {
|
|
||||||
if len(importantExcludeDomain) == 0 && len(importantExcludeDomainRegex) == 0 {
|
|
||||||
importantExcludeDomain = rule.LogicalOptions.Rules[0].DefaultOptions.AdGuardDomain
|
|
||||||
importantExcludeDomainRegex = rule.LogicalOptions.Rules[0].DefaultOptions.DomainRegex
|
|
||||||
if len(importantExcludeDomain)+len(importantExcludeDomainRegex) == 0 {
|
|
||||||
return nil, ErrInvalid
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
excludeDomain = rule.LogicalOptions.Rules[0].DefaultOptions.AdGuardDomain
|
|
||||||
excludeDomainRegex = rule.LogicalOptions.Rules[0].DefaultOptions.DomainRegex
|
|
||||||
if len(excludeDomain)+len(excludeDomainRegex) == 0 {
|
|
||||||
return nil, ErrInvalid
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if rule.LogicalOptions.Mode == C.LogicalTypeOr && !rule.LogicalOptions.Rules[0].DefaultOptions.Invert {
|
|
||||||
importantDomain = rule.LogicalOptions.Rules[0].DefaultOptions.AdGuardDomain
|
|
||||||
importantDomainRegex = rule.LogicalOptions.Rules[0].DefaultOptions.DomainRegex
|
|
||||||
if len(importantDomain)+len(importantDomainRegex) == 0 {
|
|
||||||
return nil, ErrInvalid
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return nil, ErrInvalid
|
|
||||||
}
|
|
||||||
rule = rule.LogicalOptions.Rules[1]
|
|
||||||
case C.RuleTypeDefault:
|
|
||||||
domain = rule.DefaultOptions.AdGuardDomain
|
|
||||||
domainRegex = rule.DefaultOptions.DomainRegex
|
|
||||||
if len(domain)+len(domainRegex) == 0 {
|
|
||||||
return nil, ErrInvalid
|
|
||||||
}
|
|
||||||
break parse
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var output bytes.Buffer
|
|
||||||
for _, ruleLine := range importantDomain {
|
|
||||||
output.WriteString(ruleLine)
|
|
||||||
output.WriteString("$important\n")
|
|
||||||
}
|
|
||||||
for _, ruleLine := range importantDomainRegex {
|
|
||||||
output.WriteString("/")
|
|
||||||
output.WriteString(ruleLine)
|
|
||||||
output.WriteString("/$important\n")
|
|
||||||
|
|
||||||
}
|
|
||||||
for _, ruleLine := range importantExcludeDomain {
|
|
||||||
output.WriteString("@@")
|
|
||||||
output.WriteString(ruleLine)
|
|
||||||
output.WriteString("$important\n")
|
|
||||||
}
|
|
||||||
for _, ruleLine := range importantExcludeDomainRegex {
|
|
||||||
output.WriteString("@@/")
|
|
||||||
output.WriteString(ruleLine)
|
|
||||||
output.WriteString("/$important\n")
|
|
||||||
}
|
|
||||||
for _, ruleLine := range domain {
|
|
||||||
output.WriteString(ruleLine)
|
|
||||||
output.WriteString("\n")
|
|
||||||
}
|
|
||||||
for _, ruleLine := range domainRegex {
|
|
||||||
output.WriteString("/")
|
|
||||||
output.WriteString(ruleLine)
|
|
||||||
output.WriteString("/\n")
|
|
||||||
}
|
|
||||||
for _, ruleLine := range excludeDomain {
|
|
||||||
output.WriteString("@@")
|
|
||||||
output.WriteString(ruleLine)
|
|
||||||
output.WriteString("\n")
|
|
||||||
}
|
|
||||||
for _, ruleLine := range excludeDomainRegex {
|
|
||||||
output.WriteString("@@/")
|
|
||||||
output.WriteString(ruleLine)
|
|
||||||
output.WriteString("/\n")
|
|
||||||
}
|
|
||||||
return output.Bytes(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func ignoreIPCIDRRegexp(ruleLine string) bool {
|
func ignoreIPCIDRRegexp(ruleLine string) bool {
|
||||||
if strings.HasPrefix(ruleLine, "(http?:\\/\\/)") {
|
if strings.HasPrefix(ruleLine, "(http?:\\/\\/)") {
|
||||||
ruleLine = ruleLine[12:]
|
ruleLine = ruleLine[12:]
|
||||||
@@ -408,9 +294,11 @@ func ignoreIPCIDRRegexp(ruleLine string) bool {
|
|||||||
ruleLine = ruleLine[13:]
|
ruleLine = ruleLine[13:]
|
||||||
} else if strings.HasPrefix(ruleLine, "^") {
|
} else if strings.HasPrefix(ruleLine, "^") {
|
||||||
ruleLine = ruleLine[1:]
|
ruleLine = ruleLine[1:]
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
return common.Error(strconv.ParseUint(common.SubstringBefore(ruleLine, "\\."), 10, 8)) == nil ||
|
_, parseErr := strconv.ParseUint(common.SubstringBefore(ruleLine, "\\."), 10, 8)
|
||||||
common.Error(strconv.ParseUint(common.SubstringBefore(ruleLine, "."), 10, 8)) == nil
|
return parseErr == nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseAdGuardHostLine(ruleLine string) (string, error) {
|
func parseAdGuardHostLine(ruleLine string) (string, error) {
|
||||||
@@ -454,5 +342,5 @@ func parseADGuardIPCIDRLine(ruleLine string) (netip.Prefix, error) {
|
|||||||
for len(ruleParts) < 4 {
|
for len(ruleParts) < 4 {
|
||||||
ruleParts = append(ruleParts, 0)
|
ruleParts = append(ruleParts, 0)
|
||||||
}
|
}
|
||||||
return netip.PrefixFrom(netip.AddrFrom4([4]byte(ruleParts)), bitLen), nil
|
return netip.PrefixFrom(netip.AddrFrom4(*(*[4]byte)(ruleParts)), bitLen), nil
|
||||||
}
|
}
|
||||||
@@ -7,15 +7,13 @@ import (
|
|||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"github.com/sagernet/sing-box/route/rule"
|
"github.com/sagernet/sing-box/route/rule"
|
||||||
"github.com/sagernet/sing/common/logger"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestConverter(t *testing.T) {
|
func TestConverter(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
ruleString := `||sagernet.org^$important
|
rules, err := Convert(strings.NewReader(`
|
||||||
@@|sing-box.sagernet.org^$important
|
|
||||||
||example.org^
|
||example.org^
|
||||||
|example.com^
|
|example.com^
|
||||||
example.net^
|
example.net^
|
||||||
@@ -23,9 +21,10 @@ example.net^
|
|||||||
||example.edu.tw^
|
||example.edu.tw^
|
||||||
|example.gov
|
|example.gov
|
||||||
example.arpa
|
example.arpa
|
||||||
@@|sagernet.example.org^
|
@@|sagernet.example.org|
|
||||||
`
|
||sagernet.org^$important
|
||||||
rules, err := ToOptions(strings.NewReader(ruleString), logger.NOP())
|
@@|sing-box.sagernet.org^$important
|
||||||
|
`))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Len(t, rules, 1)
|
require.Len(t, rules, 1)
|
||||||
rule, err := rule.NewHeadlessRule(context.Background(), rules[0])
|
rule, err := rule.NewHeadlessRule(context.Background(), rules[0])
|
||||||
@@ -76,18 +75,15 @@ example.arpa
|
|||||||
Domain: domain,
|
Domain: domain,
|
||||||
}), domain)
|
}), domain)
|
||||||
}
|
}
|
||||||
ruleFromOptions, err := FromOptions(rules)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, ruleString, string(ruleFromOptions))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestHosts(t *testing.T) {
|
func TestHosts(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
rules, err := ToOptions(strings.NewReader(`
|
rules, err := Convert(strings.NewReader(`
|
||||||
127.0.0.1 localhost
|
127.0.0.1 localhost
|
||||||
::1 localhost #[IPv6]
|
::1 localhost #[IPv6]
|
||||||
0.0.0.0 google.com
|
0.0.0.0 google.com
|
||||||
`), logger.NOP())
|
`))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Len(t, rules, 1)
|
require.Len(t, rules, 1)
|
||||||
rule, err := rule.NewHeadlessRule(context.Background(), rules[0])
|
rule, err := rule.NewHeadlessRule(context.Background(), rules[0])
|
||||||
@@ -114,10 +110,10 @@ func TestHosts(t *testing.T) {
|
|||||||
|
|
||||||
func TestSimpleHosts(t *testing.T) {
|
func TestSimpleHosts(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
rules, err := ToOptions(strings.NewReader(`
|
rules, err := Convert(strings.NewReader(`
|
||||||
example.com
|
example.com
|
||||||
www.example.org
|
www.example.org
|
||||||
`), logger.NOP())
|
`))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Len(t, rules, 1)
|
require.Len(t, rules, 1)
|
||||||
rule, err := rule.NewHeadlessRule(context.Background(), rules[0])
|
rule, err := rule.NewHeadlessRule(context.Background(), rules[0])
|
||||||
@@ -128,10 +128,6 @@ func (c *ReadWaitConn) Upstream() any {
|
|||||||
return c.Conn
|
return c.Conn
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *ReadWaitConn) ReaderReplaceable() bool {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
var tlsRegistry []func(conn net.Conn) (loaded bool, tlsReadRecord func() error, tlsHandlePostHandshakeMessage func() error)
|
var tlsRegistry []func(conn net.Conn) (loaded bool, tlsReadRecord func() error, tlsHandlePostHandshakeMessage func() error)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
|||||||
@@ -6,26 +6,22 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
_ "unsafe"
|
_ "unsafe"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing/common"
|
||||||
|
|
||||||
"github.com/metacubex/utls"
|
"github.com/metacubex/utls"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
tlsRegistry = append(tlsRegistry, func(conn net.Conn) (loaded bool, tlsReadRecord func() error, tlsHandlePostHandshakeMessage func() error) {
|
tlsRegistry = append(tlsRegistry, func(conn net.Conn) (loaded bool, tlsReadRecord func() error, tlsHandlePostHandshakeMessage func() error) {
|
||||||
switch tlsConn := conn.(type) {
|
tlsConn, loaded := common.Cast[*tls.UConn](conn)
|
||||||
case *tls.UConn:
|
if !loaded {
|
||||||
|
return
|
||||||
|
}
|
||||||
return true, func() error {
|
return true, func() error {
|
||||||
return utlsReadRecord(tlsConn.Conn)
|
return utlsReadRecord(tlsConn.Conn)
|
||||||
}, func() error {
|
}, func() error {
|
||||||
return utlsHandlePostHandshakeMessage(tlsConn.Conn)
|
return utlsHandlePostHandshakeMessage(tlsConn.Conn)
|
||||||
}
|
}
|
||||||
case *tls.Conn:
|
|
||||||
return true, func() error {
|
|
||||||
return utlsReadRecord(tlsConn)
|
|
||||||
}, func() error {
|
|
||||||
return utlsHandlePostHandshakeMessage(tlsConn)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
|
||||||
|
|
||||||
"github.com/sagernet/fswatch"
|
"github.com/sagernet/fswatch"
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
@@ -22,7 +21,6 @@ import (
|
|||||||
var _ adapter.CertificateStore = (*Store)(nil)
|
var _ adapter.CertificateStore = (*Store)(nil)
|
||||||
|
|
||||||
type Store struct {
|
type Store struct {
|
||||||
access sync.RWMutex
|
|
||||||
systemPool *x509.CertPool
|
systemPool *x509.CertPool
|
||||||
currentPool *x509.CertPool
|
currentPool *x509.CertPool
|
||||||
certificate string
|
certificate string
|
||||||
@@ -117,14 +115,10 @@ func (s *Store) Close() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *Store) Pool() *x509.CertPool {
|
func (s *Store) Pool() *x509.CertPool {
|
||||||
s.access.RLock()
|
|
||||||
defer s.access.RUnlock()
|
|
||||||
return s.currentPool
|
return s.currentPool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Store) update() error {
|
func (s *Store) update() error {
|
||||||
s.access.Lock()
|
|
||||||
defer s.access.Unlock()
|
|
||||||
var currentPool *x509.CertPool
|
var currentPool *x509.CertPool
|
||||||
if s.systemPool == nil {
|
if s.systemPool == nil {
|
||||||
currentPool = x509.NewCertPool()
|
currentPool = x509.NewCertPool()
|
||||||
|
|||||||
@@ -1,74 +0,0 @@
|
|||||||
package cloudflare
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"net/http"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/tidwall/gjson"
|
|
||||||
)
|
|
||||||
|
|
||||||
type CloudflareApi struct {
|
|
||||||
client http.Client
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewCloudflareApi(opts ...CloudflareApiOption) *CloudflareApi {
|
|
||||||
api := &CloudflareApi{http.Client{Timeout: 30 * time.Second}}
|
|
||||||
for _, opt := range opts {
|
|
||||||
opt(api)
|
|
||||||
}
|
|
||||||
return api
|
|
||||||
}
|
|
||||||
|
|
||||||
func (api *CloudflareApi) CreateProfile(ctx context.Context, publicKey string) (*CloudflareProfile, error) {
|
|
||||||
request, err := http.NewRequest("POST", "https://api.cloudflareclient.com/v0i1909051800/reg", strings.NewReader(
|
|
||||||
fmt.Sprintf(
|
|
||||||
"{\"install_id\":\"\",\"tos\":\"%s\",\"key\":\"%s\",\"fcm_token\":\"\",\"type\":\"ios\",\"locale\":\"en_US\"}",
|
|
||||||
time.Now().Format("2006-01-02T15:04:05.000Z"),
|
|
||||||
publicKey,
|
|
||||||
),
|
|
||||||
))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
response, err := api.client.Do(request.WithContext(ctx))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer response.Body.Close()
|
|
||||||
if response.StatusCode != 200 {
|
|
||||||
return nil, fmt.Errorf("status code is not 200")
|
|
||||||
}
|
|
||||||
content, err := io.ReadAll(response.Body)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
profile := new(CloudflareProfile)
|
|
||||||
return profile, json.NewDecoder(strings.NewReader(gjson.Get(string(content), "result").Raw)).Decode(profile)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (api *CloudflareApi) GetProfile(ctx context.Context, authToken string, id string) (*CloudflareProfile, error) {
|
|
||||||
request, err := http.NewRequest("GET", "https://api.cloudflareclient.com/v0i1909051800/reg/"+id, nil)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
request.Header.Set("Authorization", "Bearer "+authToken)
|
|
||||||
response, err := api.client.Do(request.WithContext(ctx))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer response.Body.Close()
|
|
||||||
if response.StatusCode != 200 {
|
|
||||||
return nil, fmt.Errorf("status code is not 200")
|
|
||||||
}
|
|
||||||
content, err := io.ReadAll(response.Body)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
profile := new(CloudflareProfile)
|
|
||||||
return profile, json.NewDecoder(strings.NewReader(gjson.Get(string(content), "result").Raw)).Decode(profile)
|
|
||||||
}
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
package cloudflare
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"net"
|
|
||||||
"net/http"
|
|
||||||
)
|
|
||||||
|
|
||||||
type CloudflareApiOption func(api *CloudflareApi)
|
|
||||||
|
|
||||||
func WithDialContext(dialContext func(ctx context.Context, network, addr string) (net.Conn, error)) CloudflareApiOption {
|
|
||||||
return func(api *CloudflareApi) {
|
|
||||||
api.client.Transport = &http.Transport{
|
|
||||||
DialContext: dialContext,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,64 +0,0 @@
|
|||||||
package cloudflare
|
|
||||||
|
|
||||||
import "time"
|
|
||||||
|
|
||||||
type CloudflareProfile struct {
|
|
||||||
ID string `json:"id"`
|
|
||||||
Type string `json:"type"`
|
|
||||||
Name string `json:"name"`
|
|
||||||
Key string `json:"key"`
|
|
||||||
Account struct {
|
|
||||||
ID string `json:"id"`
|
|
||||||
AccountType string `json:"account_type"`
|
|
||||||
Created time.Time `json:"created"`
|
|
||||||
Updated time.Time `json:"updated"`
|
|
||||||
PremiumData int `json:"premium_data"`
|
|
||||||
Quota int `json:"quota"`
|
|
||||||
Usage int `json:"usage"`
|
|
||||||
WARPPlus bool `json:"warp_plus"`
|
|
||||||
ReferralCount int `json:"referral_count"`
|
|
||||||
ReferralRenewalCountdown int `json:"referral_renewal_countdown"`
|
|
||||||
Role string `json:"role"`
|
|
||||||
License string `json:"license"`
|
|
||||||
TTL time.Time `json:"ttl"`
|
|
||||||
} `json:"account"`
|
|
||||||
Config struct {
|
|
||||||
ClientID string `json:"client_id"`
|
|
||||||
Interface struct {
|
|
||||||
Addresses struct {
|
|
||||||
V4 string `json:"v4"`
|
|
||||||
V6 string `json:"v6"`
|
|
||||||
} `json:"addresses"`
|
|
||||||
} `json:"interface"`
|
|
||||||
Peers []struct {
|
|
||||||
PublicKey string `json:"public_key"`
|
|
||||||
Endpoint struct {
|
|
||||||
V4 string `json:"v4"`
|
|
||||||
V6 string `json:"v6"`
|
|
||||||
Host string `json:"host"`
|
|
||||||
Ports []int `json:"ports"`
|
|
||||||
} `json:"endpoint"`
|
|
||||||
} `json:"peers"`
|
|
||||||
Services struct {
|
|
||||||
HTTPProxy string `json:"http_proxy"`
|
|
||||||
} `json:"services"`
|
|
||||||
Metrics struct {
|
|
||||||
Ping int `json:"ping"`
|
|
||||||
Report int `json:"report"`
|
|
||||||
} `json:"metrics"`
|
|
||||||
} `json:"config"`
|
|
||||||
Token string `json:"token"`
|
|
||||||
WARPEnabled bool `json:"warp_enabled"`
|
|
||||||
WaitlistEnabled bool `json:"waitlist_enabled"`
|
|
||||||
Created time.Time `json:"created"`
|
|
||||||
Updated time.Time `json:"updated"`
|
|
||||||
Tos time.Time `json:"tos"`
|
|
||||||
Place int `json:"place"`
|
|
||||||
Locale string `json:"locale"`
|
|
||||||
Enabled bool `json:"enabled"`
|
|
||||||
InstallID string `json:"install_id"`
|
|
||||||
FcmToken string `json:"fcm_token"`
|
|
||||||
Policy struct {
|
|
||||||
TunnelProtocol string `json:"tunnel_protocol"`
|
|
||||||
} `json:"policy"`
|
|
||||||
}
|
|
||||||
@@ -15,6 +15,7 @@ import (
|
|||||||
"github.com/sagernet/sing-box/experimental/libbox/platform"
|
"github.com/sagernet/sing-box/experimental/libbox/platform"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
"github.com/sagernet/sing/common"
|
"github.com/sagernet/sing/common"
|
||||||
|
"github.com/sagernet/sing/common/atomic"
|
||||||
"github.com/sagernet/sing/common/control"
|
"github.com/sagernet/sing/common/control"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
@@ -42,7 +43,7 @@ type DefaultDialer struct {
|
|||||||
networkType []C.InterfaceType
|
networkType []C.InterfaceType
|
||||||
fallbackNetworkType []C.InterfaceType
|
fallbackNetworkType []C.InterfaceType
|
||||||
networkFallbackDelay time.Duration
|
networkFallbackDelay time.Duration
|
||||||
networkLastFallback common.TypedValue[time.Time]
|
networkLastFallback atomic.TypedValue[time.Time]
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDialer, error) {
|
func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDialer, error) {
|
||||||
@@ -88,11 +89,12 @@ func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDial
|
|||||||
|
|
||||||
if networkManager != nil {
|
if networkManager != nil {
|
||||||
defaultOptions := networkManager.DefaultOptions()
|
defaultOptions := networkManager.DefaultOptions()
|
||||||
|
if !disableDefaultBind {
|
||||||
if defaultOptions.BindInterface != "" {
|
if defaultOptions.BindInterface != "" {
|
||||||
bindFunc := control.BindToInterface(networkManager.InterfaceFinder(), defaultOptions.BindInterface, -1)
|
bindFunc := control.BindToInterface(networkManager.InterfaceFinder(), defaultOptions.BindInterface, -1)
|
||||||
dialer.Control = control.Append(dialer.Control, bindFunc)
|
dialer.Control = control.Append(dialer.Control, bindFunc)
|
||||||
listener.Control = control.Append(listener.Control, bindFunc)
|
listener.Control = control.Append(listener.Control, bindFunc)
|
||||||
} else if networkManager.AutoDetectInterface() && !disableDefaultBind {
|
} else if networkManager.AutoDetectInterface() {
|
||||||
if platformInterface != nil {
|
if platformInterface != nil {
|
||||||
networkStrategy = (*C.NetworkStrategy)(options.NetworkStrategy)
|
networkStrategy = (*C.NetworkStrategy)(options.NetworkStrategy)
|
||||||
networkType = common.Map(options.NetworkType, option.InterfaceType.Build)
|
networkType = common.Map(options.NetworkType, option.InterfaceType.Build)
|
||||||
@@ -119,16 +121,12 @@ func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDial
|
|||||||
listener.Control = control.Append(listener.Control, bindFunc)
|
listener.Control = control.Append(listener.Control, bindFunc)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
if options.RoutingMark == 0 && defaultOptions.RoutingMark != 0 {
|
if options.RoutingMark == 0 && defaultOptions.RoutingMark != 0 {
|
||||||
dialer.Control = control.Append(dialer.Control, setMarkWrapper(networkManager, defaultOptions.RoutingMark, true))
|
dialer.Control = control.Append(dialer.Control, setMarkWrapper(networkManager, defaultOptions.RoutingMark, true))
|
||||||
listener.Control = control.Append(listener.Control, setMarkWrapper(networkManager, defaultOptions.RoutingMark, true))
|
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 {
|
if options.ReuseAddr {
|
||||||
listener.Control = control.Append(listener.Control, control.ReuseAddr())
|
listener.Control = control.Append(listener.Control, control.ReuseAddr())
|
||||||
}
|
}
|
||||||
@@ -273,7 +271,7 @@ func (d *DefaultDialer) DialParallelInterface(ctx context.Context, network strin
|
|||||||
} else {
|
} else {
|
||||||
dialer = d.udpDialer4
|
dialer = d.udpDialer4
|
||||||
}
|
}
|
||||||
fastFallback := time.Since(d.networkLastFallback.Load()) < C.TCPTimeout
|
fastFallback := time.Now().Sub(d.networkLastFallback.Load()) < C.TCPTimeout
|
||||||
var (
|
var (
|
||||||
conn net.Conn
|
conn net.Conn
|
||||||
isPrimary bool
|
isPrimary bool
|
||||||
|
|||||||
@@ -111,7 +111,7 @@ func NewWithOptions(options Options) (N.Dialer, error) {
|
|||||||
dnsQueryOptions.Transport = dnsTransport.Default()
|
dnsQueryOptions.Transport = dnsTransport.Default()
|
||||||
} else if options.NewDialer {
|
} else if options.NewDialer {
|
||||||
return nil, E.New("missing domain resolver for domain server address")
|
return nil, E.New("missing domain resolver for domain server address")
|
||||||
} else {
|
} else if !options.DirectOutbound {
|
||||||
deprecated.Report(options.Context, deprecated.OptionMissingDomainResolver)
|
deprecated.Report(options.Context, deprecated.OptionMissingDomainResolver)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/sagernet/sing/common"
|
"github.com/sagernet/sing/common"
|
||||||
@@ -24,11 +23,9 @@ type slowOpenConn struct {
|
|||||||
ctx context.Context
|
ctx context.Context
|
||||||
network string
|
network string
|
||||||
destination M.Socksaddr
|
destination M.Socksaddr
|
||||||
conn atomic.Pointer[net.TCPConn]
|
conn net.Conn
|
||||||
create chan struct{}
|
create chan struct{}
|
||||||
done chan struct{}
|
|
||||||
access sync.Mutex
|
access sync.Mutex
|
||||||
closeOnce sync.Once
|
|
||||||
err error
|
err error
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -47,30 +44,26 @@ func DialSlowContext(dialer *tcpDialer, ctx context.Context, network string, des
|
|||||||
network: network,
|
network: network,
|
||||||
destination: destination,
|
destination: destination,
|
||||||
create: make(chan struct{}),
|
create: make(chan struct{}),
|
||||||
done: make(chan struct{}),
|
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *slowOpenConn) Read(b []byte) (n int, err error) {
|
func (c *slowOpenConn) Read(b []byte) (n int, err error) {
|
||||||
conn := c.conn.Load()
|
if c.conn == nil {
|
||||||
if conn != nil {
|
|
||||||
return conn.Read(b)
|
|
||||||
}
|
|
||||||
select {
|
select {
|
||||||
case <-c.create:
|
case <-c.create:
|
||||||
if c.err != nil {
|
if c.err != nil {
|
||||||
return 0, c.err
|
return 0, c.err
|
||||||
}
|
}
|
||||||
return c.conn.Load().Read(b)
|
case <-c.ctx.Done():
|
||||||
case <-c.done:
|
return 0, c.ctx.Err()
|
||||||
return 0, os.ErrClosed
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return c.conn.Read(b)
|
||||||
|
}
|
||||||
|
|
||||||
func (c *slowOpenConn) Write(b []byte) (n int, err error) {
|
func (c *slowOpenConn) Write(b []byte) (n int, err error) {
|
||||||
tcpConn := c.conn.Load()
|
if c.conn != nil {
|
||||||
if tcpConn != nil {
|
return c.conn.Write(b)
|
||||||
return tcpConn.Write(b)
|
|
||||||
}
|
}
|
||||||
c.access.Lock()
|
c.access.Lock()
|
||||||
defer c.access.Unlock()
|
defer c.access.Unlock()
|
||||||
@@ -79,16 +72,14 @@ func (c *slowOpenConn) Write(b []byte) (n int, err error) {
|
|||||||
if c.err != nil {
|
if c.err != nil {
|
||||||
return 0, c.err
|
return 0, c.err
|
||||||
}
|
}
|
||||||
return c.conn.Load().Write(b)
|
return c.conn.Write(b)
|
||||||
case <-c.done:
|
|
||||||
return 0, os.ErrClosed
|
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
conn, err := c.dialer.DialContext(c.ctx, c.network, c.destination.String(), b)
|
conn, err := c.dialer.DialContext(c.ctx, c.network, c.destination.String(), b)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.err = err
|
c.err = err
|
||||||
} else {
|
} else {
|
||||||
c.conn.Store(conn.(*net.TCPConn))
|
c.conn = conn
|
||||||
}
|
}
|
||||||
n = len(b)
|
n = len(b)
|
||||||
close(c.create)
|
close(c.create)
|
||||||
@@ -96,87 +87,74 @@ func (c *slowOpenConn) Write(b []byte) (n int, err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *slowOpenConn) Close() error {
|
func (c *slowOpenConn) Close() error {
|
||||||
c.closeOnce.Do(func() {
|
return common.Close(c.conn)
|
||||||
close(c.done)
|
|
||||||
conn := c.conn.Load()
|
|
||||||
if conn != nil {
|
|
||||||
conn.Close()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *slowOpenConn) LocalAddr() net.Addr {
|
func (c *slowOpenConn) LocalAddr() net.Addr {
|
||||||
conn := c.conn.Load()
|
if c.conn == nil {
|
||||||
if conn == nil {
|
|
||||||
return M.Socksaddr{}
|
return M.Socksaddr{}
|
||||||
}
|
}
|
||||||
return conn.LocalAddr()
|
return c.conn.LocalAddr()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *slowOpenConn) RemoteAddr() net.Addr {
|
func (c *slowOpenConn) RemoteAddr() net.Addr {
|
||||||
conn := c.conn.Load()
|
if c.conn == nil {
|
||||||
if conn == nil {
|
|
||||||
return M.Socksaddr{}
|
return M.Socksaddr{}
|
||||||
}
|
}
|
||||||
return conn.RemoteAddr()
|
return c.conn.RemoteAddr()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *slowOpenConn) SetDeadline(t time.Time) error {
|
func (c *slowOpenConn) SetDeadline(t time.Time) error {
|
||||||
conn := c.conn.Load()
|
if c.conn == nil {
|
||||||
if conn == nil {
|
|
||||||
return os.ErrInvalid
|
return os.ErrInvalid
|
||||||
}
|
}
|
||||||
return conn.SetDeadline(t)
|
return c.conn.SetDeadline(t)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *slowOpenConn) SetReadDeadline(t time.Time) error {
|
func (c *slowOpenConn) SetReadDeadline(t time.Time) error {
|
||||||
conn := c.conn.Load()
|
if c.conn == nil {
|
||||||
if conn == nil {
|
|
||||||
return os.ErrInvalid
|
return os.ErrInvalid
|
||||||
}
|
}
|
||||||
return conn.SetReadDeadline(t)
|
return c.conn.SetReadDeadline(t)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *slowOpenConn) SetWriteDeadline(t time.Time) error {
|
func (c *slowOpenConn) SetWriteDeadline(t time.Time) error {
|
||||||
conn := c.conn.Load()
|
if c.conn == nil {
|
||||||
if conn == nil {
|
|
||||||
return os.ErrInvalid
|
return os.ErrInvalid
|
||||||
}
|
}
|
||||||
return conn.SetWriteDeadline(t)
|
return c.conn.SetWriteDeadline(t)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *slowOpenConn) Upstream() any {
|
func (c *slowOpenConn) Upstream() any {
|
||||||
return common.PtrOrNil(c.conn.Load())
|
return c.conn
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *slowOpenConn) ReaderReplaceable() bool {
|
func (c *slowOpenConn) ReaderReplaceable() bool {
|
||||||
return c.conn.Load() != nil
|
return c.conn != nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *slowOpenConn) WriterReplaceable() bool {
|
func (c *slowOpenConn) WriterReplaceable() bool {
|
||||||
return c.conn.Load() != nil
|
return c.conn != nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *slowOpenConn) LazyHeadroom() bool {
|
func (c *slowOpenConn) LazyHeadroom() bool {
|
||||||
return c.conn.Load() == nil
|
return c.conn == nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *slowOpenConn) NeedHandshake() bool {
|
func (c *slowOpenConn) NeedHandshake() bool {
|
||||||
return c.conn.Load() == nil
|
return c.conn == nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *slowOpenConn) WriteTo(w io.Writer) (n int64, err error) {
|
func (c *slowOpenConn) WriteTo(w io.Writer) (n int64, err error) {
|
||||||
conn := c.conn.Load()
|
if c.conn == nil {
|
||||||
if conn == nil {
|
|
||||||
select {
|
select {
|
||||||
case <-c.create:
|
case <-c.create:
|
||||||
if c.err != nil {
|
if c.err != nil {
|
||||||
return 0, c.err
|
return 0, c.err
|
||||||
}
|
}
|
||||||
case <-c.done:
|
case <-c.ctx.Done():
|
||||||
return 0, c.err
|
return 0, c.ctx.Err()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return bufio.Copy(w, c.conn.Load())
|
return bufio.Copy(w, c.conn)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ package interrupt
|
|||||||
import (
|
import (
|
||||||
"net"
|
"net"
|
||||||
|
|
||||||
N "github.com/sagernet/sing/common/network"
|
|
||||||
"github.com/sagernet/sing/common/x/list"
|
"github.com/sagernet/sing/common/x/list"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -74,32 +73,3 @@ func (c *PacketConn) WriterReplaceable() bool {
|
|||||||
func (c *PacketConn) Upstream() any {
|
func (c *PacketConn) Upstream() any {
|
||||||
return c.PacketConn
|
return c.PacketConn
|
||||||
}
|
}
|
||||||
|
|
||||||
type SingPacketConn struct {
|
|
||||||
N.PacketConn
|
|
||||||
group *Group
|
|
||||||
element *list.Element[*groupConnItem]
|
|
||||||
}
|
|
||||||
|
|
||||||
/*func (c *SingPacketConn) MarkAsInternal() {
|
|
||||||
c.element.Value.internal = true
|
|
||||||
}*/
|
|
||||||
|
|
||||||
func (c *SingPacketConn) Close() error {
|
|
||||||
c.group.access.Lock()
|
|
||||||
defer c.group.access.Unlock()
|
|
||||||
c.group.connections.Remove(c.element)
|
|
||||||
return c.PacketConn.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *SingPacketConn) ReaderReplaceable() bool {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *SingPacketConn) WriterReplaceable() bool {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *SingPacketConn) Upstream() any {
|
|
||||||
return c.PacketConn
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
N "github.com/sagernet/sing/common/network"
|
|
||||||
"github.com/sagernet/sing/common/x/list"
|
"github.com/sagernet/sing/common/x/list"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -37,13 +36,6 @@ func (g *Group) NewPacketConn(conn net.PacketConn, isExternal bool) net.PacketCo
|
|||||||
return &PacketConn{PacketConn: conn, group: g, element: item}
|
return &PacketConn{PacketConn: conn, group: g, element: item}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *Group) NewSingPacketConn(conn N.PacketConn, isExternal bool) N.PacketConn {
|
|
||||||
g.access.Lock()
|
|
||||||
defer g.access.Unlock()
|
|
||||||
item := g.connections.PushBack(&groupConnItem{conn, isExternal})
|
|
||||||
return &SingPacketConn{PacketConn: conn, group: g, element: item}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (g *Group) Interrupt(interruptExternalConnections bool) {
|
func (g *Group) Interrupt(interruptExternalConnections bool) {
|
||||||
g.access.Lock()
|
g.access.Lock()
|
||||||
defer g.access.Unlock()
|
defer g.access.Unlock()
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ package listener
|
|||||||
import (
|
import (
|
||||||
"net"
|
"net"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"strings"
|
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -57,7 +56,7 @@ func (l *Listener) ListenTCP() (net.Listener, error) {
|
|||||||
if l.tproxy {
|
if l.tproxy {
|
||||||
listenConfig.Control = control.Append(listenConfig.Control, func(network, address string, conn syscall.RawConn) error {
|
listenConfig.Control = control.Append(listenConfig.Control, func(network, address string, conn syscall.RawConn) error {
|
||||||
return control.Raw(conn, func(fd uintptr) error {
|
return control.Raw(conn, func(fd uintptr) error {
|
||||||
return redir.TProxy(fd, !strings.HasSuffix(network, "4"), false)
|
return redir.TProxy(fd, M.ParseSocksaddr(address).IsIPv6(), false)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
@@ -42,7 +41,7 @@ func (l *Listener) ListenUDP() (net.PacketConn, error) {
|
|||||||
if l.tproxy {
|
if l.tproxy {
|
||||||
listenConfig.Control = control.Append(listenConfig.Control, func(network, address string, conn syscall.RawConn) error {
|
listenConfig.Control = control.Append(listenConfig.Control, func(network, address string, conn syscall.RawConn) error {
|
||||||
return control.Raw(conn, func(fd uintptr) error {
|
return control.Raw(conn, func(fd uintptr) error {
|
||||||
return redir.TProxy(fd, !strings.HasSuffix(network, "4"), true)
|
return redir.TProxy(fd, M.ParseSocksaddr(address).IsIPv6(), true)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -165,8 +164,9 @@ func (l *Listener) loopUDPOut() {
|
|||||||
if l.shutdown.Load() && E.IsClosed(err) {
|
if l.shutdown.Load() && E.IsClosed(err) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
l.udpConn.Close()
|
||||||
l.logger.Error("udp listener write back: ", destination, ": ", err)
|
l.logger.Error("udp listener write back: ", destination, ": ", err)
|
||||||
continue
|
return
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
case <-l.packetOutboundClosed:
|
case <-l.packetOutboundClosed:
|
||||||
|
|||||||
@@ -76,8 +76,6 @@ func findProcessName(network string, ip netip.Addr, port int) (string, error) {
|
|||||||
// rup8(sizeof(xtcpcb_n))
|
// rup8(sizeof(xtcpcb_n))
|
||||||
itemSize += 208
|
itemSize += 208
|
||||||
}
|
}
|
||||||
|
|
||||||
var fallbackUDPProcess string
|
|
||||||
// skip the first xinpgen(24 bytes) block
|
// skip the first xinpgen(24 bytes) block
|
||||||
for i := 24; i+itemSize <= len(buf); i += itemSize {
|
for i := 24; i+itemSize <= len(buf); i += itemSize {
|
||||||
// offset of xinpcb_n and xsocket_n
|
// offset of xinpcb_n and xsocket_n
|
||||||
@@ -92,36 +90,26 @@ func findProcessName(network string, ip netip.Addr, port int) (string, error) {
|
|||||||
flag := buf[inp+44]
|
flag := buf[inp+44]
|
||||||
|
|
||||||
var srcIP netip.Addr
|
var srcIP netip.Addr
|
||||||
srcIsIPv4 := false
|
|
||||||
switch {
|
switch {
|
||||||
case flag&0x1 > 0 && isIPv4:
|
case flag&0x1 > 0 && isIPv4:
|
||||||
// ipv4
|
// 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:
|
case flag&0x2 > 0 && !isIPv4:
|
||||||
// ipv6
|
// ipv6
|
||||||
srcIP = netip.AddrFrom16([16]byte(buf[inp+64 : inp+80]))
|
srcIP = netip.AddrFrom16(*(*[16]byte)(buf[inp+64 : inp+80]))
|
||||||
default:
|
default:
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if ip == srcIP {
|
if ip != srcIP {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
// xsocket_n.so_last_pid
|
// xsocket_n.so_last_pid
|
||||||
pid := readNativeUint32(buf[so+68 : so+72])
|
pid := readNativeUint32(buf[so+68 : so+72])
|
||||||
return getExecPathFromPID(pid)
|
return getExecPathFromPID(pid)
|
||||||
}
|
}
|
||||||
|
|
||||||
// udp packet connection may be not equal with srcIP
|
|
||||||
if network == N.NetworkUDP && srcIP.IsUnspecified() && isIPv4 == srcIsIPv4 {
|
|
||||||
pid := readNativeUint32(buf[so+68 : so+72])
|
|
||||||
fallbackUDPProcess, _ = getExecPathFromPID(pid)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if network == N.NetworkUDP && len(fallbackUDPProcess) > 0 {
|
|
||||||
return fallbackUDPProcess, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return "", ErrNotFound
|
return "", ErrNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,5 +17,8 @@ var uQUICChrome115 = &ja3.ClientHello{
|
|||||||
}
|
}
|
||||||
|
|
||||||
func maybeUQUIC(fingerprint *ja3.ClientHello) bool {
|
func maybeUQUIC(fingerprint *ja3.ClientHello) bool {
|
||||||
return !uQUICChrome115.Equals(fingerprint, true)
|
if uQUICChrome115.Equals(fingerprint, true) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ func TestSniffUQUICChrome115(t *testing.T) {
|
|||||||
err = sniff.QUICClientHello(context.Background(), &metadata, pkt)
|
err = sniff.QUICClientHello(context.Background(), &metadata, pkt)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, metadata.Protocol, C.ProtocolQUIC)
|
require.Equal(t, metadata.Protocol, C.ProtocolQUIC)
|
||||||
require.Equal(t, metadata.Client, C.ClientChromium)
|
require.Equal(t, metadata.Client, C.ClientQUICGo)
|
||||||
require.Equal(t, metadata.Domain, "www.google.com")
|
require.Equal(t, metadata.Domain, "www.google.com")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -215,15 +215,16 @@ func readDefaultRule(reader varbin.Reader, recover bool) (rule option.DefaultHea
|
|||||||
case ruleItemWIFIBSSID:
|
case ruleItemWIFIBSSID:
|
||||||
rule.WIFIBSSID, err = readRuleItemString(reader)
|
rule.WIFIBSSID, err = readRuleItemString(reader)
|
||||||
case ruleItemAdGuardDomain:
|
case ruleItemAdGuardDomain:
|
||||||
|
if recover {
|
||||||
|
err = E.New("unable to decompile binary AdGuard rules to rule-set")
|
||||||
|
return
|
||||||
|
}
|
||||||
var matcher *domain.AdGuardMatcher
|
var matcher *domain.AdGuardMatcher
|
||||||
matcher, err = domain.ReadAdGuardMatcher(reader)
|
matcher, err = domain.ReadAdGuardMatcher(reader)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
rule.AdGuardDomainMatcher = matcher
|
rule.AdGuardDomainMatcher = matcher
|
||||||
if recover {
|
|
||||||
rule.AdGuardDomain = matcher.Dump()
|
|
||||||
}
|
|
||||||
case ruleItemNetworkType:
|
case ruleItemNetworkType:
|
||||||
rule.NetworkType, err = readRuleItemUint8[option.InterfaceType](reader)
|
rule.NetworkType, err = readRuleItemUint8[option.InterfaceType](reader)
|
||||||
case ruleItemNetworkIsExpensive:
|
case ruleItemNetworkIsExpensive:
|
||||||
|
|||||||
@@ -5,13 +5,13 @@ package tls
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
"github.com/sagernet/sing/common/logger"
|
|
||||||
|
|
||||||
"github.com/caddyserver/certmagic"
|
"github.com/caddyserver/certmagic"
|
||||||
"github.com/libdns/alidns"
|
"github.com/libdns/alidns"
|
||||||
@@ -37,38 +37,7 @@ func (w *acmeWrapper) Close() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type acmeLogWriter struct {
|
func startACME(ctx context.Context, options option.InboundACMEOptions) (*tls.Config, adapter.SimpleLifecycle, error) {
|
||||||
logger logger.Logger
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *acmeLogWriter) Write(p []byte) (n int, err error) {
|
|
||||||
logLine := strings.ReplaceAll(string(p), " ", ": ")
|
|
||||||
switch {
|
|
||||||
case strings.HasPrefix(logLine, "error: "):
|
|
||||||
w.logger.Error(logLine[7:])
|
|
||||||
case strings.HasPrefix(logLine, "warn: "):
|
|
||||||
w.logger.Warn(logLine[6:])
|
|
||||||
case strings.HasPrefix(logLine, "info: "):
|
|
||||||
w.logger.Info(logLine[6:])
|
|
||||||
case strings.HasPrefix(logLine, "debug: "):
|
|
||||||
w.logger.Debug(logLine[7:])
|
|
||||||
default:
|
|
||||||
w.logger.Debug(logLine)
|
|
||||||
}
|
|
||||||
return len(p), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *acmeLogWriter) Sync() error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func encoderConfig() zapcore.EncoderConfig {
|
|
||||||
config := zap.NewProductionEncoderConfig()
|
|
||||||
config.TimeKey = zapcore.OmitKey
|
|
||||||
return config
|
|
||||||
}
|
|
||||||
|
|
||||||
func startACME(ctx context.Context, logger logger.Logger, options option.InboundACMEOptions) (*tls.Config, adapter.SimpleLifecycle, error) {
|
|
||||||
var acmeServer string
|
var acmeServer string
|
||||||
switch options.Provider {
|
switch options.Provider {
|
||||||
case "", "letsencrypt":
|
case "", "letsencrypt":
|
||||||
@@ -89,15 +58,14 @@ func startACME(ctx context.Context, logger logger.Logger, options option.Inbound
|
|||||||
} else {
|
} else {
|
||||||
storage = certmagic.Default.Storage
|
storage = certmagic.Default.Storage
|
||||||
}
|
}
|
||||||
zapLogger := zap.New(zapcore.NewCore(
|
|
||||||
zapcore.NewConsoleEncoder(encoderConfig()),
|
|
||||||
&acmeLogWriter{logger: logger},
|
|
||||||
zap.DebugLevel,
|
|
||||||
))
|
|
||||||
config := &certmagic.Config{
|
config := &certmagic.Config{
|
||||||
DefaultServerName: options.DefaultServerName,
|
DefaultServerName: options.DefaultServerName,
|
||||||
Storage: storage,
|
Storage: storage,
|
||||||
Logger: zapLogger,
|
Logger: zap.New(zapcore.NewCore(
|
||||||
|
zapcore.NewConsoleEncoder(zap.NewProductionEncoderConfig()),
|
||||||
|
os.Stderr,
|
||||||
|
zap.InfoLevel,
|
||||||
|
)),
|
||||||
}
|
}
|
||||||
acmeConfig := certmagic.ACMEIssuer{
|
acmeConfig := certmagic.ACMEIssuer{
|
||||||
CA: acmeServer,
|
CA: acmeServer,
|
||||||
@@ -107,7 +75,7 @@ func startACME(ctx context.Context, logger logger.Logger, options option.Inbound
|
|||||||
DisableTLSALPNChallenge: options.DisableTLSALPNChallenge,
|
DisableTLSALPNChallenge: options.DisableTLSALPNChallenge,
|
||||||
AltHTTPPort: int(options.AlternativeHTTPPort),
|
AltHTTPPort: int(options.AlternativeHTTPPort),
|
||||||
AltTLSALPNPort: int(options.AlternativeTLSPort),
|
AltTLSALPNPort: int(options.AlternativeTLSPort),
|
||||||
Logger: zapLogger,
|
Logger: config.Logger,
|
||||||
}
|
}
|
||||||
if dnsOptions := options.DNS01Challenge; dnsOptions != nil && dnsOptions.Provider != "" {
|
if dnsOptions := options.DNS01Challenge; dnsOptions != nil && dnsOptions.Provider != "" {
|
||||||
var solver certmagic.DNS01Solver
|
var solver certmagic.DNS01Solver
|
||||||
@@ -135,7 +103,6 @@ func startACME(ctx context.Context, logger logger.Logger, options option.Inbound
|
|||||||
GetConfigForCert: func(certificate certmagic.Certificate) (*certmagic.Config, error) {
|
GetConfigForCert: func(certificate certmagic.Certificate) (*certmagic.Config, error) {
|
||||||
return config, nil
|
return config, nil
|
||||||
},
|
},
|
||||||
Logger: zapLogger,
|
|
||||||
})
|
})
|
||||||
config = certmagic.New(cache, *config)
|
config = certmagic.New(cache, *config)
|
||||||
var tlsConfig *tls.Config
|
var tlsConfig *tls.Config
|
||||||
|
|||||||
@@ -9,9 +9,8 @@ import (
|
|||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
"github.com/sagernet/sing/common/logger"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func startACME(ctx context.Context, logger logger.Logger, options option.InboundACMEOptions) (*tls.Config, adapter.SimpleLifecycle, error) {
|
func startACME(ctx context.Context, options option.InboundACMEOptions) (*tls.Config, adapter.SimpleLifecycle, error) {
|
||||||
return nil, nil, E.New(`ACME is not included in this build, rebuild with -tags with_acme`)
|
return nil, nil, E.New(`ACME is not included in this build, rebuild with -tags with_acme`)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ import (
|
|||||||
"golang.org/x/crypto/cryptobyte"
|
"golang.org/x/crypto/cryptobyte"
|
||||||
)
|
)
|
||||||
|
|
||||||
func parseECHClientConfig(ctx context.Context, clientConfig ECHCapableConfig, options option.OutboundTLSOptions) (Config, error) {
|
func parseECHClientConfig(ctx context.Context, options option.OutboundTLSOptions, tlsConfig *tls.Config) (Config, error) {
|
||||||
var echConfig []byte
|
var echConfig []byte
|
||||||
if len(options.ECH.Config) > 0 {
|
if len(options.ECH.Config) > 0 {
|
||||||
echConfig = []byte(strings.Join(options.ECH.Config, "\n"))
|
echConfig = []byte(strings.Join(options.ECH.Config, "\n"))
|
||||||
@@ -45,11 +45,11 @@ func parseECHClientConfig(ctx context.Context, clientConfig ECHCapableConfig, op
|
|||||||
if block == nil || block.Type != "ECH CONFIGS" || len(rest) > 0 {
|
if block == nil || block.Type != "ECH CONFIGS" || len(rest) > 0 {
|
||||||
return nil, E.New("invalid ECH configs pem")
|
return nil, E.New("invalid ECH configs pem")
|
||||||
}
|
}
|
||||||
clientConfig.SetECHConfigList(block.Bytes)
|
tlsConfig.EncryptedClientHelloConfigList = block.Bytes
|
||||||
return clientConfig, nil
|
return &STDClientConfig{tlsConfig}, nil
|
||||||
} else {
|
} else {
|
||||||
return &ECHClientConfig{
|
return &STDECHClientConfig{
|
||||||
ECHCapableConfig: clientConfig,
|
STDClientConfig: STDClientConfig{tlsConfig},
|
||||||
dnsRouter: service.FromContext[adapter.DNSRouter](ctx),
|
dnsRouter: service.FromContext[adapter.DNSRouter](ctx),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
@@ -69,7 +69,11 @@ func parseECHServerConfig(ctx context.Context, options option.InboundTLSOptions,
|
|||||||
} else {
|
} else {
|
||||||
return E.New("missing ECH keys")
|
return E.New("missing ECH keys")
|
||||||
}
|
}
|
||||||
echKeys, err := parseECHKeys(echKey)
|
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)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return E.Cause(err, "parse ECH keys")
|
return E.Cause(err, "parse ECH keys")
|
||||||
}
|
}
|
||||||
@@ -81,40 +85,32 @@ func parseECHServerConfig(ctx context.Context, options option.InboundTLSOptions,
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *STDServerConfig) setECHServerConfig(echKey []byte) error {
|
func reloadECHKeys(echKeyPath string, tlsConfig *tls.Config) error {
|
||||||
echKeys, err := parseECHKeys(echKey)
|
echKey, err := os.ReadFile(echKeyPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return E.Cause(err, "reload ECH keys from ", echKeyPath)
|
||||||
}
|
}
|
||||||
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)
|
block, _ := pem.Decode(echKey)
|
||||||
if block == nil || block.Type != "ECH KEYS" {
|
if block == nil || block.Type != "ECH KEYS" {
|
||||||
return nil, E.New("invalid ECH keys pem")
|
return E.New("invalid ECH keys pem")
|
||||||
}
|
}
|
||||||
echKeys, err := UnmarshalECHKeys(block.Bytes)
|
echKeys, err := UnmarshalECHKeys(block.Bytes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, E.Cause(err, "parse ECH keys")
|
return E.Cause(err, "parse ECH keys")
|
||||||
}
|
}
|
||||||
return echKeys, nil
|
tlsConfig.EncryptedClientHelloKeys = echKeys
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type ECHClientConfig struct {
|
type STDECHClientConfig struct {
|
||||||
ECHCapableConfig
|
STDClientConfig
|
||||||
access sync.Mutex
|
access sync.Mutex
|
||||||
dnsRouter adapter.DNSRouter
|
dnsRouter adapter.DNSRouter
|
||||||
lastTTL time.Duration
|
lastTTL time.Duration
|
||||||
lastUpdate time.Time
|
lastUpdate time.Time
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ECHClientConfig) ClientHandshake(ctx context.Context, conn net.Conn) (aTLS.Conn, error) {
|
func (s *STDECHClientConfig) ClientHandshake(ctx context.Context, conn net.Conn) (aTLS.Conn, error) {
|
||||||
tlsConn, err := s.fetchAndHandshake(ctx, conn)
|
tlsConn, err := s.fetchAndHandshake(ctx, conn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -126,17 +122,17 @@ func (s *ECHClientConfig) ClientHandshake(ctx context.Context, conn net.Conn) (a
|
|||||||
return tlsConn, nil
|
return tlsConn, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ECHClientConfig) fetchAndHandshake(ctx context.Context, conn net.Conn) (aTLS.Conn, error) {
|
func (s *STDECHClientConfig) fetchAndHandshake(ctx context.Context, conn net.Conn) (aTLS.Conn, error) {
|
||||||
s.access.Lock()
|
s.access.Lock()
|
||||||
defer s.access.Unlock()
|
defer s.access.Unlock()
|
||||||
if len(s.ECHConfigList()) == 0 || s.lastTTL == 0 || time.Since(s.lastUpdate) > s.lastTTL {
|
if len(s.config.EncryptedClientHelloConfigList) == 0 || s.lastTTL == 0 || time.Now().Sub(s.lastUpdate) > s.lastTTL {
|
||||||
message := &mDNS.Msg{
|
message := &mDNS.Msg{
|
||||||
MsgHdr: mDNS.MsgHdr{
|
MsgHdr: mDNS.MsgHdr{
|
||||||
RecursionDesired: true,
|
RecursionDesired: true,
|
||||||
},
|
},
|
||||||
Question: []mDNS.Question{
|
Question: []mDNS.Question{
|
||||||
{
|
{
|
||||||
Name: mDNS.Fqdn(s.ServerName()),
|
Name: mDNS.Fqdn(s.config.ServerName),
|
||||||
Qtype: mDNS.TypeHTTPS,
|
Qtype: mDNS.TypeHTTPS,
|
||||||
Qclass: mDNS.ClassINET,
|
Qclass: mDNS.ClassINET,
|
||||||
},
|
},
|
||||||
@@ -161,21 +157,21 @@ func (s *ECHClientConfig) fetchAndHandshake(ctx context.Context, conn net.Conn)
|
|||||||
}
|
}
|
||||||
s.lastTTL = time.Duration(rr.Header().Ttl) * time.Second
|
s.lastTTL = time.Duration(rr.Header().Ttl) * time.Second
|
||||||
s.lastUpdate = time.Now()
|
s.lastUpdate = time.Now()
|
||||||
s.SetECHConfigList(echConfigList)
|
s.config.EncryptedClientHelloConfigList = echConfigList
|
||||||
break match
|
break match
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if len(s.ECHConfigList()) == 0 {
|
if len(s.config.EncryptedClientHelloConfigList) == 0 {
|
||||||
return nil, E.New("no ECH config found in DNS records")
|
return nil, E.New("no ECH config found in DNS records")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return s.Client(conn)
|
return s.Client(conn)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ECHClientConfig) Clone() Config {
|
func (s *STDECHClientConfig) Clone() Config {
|
||||||
return &ECHClientConfig{ECHCapableConfig: s.ECHCapableConfig.Clone().(ECHCapableConfig), dnsRouter: s.dnsRouter, lastUpdate: s.lastUpdate}
|
return &STDECHClientConfig{STDClientConfig: STDClientConfig{s.config.Clone()}, dnsRouter: s.dnsRouter, lastUpdate: s.lastUpdate}
|
||||||
}
|
}
|
||||||
|
|
||||||
func UnmarshalECHKeys(raw []byte) ([]tls.EncryptedClientHelloKey, error) {
|
func UnmarshalECHKeys(raw []byte) ([]tls.EncryptedClientHelloKey, error) {
|
||||||
|
|||||||
155
common/tls/ech_keygen.go
Normal file
155
common/tls/ech_keygen.go
Normal file
@@ -0,0 +1,155 @@
|
|||||||
|
package tls
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/binary"
|
||||||
|
"encoding/pem"
|
||||||
|
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
|
||||||
|
"github.com/cloudflare/circl/hpke"
|
||||||
|
"github.com/cloudflare/circl/kem"
|
||||||
|
)
|
||||||
|
|
||||||
|
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)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var configBuffer bytes.Buffer
|
||||||
|
var totalLen uint16
|
||||||
|
for _, keyPair := range keyPairs {
|
||||||
|
totalLen += uint16(len(keyPair.rawConf))
|
||||||
|
}
|
||||||
|
binary.Write(&configBuffer, binary.BigEndian, totalLen)
|
||||||
|
for _, keyPair := range keyPairs {
|
||||||
|
configBuffer.Write(keyPair.rawConf)
|
||||||
|
}
|
||||||
|
|
||||||
|
var keyBuffer bytes.Buffer
|
||||||
|
for _, keyPair := range keyPairs {
|
||||||
|
keyBuffer.Write(keyPair.rawKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
configPem = string(pem.EncodeToMemory(&pem.Block{Type: "ECH CONFIGS", Bytes: configBuffer.Bytes()}))
|
||||||
|
keyPem = string(pem.EncodeToMemory(&pem.Block{Type: "ECH KEYS", Bytes: keyBuffer.Bytes()}))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
type echKeyConfigPair struct {
|
||||||
|
id uint8
|
||||||
|
rawKey []byte
|
||||||
|
conf myECHKeyConfig
|
||||||
|
rawConf []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
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
|
||||||
|
}
|
||||||
@@ -1,81 +0,0 @@
|
|||||||
package tls
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/ecdh"
|
|
||||||
"crypto/rand"
|
|
||||||
"encoding/pem"
|
|
||||||
|
|
||||||
"golang.org/x/crypto/cryptobyte"
|
|
||||||
)
|
|
||||||
|
|
||||||
type ECHCapableConfig interface {
|
|
||||||
Config
|
|
||||||
ECHConfigList() []byte
|
|
||||||
SetECHConfigList([]byte)
|
|
||||||
}
|
|
||||||
|
|
||||||
func ECHKeygenDefault(publicName string) (configPem string, keyPem string, err error) {
|
|
||||||
echKey, err := ecdh.X25519().GenerateKey(rand.Reader)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
echConfig, err := marshalECHConfig(0, echKey.PublicKey().Bytes(), publicName, 0)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
configBuilder := cryptobyte.NewBuilder(nil)
|
|
||||||
configBuilder.AddUint16LengthPrefixed(func(builder *cryptobyte.Builder) {
|
|
||||||
builder.AddBytes(echConfig)
|
|
||||||
})
|
|
||||||
configBytes, err := configBuilder.Bytes()
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
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: configBytes}))
|
|
||||||
keyPem = string(pem.EncodeToMemory(&pem.Block{Type: "ECH KEYS", Bytes: keyBytes}))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
builder.AddUint8(maxNameLen)
|
|
||||||
builder.AddUint8LengthPrefixed(func(builder *cryptobyte.Builder) {
|
|
||||||
builder.AddBytes([]byte(publicName))
|
|
||||||
})
|
|
||||||
builder.AddUint16(0) // extensions
|
|
||||||
})
|
|
||||||
return builder.Bytes()
|
|
||||||
}
|
|
||||||
@@ -10,7 +10,7 @@ import (
|
|||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
)
|
)
|
||||||
|
|
||||||
func parseECHClientConfig(ctx context.Context, clientConfig ECHCapableConfig, options option.OutboundTLSOptions) (Config, error) {
|
func parseECHClientConfig(ctx context.Context, options option.OutboundTLSOptions, tlsConfig *tls.Config) (Config, error) {
|
||||||
return nil, E.New("ECH requires go1.24, please recompile your binary.")
|
return nil, E.New("ECH requires go1.24, please recompile your binary.")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -18,6 +18,6 @@ func parseECHServerConfig(ctx context.Context, options option.InboundTLSOptions,
|
|||||||
return E.New("ECH requires go1.24, please recompile your binary.")
|
return E.New("ECH requires go1.24, please recompile your binary.")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *STDServerConfig) setECHServerConfig(echKey []byte) error {
|
func reloadECHKeys(echKeyPath string, tlsConfig *tls.Config) error {
|
||||||
panic("unreachable")
|
return E.New("ECH requires go1.24, please recompile your binary.")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ func NewRealityClient(ctx context.Context, serverAddress string, options option.
|
|||||||
if decodedLen > 8 {
|
if decodedLen > 8 {
|
||||||
return nil, E.New("invalid short_id")
|
return nil, E.New("invalid short_id")
|
||||||
}
|
}
|
||||||
return &RealityClientConfig{ctx, uClient.(*UTLSClientConfig), publicKey, shortID}, nil
|
return &RealityClientConfig{ctx, uClient, publicKey, shortID}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *RealityClientConfig) ServerName() string {
|
func (e *RealityClientConfig) ServerName() string {
|
||||||
@@ -307,11 +307,3 @@ func (c *realityClientConnWrapper) Upstream() any {
|
|||||||
func (c *realityClientConnWrapper) CloseWrite() error {
|
func (c *realityClientConnWrapper) CloseWrite() error {
|
||||||
return c.Close()
|
return c.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *realityClientConnWrapper) ReaderReplaceable() bool {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *realityClientConnWrapper) WriterReplaceable() bool {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -206,11 +206,3 @@ func (c *realityConnWrapper) Upstream() any {
|
|||||||
func (c *realityConnWrapper) CloseWrite() error {
|
func (c *realityConnWrapper) CloseWrite() error {
|
||||||
return c.Close()
|
return c.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *realityConnWrapper) ReaderReplaceable() bool {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *realityConnWrapper) WriterReplaceable() bool {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -7,60 +7,43 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"github.com/sagernet/sing-box/common/tlsfragment"
|
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
"github.com/sagernet/sing/common/ntp"
|
"github.com/sagernet/sing/common/ntp"
|
||||||
)
|
)
|
||||||
|
|
||||||
type STDClientConfig struct {
|
type STDClientConfig struct {
|
||||||
ctx context.Context
|
|
||||||
config *tls.Config
|
config *tls.Config
|
||||||
fragment bool
|
|
||||||
fragmentFallbackDelay time.Duration
|
|
||||||
recordFragment bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *STDClientConfig) ServerName() string {
|
func (s *STDClientConfig) ServerName() string {
|
||||||
return c.config.ServerName
|
return s.config.ServerName
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *STDClientConfig) SetServerName(serverName string) {
|
func (s *STDClientConfig) SetServerName(serverName string) {
|
||||||
c.config.ServerName = serverName
|
s.config.ServerName = serverName
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *STDClientConfig) NextProtos() []string {
|
func (s *STDClientConfig) NextProtos() []string {
|
||||||
return c.config.NextProtos
|
return s.config.NextProtos
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *STDClientConfig) SetNextProtos(nextProto []string) {
|
func (s *STDClientConfig) SetNextProtos(nextProto []string) {
|
||||||
c.config.NextProtos = nextProto
|
s.config.NextProtos = nextProto
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *STDClientConfig) Config() (*STDConfig, error) {
|
func (s *STDClientConfig) Config() (*STDConfig, error) {
|
||||||
return c.config, nil
|
return s.config, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *STDClientConfig) Client(conn net.Conn) (Conn, error) {
|
func (s *STDClientConfig) Client(conn net.Conn) (Conn, error) {
|
||||||
if c.recordFragment {
|
return tls.Client(conn, s.config), nil
|
||||||
conn = tf.NewConn(conn, c.ctx, c.fragment, c.recordFragment, c.fragmentFallbackDelay)
|
|
||||||
}
|
|
||||||
return tls.Client(conn, c.config), nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *STDClientConfig) Clone() Config {
|
func (s *STDClientConfig) Clone() Config {
|
||||||
return &STDClientConfig{c.ctx, c.config.Clone(), c.fragment, c.fragmentFallbackDelay, c.recordFragment}
|
return &STDClientConfig{s.config.Clone()}
|
||||||
}
|
|
||||||
|
|
||||||
func (c *STDClientConfig) ECHConfigList() []byte {
|
|
||||||
return c.config.EncryptedClientHelloConfigList
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *STDClientConfig) SetECHConfigList(EncryptedClientHelloConfigList []byte) {
|
|
||||||
c.config.EncryptedClientHelloConfigList = EncryptedClientHelloConfigList
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewSTDClient(ctx context.Context, serverAddress string, options option.OutboundTLSOptions) (Config, error) {
|
func NewSTDClient(ctx context.Context, serverAddress string, options option.OutboundTLSOptions) (Config, error) {
|
||||||
@@ -77,7 +60,9 @@ func NewSTDClient(ctx context.Context, serverAddress string, options option.Outb
|
|||||||
var tlsConfig tls.Config
|
var tlsConfig tls.Config
|
||||||
tlsConfig.Time = ntp.TimeFuncFromContext(ctx)
|
tlsConfig.Time = ntp.TimeFuncFromContext(ctx)
|
||||||
tlsConfig.RootCAs = adapter.RootPoolFromContext(ctx)
|
tlsConfig.RootCAs = adapter.RootPoolFromContext(ctx)
|
||||||
if !options.DisableSNI {
|
if options.DisableSNI {
|
||||||
|
tlsConfig.ServerName = "127.0.0.1"
|
||||||
|
} else {
|
||||||
tlsConfig.ServerName = serverName
|
tlsConfig.ServerName = serverName
|
||||||
}
|
}
|
||||||
if options.Insecure {
|
if options.Insecure {
|
||||||
@@ -86,16 +71,12 @@ func NewSTDClient(ctx context.Context, serverAddress string, options option.Outb
|
|||||||
tlsConfig.InsecureSkipVerify = true
|
tlsConfig.InsecureSkipVerify = true
|
||||||
tlsConfig.VerifyConnection = func(state tls.ConnectionState) error {
|
tlsConfig.VerifyConnection = func(state tls.ConnectionState) error {
|
||||||
verifyOptions := x509.VerifyOptions{
|
verifyOptions := x509.VerifyOptions{
|
||||||
Roots: tlsConfig.RootCAs,
|
|
||||||
DNSName: serverName,
|
DNSName: serverName,
|
||||||
Intermediates: x509.NewCertPool(),
|
Intermediates: x509.NewCertPool(),
|
||||||
}
|
}
|
||||||
for _, cert := range state.PeerCertificates[1:] {
|
for _, cert := range state.PeerCertificates[1:] {
|
||||||
verifyOptions.Intermediates.AddCert(cert)
|
verifyOptions.Intermediates.AddCert(cert)
|
||||||
}
|
}
|
||||||
if tlsConfig.Time != nil {
|
|
||||||
verifyOptions.CurrentTime = tlsConfig.Time()
|
|
||||||
}
|
|
||||||
_, err := state.PeerCertificates[0].Verify(verifyOptions)
|
_, err := state.PeerCertificates[0].Verify(verifyOptions)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -146,10 +127,8 @@ func NewSTDClient(ctx context.Context, serverAddress string, options option.Outb
|
|||||||
}
|
}
|
||||||
tlsConfig.RootCAs = certPool
|
tlsConfig.RootCAs = certPool
|
||||||
}
|
}
|
||||||
stdConfig := &STDClientConfig{ctx, &tlsConfig, options.Fragment, time.Duration(options.FragmentFallbackDelay), options.RecordFragment}
|
|
||||||
if options.ECH != nil && options.ECH.Enabled {
|
if options.ECH != nil && options.ECH.Enabled {
|
||||||
return parseECHClientConfig(ctx, stdConfig, options)
|
return parseECHClientConfig(ctx, options, &tlsConfig)
|
||||||
} else {
|
|
||||||
return stdConfig, nil
|
|
||||||
}
|
}
|
||||||
|
return &STDClientConfig{&tlsConfig}, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/sagernet/fswatch"
|
"github.com/sagernet/fswatch"
|
||||||
@@ -21,7 +20,6 @@ import (
|
|||||||
var errInsecureUnused = E.New("tls: insecure unused")
|
var errInsecureUnused = E.New("tls: insecure unused")
|
||||||
|
|
||||||
type STDServerConfig struct {
|
type STDServerConfig struct {
|
||||||
access sync.RWMutex
|
|
||||||
config *tls.Config
|
config *tls.Config
|
||||||
logger log.Logger
|
logger log.Logger
|
||||||
acmeService adapter.SimpleLifecycle
|
acmeService adapter.SimpleLifecycle
|
||||||
@@ -34,22 +32,14 @@ type STDServerConfig struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *STDServerConfig) ServerName() string {
|
func (c *STDServerConfig) ServerName() string {
|
||||||
c.access.RLock()
|
|
||||||
defer c.access.RUnlock()
|
|
||||||
return c.config.ServerName
|
return c.config.ServerName
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *STDServerConfig) SetServerName(serverName string) {
|
func (c *STDServerConfig) SetServerName(serverName string) {
|
||||||
c.access.Lock()
|
c.config.ServerName = serverName
|
||||||
defer c.access.Unlock()
|
|
||||||
config := c.config.Clone()
|
|
||||||
config.ServerName = serverName
|
|
||||||
c.config = config
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *STDServerConfig) NextProtos() []string {
|
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 {
|
if c.acmeService != nil && len(c.config.NextProtos) > 1 && c.config.NextProtos[0] == ACMETLS1Protocol {
|
||||||
return c.config.NextProtos[1:]
|
return c.config.NextProtos[1:]
|
||||||
} else {
|
} else {
|
||||||
@@ -58,15 +48,11 @@ func (c *STDServerConfig) NextProtos() []string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *STDServerConfig) SetNextProtos(nextProto []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 {
|
if c.acmeService != nil && len(c.config.NextProtos) > 1 && c.config.NextProtos[0] == ACMETLS1Protocol {
|
||||||
config.NextProtos = append(c.config.NextProtos[:1], nextProto...)
|
c.config.NextProtos = append(c.config.NextProtos[:1], nextProto...)
|
||||||
} else {
|
} else {
|
||||||
config.NextProtos = nextProto
|
c.config.NextProtos = nextProto
|
||||||
}
|
}
|
||||||
c.config = config
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *STDServerConfig) Config() (*STDConfig, error) {
|
func (c *STDServerConfig) Config() (*STDConfig, error) {
|
||||||
@@ -91,6 +77,9 @@ func (c *STDServerConfig) Start() error {
|
|||||||
if c.acmeService != nil {
|
if c.acmeService != nil {
|
||||||
return c.acmeService.Start()
|
return c.acmeService.Start()
|
||||||
} else {
|
} else {
|
||||||
|
if c.certificatePath == "" && c.keyPath == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
err := c.startWatcher()
|
err := c.startWatcher()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.logger.Warn("create fsnotify watcher: ", err)
|
c.logger.Warn("create fsnotify watcher: ", err)
|
||||||
@@ -110,9 +99,6 @@ func (c *STDServerConfig) startWatcher() error {
|
|||||||
if c.echKeyPath != "" {
|
if c.echKeyPath != "" {
|
||||||
watchPath = append(watchPath, c.echKeyPath)
|
watchPath = append(watchPath, c.echKeyPath)
|
||||||
}
|
}
|
||||||
if len(watchPath) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
watcher, err := fswatch.NewWatcher(fswatch.Options{
|
watcher, err := fswatch.NewWatcher(fswatch.Options{
|
||||||
Path: watchPath,
|
Path: watchPath,
|
||||||
Callback: func(path string) {
|
Callback: func(path string) {
|
||||||
@@ -152,18 +138,10 @@ func (c *STDServerConfig) certificateUpdated(path string) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return E.Cause(err, "reload key pair")
|
return E.Cause(err, "reload key pair")
|
||||||
}
|
}
|
||||||
c.access.Lock()
|
c.config.Certificates = []tls.Certificate{keyPair}
|
||||||
config := c.config.Clone()
|
|
||||||
config.Certificates = []tls.Certificate{keyPair}
|
|
||||||
c.config = config
|
|
||||||
c.access.Unlock()
|
|
||||||
c.logger.Info("reloaded TLS certificate")
|
c.logger.Info("reloaded TLS certificate")
|
||||||
} else if path == c.echKeyPath {
|
} else if path == c.echKeyPath {
|
||||||
echKey, err := os.ReadFile(c.echKeyPath)
|
err := reloadECHKeys(c.echKeyPath, c.config)
|
||||||
if err != nil {
|
|
||||||
return E.Cause(err, "reload ECH keys from ", c.echKeyPath)
|
|
||||||
}
|
|
||||||
err = c.setECHServerConfig(echKey)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -191,7 +169,7 @@ func NewSTDServer(ctx context.Context, logger log.Logger, options option.Inbound
|
|||||||
var err error
|
var err error
|
||||||
if options.ACME != nil && len(options.ACME.Domain) > 0 {
|
if options.ACME != nil && len(options.ACME.Domain) > 0 {
|
||||||
//nolint:staticcheck
|
//nolint:staticcheck
|
||||||
tlsConfig, acmeService, err = startACME(ctx, logger, common.PtrValueOrDefault(options.ACME))
|
tlsConfig, acmeService, err = startACME(ctx, common.PtrValueOrDefault(options.ACME))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -284,7 +262,7 @@ func NewSTDServer(ctx context.Context, logger log.Logger, options option.Inbound
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
serverConfig := &STDServerConfig{
|
return &STDServerConfig{
|
||||||
config: tlsConfig,
|
config: tlsConfig,
|
||||||
logger: logger,
|
logger: logger,
|
||||||
acmeService: acmeService,
|
acmeService: acmeService,
|
||||||
@@ -293,11 +271,5 @@ func NewSTDServer(ctx context.Context, logger log.Logger, options option.Inbound
|
|||||||
certificatePath: options.CertificatePath,
|
certificatePath: options.CertificatePath,
|
||||||
keyPath: options.KeyPath,
|
keyPath: options.KeyPath,
|
||||||
echKeyPath: echKeyPath,
|
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,13 +11,10 @@ type TimeServiceWrapper struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (w *TimeServiceWrapper) TimeFunc() func() time.Time {
|
func (w *TimeServiceWrapper) TimeFunc() func() time.Time {
|
||||||
return func() time.Time {
|
if w.TimeService == nil {
|
||||||
if w.TimeService != nil {
|
return nil
|
||||||
return w.TimeService.TimeFunc()()
|
|
||||||
} else {
|
|
||||||
return time.Now()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
return w.TimeService.TimeFunc()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *TimeServiceWrapper) Upstream() any {
|
func (w *TimeServiceWrapper) Upstream() any {
|
||||||
|
|||||||
@@ -8,12 +8,11 @@ import (
|
|||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"net"
|
"net"
|
||||||
|
"net/netip"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"github.com/sagernet/sing-box/common/tlsfragment"
|
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
"github.com/sagernet/sing/common/ntp"
|
"github.com/sagernet/sing/common/ntp"
|
||||||
@@ -23,62 +22,48 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type UTLSClientConfig struct {
|
type UTLSClientConfig struct {
|
||||||
ctx context.Context
|
|
||||||
config *utls.Config
|
config *utls.Config
|
||||||
id utls.ClientHelloID
|
id utls.ClientHelloID
|
||||||
fragment bool
|
|
||||||
fragmentFallbackDelay time.Duration
|
|
||||||
recordFragment bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *UTLSClientConfig) ServerName() string {
|
func (e *UTLSClientConfig) ServerName() string {
|
||||||
return c.config.ServerName
|
return e.config.ServerName
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *UTLSClientConfig) SetServerName(serverName string) {
|
func (e *UTLSClientConfig) SetServerName(serverName string) {
|
||||||
c.config.ServerName = serverName
|
e.config.ServerName = serverName
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *UTLSClientConfig) NextProtos() []string {
|
func (e *UTLSClientConfig) NextProtos() []string {
|
||||||
return c.config.NextProtos
|
return e.config.NextProtos
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *UTLSClientConfig) SetNextProtos(nextProto []string) {
|
func (e *UTLSClientConfig) SetNextProtos(nextProto []string) {
|
||||||
if len(nextProto) == 1 && nextProto[0] == http2.NextProtoTLS {
|
if len(nextProto) == 1 && nextProto[0] == http2.NextProtoTLS {
|
||||||
nextProto = append(nextProto, "http/1.1")
|
nextProto = append(nextProto, "http/1.1")
|
||||||
}
|
}
|
||||||
c.config.NextProtos = nextProto
|
e.config.NextProtos = nextProto
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *UTLSClientConfig) Config() (*STDConfig, error) {
|
func (e *UTLSClientConfig) Config() (*STDConfig, error) {
|
||||||
return nil, E.New("unsupported usage for uTLS")
|
return nil, E.New("unsupported usage for uTLS")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *UTLSClientConfig) Client(conn net.Conn) (Conn, error) {
|
func (e *UTLSClientConfig) Client(conn net.Conn) (Conn, error) {
|
||||||
if c.recordFragment {
|
return &utlsALPNWrapper{utlsConnWrapper{utls.UClient(conn, e.config.Clone(), e.id)}, e.config.NextProtos}, nil
|
||||||
conn = tf.NewConn(conn, c.ctx, c.fragment, c.recordFragment, c.fragmentFallbackDelay)
|
|
||||||
}
|
|
||||||
return &utlsALPNWrapper{utlsConnWrapper{utls.UClient(conn, c.config.Clone(), c.id)}, c.config.NextProtos}, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *UTLSClientConfig) SetSessionIDGenerator(generator func(clientHello []byte, sessionID []byte) error) {
|
func (e *UTLSClientConfig) SetSessionIDGenerator(generator func(clientHello []byte, sessionID []byte) error) {
|
||||||
c.config.SessionIDGenerator = generator
|
e.config.SessionIDGenerator = generator
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *UTLSClientConfig) Clone() Config {
|
func (e *UTLSClientConfig) Clone() Config {
|
||||||
return &UTLSClientConfig{
|
return &UTLSClientConfig{
|
||||||
c.ctx, c.config.Clone(), c.id, c.fragment, c.fragmentFallbackDelay, c.recordFragment,
|
config: e.config.Clone(),
|
||||||
|
id: e.id,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *UTLSClientConfig) ECHConfigList() []byte {
|
|
||||||
return c.config.EncryptedClientHelloConfigList
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *UTLSClientConfig) SetECHConfigList(EncryptedClientHelloConfigList []byte) {
|
|
||||||
c.config.EncryptedClientHelloConfigList = EncryptedClientHelloConfigList
|
|
||||||
}
|
|
||||||
|
|
||||||
type utlsConnWrapper struct {
|
type utlsConnWrapper struct {
|
||||||
*utls.UConn
|
*utls.UConn
|
||||||
}
|
}
|
||||||
@@ -106,14 +91,6 @@ func (c *utlsConnWrapper) Upstream() any {
|
|||||||
return c.UConn
|
return c.UConn
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *utlsConnWrapper) ReaderReplaceable() bool {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *utlsConnWrapper) WriterReplaceable() bool {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
type utlsALPNWrapper struct {
|
type utlsALPNWrapper struct {
|
||||||
utlsConnWrapper
|
utlsConnWrapper
|
||||||
nextProtocols []string
|
nextProtocols []string
|
||||||
@@ -139,13 +116,15 @@ func (c *utlsALPNWrapper) HandshakeContext(ctx context.Context) error {
|
|||||||
return c.UConn.HandshakeContext(ctx)
|
return c.UConn.HandshakeContext(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewUTLSClient(ctx context.Context, serverAddress string, options option.OutboundTLSOptions) (Config, error) {
|
func NewUTLSClient(ctx context.Context, serverAddress string, options option.OutboundTLSOptions) (*UTLSClientConfig, error) {
|
||||||
var serverName string
|
var serverName string
|
||||||
if options.ServerName != "" {
|
if options.ServerName != "" {
|
||||||
serverName = options.ServerName
|
serverName = options.ServerName
|
||||||
} else if serverAddress != "" {
|
} else if serverAddress != "" {
|
||||||
|
if _, err := netip.ParseAddr(serverName); err != nil {
|
||||||
serverName = serverAddress
|
serverName = serverAddress
|
||||||
}
|
}
|
||||||
|
}
|
||||||
if serverName == "" && !options.Insecure {
|
if serverName == "" && !options.Insecure {
|
||||||
return nil, E.New("missing server_name or insecure=true")
|
return nil, E.New("missing server_name or insecure=true")
|
||||||
}
|
}
|
||||||
@@ -153,16 +132,15 @@ func NewUTLSClient(ctx context.Context, serverAddress string, options option.Out
|
|||||||
var tlsConfig utls.Config
|
var tlsConfig utls.Config
|
||||||
tlsConfig.Time = ntp.TimeFuncFromContext(ctx)
|
tlsConfig.Time = ntp.TimeFuncFromContext(ctx)
|
||||||
tlsConfig.RootCAs = adapter.RootPoolFromContext(ctx)
|
tlsConfig.RootCAs = adapter.RootPoolFromContext(ctx)
|
||||||
if !options.DisableSNI {
|
if options.DisableSNI {
|
||||||
|
tlsConfig.ServerName = "127.0.0.1"
|
||||||
|
} else {
|
||||||
tlsConfig.ServerName = serverName
|
tlsConfig.ServerName = serverName
|
||||||
}
|
}
|
||||||
if options.Insecure {
|
if options.Insecure {
|
||||||
tlsConfig.InsecureSkipVerify = options.Insecure
|
tlsConfig.InsecureSkipVerify = options.Insecure
|
||||||
} else if options.DisableSNI {
|
} else if options.DisableSNI {
|
||||||
if options.Reality != nil && options.Reality.Enabled {
|
return nil, E.New("disable_sni is unsupported in uTLS")
|
||||||
return nil, E.New("disable_sni is unsupported in reality")
|
|
||||||
}
|
|
||||||
tlsConfig.InsecureServerNameToVerify = serverName
|
|
||||||
}
|
}
|
||||||
if len(options.ALPN) > 0 {
|
if len(options.ALPN) > 0 {
|
||||||
tlsConfig.NextProtos = options.ALPN
|
tlsConfig.NextProtos = options.ALPN
|
||||||
@@ -214,15 +192,7 @@ func NewUTLSClient(ctx context.Context, serverAddress string, options option.Out
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
uConfig := &UTLSClientConfig{ctx, &tlsConfig, id, options.Fragment, time.Duration(options.FragmentFallbackDelay), options.RecordFragment}
|
return &UTLSClientConfig{&tlsConfig, id}, nil
|
||||||
if options.ECH != nil && options.ECH.Enabled {
|
|
||||||
if options.Reality != nil && options.Reality.Enabled {
|
|
||||||
return nil, E.New("Reality is conflict with ECH")
|
|
||||||
}
|
|
||||||
return parseECHClientConfig(ctx, uConfig, options)
|
|
||||||
} else {
|
|
||||||
return uConfig, nil
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -250,7 +220,7 @@ func init() {
|
|||||||
|
|
||||||
func uTLSClientHelloID(name string) (utls.ClientHelloID, error) {
|
func uTLSClientHelloID(name string) (utls.ClientHelloID, error) {
|
||||||
switch name {
|
switch name {
|
||||||
case "chrome_psk", "chrome_psk_shuffle", "chrome_padding_psk_shuffle", "chrome_pq", "chrome_pq_psk":
|
case "chrome_psk", "chrome_psk_shuffle", "chrome_padding_psk_shuffle", "chrome_pq":
|
||||||
fallthrough
|
fallthrough
|
||||||
case "chrome", "":
|
case "chrome", "":
|
||||||
return utls.HelloChrome_Auto, nil
|
return utls.HelloChrome_Auto, nil
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
C "github.com/sagernet/sing-box/constant"
|
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
|
|
||||||
"golang.org/x/net/publicsuffix"
|
"golang.org/x/net/publicsuffix"
|
||||||
@@ -20,21 +19,16 @@ type Conn struct {
|
|||||||
tcpConn *net.TCPConn
|
tcpConn *net.TCPConn
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
firstPacketWritten bool
|
firstPacketWritten bool
|
||||||
splitPacket bool
|
|
||||||
splitRecord bool
|
splitRecord bool
|
||||||
fallbackDelay time.Duration
|
fallbackDelay time.Duration
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewConn(conn net.Conn, ctx context.Context, splitPacket bool, splitRecord bool, fallbackDelay time.Duration) *Conn {
|
func NewConn(conn net.Conn, ctx context.Context, splitRecord bool, fallbackDelay time.Duration) *Conn {
|
||||||
if fallbackDelay == 0 {
|
|
||||||
fallbackDelay = C.TLSFragmentFallbackDelay
|
|
||||||
}
|
|
||||||
tcpConn, _ := N.UnwrapReader(conn).(*net.TCPConn)
|
tcpConn, _ := N.UnwrapReader(conn).(*net.TCPConn)
|
||||||
return &Conn{
|
return &Conn{
|
||||||
Conn: conn,
|
Conn: conn,
|
||||||
tcpConn: tcpConn,
|
tcpConn: tcpConn,
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
splitPacket: splitPacket,
|
|
||||||
splitRecord: splitRecord,
|
splitRecord: splitRecord,
|
||||||
fallbackDelay: fallbackDelay,
|
fallbackDelay: fallbackDelay,
|
||||||
}
|
}
|
||||||
@@ -45,9 +39,9 @@ func (c *Conn) Write(b []byte) (n int, err error) {
|
|||||||
defer func() {
|
defer func() {
|
||||||
c.firstPacketWritten = true
|
c.firstPacketWritten = true
|
||||||
}()
|
}()
|
||||||
serverName := IndexTLSServerName(b)
|
serverName := indexTLSServerName(b)
|
||||||
if serverName != nil {
|
if serverName != nil {
|
||||||
if c.splitPacket {
|
if !c.splitRecord {
|
||||||
if c.tcpConn != nil {
|
if c.tcpConn != nil {
|
||||||
err = c.tcpConn.SetNoDelay(true)
|
err = c.tcpConn.SetNoDelay(true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -87,19 +81,11 @@ func (c *Conn) Write(b []byte) (n int, err error) {
|
|||||||
payload = b[splitIndexes[i-1]:splitIndexes[i]]
|
payload = b[splitIndexes[i-1]:splitIndexes[i]]
|
||||||
}
|
}
|
||||||
if c.splitRecord {
|
if c.splitRecord {
|
||||||
if c.splitPacket {
|
|
||||||
buffer.Reset()
|
|
||||||
}
|
|
||||||
payloadLen := uint16(len(payload))
|
payloadLen := uint16(len(payload))
|
||||||
buffer.Write(b[:3])
|
buffer.Write(b[:3])
|
||||||
binary.Write(&buffer, binary.BigEndian, payloadLen)
|
binary.Write(&buffer, binary.BigEndian, payloadLen)
|
||||||
buffer.Write(payload)
|
buffer.Write(payload)
|
||||||
if c.splitPacket {
|
} else if c.tcpConn != nil && i != len(splitIndexes) {
|
||||||
payload = buffer.Bytes()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if c.splitPacket {
|
|
||||||
if c.tcpConn != nil && i != len(splitIndexes) {
|
|
||||||
err = writeAndWaitAck(c.ctx, c.tcpConn, payload, c.fallbackDelay)
|
err = writeAndWaitAck(c.ctx, c.tcpConn, payload, c.fallbackDelay)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
@@ -109,24 +95,21 @@ func (c *Conn) Write(b []byte) (n int, err error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if i != len(splitIndexes) {
|
|
||||||
time.Sleep(c.fallbackDelay)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
if c.splitRecord {
|
||||||
}
|
|
||||||
if c.splitRecord && !c.splitPacket {
|
|
||||||
_, err = c.Conn.Write(buffer.Bytes())
|
_, err = c.Conn.Write(buffer.Bytes())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
if c.tcpConn != nil {
|
if c.tcpConn != nil {
|
||||||
err = c.tcpConn.SetNoDelay(false)
|
err = c.tcpConn.SetNoDelay(false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return len(b), nil
|
return len(b), nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ func TestTLSFragment(t *testing.T) {
|
|||||||
t.Parallel()
|
t.Parallel()
|
||||||
tcpConn, err := net.Dial("tcp", "1.1.1.1:443")
|
tcpConn, err := net.Dial("tcp", "1.1.1.1:443")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
tlsConn := tls.Client(tf.NewConn(tcpConn, context.Background(), true, false, 0), &tls.Config{
|
tlsConn := tls.Client(tf.NewConn(tcpConn, context.Background(), false, 0), &tls.Config{
|
||||||
ServerName: "www.cloudflare.com",
|
ServerName: "www.cloudflare.com",
|
||||||
})
|
})
|
||||||
require.NoError(t, tlsConn.Handshake())
|
require.NoError(t, tlsConn.Handshake())
|
||||||
@@ -25,17 +25,7 @@ func TestTLSRecordFragment(t *testing.T) {
|
|||||||
t.Parallel()
|
t.Parallel()
|
||||||
tcpConn, err := net.Dial("tcp", "1.1.1.1:443")
|
tcpConn, err := net.Dial("tcp", "1.1.1.1:443")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
tlsConn := tls.Client(tf.NewConn(tcpConn, context.Background(), false, true, 0), &tls.Config{
|
tlsConn := tls.Client(tf.NewConn(tcpConn, context.Background(), true, 0), &tls.Config{
|
||||||
ServerName: "www.cloudflare.com",
|
|
||||||
})
|
|
||||||
require.NoError(t, tlsConn.Handshake())
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestTLS2Fragment(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
tcpConn, err := net.Dial("tcp", "1.1.1.1:443")
|
|
||||||
require.NoError(t, err)
|
|
||||||
tlsConn := tls.Client(tf.NewConn(tcpConn, context.Background(), true, true, 0), &tls.Config{
|
|
||||||
ServerName: "www.cloudflare.com",
|
ServerName: "www.cloudflare.com",
|
||||||
})
|
})
|
||||||
require.NoError(t, tlsConn.Handshake())
|
require.NoError(t, tlsConn.Handshake())
|
||||||
|
|||||||
@@ -22,13 +22,13 @@ const (
|
|||||||
tls13 uint16 = 0x0304
|
tls13 uint16 = 0x0304
|
||||||
)
|
)
|
||||||
|
|
||||||
type MyServerName struct {
|
type myServerName struct {
|
||||||
Index int
|
Index int
|
||||||
Length int
|
Length int
|
||||||
ServerName string
|
ServerName string
|
||||||
}
|
}
|
||||||
|
|
||||||
func IndexTLSServerName(payload []byte) *MyServerName {
|
func indexTLSServerName(payload []byte) *myServerName {
|
||||||
if len(payload) < recordLayerHeaderLen || payload[0] != contentType {
|
if len(payload) < recordLayerHeaderLen || payload[0] != contentType {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -36,48 +36,47 @@ func IndexTLSServerName(payload []byte) *MyServerName {
|
|||||||
if len(payload) < recordLayerHeaderLen+int(segmentLen) {
|
if len(payload) < recordLayerHeaderLen+int(segmentLen) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
serverName := indexTLSServerNameFromHandshake(payload[recordLayerHeaderLen:])
|
serverName := indexTLSServerNameFromHandshake(payload[recordLayerHeaderLen : recordLayerHeaderLen+int(segmentLen)])
|
||||||
if serverName == nil {
|
if serverName == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
serverName.Index += recordLayerHeaderLen
|
serverName.Length += recordLayerHeaderLen
|
||||||
return serverName
|
return serverName
|
||||||
}
|
}
|
||||||
|
|
||||||
func indexTLSServerNameFromHandshake(handshake []byte) *MyServerName {
|
func indexTLSServerNameFromHandshake(hs []byte) *myServerName {
|
||||||
if len(handshake) < handshakeHeaderLen+randomDataLen+sessionIDHeaderLen {
|
if len(hs) < handshakeHeaderLen+randomDataLen+sessionIDHeaderLen {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if handshake[0] != handshakeType {
|
if hs[0] != handshakeType {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
handshakeLen := uint32(handshake[1])<<16 | uint32(handshake[2])<<8 | uint32(handshake[3])
|
handshakeLen := uint32(hs[1])<<16 | uint32(hs[2])<<8 | uint32(hs[3])
|
||||||
if len(handshake[4:]) != int(handshakeLen) {
|
if len(hs[4:]) != int(handshakeLen) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
tlsVersion := uint16(handshake[4])<<8 | uint16(handshake[5])
|
tlsVersion := uint16(hs[4])<<8 | uint16(hs[5])
|
||||||
if tlsVersion&tlsVersionBitmask != 0x0300 && tlsVersion != tls13 {
|
if tlsVersion&tlsVersionBitmask != 0x0300 && tlsVersion != tls13 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
sessionIDLen := handshake[38]
|
sessionIDLen := hs[38]
|
||||||
currentIndex := handshakeHeaderLen + randomDataLen + sessionIDHeaderLen + int(sessionIDLen)
|
if len(hs) < handshakeHeaderLen+randomDataLen+sessionIDHeaderLen+int(sessionIDLen) {
|
||||||
if len(handshake) < currentIndex {
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
cipherSuites := handshake[currentIndex:]
|
cs := hs[handshakeHeaderLen+randomDataLen+sessionIDHeaderLen+int(sessionIDLen):]
|
||||||
if len(cipherSuites) < cipherSuiteHeaderLen {
|
if len(cs) < cipherSuiteHeaderLen {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
csLen := uint16(cipherSuites[0])<<8 | uint16(cipherSuites[1])
|
csLen := uint16(cs[0])<<8 | uint16(cs[1])
|
||||||
if len(cipherSuites) < cipherSuiteHeaderLen+int(csLen)+compressMethodHeaderLen {
|
if len(cs) < cipherSuiteHeaderLen+int(csLen)+compressMethodHeaderLen {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
compressMethodLen := uint16(cipherSuites[cipherSuiteHeaderLen+int(csLen)])
|
compressMethodLen := uint16(cs[cipherSuiteHeaderLen+int(csLen)])
|
||||||
currentIndex += cipherSuiteHeaderLen + int(csLen) + compressMethodHeaderLen + int(compressMethodLen)
|
if len(cs) < cipherSuiteHeaderLen+int(csLen)+compressMethodHeaderLen+int(compressMethodLen) {
|
||||||
if len(handshake) < currentIndex {
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
serverName := indexTLSServerNameFromExtensions(handshake[currentIndex:])
|
currentIndex := cipherSuiteHeaderLen + int(csLen) + compressMethodHeaderLen + int(compressMethodLen)
|
||||||
|
serverName := indexTLSServerNameFromExtensions(cs[currentIndex:])
|
||||||
if serverName == nil {
|
if serverName == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -85,7 +84,7 @@ func indexTLSServerNameFromHandshake(handshake []byte) *MyServerName {
|
|||||||
return serverName
|
return serverName
|
||||||
}
|
}
|
||||||
|
|
||||||
func indexTLSServerNameFromExtensions(exs []byte) *MyServerName {
|
func indexTLSServerNameFromExtensions(exs []byte) *myServerName {
|
||||||
if len(exs) == 0 {
|
if len(exs) == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -119,8 +118,7 @@ func indexTLSServerNameFromExtensions(exs []byte) *MyServerName {
|
|||||||
}
|
}
|
||||||
sniLen := uint16(sex[3])<<8 | uint16(sex[4])
|
sniLen := uint16(sex[3])<<8 | uint16(sex[4])
|
||||||
sex = sex[sniExtensionHeaderLen:]
|
sex = sex[sniExtensionHeaderLen:]
|
||||||
|
return &myServerName{
|
||||||
return &MyServerName{
|
|
||||||
Index: currentIndex + extensionHeaderLen + sniExtensionHeaderLen,
|
Index: currentIndex + extensionHeaderLen + sniExtensionHeaderLen,
|
||||||
Length: int(sniLen),
|
Length: int(sniLen),
|
||||||
ServerName: string(sex),
|
ServerName: string(sex),
|
||||||
|
|||||||
@@ -1,20 +0,0 @@
|
|||||||
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,10 +9,6 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func writeAndWaitAck(ctx context.Context, conn *net.TCPConn, payload []byte, fallbackDelay time.Duration) error {
|
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)
|
time.Sleep(fallbackDelay)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,9 +16,6 @@ func writeAndWaitAck(ctx context.Context, conn *net.TCPConn, payload []byte, fal
|
|||||||
err := winiphlpapi.WriteAndWaitAck(ctx, conn, payload)
|
err := winiphlpapi.WriteAndWaitAck(ctx, conn, payload)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, windows.ERROR_ACCESS_DENIED) {
|
if errors.Is(err, windows.ERROR_ACCESS_DENIED) {
|
||||||
if _, err := conn.Write(payload); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
time.Sleep(fallbackDelay)
|
time.Sleep(fallbackDelay)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +0,0 @@
|
|||||||
package urltest
|
|
||||||
|
|
||||||
import "context"
|
|
||||||
|
|
||||||
type contextKeyIsUnifiedDelay struct{}
|
|
||||||
|
|
||||||
func ContextWithIsUnifiedDelay(ctx context.Context) context.Context {
|
|
||||||
return context.WithValue(ctx, contextKeyIsUnifiedDelay{}, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
func IsUnifiedDelayFromContext(ctx context.Context) bool {
|
|
||||||
return ctx.Value(contextKeyIsUnifiedDelay{}) != nil
|
|
||||||
}
|
|
||||||
@@ -47,15 +47,15 @@ func (s *HistoryStorage) LoadURLTestHistory(tag string) *adapter.URLTestHistory
|
|||||||
func (s *HistoryStorage) DeleteURLTestHistory(tag string) {
|
func (s *HistoryStorage) DeleteURLTestHistory(tag string) {
|
||||||
s.access.Lock()
|
s.access.Lock()
|
||||||
delete(s.delayHistory, tag)
|
delete(s.delayHistory, tag)
|
||||||
s.notifyUpdated()
|
|
||||||
s.access.Unlock()
|
s.access.Unlock()
|
||||||
|
s.notifyUpdated()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *HistoryStorage) StoreURLTestHistory(tag string, history *adapter.URLTestHistory) {
|
func (s *HistoryStorage) StoreURLTestHistory(tag string, history *adapter.URLTestHistory) {
|
||||||
s.access.Lock()
|
s.access.Lock()
|
||||||
s.delayHistory[tag] = history
|
s.delayHistory[tag] = history
|
||||||
s.notifyUpdated()
|
|
||||||
s.access.Unlock()
|
s.access.Unlock()
|
||||||
|
s.notifyUpdated()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *HistoryStorage) notifyUpdated() {
|
func (s *HistoryStorage) notifyUpdated() {
|
||||||
@@ -69,8 +69,6 @@ func (s *HistoryStorage) notifyUpdated() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *HistoryStorage) Close() error {
|
func (s *HistoryStorage) Close() error {
|
||||||
s.access.Lock()
|
|
||||||
defer s.access.Unlock()
|
|
||||||
s.updateHook = nil
|
s.updateHook = nil
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -128,15 +126,6 @@ func URLTest(ctx context.Context, link string, detour N.Dialer) (t uint16, err e
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
resp.Body.Close()
|
resp.Body.Close()
|
||||||
if IsUnifiedDelayFromContext(ctx) {
|
|
||||||
second := time.Now()
|
|
||||||
resp, err = client.Do(req)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
resp.Body.Close()
|
|
||||||
start = second
|
|
||||||
}
|
|
||||||
t = uint16(time.Since(start) / time.Millisecond)
|
t = uint16(time.Since(start) / time.Millisecond)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,337 +0,0 @@
|
|||||||
package buf
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/common/xray/bytespool"
|
|
||||||
"github.com/sagernet/sing-box/common/xray/net"
|
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
// Size of a regular buffer.
|
|
||||||
Size = 8192
|
|
||||||
)
|
|
||||||
|
|
||||||
var zero = [Size * 10]byte{0}
|
|
||||||
|
|
||||||
var pool = bytespool.GetPool(Size)
|
|
||||||
|
|
||||||
// ownership represents the data owner of the buffer.
|
|
||||||
type ownership uint8
|
|
||||||
|
|
||||||
const (
|
|
||||||
managed ownership = iota
|
|
||||||
unmanaged
|
|
||||||
bytespools
|
|
||||||
)
|
|
||||||
|
|
||||||
// Buffer is a recyclable allocation of a byte array. Buffer.Release() recycles
|
|
||||||
// the buffer into an internal buffer pool, in order to recreate a buffer more
|
|
||||||
// quickly.
|
|
||||||
type Buffer struct {
|
|
||||||
v []byte
|
|
||||||
start int32
|
|
||||||
end int32
|
|
||||||
ownership ownership
|
|
||||||
UDP *net.Destination
|
|
||||||
}
|
|
||||||
|
|
||||||
// New creates a Buffer with 0 length and 8K capacity, managed.
|
|
||||||
func New() *Buffer {
|
|
||||||
buf := pool.Get().([]byte)
|
|
||||||
if cap(buf) >= Size {
|
|
||||||
buf = buf[:Size]
|
|
||||||
} else {
|
|
||||||
buf = make([]byte, Size)
|
|
||||||
}
|
|
||||||
|
|
||||||
return &Buffer{
|
|
||||||
v: buf,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewExisted creates a standard size Buffer with an existed bytearray, managed.
|
|
||||||
func NewExisted(b []byte) *Buffer {
|
|
||||||
if cap(b) < Size {
|
|
||||||
panic("Invalid buffer")
|
|
||||||
}
|
|
||||||
|
|
||||||
oLen := len(b)
|
|
||||||
if oLen < Size {
|
|
||||||
b = b[:Size]
|
|
||||||
}
|
|
||||||
|
|
||||||
return &Buffer{
|
|
||||||
v: b,
|
|
||||||
end: int32(oLen),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// FromBytes creates a Buffer with an existed bytearray, unmanaged.
|
|
||||||
func FromBytes(b []byte) *Buffer {
|
|
||||||
return &Buffer{
|
|
||||||
v: b,
|
|
||||||
end: int32(len(b)),
|
|
||||||
ownership: unmanaged,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// StackNew creates a new Buffer object on stack, managed.
|
|
||||||
// This method is for buffers that is released in the same function.
|
|
||||||
func StackNew() Buffer {
|
|
||||||
buf := pool.Get().([]byte)
|
|
||||||
if cap(buf) >= Size {
|
|
||||||
buf = buf[:Size]
|
|
||||||
} else {
|
|
||||||
buf = make([]byte, Size)
|
|
||||||
}
|
|
||||||
|
|
||||||
return Buffer{
|
|
||||||
v: buf,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewWithSize creates a Buffer with 0 length and capacity with at least the given size, bytespool's.
|
|
||||||
func NewWithSize(size int32) *Buffer {
|
|
||||||
return &Buffer{
|
|
||||||
v: bytespool.Alloc(size),
|
|
||||||
ownership: bytespools,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Release recycles the buffer into an internal buffer pool.
|
|
||||||
func (b *Buffer) Release() {
|
|
||||||
if b == nil || b.v == nil || b.ownership == unmanaged {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
p := b.v
|
|
||||||
b.v = nil
|
|
||||||
b.Clear()
|
|
||||||
|
|
||||||
switch b.ownership {
|
|
||||||
case managed:
|
|
||||||
if cap(p) == Size {
|
|
||||||
pool.Put(p)
|
|
||||||
}
|
|
||||||
case bytespools:
|
|
||||||
bytespool.Free(p)
|
|
||||||
}
|
|
||||||
b.UDP = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clear clears the content of the buffer, results an empty buffer with
|
|
||||||
// Len() = 0.
|
|
||||||
func (b *Buffer) Clear() {
|
|
||||||
b.start = 0
|
|
||||||
b.end = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// Byte returns the bytes at index.
|
|
||||||
func (b *Buffer) Byte(index int32) byte {
|
|
||||||
return b.v[b.start+index]
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetByte sets the byte value at index.
|
|
||||||
func (b *Buffer) SetByte(index int32, value byte) {
|
|
||||||
b.v[b.start+index] = value
|
|
||||||
}
|
|
||||||
|
|
||||||
// Bytes returns the content bytes of this Buffer.
|
|
||||||
func (b *Buffer) Bytes() []byte {
|
|
||||||
return b.v[b.start:b.end]
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extend increases the buffer size by n bytes, and returns the extended part.
|
|
||||||
// It panics if result size is larger than buf.Size.
|
|
||||||
func (b *Buffer) Extend(n int32) []byte {
|
|
||||||
end := b.end + n
|
|
||||||
if end > int32(len(b.v)) {
|
|
||||||
panic("extending out of bound")
|
|
||||||
}
|
|
||||||
ext := b.v[b.end:end]
|
|
||||||
b.end = end
|
|
||||||
copy(ext, zero[:])
|
|
||||||
return ext
|
|
||||||
}
|
|
||||||
|
|
||||||
// BytesRange returns a slice of this buffer with given from and to boundary.
|
|
||||||
func (b *Buffer) BytesRange(from, to int32) []byte {
|
|
||||||
if from < 0 {
|
|
||||||
from += b.Len()
|
|
||||||
}
|
|
||||||
if to < 0 {
|
|
||||||
to += b.Len()
|
|
||||||
}
|
|
||||||
return b.v[b.start+from : b.start+to]
|
|
||||||
}
|
|
||||||
|
|
||||||
// BytesFrom returns a slice of this Buffer starting from the given position.
|
|
||||||
func (b *Buffer) BytesFrom(from int32) []byte {
|
|
||||||
if from < 0 {
|
|
||||||
from += b.Len()
|
|
||||||
}
|
|
||||||
return b.v[b.start+from : b.end]
|
|
||||||
}
|
|
||||||
|
|
||||||
// BytesTo returns a slice of this Buffer from start to the given position.
|
|
||||||
func (b *Buffer) BytesTo(to int32) []byte {
|
|
||||||
if to < 0 {
|
|
||||||
to += b.Len()
|
|
||||||
}
|
|
||||||
if to < 0 {
|
|
||||||
to = 0
|
|
||||||
}
|
|
||||||
return b.v[b.start : b.start+to]
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check makes sure that 0 <= b.start <= b.end.
|
|
||||||
func (b *Buffer) Check() {
|
|
||||||
if b.start < 0 {
|
|
||||||
b.start = 0
|
|
||||||
}
|
|
||||||
if b.end < 0 {
|
|
||||||
b.end = 0
|
|
||||||
}
|
|
||||||
if b.start > b.end {
|
|
||||||
b.start = b.end
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Resize cuts the buffer at the given position.
|
|
||||||
func (b *Buffer) Resize(from, to int32) {
|
|
||||||
oldEnd := b.end
|
|
||||||
if from < 0 {
|
|
||||||
from += b.Len()
|
|
||||||
}
|
|
||||||
if to < 0 {
|
|
||||||
to += b.Len()
|
|
||||||
}
|
|
||||||
if to < from {
|
|
||||||
panic("Invalid slice")
|
|
||||||
}
|
|
||||||
b.end = b.start + to
|
|
||||||
b.start += from
|
|
||||||
b.Check()
|
|
||||||
if b.end > oldEnd {
|
|
||||||
copy(b.v[oldEnd:b.end], zero[:])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Advance cuts the buffer at the given position.
|
|
||||||
func (b *Buffer) Advance(from int32) {
|
|
||||||
if from < 0 {
|
|
||||||
from += b.Len()
|
|
||||||
}
|
|
||||||
b.start += from
|
|
||||||
b.Check()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Len returns the length of the buffer content.
|
|
||||||
func (b *Buffer) Len() int32 {
|
|
||||||
if b == nil {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
return b.end - b.start
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cap returns the capacity of the buffer content.
|
|
||||||
func (b *Buffer) Cap() int32 {
|
|
||||||
if b == nil {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
return int32(len(b.v))
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsEmpty returns true if the buffer is empty.
|
|
||||||
func (b *Buffer) IsEmpty() bool {
|
|
||||||
return b.Len() == 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsFull returns true if the buffer has no more room to grow.
|
|
||||||
func (b *Buffer) IsFull() bool {
|
|
||||||
return b != nil && b.end == int32(len(b.v))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write implements Write method in io.Writer.
|
|
||||||
func (b *Buffer) Write(data []byte) (int, error) {
|
|
||||||
nBytes := copy(b.v[b.end:], data)
|
|
||||||
b.end += int32(nBytes)
|
|
||||||
return nBytes, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// WriteByte writes a single byte into the buffer.
|
|
||||||
func (b *Buffer) WriteByte(v byte) error {
|
|
||||||
if b.IsFull() {
|
|
||||||
return E.New("buffer full")
|
|
||||||
}
|
|
||||||
b.v[b.end] = v
|
|
||||||
b.end++
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// WriteString implements io.StringWriter.
|
|
||||||
func (b *Buffer) WriteString(s string) (int, error) {
|
|
||||||
return b.Write([]byte(s))
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReadByte implements io.ByteReader
|
|
||||||
func (b *Buffer) ReadByte() (byte, error) {
|
|
||||||
if b.start == b.end {
|
|
||||||
return 0, io.EOF
|
|
||||||
}
|
|
||||||
|
|
||||||
nb := b.v[b.start]
|
|
||||||
b.start++
|
|
||||||
return nb, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReadBytes implements bufio.Reader.ReadBytes
|
|
||||||
func (b *Buffer) ReadBytes(length int32) ([]byte, error) {
|
|
||||||
if b.end-b.start < length {
|
|
||||||
return nil, io.EOF
|
|
||||||
}
|
|
||||||
|
|
||||||
nb := b.v[b.start : b.start+length]
|
|
||||||
b.start += length
|
|
||||||
return nb, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read implements io.Reader.Read().
|
|
||||||
func (b *Buffer) Read(data []byte) (int, error) {
|
|
||||||
if b.Len() == 0 {
|
|
||||||
return 0, io.EOF
|
|
||||||
}
|
|
||||||
nBytes := copy(data, b.v[b.start:b.end])
|
|
||||||
if int32(nBytes) == b.Len() {
|
|
||||||
b.Clear()
|
|
||||||
} else {
|
|
||||||
b.start += int32(nBytes)
|
|
||||||
}
|
|
||||||
return nBytes, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReadFrom implements io.ReaderFrom.
|
|
||||||
func (b *Buffer) ReadFrom(reader io.Reader) (int64, error) {
|
|
||||||
n, err := reader.Read(b.v[b.end:])
|
|
||||||
b.end += int32(n)
|
|
||||||
return int64(n), err
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReadFullFrom reads exact size of bytes from given reader, or until error occurs.
|
|
||||||
func (b *Buffer) ReadFullFrom(reader io.Reader, size int32) (int64, error) {
|
|
||||||
end := b.end + size
|
|
||||||
if end > int32(len(b.v)) {
|
|
||||||
v := end
|
|
||||||
return 0, E.New("out of bound: ", v)
|
|
||||||
}
|
|
||||||
n, err := io.ReadFull(reader, b.v[b.end:end])
|
|
||||||
b.end += int32(n)
|
|
||||||
return int64(n), err
|
|
||||||
}
|
|
||||||
|
|
||||||
// String returns the string form of this Buffer.
|
|
||||||
func (b *Buffer) String() string {
|
|
||||||
return string(b.Bytes())
|
|
||||||
}
|
|
||||||
@@ -1,124 +0,0 @@
|
|||||||
package buf
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/common/xray/errors"
|
|
||||||
"github.com/sagernet/sing-box/common/xray/signal"
|
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
|
||||||
)
|
|
||||||
|
|
||||||
type dataHandler func(MultiBuffer)
|
|
||||||
|
|
||||||
type copyHandler struct {
|
|
||||||
onData []dataHandler
|
|
||||||
}
|
|
||||||
|
|
||||||
// SizeCounter is for counting bytes copied by Copy().
|
|
||||||
type SizeCounter struct {
|
|
||||||
Size int64
|
|
||||||
}
|
|
||||||
|
|
||||||
// CopyOption is an option for copying data.
|
|
||||||
type CopyOption func(*copyHandler)
|
|
||||||
|
|
||||||
// UpdateActivity is a CopyOption to update activity on each data copy operation.
|
|
||||||
func UpdateActivity(timer signal.ActivityUpdater) CopyOption {
|
|
||||||
return func(handler *copyHandler) {
|
|
||||||
handler.onData = append(handler.onData, func(MultiBuffer) {
|
|
||||||
timer.Update()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// CountSize is a CopyOption that sums the total size of data copied into the given SizeCounter.
|
|
||||||
func CountSize(sc *SizeCounter) CopyOption {
|
|
||||||
return func(handler *copyHandler) {
|
|
||||||
handler.onData = append(handler.onData, func(b MultiBuffer) {
|
|
||||||
sc.Size += int64(b.Len())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type readError struct {
|
|
||||||
error
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e readError) Error() string {
|
|
||||||
return e.error.Error()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e readError) Unwrap() error {
|
|
||||||
return e.error
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsReadError returns true if the error in Copy() comes from reading.
|
|
||||||
func IsReadError(err error) bool {
|
|
||||||
_, ok := err.(readError)
|
|
||||||
return ok
|
|
||||||
}
|
|
||||||
|
|
||||||
type writeError struct {
|
|
||||||
error
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e writeError) Error() string {
|
|
||||||
return e.error.Error()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e writeError) Unwrap() error {
|
|
||||||
return e.error
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsWriteError returns true if the error in Copy() comes from writing.
|
|
||||||
func IsWriteError(err error) bool {
|
|
||||||
_, ok := err.(writeError)
|
|
||||||
return ok
|
|
||||||
}
|
|
||||||
|
|
||||||
func copyInternal(reader Reader, writer Writer, handler *copyHandler) error {
|
|
||||||
for {
|
|
||||||
buffer, err := reader.ReadMultiBuffer()
|
|
||||||
if !buffer.IsEmpty() {
|
|
||||||
for _, handler := range handler.onData {
|
|
||||||
handler(buffer)
|
|
||||||
}
|
|
||||||
|
|
||||||
if werr := writer.WriteMultiBuffer(buffer); werr != nil {
|
|
||||||
return writeError{werr}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return readError{err}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Copy dumps all payload from reader to writer or stops when an error occurs. It returns nil when EOF.
|
|
||||||
func Copy(reader Reader, writer Writer, options ...CopyOption) error {
|
|
||||||
var handler copyHandler
|
|
||||||
for _, option := range options {
|
|
||||||
option(&handler)
|
|
||||||
}
|
|
||||||
err := copyInternal(reader, writer, &handler)
|
|
||||||
if err != nil && errors.Cause(err) != io.EOF {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var ErrNotTimeoutReader = E.New("not a TimeoutReader")
|
|
||||||
|
|
||||||
func CopyOnceTimeout(reader Reader, writer Writer, timeout time.Duration) error {
|
|
||||||
timeoutReader, ok := reader.(TimeoutReader)
|
|
||||||
if !ok {
|
|
||||||
return ErrNotTimeoutReader
|
|
||||||
}
|
|
||||||
mb, err := timeoutReader.ReadMultiBufferTimeout(timeout)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return writer.WriteMultiBuffer(mb)
|
|
||||||
}
|
|
||||||
@@ -1,126 +0,0 @@
|
|||||||
package buf
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io"
|
|
||||||
"net"
|
|
||||||
"syscall"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/common/xray/stat"
|
|
||||||
"github.com/sagernet/sing-box/common/xray/stats"
|
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Reader extends io.Reader with MultiBuffer.
|
|
||||||
type Reader interface {
|
|
||||||
// ReadMultiBuffer reads content from underlying reader, and put it into a MultiBuffer.
|
|
||||||
ReadMultiBuffer() (MultiBuffer, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ErrReadTimeout is an error that happens with IO timeout.
|
|
||||||
var ErrReadTimeout = E.New("IO timeout")
|
|
||||||
|
|
||||||
// TimeoutReader is a reader that returns error if Read() operation takes longer than the given timeout.
|
|
||||||
type TimeoutReader interface {
|
|
||||||
ReadMultiBufferTimeout(time.Duration) (MultiBuffer, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Writer extends io.Writer with MultiBuffer.
|
|
||||||
type Writer interface {
|
|
||||||
// WriteMultiBuffer writes a MultiBuffer into underlying writer.
|
|
||||||
WriteMultiBuffer(MultiBuffer) error
|
|
||||||
}
|
|
||||||
|
|
||||||
// WriteAllBytes ensures all bytes are written into the given writer.
|
|
||||||
func WriteAllBytes(writer io.Writer, payload []byte, c stats.Counter) error {
|
|
||||||
wc := 0
|
|
||||||
defer func() {
|
|
||||||
if c != nil {
|
|
||||||
c.Add(int64(wc))
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
for len(payload) > 0 {
|
|
||||||
n, err := writer.Write(payload)
|
|
||||||
wc += n
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
payload = payload[n:]
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func isPacketReader(reader io.Reader) bool {
|
|
||||||
_, ok := reader.(net.PacketConn)
|
|
||||||
return ok
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewReader creates a new Reader.
|
|
||||||
// The Reader instance doesn't take the ownership of reader.
|
|
||||||
func NewReader(reader io.Reader) Reader {
|
|
||||||
if mr, ok := reader.(Reader); ok {
|
|
||||||
return mr
|
|
||||||
}
|
|
||||||
|
|
||||||
if isPacketReader(reader) {
|
|
||||||
return &PacketReader{
|
|
||||||
Reader: reader,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return &SingleReader{
|
|
||||||
Reader: reader,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewPacketReader creates a new PacketReader based on the given reader.
|
|
||||||
func NewPacketReader(reader io.Reader) Reader {
|
|
||||||
if mr, ok := reader.(Reader); ok {
|
|
||||||
return mr
|
|
||||||
}
|
|
||||||
|
|
||||||
return &PacketReader{
|
|
||||||
Reader: reader,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func isPacketWriter(writer io.Writer) bool {
|
|
||||||
if _, ok := writer.(net.PacketConn); ok {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the writer doesn't implement syscall.Conn, it is probably not a TCP connection.
|
|
||||||
if _, ok := writer.(syscall.Conn); !ok {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewWriter creates a new Writer.
|
|
||||||
func NewWriter(writer io.Writer) Writer {
|
|
||||||
if mw, ok := writer.(Writer); ok {
|
|
||||||
return mw
|
|
||||||
}
|
|
||||||
|
|
||||||
iConn := writer
|
|
||||||
if statConn, ok := writer.(*stat.CounterConnection); ok {
|
|
||||||
iConn = statConn.Connection
|
|
||||||
}
|
|
||||||
|
|
||||||
if isPacketWriter(iConn) {
|
|
||||||
return &SequentialWriter{
|
|
||||||
Writer: writer,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var counter stats.Counter
|
|
||||||
|
|
||||||
if statConn, ok := writer.(*stat.CounterConnection); ok {
|
|
||||||
counter = statConn.WriteCounter
|
|
||||||
}
|
|
||||||
return &BufferToBytesWriter{
|
|
||||||
Writer: iConn,
|
|
||||||
counter: counter,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,310 +0,0 @@
|
|||||||
package buf
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/common/xray"
|
|
||||||
"github.com/sagernet/sing-box/common/xray/errors"
|
|
||||||
"github.com/sagernet/sing-box/common/xray/serial"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ReadAllToBytes reads all content from the reader into a byte array, until EOF.
|
|
||||||
func ReadAllToBytes(reader io.Reader) ([]byte, error) {
|
|
||||||
mb, err := ReadFrom(reader)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if mb.Len() == 0 {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
b := make([]byte, mb.Len())
|
|
||||||
mb, _ = SplitBytes(mb, b)
|
|
||||||
ReleaseMulti(mb)
|
|
||||||
return b, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// MultiBuffer is a list of Buffers. The order of Buffer matters.
|
|
||||||
type MultiBuffer []*Buffer
|
|
||||||
|
|
||||||
// MergeMulti merges content from src to dest, and returns the new address of dest and src
|
|
||||||
func MergeMulti(dest MultiBuffer, src MultiBuffer) (MultiBuffer, MultiBuffer) {
|
|
||||||
dest = append(dest, src...)
|
|
||||||
for idx := range src {
|
|
||||||
src[idx] = nil
|
|
||||||
}
|
|
||||||
return dest, src[:0]
|
|
||||||
}
|
|
||||||
|
|
||||||
// MergeBytes merges the given bytes into MultiBuffer and return the new address of the merged MultiBuffer.
|
|
||||||
func MergeBytes(dest MultiBuffer, src []byte) MultiBuffer {
|
|
||||||
n := len(dest)
|
|
||||||
if n > 0 && !(dest)[n-1].IsFull() {
|
|
||||||
nBytes, _ := (dest)[n-1].Write(src)
|
|
||||||
src = src[nBytes:]
|
|
||||||
}
|
|
||||||
|
|
||||||
for len(src) > 0 {
|
|
||||||
b := New()
|
|
||||||
nBytes, _ := b.Write(src)
|
|
||||||
src = src[nBytes:]
|
|
||||||
dest = append(dest, b)
|
|
||||||
}
|
|
||||||
|
|
||||||
return dest
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReleaseMulti releases all content of the MultiBuffer, and returns an empty MultiBuffer.
|
|
||||||
func ReleaseMulti(mb MultiBuffer) MultiBuffer {
|
|
||||||
for i := range mb {
|
|
||||||
mb[i].Release()
|
|
||||||
mb[i] = nil
|
|
||||||
}
|
|
||||||
return mb[:0]
|
|
||||||
}
|
|
||||||
|
|
||||||
// Copy copied the beginning part of the MultiBuffer into the given byte array.
|
|
||||||
func (mb MultiBuffer) Copy(b []byte) int {
|
|
||||||
total := 0
|
|
||||||
for _, bb := range mb {
|
|
||||||
nBytes := copy(b[total:], bb.Bytes())
|
|
||||||
total += nBytes
|
|
||||||
if int32(nBytes) < bb.Len() {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return total
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReadFrom reads all content from reader until EOF.
|
|
||||||
func ReadFrom(reader io.Reader) (MultiBuffer, error) {
|
|
||||||
mb := make(MultiBuffer, 0, 16)
|
|
||||||
for {
|
|
||||||
b := New()
|
|
||||||
_, err := b.ReadFullFrom(reader, Size)
|
|
||||||
if b.IsEmpty() {
|
|
||||||
b.Release()
|
|
||||||
} else {
|
|
||||||
mb = append(mb, b)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
if errors.Cause(err) == io.EOF || errors.Cause(err) == io.ErrUnexpectedEOF {
|
|
||||||
return mb, nil
|
|
||||||
}
|
|
||||||
return mb, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// SplitBytes splits the given amount of bytes from the beginning of the MultiBuffer.
|
|
||||||
// It returns the new address of MultiBuffer leftover, and number of bytes written into the input byte slice.
|
|
||||||
func SplitBytes(mb MultiBuffer, b []byte) (MultiBuffer, int) {
|
|
||||||
totalBytes := 0
|
|
||||||
endIndex := -1
|
|
||||||
for i := range mb {
|
|
||||||
pBuffer := mb[i]
|
|
||||||
nBytes, _ := pBuffer.Read(b)
|
|
||||||
totalBytes += nBytes
|
|
||||||
b = b[nBytes:]
|
|
||||||
if !pBuffer.IsEmpty() {
|
|
||||||
endIndex = i
|
|
||||||
break
|
|
||||||
}
|
|
||||||
pBuffer.Release()
|
|
||||||
mb[i] = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if endIndex == -1 {
|
|
||||||
mb = mb[:0]
|
|
||||||
} else {
|
|
||||||
mb = mb[endIndex:]
|
|
||||||
}
|
|
||||||
|
|
||||||
return mb, totalBytes
|
|
||||||
}
|
|
||||||
|
|
||||||
// SplitFirstBytes splits the first buffer from MultiBuffer, and then copy its content into the given slice.
|
|
||||||
func SplitFirstBytes(mb MultiBuffer, p []byte) (MultiBuffer, int) {
|
|
||||||
mb, b := SplitFirst(mb)
|
|
||||||
if b == nil {
|
|
||||||
return mb, 0
|
|
||||||
}
|
|
||||||
n := copy(p, b.Bytes())
|
|
||||||
b.Release()
|
|
||||||
return mb, n
|
|
||||||
}
|
|
||||||
|
|
||||||
// Compact returns another MultiBuffer by merging all content of the given one together.
|
|
||||||
func Compact(mb MultiBuffer) MultiBuffer {
|
|
||||||
if len(mb) == 0 {
|
|
||||||
return mb
|
|
||||||
}
|
|
||||||
|
|
||||||
mb2 := make(MultiBuffer, 0, len(mb))
|
|
||||||
last := mb[0]
|
|
||||||
|
|
||||||
for i := 1; i < len(mb); i++ {
|
|
||||||
curr := mb[i]
|
|
||||||
if last.Len()+curr.Len() > Size {
|
|
||||||
mb2 = append(mb2, last)
|
|
||||||
last = curr
|
|
||||||
} else {
|
|
||||||
common.Must2(last.ReadFrom(curr))
|
|
||||||
curr.Release()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
mb2 = append(mb2, last)
|
|
||||||
return mb2
|
|
||||||
}
|
|
||||||
|
|
||||||
// SplitFirst splits the first Buffer from the beginning of the MultiBuffer.
|
|
||||||
func SplitFirst(mb MultiBuffer) (MultiBuffer, *Buffer) {
|
|
||||||
if len(mb) == 0 {
|
|
||||||
return mb, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
b := mb[0]
|
|
||||||
mb[0] = nil
|
|
||||||
mb = mb[1:]
|
|
||||||
return mb, b
|
|
||||||
}
|
|
||||||
|
|
||||||
// SplitSize splits the beginning of the MultiBuffer into another one, for at most size bytes.
|
|
||||||
func SplitSize(mb MultiBuffer, size int32) (MultiBuffer, MultiBuffer) {
|
|
||||||
if len(mb) == 0 {
|
|
||||||
return mb, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if mb[0].Len() > size {
|
|
||||||
b := New()
|
|
||||||
copy(b.Extend(size), mb[0].BytesTo(size))
|
|
||||||
mb[0].Advance(size)
|
|
||||||
return mb, MultiBuffer{b}
|
|
||||||
}
|
|
||||||
|
|
||||||
totalBytes := int32(0)
|
|
||||||
var r MultiBuffer
|
|
||||||
endIndex := -1
|
|
||||||
for i := range mb {
|
|
||||||
if totalBytes+mb[i].Len() > size {
|
|
||||||
endIndex = i
|
|
||||||
break
|
|
||||||
}
|
|
||||||
totalBytes += mb[i].Len()
|
|
||||||
r = append(r, mb[i])
|
|
||||||
mb[i] = nil
|
|
||||||
}
|
|
||||||
if endIndex == -1 {
|
|
||||||
// To reuse mb array
|
|
||||||
mb = mb[:0]
|
|
||||||
} else {
|
|
||||||
mb = mb[endIndex:]
|
|
||||||
}
|
|
||||||
return mb, r
|
|
||||||
}
|
|
||||||
|
|
||||||
// SplitMulti splits the beginning of the MultiBuffer into first one, the index i and after into second one
|
|
||||||
func SplitMulti(mb MultiBuffer, i int) (MultiBuffer, MultiBuffer) {
|
|
||||||
mb2 := make(MultiBuffer, 0, len(mb))
|
|
||||||
if i < len(mb) && i >= 0 {
|
|
||||||
mb2 = append(mb2, mb[i:]...)
|
|
||||||
for j := i; j < len(mb); j++ {
|
|
||||||
mb[j] = nil
|
|
||||||
}
|
|
||||||
mb = mb[:i]
|
|
||||||
}
|
|
||||||
return mb, mb2
|
|
||||||
}
|
|
||||||
|
|
||||||
// WriteMultiBuffer writes all buffers from the MultiBuffer to the Writer one by one, and return error if any, with leftover MultiBuffer.
|
|
||||||
func WriteMultiBuffer(writer io.Writer, mb MultiBuffer) (MultiBuffer, error) {
|
|
||||||
for {
|
|
||||||
mb2, b := SplitFirst(mb)
|
|
||||||
mb = mb2
|
|
||||||
if b == nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err := writer.Write(b.Bytes())
|
|
||||||
b.Release()
|
|
||||||
if err != nil {
|
|
||||||
return mb, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Len returns the total number of bytes in the MultiBuffer.
|
|
||||||
func (mb MultiBuffer) Len() int32 {
|
|
||||||
if mb == nil {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
size := int32(0)
|
|
||||||
for _, b := range mb {
|
|
||||||
size += b.Len()
|
|
||||||
}
|
|
||||||
return size
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsEmpty returns true if the MultiBuffer has no content.
|
|
||||||
func (mb MultiBuffer) IsEmpty() bool {
|
|
||||||
for _, b := range mb {
|
|
||||||
if !b.IsEmpty() {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// String returns the content of the MultiBuffer in string.
|
|
||||||
func (mb MultiBuffer) String() string {
|
|
||||||
v := make([]interface{}, len(mb))
|
|
||||||
for i, b := range mb {
|
|
||||||
v[i] = b
|
|
||||||
}
|
|
||||||
return serial.Concat(v...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// MultiBufferContainer is a ReadWriteCloser wrapper over MultiBuffer.
|
|
||||||
type MultiBufferContainer struct {
|
|
||||||
MultiBuffer
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read implements io.Reader.
|
|
||||||
func (c *MultiBufferContainer) Read(b []byte) (int, error) {
|
|
||||||
if c.MultiBuffer.IsEmpty() {
|
|
||||||
return 0, io.EOF
|
|
||||||
}
|
|
||||||
|
|
||||||
mb, nBytes := SplitBytes(c.MultiBuffer, b)
|
|
||||||
c.MultiBuffer = mb
|
|
||||||
return nBytes, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReadMultiBuffer implements Reader.
|
|
||||||
func (c *MultiBufferContainer) ReadMultiBuffer() (MultiBuffer, error) {
|
|
||||||
mb := c.MultiBuffer
|
|
||||||
c.MultiBuffer = nil
|
|
||||||
return mb, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write implements io.Writer.
|
|
||||||
func (c *MultiBufferContainer) Write(b []byte) (int, error) {
|
|
||||||
c.MultiBuffer = MergeBytes(c.MultiBuffer, b)
|
|
||||||
return len(b), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// WriteMultiBuffer implements Writer.
|
|
||||||
func (c *MultiBufferContainer) WriteMultiBuffer(b MultiBuffer) error {
|
|
||||||
mb, _ := MergeMulti(c.MultiBuffer, b)
|
|
||||||
c.MultiBuffer = mb
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close implements io.Closer.
|
|
||||||
func (c *MultiBufferContainer) Close() error {
|
|
||||||
c.MultiBuffer = ReleaseMulti(c.MultiBuffer)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
package buf
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/sagernet/sing-box/common/xray/net"
|
|
||||||
)
|
|
||||||
|
|
||||||
type EndpointOverrideReader struct {
|
|
||||||
Reader
|
|
||||||
Dest net.Address
|
|
||||||
OriginalDest net.Address
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *EndpointOverrideReader) ReadMultiBuffer() (MultiBuffer, error) {
|
|
||||||
mb, err := r.Reader.ReadMultiBuffer()
|
|
||||||
if err == nil {
|
|
||||||
for _, b := range mb {
|
|
||||||
if b.UDP != nil && b.UDP.Address == r.OriginalDest {
|
|
||||||
b.UDP.Address = r.Dest
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return mb, err
|
|
||||||
}
|
|
||||||
|
|
||||||
type EndpointOverrideWriter struct {
|
|
||||||
Writer
|
|
||||||
Dest net.Address
|
|
||||||
OriginalDest net.Address
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *EndpointOverrideWriter) WriteMultiBuffer(mb MultiBuffer) error {
|
|
||||||
for _, b := range mb {
|
|
||||||
if b.UDP != nil && b.UDP.Address == w.Dest {
|
|
||||||
b.UDP.Address = w.OriginalDest
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return w.Writer.WriteMultiBuffer(mb)
|
|
||||||
}
|
|
||||||
@@ -1,175 +0,0 @@
|
|||||||
package buf
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/common/xray"
|
|
||||||
"github.com/sagernet/sing-box/common/xray/errors"
|
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
|
||||||
)
|
|
||||||
|
|
||||||
func readOneUDP(r io.Reader) (*Buffer, error) {
|
|
||||||
b := New()
|
|
||||||
for i := 0; i < 64; i++ {
|
|
||||||
_, err := b.ReadFrom(r)
|
|
||||||
if !b.IsEmpty() {
|
|
||||||
return b, nil
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
b.Release()
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
b.Release()
|
|
||||||
return nil, E.New("Reader returns too many empty payloads.")
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReadBuffer reads a Buffer from the given reader.
|
|
||||||
func ReadBuffer(r io.Reader) (*Buffer, error) {
|
|
||||||
b := New()
|
|
||||||
n, err := b.ReadFrom(r)
|
|
||||||
if n > 0 {
|
|
||||||
return b, err
|
|
||||||
}
|
|
||||||
b.Release()
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// BufferedReader is a Reader that keeps its internal buffer.
|
|
||||||
type BufferedReader struct {
|
|
||||||
// Reader is the underlying reader to be read from
|
|
||||||
Reader Reader
|
|
||||||
// Buffer is the internal buffer to be read from first
|
|
||||||
Buffer MultiBuffer
|
|
||||||
// Splitter is a function to read bytes from MultiBuffer
|
|
||||||
Splitter func(MultiBuffer, []byte) (MultiBuffer, int)
|
|
||||||
}
|
|
||||||
|
|
||||||
// BufferedBytes returns the number of bytes that is cached in this reader.
|
|
||||||
func (r *BufferedReader) BufferedBytes() int32 {
|
|
||||||
return r.Buffer.Len()
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReadByte implements io.ByteReader.
|
|
||||||
func (r *BufferedReader) ReadByte() (byte, error) {
|
|
||||||
var b [1]byte
|
|
||||||
_, err := r.Read(b[:])
|
|
||||||
return b[0], err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read implements io.Reader. It reads from internal buffer first (if available) and then reads from the underlying reader.
|
|
||||||
func (r *BufferedReader) Read(b []byte) (int, error) {
|
|
||||||
spliter := r.Splitter
|
|
||||||
if spliter == nil {
|
|
||||||
spliter = SplitBytes
|
|
||||||
}
|
|
||||||
|
|
||||||
if !r.Buffer.IsEmpty() {
|
|
||||||
buffer, nBytes := spliter(r.Buffer, b)
|
|
||||||
r.Buffer = buffer
|
|
||||||
if r.Buffer.IsEmpty() {
|
|
||||||
r.Buffer = nil
|
|
||||||
}
|
|
||||||
return nBytes, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
mb, err := r.Reader.ReadMultiBuffer()
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
mb, nBytes := spliter(mb, b)
|
|
||||||
if !mb.IsEmpty() {
|
|
||||||
r.Buffer = mb
|
|
||||||
}
|
|
||||||
return nBytes, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReadMultiBuffer implements Reader.
|
|
||||||
func (r *BufferedReader) ReadMultiBuffer() (MultiBuffer, error) {
|
|
||||||
if !r.Buffer.IsEmpty() {
|
|
||||||
mb := r.Buffer
|
|
||||||
r.Buffer = nil
|
|
||||||
return mb, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return r.Reader.ReadMultiBuffer()
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReadAtMost returns a MultiBuffer with at most size.
|
|
||||||
func (r *BufferedReader) ReadAtMost(size int32) (MultiBuffer, error) {
|
|
||||||
if r.Buffer.IsEmpty() {
|
|
||||||
mb, err := r.Reader.ReadMultiBuffer()
|
|
||||||
if mb.IsEmpty() && err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
r.Buffer = mb
|
|
||||||
}
|
|
||||||
|
|
||||||
rb, mb := SplitSize(r.Buffer, size)
|
|
||||||
r.Buffer = rb
|
|
||||||
if r.Buffer.IsEmpty() {
|
|
||||||
r.Buffer = nil
|
|
||||||
}
|
|
||||||
return mb, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *BufferedReader) writeToInternal(writer io.Writer) (int64, error) {
|
|
||||||
mbWriter := NewWriter(writer)
|
|
||||||
var sc SizeCounter
|
|
||||||
if r.Buffer != nil {
|
|
||||||
sc.Size = int64(r.Buffer.Len())
|
|
||||||
if err := mbWriter.WriteMultiBuffer(r.Buffer); err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
r.Buffer = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
err := Copy(r.Reader, mbWriter, CountSize(&sc))
|
|
||||||
return sc.Size, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// WriteTo implements io.WriterTo.
|
|
||||||
func (r *BufferedReader) WriteTo(writer io.Writer) (int64, error) {
|
|
||||||
nBytes, err := r.writeToInternal(writer)
|
|
||||||
if errors.Cause(err) == io.EOF {
|
|
||||||
return nBytes, nil
|
|
||||||
}
|
|
||||||
return nBytes, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Interrupt implements common.Interruptible.
|
|
||||||
func (r *BufferedReader) Interrupt() {
|
|
||||||
common.Interrupt(r.Reader)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close implements io.Closer.
|
|
||||||
func (r *BufferedReader) Close() error {
|
|
||||||
return common.Close(r.Reader)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SingleReader is a Reader that read one Buffer every time.
|
|
||||||
type SingleReader struct {
|
|
||||||
io.Reader
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReadMultiBuffer implements Reader.
|
|
||||||
func (r *SingleReader) ReadMultiBuffer() (MultiBuffer, error) {
|
|
||||||
b, err := ReadBuffer(r.Reader)
|
|
||||||
return MultiBuffer{b}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// PacketReader is a Reader that read one Buffer every time.
|
|
||||||
type PacketReader struct {
|
|
||||||
io.Reader
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReadMultiBuffer implements Reader.
|
|
||||||
func (r *PacketReader) ReadMultiBuffer() (MultiBuffer, error) {
|
|
||||||
b, err := readOneUDP(r.Reader)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return MultiBuffer{b}, nil
|
|
||||||
}
|
|
||||||
@@ -1,270 +0,0 @@
|
|||||||
package buf
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io"
|
|
||||||
"net"
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/common/xray"
|
|
||||||
"github.com/sagernet/sing-box/common/xray/errors"
|
|
||||||
"github.com/sagernet/sing-box/common/xray/stats"
|
|
||||||
)
|
|
||||||
|
|
||||||
// BufferToBytesWriter is a Writer that writes alloc.Buffer into underlying writer.
|
|
||||||
type BufferToBytesWriter struct {
|
|
||||||
io.Writer
|
|
||||||
|
|
||||||
counter stats.Counter
|
|
||||||
cache [][]byte
|
|
||||||
}
|
|
||||||
|
|
||||||
// WriteMultiBuffer implements Writer. This method takes ownership of the given buffer.
|
|
||||||
func (w *BufferToBytesWriter) WriteMultiBuffer(mb MultiBuffer) error {
|
|
||||||
defer ReleaseMulti(mb)
|
|
||||||
|
|
||||||
size := mb.Len()
|
|
||||||
if size == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(mb) == 1 {
|
|
||||||
return WriteAllBytes(w.Writer, mb[0].Bytes(), w.counter)
|
|
||||||
}
|
|
||||||
|
|
||||||
if cap(w.cache) < len(mb) {
|
|
||||||
w.cache = make([][]byte, 0, len(mb))
|
|
||||||
}
|
|
||||||
|
|
||||||
bs := w.cache
|
|
||||||
for _, b := range mb {
|
|
||||||
bs = append(bs, b.Bytes())
|
|
||||||
}
|
|
||||||
|
|
||||||
defer func() {
|
|
||||||
for idx := range bs {
|
|
||||||
bs[idx] = nil
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
nb := net.Buffers(bs)
|
|
||||||
wc := int64(0)
|
|
||||||
defer func() {
|
|
||||||
if w.counter != nil {
|
|
||||||
w.counter.Add(wc)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
for size > 0 {
|
|
||||||
n, err := nb.WriteTo(w.Writer)
|
|
||||||
wc += n
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
size -= int32(n)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReadFrom implements io.ReaderFrom.
|
|
||||||
func (w *BufferToBytesWriter) ReadFrom(reader io.Reader) (int64, error) {
|
|
||||||
var sc SizeCounter
|
|
||||||
err := Copy(NewReader(reader), w, CountSize(&sc))
|
|
||||||
return sc.Size, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// BufferedWriter is a Writer with internal buffer.
|
|
||||||
type BufferedWriter struct {
|
|
||||||
sync.Mutex
|
|
||||||
writer Writer
|
|
||||||
buffer *Buffer
|
|
||||||
buffered bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewBufferedWriter creates a new BufferedWriter.
|
|
||||||
func NewBufferedWriter(writer Writer) *BufferedWriter {
|
|
||||||
return &BufferedWriter{
|
|
||||||
writer: writer,
|
|
||||||
buffer: New(),
|
|
||||||
buffered: true,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// WriteByte implements io.ByteWriter.
|
|
||||||
func (w *BufferedWriter) WriteByte(c byte) error {
|
|
||||||
return common.Error2(w.Write([]byte{c}))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write implements io.Writer.
|
|
||||||
func (w *BufferedWriter) Write(b []byte) (int, error) {
|
|
||||||
if len(b) == 0 {
|
|
||||||
return 0, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
w.Lock()
|
|
||||||
defer w.Unlock()
|
|
||||||
|
|
||||||
if !w.buffered {
|
|
||||||
if writer, ok := w.writer.(io.Writer); ok {
|
|
||||||
return writer.Write(b)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
totalBytes := 0
|
|
||||||
for len(b) > 0 {
|
|
||||||
if w.buffer == nil {
|
|
||||||
w.buffer = New()
|
|
||||||
}
|
|
||||||
|
|
||||||
nBytes, err := w.buffer.Write(b)
|
|
||||||
totalBytes += nBytes
|
|
||||||
if err != nil {
|
|
||||||
return totalBytes, err
|
|
||||||
}
|
|
||||||
if !w.buffered || w.buffer.IsFull() {
|
|
||||||
if err := w.flushInternal(); err != nil {
|
|
||||||
return totalBytes, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
b = b[nBytes:]
|
|
||||||
}
|
|
||||||
|
|
||||||
return totalBytes, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// WriteMultiBuffer implements Writer. It takes ownership of the given MultiBuffer.
|
|
||||||
func (w *BufferedWriter) WriteMultiBuffer(b MultiBuffer) error {
|
|
||||||
if b.IsEmpty() {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
w.Lock()
|
|
||||||
defer w.Unlock()
|
|
||||||
|
|
||||||
if !w.buffered {
|
|
||||||
return w.writer.WriteMultiBuffer(b)
|
|
||||||
}
|
|
||||||
|
|
||||||
reader := MultiBufferContainer{
|
|
||||||
MultiBuffer: b,
|
|
||||||
}
|
|
||||||
defer reader.Close()
|
|
||||||
|
|
||||||
for !reader.MultiBuffer.IsEmpty() {
|
|
||||||
if w.buffer == nil {
|
|
||||||
w.buffer = New()
|
|
||||||
}
|
|
||||||
common.Must2(w.buffer.ReadFrom(&reader))
|
|
||||||
if w.buffer.IsFull() {
|
|
||||||
if err := w.flushInternal(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Flush flushes buffered content into underlying writer.
|
|
||||||
func (w *BufferedWriter) Flush() error {
|
|
||||||
w.Lock()
|
|
||||||
defer w.Unlock()
|
|
||||||
|
|
||||||
return w.flushInternal()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *BufferedWriter) flushInternal() error {
|
|
||||||
if w.buffer.IsEmpty() {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
b := w.buffer
|
|
||||||
w.buffer = nil
|
|
||||||
|
|
||||||
if writer, ok := w.writer.(io.Writer); ok {
|
|
||||||
err := WriteAllBytes(writer, b.Bytes(), nil)
|
|
||||||
b.Release()
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return w.writer.WriteMultiBuffer(MultiBuffer{b})
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetBuffered sets whether the internal buffer is used. If set to false, Flush() will be called to clear the buffer.
|
|
||||||
func (w *BufferedWriter) SetBuffered(f bool) error {
|
|
||||||
w.Lock()
|
|
||||||
defer w.Unlock()
|
|
||||||
|
|
||||||
w.buffered = f
|
|
||||||
if !f {
|
|
||||||
return w.flushInternal()
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReadFrom implements io.ReaderFrom.
|
|
||||||
func (w *BufferedWriter) ReadFrom(reader io.Reader) (int64, error) {
|
|
||||||
if err := w.SetBuffered(false); err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var sc SizeCounter
|
|
||||||
err := Copy(NewReader(reader), w, CountSize(&sc))
|
|
||||||
return sc.Size, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close implements io.Closable.
|
|
||||||
func (w *BufferedWriter) Close() error {
|
|
||||||
if err := w.Flush(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return common.Close(w.writer)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SequentialWriter is a Writer that writes MultiBuffer sequentially into the underlying io.Writer.
|
|
||||||
type SequentialWriter struct {
|
|
||||||
io.Writer
|
|
||||||
}
|
|
||||||
|
|
||||||
// WriteMultiBuffer implements Writer.
|
|
||||||
func (w *SequentialWriter) WriteMultiBuffer(mb MultiBuffer) error {
|
|
||||||
mb, err := WriteMultiBuffer(w.Writer, mb)
|
|
||||||
ReleaseMulti(mb)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
type noOpWriter byte
|
|
||||||
|
|
||||||
func (noOpWriter) WriteMultiBuffer(b MultiBuffer) error {
|
|
||||||
ReleaseMulti(b)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (noOpWriter) Write(b []byte) (int, error) {
|
|
||||||
return len(b), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (noOpWriter) ReadFrom(reader io.Reader) (int64, error) {
|
|
||||||
b := New()
|
|
||||||
defer b.Release()
|
|
||||||
|
|
||||||
totalBytes := int64(0)
|
|
||||||
for {
|
|
||||||
b.Clear()
|
|
||||||
_, err := b.ReadFrom(reader)
|
|
||||||
totalBytes += int64(b.Len())
|
|
||||||
if err != nil {
|
|
||||||
if errors.Cause(err) == io.EOF {
|
|
||||||
return totalBytes, nil
|
|
||||||
}
|
|
||||||
return totalBytes, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
// Discard is a Writer that swallows all contents written in.
|
|
||||||
Discard Writer = noOpWriter(0)
|
|
||||||
|
|
||||||
// DiscardBytes is an io.Writer that swallows all contents written in.
|
|
||||||
DiscardBytes io.Writer = noOpWriter(0)
|
|
||||||
)
|
|
||||||
@@ -1,72 +0,0 @@
|
|||||||
package bytespool
|
|
||||||
|
|
||||||
import "sync"
|
|
||||||
|
|
||||||
func createAllocFunc(size int32) func() interface{} {
|
|
||||||
return func() interface{} {
|
|
||||||
return make([]byte, size)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// The following parameters controls the size of buffer pools.
|
|
||||||
// There are numPools pools. Starting from 2k size, the size of each pool is sizeMulti of the previous one.
|
|
||||||
// Package buf is guaranteed to not use buffers larger than the largest pool.
|
|
||||||
// Other packets may use larger buffers.
|
|
||||||
const (
|
|
||||||
numPools = 4
|
|
||||||
sizeMulti = 4
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
pool [numPools]sync.Pool
|
|
||||||
poolSize [numPools]int32
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
size := int32(2048)
|
|
||||||
for i := 0; i < numPools; i++ {
|
|
||||||
pool[i] = sync.Pool{
|
|
||||||
New: createAllocFunc(size),
|
|
||||||
}
|
|
||||||
poolSize[i] = size
|
|
||||||
size *= sizeMulti
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetPool returns a sync.Pool that generates bytes array with at least the given size.
|
|
||||||
// It may return nil if no such pool exists.
|
|
||||||
//
|
|
||||||
// xray:api:stable
|
|
||||||
func GetPool(size int32) *sync.Pool {
|
|
||||||
for idx, ps := range poolSize {
|
|
||||||
if size <= ps {
|
|
||||||
return &pool[idx]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Alloc returns a byte slice with at least the given size. Minimum size of returned slice is 2048.
|
|
||||||
//
|
|
||||||
// xray:api:stable
|
|
||||||
func Alloc(size int32) []byte {
|
|
||||||
pool := GetPool(size)
|
|
||||||
if pool != nil {
|
|
||||||
return pool.Get().([]byte)
|
|
||||||
}
|
|
||||||
return make([]byte, size)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Free puts a byte slice into the internal pool.
|
|
||||||
//
|
|
||||||
// xray:api:stable
|
|
||||||
func Free(b []byte) {
|
|
||||||
size := int32(cap(b))
|
|
||||||
b = b[0:cap(b)]
|
|
||||||
for i := numPools - 1; i >= 0; i-- {
|
|
||||||
if size >= poolSize[i] {
|
|
||||||
pool[i].Put(b)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
package common
|
|
||||||
|
|
||||||
// Must panics if err is not nil.
|
|
||||||
func Must(err error) {
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Must2 panics if the second parameter is not nil, otherwise returns the first parameter.
|
|
||||||
func Must2(v interface{}, err error) interface{} {
|
|
||||||
Must(err)
|
|
||||||
return v
|
|
||||||
}
|
|
||||||
|
|
||||||
// Error2 returns the err from the 2nd parameter.
|
|
||||||
func Error2(v interface{}, err error) error {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
package crypto
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/rand"
|
|
||||||
"math/big"
|
|
||||||
)
|
|
||||||
|
|
||||||
func RandBetween(from int64, to int64) int64 {
|
|
||||||
if from == to {
|
|
||||||
return from
|
|
||||||
}
|
|
||||||
bigInt, _ := rand.Int(rand.Reader, big.NewInt(to-from))
|
|
||||||
return from + bigInt.Int64()
|
|
||||||
}
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
package errors
|
|
||||||
|
|
||||||
type hasInnerError interface {
|
|
||||||
// Unwrap returns the underlying error of this one.
|
|
||||||
Unwrap() error
|
|
||||||
}
|
|
||||||
|
|
||||||
func Cause(err error) error {
|
|
||||||
if err == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
L:
|
|
||||||
for {
|
|
||||||
switch inner := err.(type) {
|
|
||||||
case hasInnerError:
|
|
||||||
if inner.Unwrap() == nil {
|
|
||||||
break L
|
|
||||||
}
|
|
||||||
err = inner.Unwrap()
|
|
||||||
default:
|
|
||||||
break L
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
@@ -1,52 +0,0 @@
|
|||||||
package common
|
|
||||||
|
|
||||||
// Closable is the interface for objects that can release its resources.
|
|
||||||
//
|
|
||||||
// xray:api:beta
|
|
||||||
type Closable interface {
|
|
||||||
// Close release all resources used by this object, including goroutines.
|
|
||||||
Close() error
|
|
||||||
}
|
|
||||||
|
|
||||||
// Interruptible is an interface for objects that can be stopped before its completion.
|
|
||||||
//
|
|
||||||
// xray:api:beta
|
|
||||||
type Interruptible interface {
|
|
||||||
Interrupt()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close closes the obj if it is a Closable.
|
|
||||||
//
|
|
||||||
// xray:api:beta
|
|
||||||
func Close(obj interface{}) error {
|
|
||||||
if c, ok := obj.(Closable); ok {
|
|
||||||
return c.Close()
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Interrupt calls Interrupt() if object implements Interruptible interface, or Close() if the object implements Closable interface.
|
|
||||||
//
|
|
||||||
// xray:api:beta
|
|
||||||
func Interrupt(obj interface{}) error {
|
|
||||||
if c, ok := obj.(Interruptible); ok {
|
|
||||||
c.Interrupt()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return Close(obj)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Runnable is the interface for objects that can start to work and stop on demand.
|
|
||||||
type Runnable interface {
|
|
||||||
// Start starts the runnable object. Upon the method returning nil, the object begins to function properly.
|
|
||||||
Start() error
|
|
||||||
|
|
||||||
Closable
|
|
||||||
}
|
|
||||||
|
|
||||||
// HasType is the interface for objects that knows its type.
|
|
||||||
type HasType interface {
|
|
||||||
// Type returns the type of the object.
|
|
||||||
// Usually it returns (*Type)(nil) of the object.
|
|
||||||
Type() interface{}
|
|
||||||
}
|
|
||||||
@@ -1,62 +0,0 @@
|
|||||||
package badoption
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/common/xray/crypto"
|
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Range struct {
|
|
||||||
From int32 `json:"from"`
|
|
||||||
To int32 `json:"to"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Range) Build() *Range {
|
|
||||||
return (*Range)(c)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Range) MarshalJSON() ([]byte, error) {
|
|
||||||
return json.Marshal(fmt.Sprintf("%d-%d", c.From, c.To))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Range) UnmarshalJSON(content []byte) error {
|
|
||||||
var rangeValue struct {
|
|
||||||
From int32 `json:"from"`
|
|
||||||
To int32 `json:"to"`
|
|
||||||
}
|
|
||||||
var stringValue string
|
|
||||||
err := json.Unmarshal(content, &stringValue)
|
|
||||||
if err == nil {
|
|
||||||
parts := strings.Split(stringValue, "-")
|
|
||||||
if len(parts) != 2 {
|
|
||||||
return E.New("invalid length of range parts")
|
|
||||||
}
|
|
||||||
from, err := strconv.ParseInt(parts[0], 10, 32)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
to, err := strconv.ParseInt(parts[1], 10, 32)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
rangeValue.From, rangeValue.To = int32(from), int32(to)
|
|
||||||
} else {
|
|
||||||
err := json.Unmarshal(content, &rangeValue)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if rangeValue.From > rangeValue.To {
|
|
||||||
return E.New("invalid range")
|
|
||||||
}
|
|
||||||
*c = Range{rangeValue.From, rangeValue.To}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c Range) Rand() int32 {
|
|
||||||
return int32(crypto.RandBetween(int64(c.From), int64(c.To)))
|
|
||||||
}
|
|
||||||
@@ -1,181 +0,0 @@
|
|||||||
package net
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"net"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
// LocalHostIP is a constant value for localhost IP in IPv4.
|
|
||||||
LocalHostIP = IPAddress([]byte{127, 0, 0, 1})
|
|
||||||
|
|
||||||
// AnyIP is a constant value for any IP in IPv4.
|
|
||||||
AnyIP = IPAddress([]byte{0, 0, 0, 0})
|
|
||||||
|
|
||||||
// LocalHostDomain is a constant value for localhost domain.
|
|
||||||
LocalHostDomain = DomainAddress("localhost")
|
|
||||||
|
|
||||||
// LocalHostIPv6 is a constant value for localhost IP in IPv6.
|
|
||||||
LocalHostIPv6 = IPAddress([]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1})
|
|
||||||
|
|
||||||
// AnyIPv6 is a constant value for any IP in IPv6.
|
|
||||||
AnyIPv6 = IPAddress([]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0})
|
|
||||||
)
|
|
||||||
|
|
||||||
// AddressFamily is the type of address.
|
|
||||||
type AddressFamily byte
|
|
||||||
|
|
||||||
const (
|
|
||||||
// AddressFamilyIPv4 represents address as IPv4
|
|
||||||
AddressFamilyIPv4 = AddressFamily(0)
|
|
||||||
|
|
||||||
// AddressFamilyIPv6 represents address as IPv6
|
|
||||||
AddressFamilyIPv6 = AddressFamily(1)
|
|
||||||
|
|
||||||
// AddressFamilyDomain represents address as Domain
|
|
||||||
AddressFamilyDomain = AddressFamily(2)
|
|
||||||
)
|
|
||||||
|
|
||||||
// IsIPv4 returns true if current AddressFamily is IPv4.
|
|
||||||
func (af AddressFamily) IsIPv4() bool {
|
|
||||||
return af == AddressFamilyIPv4
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsIPv6 returns true if current AddressFamily is IPv6.
|
|
||||||
func (af AddressFamily) IsIPv6() bool {
|
|
||||||
return af == AddressFamilyIPv6
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsIP returns true if current AddressFamily is IPv6 or IPv4.
|
|
||||||
func (af AddressFamily) IsIP() bool {
|
|
||||||
return af == AddressFamilyIPv4 || af == AddressFamilyIPv6
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsDomain returns true if current AddressFamily is Domain.
|
|
||||||
func (af AddressFamily) IsDomain() bool {
|
|
||||||
return af == AddressFamilyDomain
|
|
||||||
}
|
|
||||||
|
|
||||||
// Address represents a network address to be communicated with. It may be an IP address or domain
|
|
||||||
// address, not both. This interface doesn't resolve IP address for a given domain.
|
|
||||||
type Address interface {
|
|
||||||
IP() net.IP // IP of this Address
|
|
||||||
Domain() string // Domain of this Address
|
|
||||||
Family() AddressFamily
|
|
||||||
|
|
||||||
String() string // String representation of this Address
|
|
||||||
}
|
|
||||||
|
|
||||||
func isAlphaNum(c byte) bool {
|
|
||||||
return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')
|
|
||||||
}
|
|
||||||
|
|
||||||
// ParseAddress parses a string into an Address. The return value will be an IPAddress when
|
|
||||||
// the string is in the form of IPv4 or IPv6 address, or a DomainAddress otherwise.
|
|
||||||
func ParseAddress(addr string) Address {
|
|
||||||
// Handle IPv6 address in form as "[2001:4860:0:2001::68]"
|
|
||||||
lenAddr := len(addr)
|
|
||||||
if lenAddr > 0 && addr[0] == '[' && addr[lenAddr-1] == ']' {
|
|
||||||
addr = addr[1 : lenAddr-1]
|
|
||||||
lenAddr -= 2
|
|
||||||
}
|
|
||||||
|
|
||||||
if lenAddr > 0 && (!isAlphaNum(addr[0]) || !isAlphaNum(addr[len(addr)-1])) {
|
|
||||||
addr = strings.TrimSpace(addr)
|
|
||||||
}
|
|
||||||
|
|
||||||
ip := net.ParseIP(addr)
|
|
||||||
if ip != nil {
|
|
||||||
return IPAddress(ip)
|
|
||||||
}
|
|
||||||
return DomainAddress(addr)
|
|
||||||
}
|
|
||||||
|
|
||||||
var bytes0 = []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
|
|
||||||
|
|
||||||
// IPAddress creates an Address with given IP.
|
|
||||||
func IPAddress(ip []byte) Address {
|
|
||||||
switch len(ip) {
|
|
||||||
case net.IPv4len:
|
|
||||||
var addr ipv4Address = [4]byte{ip[0], ip[1], ip[2], ip[3]}
|
|
||||||
return addr
|
|
||||||
case net.IPv6len:
|
|
||||||
if bytes.Equal(ip[:10], bytes0) && ip[10] == 0xff && ip[11] == 0xff {
|
|
||||||
return IPAddress(ip[12:16])
|
|
||||||
}
|
|
||||||
var addr ipv6Address = [16]byte{
|
|
||||||
ip[0], ip[1], ip[2], ip[3],
|
|
||||||
ip[4], ip[5], ip[6], ip[7],
|
|
||||||
ip[8], ip[9], ip[10], ip[11],
|
|
||||||
ip[12], ip[13], ip[14], ip[15],
|
|
||||||
}
|
|
||||||
return addr
|
|
||||||
default:
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// DomainAddress creates an Address with given domain.
|
|
||||||
// This is an internal function that forcibly converts a string to domain.
|
|
||||||
// It's mainly used in test files and mux.
|
|
||||||
// Unless you have a specific reason, use net.ParseAddress instead,
|
|
||||||
// as this function does not check whether the input is an IP address.
|
|
||||||
// Otherwise, you will get strange results like domain: 1.1.1.1
|
|
||||||
func DomainAddress(domain string) Address {
|
|
||||||
return domainAddress(domain)
|
|
||||||
}
|
|
||||||
|
|
||||||
type ipv4Address [4]byte
|
|
||||||
|
|
||||||
func (a ipv4Address) IP() net.IP {
|
|
||||||
return net.IP(a[:])
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ipv4Address) Domain() string {
|
|
||||||
panic("Calling Domain() on an IPv4Address.")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ipv4Address) Family() AddressFamily {
|
|
||||||
return AddressFamilyIPv4
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a ipv4Address) String() string {
|
|
||||||
return a.IP().String()
|
|
||||||
}
|
|
||||||
|
|
||||||
type ipv6Address [16]byte
|
|
||||||
|
|
||||||
func (a ipv6Address) IP() net.IP {
|
|
||||||
return net.IP(a[:])
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ipv6Address) Domain() string {
|
|
||||||
panic("Calling Domain() on an IPv6Address.")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ipv6Address) Family() AddressFamily {
|
|
||||||
return AddressFamilyIPv6
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a ipv6Address) String() string {
|
|
||||||
return "[" + a.IP().String() + "]"
|
|
||||||
}
|
|
||||||
|
|
||||||
type domainAddress string
|
|
||||||
|
|
||||||
func (domainAddress) IP() net.IP {
|
|
||||||
panic("Calling IP() on a DomainAddress.")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a domainAddress) Domain() string {
|
|
||||||
return string(a)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (domainAddress) Family() AddressFamily {
|
|
||||||
return AddressFamilyDomain
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a domainAddress) String() string {
|
|
||||||
return a.Domain()
|
|
||||||
}
|
|
||||||
@@ -1,146 +0,0 @@
|
|||||||
package net
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Destination represents a network destination including address and protocol (tcp / udp).
|
|
||||||
type Destination struct {
|
|
||||||
Address Address
|
|
||||||
Port Port
|
|
||||||
Network Network
|
|
||||||
}
|
|
||||||
|
|
||||||
// DestinationFromAddr generates a Destination from a net address.
|
|
||||||
func DestinationFromAddr(addr net.Addr) Destination {
|
|
||||||
switch addr := addr.(type) {
|
|
||||||
case *net.TCPAddr:
|
|
||||||
return TCPDestination(IPAddress(addr.IP), Port(addr.Port))
|
|
||||||
case *net.UDPAddr:
|
|
||||||
return UDPDestination(IPAddress(addr.IP), Port(addr.Port))
|
|
||||||
case *net.UnixAddr:
|
|
||||||
return UnixDestination(DomainAddress(addr.Name))
|
|
||||||
default:
|
|
||||||
panic("Net: Unknown address type.")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ParseDestination converts a destination from its string presentation.
|
|
||||||
func ParseDestination(dest string) (Destination, error) {
|
|
||||||
d := Destination{
|
|
||||||
Address: AnyIP,
|
|
||||||
Port: Port(0),
|
|
||||||
}
|
|
||||||
if strings.HasPrefix(dest, "tcp:") {
|
|
||||||
d.Network = Network_TCP
|
|
||||||
dest = dest[4:]
|
|
||||||
} else if strings.HasPrefix(dest, "udp:") {
|
|
||||||
d.Network = Network_UDP
|
|
||||||
dest = dest[4:]
|
|
||||||
} else if strings.HasPrefix(dest, "unix:") {
|
|
||||||
d = UnixDestination(DomainAddress(dest[5:]))
|
|
||||||
return d, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
hstr, pstr, err := SplitHostPort(dest)
|
|
||||||
if err != nil {
|
|
||||||
return d, err
|
|
||||||
}
|
|
||||||
if len(hstr) > 0 {
|
|
||||||
d.Address = ParseAddress(hstr)
|
|
||||||
}
|
|
||||||
if len(pstr) > 0 {
|
|
||||||
port, err := PortFromString(pstr)
|
|
||||||
if err != nil {
|
|
||||||
return d, err
|
|
||||||
}
|
|
||||||
d.Port = port
|
|
||||||
}
|
|
||||||
return d, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// TCPDestination creates a TCP destination with given address
|
|
||||||
func TCPDestination(address Address, port Port) Destination {
|
|
||||||
return Destination{
|
|
||||||
Network: Network_TCP,
|
|
||||||
Address: address,
|
|
||||||
Port: port,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// UDPDestination creates a UDP destination with given address
|
|
||||||
func UDPDestination(address Address, port Port) Destination {
|
|
||||||
return Destination{
|
|
||||||
Network: Network_UDP,
|
|
||||||
Address: address,
|
|
||||||
Port: port,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnixDestination creates a Unix destination with given address
|
|
||||||
func UnixDestination(address Address) Destination {
|
|
||||||
return Destination{
|
|
||||||
Network: Network_UNIX,
|
|
||||||
Address: address,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// NetAddr returns the network address in this Destination in string form.
|
|
||||||
func (d Destination) NetAddr() string {
|
|
||||||
addr := ""
|
|
||||||
if d.Network == Network_TCP || d.Network == Network_UDP {
|
|
||||||
addr = d.Address.String() + ":" + d.Port.String()
|
|
||||||
} else if d.Network == Network_UNIX {
|
|
||||||
addr = d.Address.String()
|
|
||||||
}
|
|
||||||
return addr
|
|
||||||
}
|
|
||||||
|
|
||||||
// RawNetAddr converts a net.Addr from its Destination presentation.
|
|
||||||
func (d Destination) RawNetAddr() net.Addr {
|
|
||||||
var addr net.Addr
|
|
||||||
switch d.Network {
|
|
||||||
case Network_TCP:
|
|
||||||
if d.Address.Family().IsIP() {
|
|
||||||
addr = &net.TCPAddr{
|
|
||||||
IP: d.Address.IP(),
|
|
||||||
Port: int(d.Port),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case Network_UDP:
|
|
||||||
if d.Address.Family().IsIP() {
|
|
||||||
addr = &net.UDPAddr{
|
|
||||||
IP: d.Address.IP(),
|
|
||||||
Port: int(d.Port),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case Network_UNIX:
|
|
||||||
if d.Address.Family().IsDomain() {
|
|
||||||
addr = &net.UnixAddr{
|
|
||||||
Name: d.Address.String(),
|
|
||||||
Net: d.Network.SystemString(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return addr
|
|
||||||
}
|
|
||||||
|
|
||||||
// String returns the strings form of this Destination.
|
|
||||||
func (d Destination) String() string {
|
|
||||||
prefix := "unknown:"
|
|
||||||
switch d.Network {
|
|
||||||
case Network_TCP:
|
|
||||||
prefix = "tcp:"
|
|
||||||
case Network_UDP:
|
|
||||||
prefix = "udp:"
|
|
||||||
case Network_UNIX:
|
|
||||||
prefix = "unix:"
|
|
||||||
}
|
|
||||||
return prefix + d.NetAddr()
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsValid returns true if this Destination is valid.
|
|
||||||
func (d Destination) IsValid() bool {
|
|
||||||
return d.Network != Network_Unknown
|
|
||||||
}
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
package net
|
|
||||||
|
|
||||||
import "time"
|
|
||||||
|
|
||||||
// defines the maximum time an idle TCP session can survive in the tunnel, so
|
|
||||||
// it should be consistent across HTTP versions and with other transports.
|
|
||||||
const ConnIdleTimeout = 300 * time.Second
|
|
||||||
|
|
||||||
// consistent with quic-go
|
|
||||||
const QuicgoH3KeepAlivePeriod = 10 * time.Second
|
|
||||||
|
|
||||||
// consistent with chrome
|
|
||||||
const ChromeH2KeepAlivePeriod = 45 * time.Second
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
package net
|
|
||||||
|
|
||||||
type Network int32
|
|
||||||
|
|
||||||
const (
|
|
||||||
Network_Unknown Network = 0
|
|
||||||
Network_TCP Network = 2
|
|
||||||
Network_UDP Network = 3
|
|
||||||
Network_UNIX Network = 4
|
|
||||||
)
|
|
||||||
|
|
||||||
func (n Network) SystemString() string {
|
|
||||||
switch n {
|
|
||||||
case Network_TCP:
|
|
||||||
return "tcp"
|
|
||||||
case Network_UDP:
|
|
||||||
return "udp"
|
|
||||||
case Network_UNIX:
|
|
||||||
return "unix"
|
|
||||||
default:
|
|
||||||
return "unknown"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// HasNetwork returns true if the network list has a certain network.
|
|
||||||
func HasNetwork(list []Network, network Network) bool {
|
|
||||||
for _, value := range list {
|
|
||||||
if value == network {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
@@ -1,55 +0,0 @@
|
|||||||
package net
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/binary"
|
|
||||||
"strconv"
|
|
||||||
|
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Port represents a network port in TCP and UDP protocol.
|
|
||||||
type Port uint16
|
|
||||||
|
|
||||||
// PortFromBytes converts a byte array to a Port, assuming bytes are in big endian order.
|
|
||||||
// @unsafe Caller must ensure that the byte array has at least 2 elements.
|
|
||||||
func PortFromBytes(port []byte) Port {
|
|
||||||
return Port(binary.BigEndian.Uint16(port))
|
|
||||||
}
|
|
||||||
|
|
||||||
// PortFromInt converts an integer to a Port.
|
|
||||||
// @error when the integer is not positive or larger then 65535
|
|
||||||
func PortFromInt(val uint32) (Port, error) {
|
|
||||||
if val > 65535 {
|
|
||||||
return Port(0), E.New("invalid port range: ", val)
|
|
||||||
}
|
|
||||||
return Port(val), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// PortFromString converts a string to a Port.
|
|
||||||
// @error when the string is not an integer or the integral value is a not a valid Port.
|
|
||||||
func PortFromString(s string) (Port, error) {
|
|
||||||
val, err := strconv.ParseUint(s, 10, 32)
|
|
||||||
if err != nil {
|
|
||||||
return Port(0), E.New("invalid port range: ", s)
|
|
||||||
}
|
|
||||||
return PortFromInt(uint32(val))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Value return the corresponding uint16 value of a Port.
|
|
||||||
func (p Port) Value() uint16 {
|
|
||||||
return uint16(p)
|
|
||||||
}
|
|
||||||
|
|
||||||
// String returns the string presentation of a Port.
|
|
||||||
func (p Port) String() string {
|
|
||||||
return strconv.Itoa(int(p))
|
|
||||||
}
|
|
||||||
|
|
||||||
type MemoryPortRange struct {
|
|
||||||
From Port
|
|
||||||
To Port
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r MemoryPortRange) Contains(port Port) bool {
|
|
||||||
return r.From <= port && port <= r.To
|
|
||||||
}
|
|
||||||
@@ -1,84 +0,0 @@
|
|||||||
package net
|
|
||||||
|
|
||||||
import "net"
|
|
||||||
|
|
||||||
// DialTCP is an alias of net.DialTCP.
|
|
||||||
var (
|
|
||||||
DialTCP = net.DialTCP
|
|
||||||
DialUDP = net.DialUDP
|
|
||||||
DialUnix = net.DialUnix
|
|
||||||
Dial = net.Dial
|
|
||||||
)
|
|
||||||
|
|
||||||
type ListenConfig = net.ListenConfig
|
|
||||||
|
|
||||||
var (
|
|
||||||
Listen = net.Listen
|
|
||||||
ListenTCP = net.ListenTCP
|
|
||||||
ListenUDP = net.ListenUDP
|
|
||||||
ListenUnix = net.ListenUnix
|
|
||||||
)
|
|
||||||
|
|
||||||
var LookupIP = net.LookupIP
|
|
||||||
|
|
||||||
var FileConn = net.FileConn
|
|
||||||
|
|
||||||
// ParseIP is an alias of net.ParseIP
|
|
||||||
var ParseIP = net.ParseIP
|
|
||||||
|
|
||||||
var SplitHostPort = net.SplitHostPort
|
|
||||||
|
|
||||||
var CIDRMask = net.CIDRMask
|
|
||||||
|
|
||||||
type (
|
|
||||||
Addr = net.Addr
|
|
||||||
Conn = net.Conn
|
|
||||||
PacketConn = net.PacketConn
|
|
||||||
)
|
|
||||||
|
|
||||||
type (
|
|
||||||
TCPAddr = net.TCPAddr
|
|
||||||
TCPConn = net.TCPConn
|
|
||||||
)
|
|
||||||
|
|
||||||
type (
|
|
||||||
UDPAddr = net.UDPAddr
|
|
||||||
UDPConn = net.UDPConn
|
|
||||||
)
|
|
||||||
|
|
||||||
type (
|
|
||||||
UnixAddr = net.UnixAddr
|
|
||||||
UnixConn = net.UnixConn
|
|
||||||
)
|
|
||||||
|
|
||||||
// IP is an alias for net.IP.
|
|
||||||
type (
|
|
||||||
IP = net.IP
|
|
||||||
IPMask = net.IPMask
|
|
||||||
IPNet = net.IPNet
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
IPv4len = net.IPv4len
|
|
||||||
IPv6len = net.IPv6len
|
|
||||||
)
|
|
||||||
|
|
||||||
type (
|
|
||||||
Error = net.Error
|
|
||||||
AddrError = net.AddrError
|
|
||||||
)
|
|
||||||
|
|
||||||
type (
|
|
||||||
Dialer = net.Dialer
|
|
||||||
Listener = net.Listener
|
|
||||||
TCPListener = net.TCPListener
|
|
||||||
UnixListener = net.UnixListener
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
ResolveTCPAddr = net.ResolveTCPAddr
|
|
||||||
ResolveUDPAddr = net.ResolveUDPAddr
|
|
||||||
ResolveUnixAddr = net.ResolveUnixAddr
|
|
||||||
)
|
|
||||||
|
|
||||||
type Resolver = net.Resolver
|
|
||||||
@@ -1,215 +0,0 @@
|
|||||||
package pipe
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"io"
|
|
||||||
"runtime"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/common/xray"
|
|
||||||
"github.com/sagernet/sing-box/common/xray/buf"
|
|
||||||
"github.com/sagernet/sing-box/common/xray/signal"
|
|
||||||
"github.com/sagernet/sing-box/common/xray/signal/done"
|
|
||||||
)
|
|
||||||
|
|
||||||
type state byte
|
|
||||||
|
|
||||||
const (
|
|
||||||
open state = iota
|
|
||||||
closed
|
|
||||||
errord
|
|
||||||
)
|
|
||||||
|
|
||||||
type pipeOption struct {
|
|
||||||
limit int32 // maximum buffer size in bytes
|
|
||||||
discardOverflow bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *pipeOption) isFull(curSize int32) bool {
|
|
||||||
return o.limit >= 0 && curSize > o.limit
|
|
||||||
}
|
|
||||||
|
|
||||||
type pipe struct {
|
|
||||||
sync.Mutex
|
|
||||||
data buf.MultiBuffer
|
|
||||||
readSignal *signal.Notifier
|
|
||||||
writeSignal *signal.Notifier
|
|
||||||
done *done.Instance
|
|
||||||
errChan chan error
|
|
||||||
option pipeOption
|
|
||||||
state state
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
errBufferFull = errors.New("buffer full")
|
|
||||||
errSlowDown = errors.New("slow down")
|
|
||||||
)
|
|
||||||
|
|
||||||
func (p *pipe) Len() int32 {
|
|
||||||
data := p.data
|
|
||||||
if data == nil {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
return data.Len()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *pipe) getState(forRead bool) error {
|
|
||||||
switch p.state {
|
|
||||||
case open:
|
|
||||||
if !forRead && p.option.isFull(p.data.Len()) {
|
|
||||||
return errBufferFull
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
case closed:
|
|
||||||
if !forRead {
|
|
||||||
return io.ErrClosedPipe
|
|
||||||
}
|
|
||||||
if !p.data.IsEmpty() {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return io.EOF
|
|
||||||
case errord:
|
|
||||||
return io.ErrClosedPipe
|
|
||||||
default:
|
|
||||||
panic("impossible case")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *pipe) readMultiBufferInternal() (buf.MultiBuffer, error) {
|
|
||||||
p.Lock()
|
|
||||||
defer p.Unlock()
|
|
||||||
|
|
||||||
if err := p.getState(true); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
data := p.data
|
|
||||||
p.data = nil
|
|
||||||
return data, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *pipe) ReadMultiBuffer() (buf.MultiBuffer, error) {
|
|
||||||
for {
|
|
||||||
data, err := p.readMultiBufferInternal()
|
|
||||||
if data != nil || err != nil {
|
|
||||||
p.writeSignal.Signal()
|
|
||||||
return data, err
|
|
||||||
}
|
|
||||||
|
|
||||||
select {
|
|
||||||
case <-p.readSignal.Wait():
|
|
||||||
case <-p.done.Wait():
|
|
||||||
case err = <-p.errChan:
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *pipe) ReadMultiBufferTimeout(d time.Duration) (buf.MultiBuffer, error) {
|
|
||||||
timer := time.NewTimer(d)
|
|
||||||
defer timer.Stop()
|
|
||||||
|
|
||||||
for {
|
|
||||||
data, err := p.readMultiBufferInternal()
|
|
||||||
if data != nil || err != nil {
|
|
||||||
p.writeSignal.Signal()
|
|
||||||
return data, err
|
|
||||||
}
|
|
||||||
|
|
||||||
select {
|
|
||||||
case <-p.readSignal.Wait():
|
|
||||||
case <-p.done.Wait():
|
|
||||||
case <-timer.C:
|
|
||||||
return nil, buf.ErrReadTimeout
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *pipe) writeMultiBufferInternal(mb buf.MultiBuffer) error {
|
|
||||||
p.Lock()
|
|
||||||
defer p.Unlock()
|
|
||||||
|
|
||||||
if err := p.getState(false); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if p.data == nil {
|
|
||||||
p.data = mb
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
p.data, _ = buf.MergeMulti(p.data, mb)
|
|
||||||
return errSlowDown
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *pipe) WriteMultiBuffer(mb buf.MultiBuffer) error {
|
|
||||||
if mb.IsEmpty() {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
for {
|
|
||||||
err := p.writeMultiBufferInternal(mb)
|
|
||||||
if err == nil {
|
|
||||||
p.readSignal.Signal()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if err == errSlowDown {
|
|
||||||
p.readSignal.Signal()
|
|
||||||
|
|
||||||
// Yield current goroutine. Hopefully the reading counterpart can pick up the payload.
|
|
||||||
runtime.Gosched()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if err == errBufferFull && p.option.discardOverflow {
|
|
||||||
buf.ReleaseMulti(mb)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != errBufferFull {
|
|
||||||
buf.ReleaseMulti(mb)
|
|
||||||
p.readSignal.Signal()
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
select {
|
|
||||||
case <-p.writeSignal.Wait():
|
|
||||||
case <-p.done.Wait():
|
|
||||||
return io.ErrClosedPipe
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *pipe) Close() error {
|
|
||||||
p.Lock()
|
|
||||||
defer p.Unlock()
|
|
||||||
|
|
||||||
if p.state == closed || p.state == errord {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
p.state = closed
|
|
||||||
common.Must(p.done.Close())
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Interrupt implements common.Interruptible.
|
|
||||||
func (p *pipe) Interrupt() {
|
|
||||||
p.Lock()
|
|
||||||
defer p.Unlock()
|
|
||||||
|
|
||||||
if p.state == closed || p.state == errord {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
p.state = errord
|
|
||||||
|
|
||||||
if !p.data.IsEmpty() {
|
|
||||||
buf.ReleaseMulti(p.data)
|
|
||||||
p.data = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
common.Must(p.done.Close())
|
|
||||||
}
|
|
||||||
@@ -1,53 +0,0 @@
|
|||||||
package pipe
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/sagernet/sing-box/common/xray/signal"
|
|
||||||
"github.com/sagernet/sing-box/common/xray/signal/done"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Option for creating new Pipes.
|
|
||||||
type Option func(*pipeOption)
|
|
||||||
|
|
||||||
// WithoutSizeLimit returns an Option for Pipe to have no size limit.
|
|
||||||
func WithoutSizeLimit() Option {
|
|
||||||
return func(opt *pipeOption) {
|
|
||||||
opt.limit = -1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithSizeLimit returns an Option for Pipe to have the given size limit.
|
|
||||||
func WithSizeLimit(limit int32) Option {
|
|
||||||
return func(opt *pipeOption) {
|
|
||||||
opt.limit = limit
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// DiscardOverflow returns an Option for Pipe to discard writes if full.
|
|
||||||
func DiscardOverflow() Option {
|
|
||||||
return func(opt *pipeOption) {
|
|
||||||
opt.discardOverflow = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// New creates a new Reader and Writer that connects to each other.
|
|
||||||
func New(opts ...Option) (*Reader, *Writer) {
|
|
||||||
p := &pipe{
|
|
||||||
readSignal: signal.NewNotifier(),
|
|
||||||
writeSignal: signal.NewNotifier(),
|
|
||||||
done: done.New(),
|
|
||||||
errChan: make(chan error, 1),
|
|
||||||
option: pipeOption{
|
|
||||||
limit: -1,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, opt := range opts {
|
|
||||||
opt(&(p.option))
|
|
||||||
}
|
|
||||||
|
|
||||||
return &Reader{
|
|
||||||
pipe: p,
|
|
||||||
}, &Writer{
|
|
||||||
pipe: p,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
package pipe
|
|
||||||
|
|
||||||
import (
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/common/xray/buf"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Reader is a buf.Reader that reads content from a pipe.
|
|
||||||
type Reader struct {
|
|
||||||
pipe *pipe
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReadMultiBuffer implements buf.Reader.
|
|
||||||
func (r *Reader) ReadMultiBuffer() (buf.MultiBuffer, error) {
|
|
||||||
return r.pipe.ReadMultiBuffer()
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReadMultiBufferTimeout reads content from a pipe within the given duration, or returns buf.ErrTimeout otherwise.
|
|
||||||
func (r *Reader) ReadMultiBufferTimeout(d time.Duration) (buf.MultiBuffer, error) {
|
|
||||||
return r.pipe.ReadMultiBufferTimeout(d)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Interrupt implements common.Interruptible.
|
|
||||||
func (r *Reader) Interrupt() {
|
|
||||||
r.pipe.Interrupt()
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReturnAnError makes ReadMultiBuffer return an error, only once.
|
|
||||||
func (r *Reader) ReturnAnError(err error) {
|
|
||||||
r.pipe.errChan <- err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Recover catches an error set by ReturnAnError, if exists.
|
|
||||||
func (r *Reader) Recover() (err error) {
|
|
||||||
select {
|
|
||||||
case err = <-r.pipe.errChan:
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
package pipe
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/sagernet/sing-box/common/xray/buf"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Writer is a buf.Writer that writes data into a pipe.
|
|
||||||
type Writer struct {
|
|
||||||
pipe *pipe
|
|
||||||
}
|
|
||||||
|
|
||||||
// WriteMultiBuffer implements buf.Writer.
|
|
||||||
func (w *Writer) WriteMultiBuffer(mb buf.MultiBuffer) error {
|
|
||||||
return w.pipe.WriteMultiBuffer(mb)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close implements io.Closer. After the pipe is closed, writing to the pipe will return io.ErrClosedPipe, while reading will return io.EOF.
|
|
||||||
func (w *Writer) Close() error {
|
|
||||||
return w.pipe.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *Writer) Len() int32 {
|
|
||||||
return w.pipe.Len()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Interrupt implements common.Interruptible.
|
|
||||||
func (w *Writer) Interrupt() {
|
|
||||||
w.pipe.Interrupt()
|
|
||||||
}
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
package serial
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/binary"
|
|
||||||
"io"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ReadUint16 reads first two bytes from the reader, and then converts them to an uint16 value.
|
|
||||||
func ReadUint16(reader io.Reader) (uint16, error) {
|
|
||||||
var b [2]byte
|
|
||||||
if _, err := io.ReadFull(reader, b[:]); err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
return binary.BigEndian.Uint16(b[:]), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// WriteUint16 writes an uint16 value into writer.
|
|
||||||
func WriteUint16(writer io.Writer, value uint16) (int, error) {
|
|
||||||
var b [2]byte
|
|
||||||
binary.BigEndian.PutUint16(b[:], value)
|
|
||||||
return writer.Write(b[:])
|
|
||||||
}
|
|
||||||
|
|
||||||
// WriteUint64 writes an uint64 value into writer.
|
|
||||||
func WriteUint64(writer io.Writer, value uint64) (int, error) {
|
|
||||||
var b [8]byte
|
|
||||||
binary.BigEndian.PutUint64(b[:], value)
|
|
||||||
return writer.Write(b[:])
|
|
||||||
}
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
package serial
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ToString serializes an arbitrary value into string.
|
|
||||||
func ToString(v interface{}) string {
|
|
||||||
if v == nil {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
switch value := v.(type) {
|
|
||||||
case string:
|
|
||||||
return value
|
|
||||||
case *string:
|
|
||||||
return *value
|
|
||||||
case fmt.Stringer:
|
|
||||||
return value.String()
|
|
||||||
case error:
|
|
||||||
return value.Error()
|
|
||||||
default:
|
|
||||||
return fmt.Sprintf("%+v", value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Concat concatenates all input into a single string.
|
|
||||||
func Concat(v ...interface{}) string {
|
|
||||||
builder := strings.Builder{}
|
|
||||||
for _, value := range v {
|
|
||||||
builder.WriteString(ToString(value))
|
|
||||||
}
|
|
||||||
return builder.String()
|
|
||||||
}
|
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
package done
|
|
||||||
|
|
||||||
import (
|
|
||||||
"sync"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Instance is a utility for notifications of something being done.
|
|
||||||
type Instance struct {
|
|
||||||
access sync.Mutex
|
|
||||||
c chan struct{}
|
|
||||||
closed bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// New returns a new Done.
|
|
||||||
func New() *Instance {
|
|
||||||
return &Instance{
|
|
||||||
c: make(chan struct{}),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Done returns true if Close() is called.
|
|
||||||
func (d *Instance) Done() bool {
|
|
||||||
select {
|
|
||||||
case <-d.Wait():
|
|
||||||
return true
|
|
||||||
default:
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wait returns a channel for waiting for done.
|
|
||||||
func (d *Instance) Wait() <-chan struct{} {
|
|
||||||
return d.c
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close marks this Done 'done'. This method may be called multiple times. All calls after first call will have no effect on its status.
|
|
||||||
func (d *Instance) Close() error {
|
|
||||||
d.access.Lock()
|
|
||||||
defer d.access.Unlock()
|
|
||||||
|
|
||||||
if d.closed {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
d.closed = true
|
|
||||||
close(d.c)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
package signal
|
|
||||||
|
|
||||||
// Notifier is a utility for notifying changes. The change producer may notify changes multiple time, and the consumer may get notified asynchronously.
|
|
||||||
type Notifier struct {
|
|
||||||
c chan struct{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewNotifier creates a new Notifier.
|
|
||||||
func NewNotifier() *Notifier {
|
|
||||||
return &Notifier{
|
|
||||||
c: make(chan struct{}, 1),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Signal signals a change, usually by producer. This method never blocks.
|
|
||||||
func (n *Notifier) Signal() {
|
|
||||||
select {
|
|
||||||
case n.c <- struct{}{}:
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wait returns a channel for waiting for changes. The returned channel never gets closed.
|
|
||||||
func (n *Notifier) Wait() <-chan struct{} {
|
|
||||||
return n.c
|
|
||||||
}
|
|
||||||
@@ -1,105 +0,0 @@
|
|||||||
package pubsub
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/common/xray"
|
|
||||||
"github.com/sagernet/sing-box/common/xray/signal/done"
|
|
||||||
"github.com/sagernet/sing-box/common/xray/task"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Subscriber struct {
|
|
||||||
buffer chan interface{}
|
|
||||||
done *done.Instance
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Subscriber) push(msg interface{}) {
|
|
||||||
select {
|
|
||||||
case s.buffer <- msg:
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Subscriber) Wait() <-chan interface{} {
|
|
||||||
return s.buffer
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Subscriber) Close() error {
|
|
||||||
return s.done.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Subscriber) IsClosed() bool {
|
|
||||||
return s.done.Done()
|
|
||||||
}
|
|
||||||
|
|
||||||
type Service struct {
|
|
||||||
sync.RWMutex
|
|
||||||
subs map[string][]*Subscriber
|
|
||||||
ctask *task.Periodic
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewService() *Service {
|
|
||||||
s := &Service{
|
|
||||||
subs: make(map[string][]*Subscriber),
|
|
||||||
}
|
|
||||||
s.ctask = &task.Periodic{
|
|
||||||
Execute: s.Cleanup,
|
|
||||||
Interval: time.Second * 30,
|
|
||||||
}
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cleanup cleans up internal caches of subscribers.
|
|
||||||
// Visible for testing only.
|
|
||||||
func (s *Service) Cleanup() error {
|
|
||||||
s.Lock()
|
|
||||||
defer s.Unlock()
|
|
||||||
|
|
||||||
if len(s.subs) == 0 {
|
|
||||||
return errors.New("nothing to do")
|
|
||||||
}
|
|
||||||
|
|
||||||
for name, subs := range s.subs {
|
|
||||||
newSub := make([]*Subscriber, 0, len(s.subs))
|
|
||||||
for _, sub := range subs {
|
|
||||||
if !sub.IsClosed() {
|
|
||||||
newSub = append(newSub, sub)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(newSub) == 0 {
|
|
||||||
delete(s.subs, name)
|
|
||||||
} else {
|
|
||||||
s.subs[name] = newSub
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(s.subs) == 0 {
|
|
||||||
s.subs = make(map[string][]*Subscriber)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Service) Subscribe(name string) *Subscriber {
|
|
||||||
sub := &Subscriber{
|
|
||||||
buffer: make(chan interface{}, 16),
|
|
||||||
done: done.New(),
|
|
||||||
}
|
|
||||||
s.Lock()
|
|
||||||
s.subs[name] = append(s.subs[name], sub)
|
|
||||||
s.Unlock()
|
|
||||||
common.Must(s.ctask.Start())
|
|
||||||
return sub
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Service) Publish(name string, message interface{}) {
|
|
||||||
s.RLock()
|
|
||||||
defer s.RUnlock()
|
|
||||||
|
|
||||||
for _, sub := range s.subs[name] {
|
|
||||||
if !sub.IsClosed() {
|
|
||||||
sub.push(message)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
package semaphore
|
|
||||||
|
|
||||||
// Instance is an implementation of semaphore.
|
|
||||||
type Instance struct {
|
|
||||||
token chan struct{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// New create a new Semaphore with n permits.
|
|
||||||
func New(n int) *Instance {
|
|
||||||
s := &Instance{
|
|
||||||
token: make(chan struct{}, n),
|
|
||||||
}
|
|
||||||
for i := 0; i < n; i++ {
|
|
||||||
s.token <- struct{}{}
|
|
||||||
}
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wait returns a channel for acquiring a permit.
|
|
||||||
func (s *Instance) Wait() <-chan struct{} {
|
|
||||||
return s.token
|
|
||||||
}
|
|
||||||
|
|
||||||
// Signal releases a permit into the semaphore.
|
|
||||||
func (s *Instance) Signal() {
|
|
||||||
s.token <- struct{}{}
|
|
||||||
}
|
|
||||||
@@ -1,82 +0,0 @@
|
|||||||
package signal
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/common/xray"
|
|
||||||
"github.com/sagernet/sing-box/common/xray/task"
|
|
||||||
)
|
|
||||||
|
|
||||||
type ActivityUpdater interface {
|
|
||||||
Update()
|
|
||||||
}
|
|
||||||
|
|
||||||
type ActivityTimer struct {
|
|
||||||
sync.RWMutex
|
|
||||||
updated chan struct{}
|
|
||||||
checkTask *task.Periodic
|
|
||||||
onTimeout func()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *ActivityTimer) Update() {
|
|
||||||
select {
|
|
||||||
case t.updated <- struct{}{}:
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *ActivityTimer) check() error {
|
|
||||||
select {
|
|
||||||
case <-t.updated:
|
|
||||||
default:
|
|
||||||
t.finish()
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *ActivityTimer) finish() {
|
|
||||||
t.Lock()
|
|
||||||
defer t.Unlock()
|
|
||||||
|
|
||||||
if t.onTimeout != nil {
|
|
||||||
t.onTimeout()
|
|
||||||
t.onTimeout = nil
|
|
||||||
}
|
|
||||||
if t.checkTask != nil {
|
|
||||||
t.checkTask.Close()
|
|
||||||
t.checkTask = nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *ActivityTimer) SetTimeout(timeout time.Duration) {
|
|
||||||
if timeout == 0 {
|
|
||||||
t.finish()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
checkTask := &task.Periodic{
|
|
||||||
Interval: timeout,
|
|
||||||
Execute: t.check,
|
|
||||||
}
|
|
||||||
|
|
||||||
t.Lock()
|
|
||||||
|
|
||||||
if t.checkTask != nil {
|
|
||||||
t.checkTask.Close()
|
|
||||||
}
|
|
||||||
t.checkTask = checkTask
|
|
||||||
t.Unlock()
|
|
||||||
t.Update()
|
|
||||||
common.Must(checkTask.Start())
|
|
||||||
}
|
|
||||||
|
|
||||||
func CancelAfterInactivity(ctx context.Context, cancel context.CancelFunc, timeout time.Duration) *ActivityTimer {
|
|
||||||
timer := &ActivityTimer{
|
|
||||||
updated: make(chan struct{}, 1),
|
|
||||||
onTimeout: cancel,
|
|
||||||
}
|
|
||||||
timer.SetTimeout(timeout)
|
|
||||||
return timer
|
|
||||||
}
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
package stat
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/common/xray/stats"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Connection interface {
|
|
||||||
net.Conn
|
|
||||||
}
|
|
||||||
|
|
||||||
type CounterConnection struct {
|
|
||||||
Connection
|
|
||||||
ReadCounter stats.Counter
|
|
||||||
WriteCounter stats.Counter
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *CounterConnection) Read(b []byte) (int, error) {
|
|
||||||
nBytes, err := c.Connection.Read(b)
|
|
||||||
if c.ReadCounter != nil {
|
|
||||||
c.ReadCounter.Add(int64(nBytes))
|
|
||||||
}
|
|
||||||
|
|
||||||
return nBytes, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *CounterConnection) Write(b []byte) (int, error) {
|
|
||||||
nBytes, err := c.Connection.Write(b)
|
|
||||||
if c.WriteCounter != nil {
|
|
||||||
c.WriteCounter.Add(int64(nBytes))
|
|
||||||
}
|
|
||||||
return nBytes, err
|
|
||||||
}
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
package stats
|
|
||||||
|
|
||||||
// Counter is the interface for stats counters.
|
|
||||||
//
|
|
||||||
// xray:api:stable
|
|
||||||
type Counter interface {
|
|
||||||
// Value is the current value of the counter.
|
|
||||||
Value() int64
|
|
||||||
// Set sets a new value to the counter, and returns the previous one.
|
|
||||||
Set(int64) int64
|
|
||||||
// Add adds a value to the current counter value, and returns the previous value.
|
|
||||||
Add(int64) int64
|
|
||||||
}
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
package task
|
|
||||||
|
|
||||||
import "github.com/sagernet/sing-box/common/xray"
|
|
||||||
|
|
||||||
// Close returns a func() that closes v.
|
|
||||||
func Close(v interface{}) func() error {
|
|
||||||
return func() error {
|
|
||||||
return common.Close(v)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,85 +0,0 @@
|
|||||||
package task
|
|
||||||
|
|
||||||
import (
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Periodic is a task that runs periodically.
|
|
||||||
type Periodic struct {
|
|
||||||
// Interval of the task being run
|
|
||||||
Interval time.Duration
|
|
||||||
// Execute is the task function
|
|
||||||
Execute func() error
|
|
||||||
|
|
||||||
access sync.Mutex
|
|
||||||
timer *time.Timer
|
|
||||||
running bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *Periodic) hasClosed() bool {
|
|
||||||
t.access.Lock()
|
|
||||||
defer t.access.Unlock()
|
|
||||||
|
|
||||||
return !t.running
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *Periodic) checkedExecute() error {
|
|
||||||
if t.hasClosed() {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := t.Execute(); err != nil {
|
|
||||||
t.access.Lock()
|
|
||||||
t.running = false
|
|
||||||
t.access.Unlock()
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
t.access.Lock()
|
|
||||||
defer t.access.Unlock()
|
|
||||||
|
|
||||||
if !t.running {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
t.timer = time.AfterFunc(t.Interval, func() {
|
|
||||||
t.checkedExecute()
|
|
||||||
})
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start implements common.Runnable.
|
|
||||||
func (t *Periodic) Start() error {
|
|
||||||
t.access.Lock()
|
|
||||||
if t.running {
|
|
||||||
t.access.Unlock()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
t.running = true
|
|
||||||
t.access.Unlock()
|
|
||||||
|
|
||||||
if err := t.checkedExecute(); err != nil {
|
|
||||||
t.access.Lock()
|
|
||||||
t.running = false
|
|
||||||
t.access.Unlock()
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close implements common.Closable.
|
|
||||||
func (t *Periodic) Close() error {
|
|
||||||
t.access.Lock()
|
|
||||||
defer t.access.Unlock()
|
|
||||||
|
|
||||||
t.running = false
|
|
||||||
if t.timer != nil {
|
|
||||||
t.timer.Stop()
|
|
||||||
t.timer = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -1,64 +0,0 @@
|
|||||||
package task
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/common/xray/signal/semaphore"
|
|
||||||
)
|
|
||||||
|
|
||||||
// OnSuccess executes g() after f() returns nil.
|
|
||||||
func OnSuccess(f func() error, g func() error) func() error {
|
|
||||||
return func() error {
|
|
||||||
if err := f(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return g()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Run executes a list of tasks in parallel, returns the first error encountered or nil if all tasks pass.
|
|
||||||
func Run(ctx context.Context, tasks ...func() error) error {
|
|
||||||
n := len(tasks)
|
|
||||||
s := semaphore.New(n)
|
|
||||||
done := make(chan error, 1)
|
|
||||||
|
|
||||||
for _, task := range tasks {
|
|
||||||
<-s.Wait()
|
|
||||||
go func(f func() error) {
|
|
||||||
err := f()
|
|
||||||
if err == nil {
|
|
||||||
s.Signal()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
select {
|
|
||||||
case done <- err:
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
}(task)
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
if altctx := ctx.Value("altctx"); altctx != nil {
|
|
||||||
ctx = altctx.(context.Context)
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
for i := 0; i < n; i++ {
|
|
||||||
select {
|
|
||||||
case err := <-done:
|
|
||||||
return err
|
|
||||||
case <-ctx.Done():
|
|
||||||
return ctx.Err()
|
|
||||||
case <-s.Wait():
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
if cancel := ctx.Value("cancel"); cancel != nil {
|
|
||||||
cancel.(context.CancelFunc)()
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -1,101 +0,0 @@
|
|||||||
package uuid
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"crypto/rand"
|
|
||||||
"crypto/sha1"
|
|
||||||
"encoding/hex"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/common/xray"
|
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
|
||||||
)
|
|
||||||
|
|
||||||
var byteGroups = []int{8, 4, 4, 4, 12}
|
|
||||||
|
|
||||||
type UUID [16]byte
|
|
||||||
|
|
||||||
// String returns the string representation of this UUID.
|
|
||||||
func (u *UUID) String() string {
|
|
||||||
bytes := u.Bytes()
|
|
||||||
result := hex.EncodeToString(bytes[0 : byteGroups[0]/2])
|
|
||||||
start := byteGroups[0] / 2
|
|
||||||
for i := 1; i < len(byteGroups); i++ {
|
|
||||||
nBytes := byteGroups[i] / 2
|
|
||||||
result += "-"
|
|
||||||
result += hex.EncodeToString(bytes[start : start+nBytes])
|
|
||||||
start += nBytes
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
// Bytes returns the bytes representation of this UUID.
|
|
||||||
func (u *UUID) Bytes() []byte {
|
|
||||||
return u[:]
|
|
||||||
}
|
|
||||||
|
|
||||||
// Equals returns true if this UUID equals another UUID by value.
|
|
||||||
func (u *UUID) Equals(another *UUID) bool {
|
|
||||||
if u == nil && another == nil {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if u == nil || another == nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return bytes.Equal(u.Bytes(), another.Bytes())
|
|
||||||
}
|
|
||||||
|
|
||||||
// New creates a UUID with random value.
|
|
||||||
func New() UUID {
|
|
||||||
var uuid UUID
|
|
||||||
common.Must2(rand.Read(uuid.Bytes()))
|
|
||||||
uuid[6] = (uuid[6] & 0x0f) | (4 << 4)
|
|
||||||
uuid[8] = (uuid[8]&(0xff>>2) | (0x02 << 6))
|
|
||||||
return uuid
|
|
||||||
}
|
|
||||||
|
|
||||||
// ParseBytes converts a UUID in byte form to object.
|
|
||||||
func ParseBytes(b []byte) (UUID, error) {
|
|
||||||
var uuid UUID
|
|
||||||
if len(b) != 16 {
|
|
||||||
return uuid, E.New("invalid UUID: ", b)
|
|
||||||
}
|
|
||||||
copy(uuid[:], b)
|
|
||||||
return uuid, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ParseString converts a UUID in string form to object.
|
|
||||||
func ParseString(str string) (UUID, error) {
|
|
||||||
var uuid UUID
|
|
||||||
|
|
||||||
text := []byte(str)
|
|
||||||
if l := len(text); l < 32 || l > 36 {
|
|
||||||
if l == 0 || l > 30 {
|
|
||||||
return uuid, E.New("invalid UUID: ", str)
|
|
||||||
}
|
|
||||||
h := sha1.New()
|
|
||||||
h.Write(uuid[:])
|
|
||||||
h.Write(text)
|
|
||||||
u := h.Sum(nil)[:16]
|
|
||||||
u[6] = (u[6] & 0x0f) | (5 << 4)
|
|
||||||
u[8] = (u[8]&(0xff>>2) | (0x02 << 6))
|
|
||||||
copy(uuid[:], u)
|
|
||||||
return uuid, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
b := uuid.Bytes()
|
|
||||||
|
|
||||||
for _, byteGroup := range byteGroups {
|
|
||||||
if text[0] == '-' {
|
|
||||||
text = text[1:]
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := hex.Decode(b[:byteGroup/2], text[:byteGroup]); err != nil {
|
|
||||||
return uuid, err
|
|
||||||
}
|
|
||||||
|
|
||||||
text = text[byteGroup:]
|
|
||||||
b = b[byteGroup/2:]
|
|
||||||
}
|
|
||||||
|
|
||||||
return uuid, nil
|
|
||||||
}
|
|
||||||
@@ -28,7 +28,6 @@ const (
|
|||||||
DNSTypeFakeIP = "fakeip"
|
DNSTypeFakeIP = "fakeip"
|
||||||
DNSTypeDHCP = "dhcp"
|
DNSTypeDHCP = "dhcp"
|
||||||
DNSTypeTailscale = "tailscale"
|
DNSTypeTailscale = "tailscale"
|
||||||
DNSTypeSDNS = "sdns"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|||||||
@@ -15,19 +15,15 @@ const (
|
|||||||
TypeTrojan = "trojan"
|
TypeTrojan = "trojan"
|
||||||
TypeNaive = "naive"
|
TypeNaive = "naive"
|
||||||
TypeWireGuard = "wireguard"
|
TypeWireGuard = "wireguard"
|
||||||
TypeWARP = "warp"
|
|
||||||
TypeHysteria = "hysteria"
|
TypeHysteria = "hysteria"
|
||||||
TypeTor = "tor"
|
TypeTor = "tor"
|
||||||
TypeSSH = "ssh"
|
TypeSSH = "ssh"
|
||||||
TypeShadowTLS = "shadowtls"
|
TypeShadowTLS = "shadowtls"
|
||||||
TypeMieru = "mieru"
|
|
||||||
TypeAnyTLS = "anytls"
|
TypeAnyTLS = "anytls"
|
||||||
TypeShadowsocksR = "shadowsocksr"
|
TypeShadowsocksR = "shadowsocksr"
|
||||||
TypeVLESS = "vless"
|
TypeVLESS = "vless"
|
||||||
TypeTUIC = "tuic"
|
TypeTUIC = "tuic"
|
||||||
TypeHysteria2 = "hysteria2"
|
TypeHysteria2 = "hysteria2"
|
||||||
TypeTunnelClient = "tunnel_client"
|
|
||||||
TypeTunnelServer = "tunnel_server"
|
|
||||||
TypeTailscale = "tailscale"
|
TypeTailscale = "tailscale"
|
||||||
TypeDERP = "derp"
|
TypeDERP = "derp"
|
||||||
TypeResolved = "resolved"
|
TypeResolved = "resolved"
|
||||||
@@ -69,8 +65,6 @@ func ProxyDisplayName(proxyType string) string {
|
|||||||
return "Naive"
|
return "Naive"
|
||||||
case TypeWireGuard:
|
case TypeWireGuard:
|
||||||
return "WireGuard"
|
return "WireGuard"
|
||||||
case TypeWARP:
|
|
||||||
return "WARP"
|
|
||||||
case TypeHysteria:
|
case TypeHysteria:
|
||||||
return "Hysteria"
|
return "Hysteria"
|
||||||
case TypeTor:
|
case TypeTor:
|
||||||
@@ -87,18 +81,12 @@ func ProxyDisplayName(proxyType string) string {
|
|||||||
return "TUIC"
|
return "TUIC"
|
||||||
case TypeHysteria2:
|
case TypeHysteria2:
|
||||||
return "Hysteria2"
|
return "Hysteria2"
|
||||||
case TypeMieru:
|
|
||||||
return "Mieru"
|
|
||||||
case TypeAnyTLS:
|
case TypeAnyTLS:
|
||||||
return "AnyTLS"
|
return "AnyTLS"
|
||||||
case TypeSelector:
|
case TypeSelector:
|
||||||
return "Selector"
|
return "Selector"
|
||||||
case TypeURLTest:
|
case TypeURLTest:
|
||||||
return "URLTest"
|
return "URLTest"
|
||||||
case TypeTunnelClient:
|
|
||||||
return "Tunnel Client"
|
|
||||||
case TypeTunnelServer:
|
|
||||||
return "Tunnel Server"
|
|
||||||
default:
|
default:
|
||||||
return "Unknown"
|
return "Unknown"
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user