Compare commits
2 Commits
v1.14.3
...
dev-refact
Author | SHA1 | Date | |
---|---|---|---|
637a8b6ed5 | |||
cd466f05d3 |
26
.github/release.sh
vendored
26
.github/release.sh
vendored
@ -1,26 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
FILENAMES=$(ls)
|
|
||||||
for FILENAME in $FILENAMES
|
|
||||||
do
|
|
||||||
if [[ ! ($FILENAME =~ ".exe" || $FILENAME =~ ".sh")]];then
|
|
||||||
gzip -S ".gz" $FILENAME
|
|
||||||
elif [[ $FILENAME =~ ".exe" ]];then
|
|
||||||
zip -m ${FILENAME%.*}.zip $FILENAME
|
|
||||||
else echo "skip $FILENAME"
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
FILENAMES=$(ls)
|
|
||||||
for FILENAME in $FILENAMES
|
|
||||||
do
|
|
||||||
if [[ $FILENAME =~ ".zip" ]];then
|
|
||||||
echo "rename $FILENAME"
|
|
||||||
mv $FILENAME ${FILENAME%.*}-${VERSION}.zip
|
|
||||||
elif [[ $FILENAME =~ ".gz" ]];then
|
|
||||||
echo "rename $FILENAME"
|
|
||||||
mv $FILENAME ${FILENAME%.*}-${VERSION}.gz
|
|
||||||
else
|
|
||||||
echo "skip $FILENAME"
|
|
||||||
fi
|
|
||||||
done
|
|
35
.github/rename-cgo.sh
vendored
35
.github/rename-cgo.sh
vendored
@ -1,35 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
FILENAMES=$(ls)
|
|
||||||
for FILENAME in $FILENAMES
|
|
||||||
do
|
|
||||||
if [[ $FILENAME =~ "darwin-10.16-arm64" ]];then
|
|
||||||
echo "rename darwin-10.16-arm64 $FILENAME"
|
|
||||||
mv $FILENAME clash.meta-darwin-arm64-cgo
|
|
||||||
elif [[ $FILENAME =~ "darwin-10.16-amd64" ]];then
|
|
||||||
echo "rename darwin-10.16-amd64 $FILENAME"
|
|
||||||
mv $FILENAME clash.meta-darwin-amd64-cgo
|
|
||||||
elif [[ $FILENAME =~ "windows-4.0-386" ]];then
|
|
||||||
echo "rename windows 386 $FILENAME"
|
|
||||||
mv $FILENAME clash.meta-windows-386-cgo.exe
|
|
||||||
elif [[ $FILENAME =~ "windows-4.0-amd64" ]];then
|
|
||||||
echo "rename windows amd64 $FILENAME"
|
|
||||||
mv $FILENAME clash.meta-windows-amd64-cgo.exe
|
|
||||||
elif [[ $FILENAME =~ "clash.meta-linux-arm-5" ]];then
|
|
||||||
echo "rename clash.meta-linux-arm-5 $FILENAME"
|
|
||||||
mv $FILENAME clash.meta-linux-armv5-cgo
|
|
||||||
elif [[ $FILENAME =~ "clash.meta-linux-arm-6" ]];then
|
|
||||||
echo "rename clash.meta-linux-arm-6 $FILENAME"
|
|
||||||
mv $FILENAME clash.meta-linux-armv6-cgo
|
|
||||||
elif [[ $FILENAME =~ "clash.meta-linux-arm-7" ]];then
|
|
||||||
echo "rename clash.meta-linux-arm-7 $FILENAME"
|
|
||||||
mv $FILENAME clash.meta-linux-armv7-cgo
|
|
||||||
elif [[ $FILENAME =~ "linux" ]];then
|
|
||||||
echo "rename linux $FILENAME"
|
|
||||||
mv $FILENAME $FILENAME-cgo
|
|
||||||
elif [[ $FILENAME =~ "android" ]];then
|
|
||||||
echo "rename android $FILENAME"
|
|
||||||
mv $FILENAME $FILENAME-cgo
|
|
||||||
else echo "skip $FILENAME"
|
|
||||||
fi
|
|
||||||
done
|
|
16
.github/workflows/Delete.yml
vendored
16
.github/workflows/Delete.yml
vendored
@ -1,16 +0,0 @@
|
|||||||
name: Delete old workflow runs
|
|
||||||
on:
|
|
||||||
schedule:
|
|
||||||
- cron: '0 0 1 * *'
|
|
||||||
# Run monthly, at 00:00 on the 1st day of month.
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
del_runs:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Delete workflow runs
|
|
||||||
uses: GitRML/delete-workflow-runs@main
|
|
||||||
with:
|
|
||||||
token: ${{ secrets.AUTH_PAT }}
|
|
||||||
repository: ${{ github.repository }}
|
|
||||||
retain_days: 30
|
|
20
.github/workflows/build.yaml
vendored
Normal file
20
.github/workflows/build.yaml
vendored
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
name: Build All
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Set up Go
|
||||||
|
uses: actions/setup-go@v1
|
||||||
|
with:
|
||||||
|
go-version: 1.18
|
||||||
|
- name: Check out code
|
||||||
|
uses: actions/checkout@v1
|
||||||
|
- name: Build
|
||||||
|
run: make all
|
||||||
|
- name: Release
|
||||||
|
uses: softprops/action-gh-release@v1
|
||||||
|
with:
|
||||||
|
files: bin/*
|
||||||
|
draft: true
|
357
.github/workflows/build.yml
vendored
357
.github/workflows/build.yml
vendored
@ -1,357 +0,0 @@
|
|||||||
name: Build
|
|
||||||
on:
|
|
||||||
workflow_dispatch:
|
|
||||||
push:
|
|
||||||
paths-ignore:
|
|
||||||
- "docs/**"
|
|
||||||
- "README.md"
|
|
||||||
branches:
|
|
||||||
- Alpha
|
|
||||||
- Beta
|
|
||||||
- Meta
|
|
||||||
tags:
|
|
||||||
- "v*"
|
|
||||||
pull_request_target:
|
|
||||||
branches:
|
|
||||||
- Alpha
|
|
||||||
- Beta
|
|
||||||
- Meta
|
|
||||||
|
|
||||||
concurrency:
|
|
||||||
group: ${{ github.ref }}-${{ github.workflow }}
|
|
||||||
cancel-in-progress: true
|
|
||||||
|
|
||||||
env:
|
|
||||||
REGISTRY: docker.io
|
|
||||||
jobs:
|
|
||||||
Build:
|
|
||||||
permissions: write-all
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
strategy:
|
|
||||||
fail-fast: false
|
|
||||||
matrix:
|
|
||||||
job:
|
|
||||||
- {
|
|
||||||
type: "WithoutCGO",
|
|
||||||
target: "linux-amd64 linux-amd64-compatible",
|
|
||||||
id: "1",
|
|
||||||
}
|
|
||||||
- {
|
|
||||||
type: "WithoutCGO",
|
|
||||||
target: "linux-armv5 linux-armv6 linux-armv7",
|
|
||||||
id: "2",
|
|
||||||
}
|
|
||||||
- {
|
|
||||||
type: "WithoutCGO",
|
|
||||||
target: "linux-arm64 linux-mips64 linux-mips64le",
|
|
||||||
id: "3",
|
|
||||||
}
|
|
||||||
- {
|
|
||||||
type: "WithoutCGO",
|
|
||||||
target: "linux-mips-softfloat linux-mips-hardfloat linux-mipsle-softfloat linux-mipsle-hardfloat",
|
|
||||||
id: "4",
|
|
||||||
}
|
|
||||||
- { type: "WithoutCGO", target: "linux-386 linux-riscv64", id: "5" }
|
|
||||||
- {
|
|
||||||
type: "WithoutCGO",
|
|
||||||
target: "freebsd-386 freebsd-amd64 freebsd-arm64",
|
|
||||||
id: "6",
|
|
||||||
}
|
|
||||||
- {
|
|
||||||
type: "WithoutCGO",
|
|
||||||
target: "windows-amd64-compatible windows-amd64 windows-386",
|
|
||||||
id: "7",
|
|
||||||
}
|
|
||||||
- {
|
|
||||||
type: "WithoutCGO",
|
|
||||||
target: "windows-arm64 windows-arm32v7",
|
|
||||||
id: "8",
|
|
||||||
}
|
|
||||||
- {
|
|
||||||
type: "WithoutCGO",
|
|
||||||
target: "darwin-amd64 darwin-arm64 android-arm64",
|
|
||||||
id: "9",
|
|
||||||
}
|
|
||||||
- { type: "WithCGO", target: "windows/*", id: "1" }
|
|
||||||
- { type: "WithCGO", target: "linux/386", id: "2" }
|
|
||||||
- { type: "WithCGO", target: "linux/amd64", id: "3" }
|
|
||||||
- { type: "WithCGO", target: "linux/arm64,linux/riscv64", id: "4" }
|
|
||||||
- { type: "WithCGO", target: "linux/arm,", id: "5" }
|
|
||||||
- { type: "WithCGO", target: "linux/arm-6,linux/arm-7", id: "6" }
|
|
||||||
- { type: "WithCGO", target: "linux/mips,linux/mipsle", id: "7" }
|
|
||||||
- { type: "WithCGO", target: "linux/mips64", id: "8" }
|
|
||||||
- { type: "WithCGO", target: "linux/mips64le", id: "9" }
|
|
||||||
- { type: "WithCGO", target: "darwin-10.16/*", id: "10" }
|
|
||||||
- { type: "WithCGO", target: "android", id: "11" }
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Check out code into the Go module directory
|
|
||||||
uses: actions/checkout@v3
|
|
||||||
|
|
||||||
- name: Set variables
|
|
||||||
run: echo "VERSION=$(git rev-parse --short HEAD)" >> $GITHUB_ENV
|
|
||||||
shell: bash
|
|
||||||
|
|
||||||
- name: Set variables
|
|
||||||
if: ${{github.ref_name=='Alpha'}}
|
|
||||||
run: echo "VERSION=alpha-$(git rev-parse --short HEAD)" >> $GITHUB_ENV
|
|
||||||
shell: bash
|
|
||||||
|
|
||||||
- name: Set variables
|
|
||||||
if: ${{github.ref_name=='Beta'}}
|
|
||||||
run: echo "VERSION=beta-$(git rev-parse --short HEAD)" >> $GITHUB_ENV
|
|
||||||
shell: bash
|
|
||||||
|
|
||||||
- name: Set variables
|
|
||||||
if: ${{github.ref_name=='Meta'}}
|
|
||||||
run: echo "VERSION=meta-$(git rev-parse --short HEAD)" >> $GITHUB_ENV
|
|
||||||
shell: bash
|
|
||||||
|
|
||||||
- name: Set variables
|
|
||||||
if: ${{github.ref_name=='' || github.ref_type=='tag'}}
|
|
||||||
run: echo "VERSION=$(git describe --tags)" >> $GITHUB_ENV
|
|
||||||
shell: bash
|
|
||||||
|
|
||||||
- name: Set ENV
|
|
||||||
run: |
|
|
||||||
sudo timedatectl set-timezone "Asia/Shanghai"
|
|
||||||
echo "NAME=clash.meta" >> $GITHUB_ENV
|
|
||||||
echo "REPO=${{ github.repository }}" >> $GITHUB_ENV
|
|
||||||
echo "ShortSHA=$(git rev-parse --short ${{ github.sha }})" >> $GITHUB_ENV
|
|
||||||
echo "BUILDTIME=$(date)" >> $GITHUB_ENV
|
|
||||||
echo "BRANCH=$(git rev-parse --abbrev-ref HEAD)" >> $GITHUB_ENV
|
|
||||||
shell: bash
|
|
||||||
|
|
||||||
- name: Set ENV
|
|
||||||
run: |
|
|
||||||
echo "TAGS=with_gvisor,with_lwip" >> $GITHUB_ENV
|
|
||||||
echo "LDFLAGS=-X 'github.com/Dreamacro/clash/constant.Version=${VERSION}' -X 'github.com/Dreamacro/clash/constant.BuildTime=${BUILDTIME}' -w -s -buildid=" >> $GITHUB_ENV
|
|
||||||
shell: bash
|
|
||||||
|
|
||||||
- name: Setup Go
|
|
||||||
uses: actions/setup-go@v3
|
|
||||||
with:
|
|
||||||
go-version: "1.20"
|
|
||||||
check-latest: true
|
|
||||||
|
|
||||||
- name: Test
|
|
||||||
if: ${{ matrix.job.id=='1' && matrix.job.type=='WithoutCGO' }}
|
|
||||||
run: |
|
|
||||||
go test ./...
|
|
||||||
|
|
||||||
- name: Build WithoutCGO
|
|
||||||
if: ${{ matrix.job.type=='WithoutCGO' }}
|
|
||||||
env:
|
|
||||||
NAME: Clash.Meta
|
|
||||||
BINDIR: bin
|
|
||||||
run: make -j$(($(nproc) + 1)) ${{ matrix.job.target }}
|
|
||||||
|
|
||||||
- uses: nttld/setup-ndk@v1
|
|
||||||
if: ${{ matrix.job.type=='WithCGO' && matrix.job.target=='android' }}
|
|
||||||
id: setup-ndk
|
|
||||||
with:
|
|
||||||
ndk-version: r25b
|
|
||||||
add-to-path: false
|
|
||||||
local-cache: true
|
|
||||||
|
|
||||||
- name: Build Android
|
|
||||||
if: ${{ matrix.job.type=='WithCGO' && matrix.job.target=='android' }}
|
|
||||||
env:
|
|
||||||
ANDROID_NDK_HOME: ${{ steps.setup-ndk.outputs.ndk-path }}
|
|
||||||
run: |
|
|
||||||
mkdir bin
|
|
||||||
CC=${ANDROID_NDK_HOME}/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android33-clang
|
|
||||||
CGO_ENABLED=1 CC=${CC} GOARCH=arm64 GOOS=android go build -tags ${TAGS} -trimpath -ldflags "${LDFLAGS}" -o bin/${NAME}-android-arm64
|
|
||||||
|
|
||||||
- name: Set up xgo
|
|
||||||
if: ${{ matrix.job.type=='WithCGO' && matrix.job.target!='android' }}
|
|
||||||
run: |
|
|
||||||
docker pull techknowlogick/xgo:latest
|
|
||||||
go install src.techknowlogick.com/xgo@latest
|
|
||||||
|
|
||||||
- name: Build by xgo
|
|
||||||
if: ${{ matrix.job.type=='WithCGO' && matrix.job.target!='android' }}
|
|
||||||
env:
|
|
||||||
ANDROID_NDK_HOME: ${{ steps.setup-ndk.outputs.ndk-path }}
|
|
||||||
run: |
|
|
||||||
mkdir bin
|
|
||||||
xgo --targets="${{ matrix.job.target }}" --tags="${TAGS}" -ldflags="${LDFLAGS}" --out bin/${NAME} ./
|
|
||||||
|
|
||||||
- name: Rename
|
|
||||||
if: ${{ matrix.job.type=='WithCGO' }}
|
|
||||||
run: |
|
|
||||||
cd bin
|
|
||||||
ls -la
|
|
||||||
cp ../.github/rename-cgo.sh ./
|
|
||||||
bash ./rename-cgo.sh
|
|
||||||
rm ./rename-cgo.sh
|
|
||||||
ls -la
|
|
||||||
cd ..
|
|
||||||
|
|
||||||
- name: Zip
|
|
||||||
if: ${{ success() }}
|
|
||||||
run: |
|
|
||||||
cd bin
|
|
||||||
ls -la
|
|
||||||
chmod +x *
|
|
||||||
cp ../.github/release.sh ./
|
|
||||||
bash ./release.sh
|
|
||||||
rm ./release.sh
|
|
||||||
ls -la
|
|
||||||
cd ..
|
|
||||||
|
|
||||||
- name: Save version
|
|
||||||
run: echo ${VERSION} > bin/version.txt
|
|
||||||
shell: bash
|
|
||||||
|
|
||||||
- uses: actions/upload-artifact@v3
|
|
||||||
if: ${{ success() }}
|
|
||||||
with:
|
|
||||||
name: artifact
|
|
||||||
path: bin/
|
|
||||||
|
|
||||||
Upload-Prerelease:
|
|
||||||
permissions: write-all
|
|
||||||
if: ${{ github.ref_type=='branch' }}
|
|
||||||
needs: [Build]
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/download-artifact@v3
|
|
||||||
with:
|
|
||||||
name: artifact
|
|
||||||
path: bin/
|
|
||||||
|
|
||||||
- name: Display structure of downloaded files
|
|
||||||
run: ls -R
|
|
||||||
working-directory: bin
|
|
||||||
|
|
||||||
- name: Delete current release assets
|
|
||||||
uses: andreaswilli/delete-release-assets-action@v2.0.0
|
|
||||||
with:
|
|
||||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
tag: Prerelease-${{ github.ref_name }}
|
|
||||||
deleteOnlyFromDrafts: false
|
|
||||||
|
|
||||||
- name: Set Env
|
|
||||||
run: |
|
|
||||||
echo "BUILDTIME=$(TZ=Asia/Shanghai date)" >> $GITHUB_ENV
|
|
||||||
shell: bash
|
|
||||||
|
|
||||||
- name: Tag Repo
|
|
||||||
uses: richardsimko/update-tag@v1.0.6
|
|
||||||
with:
|
|
||||||
tag_name: Prerelease-${{ github.ref_name }}
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
|
|
||||||
- run: |
|
|
||||||
cat > release.txt << 'EOF'
|
|
||||||
Release created at ${{ env.BUILDTIME }}
|
|
||||||
Synchronize ${{ github.ref_name }} branch code updates, keeping only the latest version
|
|
||||||
<br>
|
|
||||||
### release version
|
|
||||||
`default(not specified in file name)`: compiled with GOAMD64=v3
|
|
||||||
`cgo`: support lwip tun stack, compiled with GOAMD64=v1
|
|
||||||
`compatible`: compiled with GOAMD64=v1
|
|
||||||
Check details between different architectural levels [here](https://github.com/golang/go/wiki/MinimumRequirements#amd64).
|
|
||||||
EOF
|
|
||||||
|
|
||||||
- name: Upload Prerelease
|
|
||||||
uses: softprops/action-gh-release@v1
|
|
||||||
if: ${{ success() }}
|
|
||||||
with:
|
|
||||||
tag: ${{ github.ref_name }}
|
|
||||||
tag_name: Prerelease-${{ github.ref_name }}
|
|
||||||
files: |
|
|
||||||
bin/*
|
|
||||||
prerelease: true
|
|
||||||
generate_release_notes: true
|
|
||||||
body_path: release.txt
|
|
||||||
|
|
||||||
Upload-Release:
|
|
||||||
permissions: write-all
|
|
||||||
if: ${{ github.ref_type=='tag' }}
|
|
||||||
needs: [Build]
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/download-artifact@v3
|
|
||||||
with:
|
|
||||||
name: artifact
|
|
||||||
path: bin/
|
|
||||||
|
|
||||||
- name: Display structure of downloaded files
|
|
||||||
run: ls -R
|
|
||||||
working-directory: bin
|
|
||||||
|
|
||||||
- name: Upload Release
|
|
||||||
uses: softprops/action-gh-release@v1
|
|
||||||
if: ${{ success() }}
|
|
||||||
with:
|
|
||||||
tag: ${{ github.ref_name }}
|
|
||||||
tag_name: ${{ github.ref_name }}
|
|
||||||
files: bin/*
|
|
||||||
generate_release_notes: true
|
|
||||||
|
|
||||||
Docker:
|
|
||||||
permissions: write-all
|
|
||||||
needs: [Build]
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Checkout repository
|
|
||||||
uses: actions/checkout@v3
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
|
|
||||||
- uses: actions/download-artifact@v3
|
|
||||||
with:
|
|
||||||
name: artifact
|
|
||||||
path: bin/
|
|
||||||
|
|
||||||
- name: Display structure of downloaded files
|
|
||||||
run: ls -R
|
|
||||||
working-directory: bin
|
|
||||||
|
|
||||||
- name: Set up QEMU
|
|
||||||
uses: docker/setup-qemu-action@v1
|
|
||||||
|
|
||||||
- name: Setup Docker buildx
|
|
||||||
uses: docker/setup-buildx-action@v1
|
|
||||||
with:
|
|
||||||
version: latest
|
|
||||||
|
|
||||||
# Extract metadata (tags, labels) for Docker
|
|
||||||
# https://github.com/docker/metadata-action
|
|
||||||
- name: Extract Docker metadata
|
|
||||||
id: meta
|
|
||||||
uses: docker/metadata-action@v3
|
|
||||||
with:
|
|
||||||
images: ${{ env.REGISTRY }}/${{ secrets.DOCKERHUB_ACCOUNT }}/${{secrets.DOCKERHUB_REPO}}
|
|
||||||
- name: Show files
|
|
||||||
run: |
|
|
||||||
ls .
|
|
||||||
ls bin/
|
|
||||||
- name: Log into registry
|
|
||||||
if: github.event_name != 'pull_request'
|
|
||||||
uses: docker/login-action@v1
|
|
||||||
with:
|
|
||||||
registry: ${{ env.REGISTRY }}
|
|
||||||
username: ${{ secrets.DOCKER_HUB_USER }}
|
|
||||||
password: ${{ secrets.DOCKER_HUB_TOKEN }}
|
|
||||||
|
|
||||||
# Build and push Docker image with Buildx (don't push on PR)
|
|
||||||
# https://github.com/docker/build-push-action
|
|
||||||
- name: Build and push Docker image
|
|
||||||
id: build-and-push
|
|
||||||
uses: docker/build-push-action@v2
|
|
||||||
with:
|
|
||||||
context: .
|
|
||||||
file: ./Dockerfile
|
|
||||||
push: ${{ github.event_name != 'pull_request' }}
|
|
||||||
platforms: |
|
|
||||||
linux/386
|
|
||||||
linux/amd64
|
|
||||||
linux/arm64/v8
|
|
||||||
linux/arm/v7
|
|
||||||
# linux/riscv64
|
|
||||||
tags: ${{ steps.meta.outputs.tags }}
|
|
||||||
labels: ${{ steps.meta.outputs.labels }}
|
|
16
.github/workflows/delete.yml
vendored
16
.github/workflows/delete.yml
vendored
@ -1,16 +0,0 @@
|
|||||||
name: Delete old workflow runs
|
|
||||||
on:
|
|
||||||
schedule:
|
|
||||||
- cron: '0 0 1 * *'
|
|
||||||
# Run monthly, at 00:00 on the 1st day of month.
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
del_runs:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Delete workflow runs
|
|
||||||
uses: GitRML/delete-workflow-runs@main
|
|
||||||
with:
|
|
||||||
token: ${{ secrets.AUTH_PAT }}
|
|
||||||
repository: ${{ github.repository }}
|
|
||||||
retain_days: 30
|
|
61
.github/workflows/docker.yaml
vendored
Normal file
61
.github/workflows/docker.yaml
vendored
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
name: Docker
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- Beta
|
||||||
|
tags:
|
||||||
|
- "v*"
|
||||||
|
env:
|
||||||
|
REGISTRY: docker.io
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Set up QEMU
|
||||||
|
uses: docker/setup-qemu-action@v1
|
||||||
|
|
||||||
|
- name: Setup Docker buildx
|
||||||
|
uses: docker/setup-buildx-action@v1
|
||||||
|
with:
|
||||||
|
version: latest
|
||||||
|
|
||||||
|
# Extract metadata (tags, labels) for Docker
|
||||||
|
# https://github.com/docker/metadata-action
|
||||||
|
- name: Extract Docker metadata
|
||||||
|
id: meta
|
||||||
|
uses: docker/metadata-action@v3
|
||||||
|
with:
|
||||||
|
images: ${{ env.REGISTRY }}/${{ secrets.DOCKERHUB_ACCOUNT }}/${{secrets.DOCKERHUB_REPO}}
|
||||||
|
|
||||||
|
- name: Log into registry
|
||||||
|
if: github.event_name != 'pull_request'
|
||||||
|
uses: docker/login-action@v1
|
||||||
|
with:
|
||||||
|
registry: ${{ env.REGISTRY }}
|
||||||
|
username: ${{ secrets.DOCKER_HUB_USER }}
|
||||||
|
password: ${{ secrets.DOCKER_HUB_TOKEN }}
|
||||||
|
|
||||||
|
# Build and push Docker image with Buildx (don't push on PR)
|
||||||
|
# https://github.com/docker/build-push-action
|
||||||
|
- name: Build and push Docker image
|
||||||
|
id: build-and-push
|
||||||
|
uses: docker/build-push-action@v2
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
file: ./Dockerfile
|
||||||
|
push: ${{ github.event_name != 'pull_request' }}
|
||||||
|
platforms: |
|
||||||
|
linux/386
|
||||||
|
linux/amd64
|
||||||
|
linux/arm64/v8
|
||||||
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
|
labels: ${{ steps.meta.outputs.labels }}
|
70
.github/workflows/prerelease.yml
vendored
Normal file
70
.github/workflows/prerelease.yml
vendored
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
name: Prerelease
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- Alpha
|
||||||
|
- Beta
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- Alpha
|
||||||
|
- Beta
|
||||||
|
jobs:
|
||||||
|
Build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Get latest go version
|
||||||
|
id: version
|
||||||
|
run: |
|
||||||
|
echo ::set-output name=go_version::$(curl -s https://raw.githubusercontent.com/actions/go-versions/main/versions-manifest.json | grep -oE '"version": "[0-9]{1}.[0-9]{1,}(.[0-9]{1,})?"' | head -1 | cut -d':' -f2 | sed 's/ //g; s/"//g')
|
||||||
|
- name: Setup Go
|
||||||
|
uses: actions/setup-go@v2
|
||||||
|
with:
|
||||||
|
go-version: ${{ steps.version.outputs.go_version }}
|
||||||
|
|
||||||
|
- name: Check out code into the Go module directory
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- name: Cache go module
|
||||||
|
uses: actions/cache@v2
|
||||||
|
with:
|
||||||
|
path: ~/go/pkg/mod
|
||||||
|
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-go-
|
||||||
|
|
||||||
|
|
||||||
|
- name: Test
|
||||||
|
if: ${{github.ref_name=='Beta'}}
|
||||||
|
run: |
|
||||||
|
go test ./...
|
||||||
|
|
||||||
|
- name: Build
|
||||||
|
if: success()
|
||||||
|
env:
|
||||||
|
NAME: Clash.Meta
|
||||||
|
BINDIR: bin
|
||||||
|
run: make -j$(($(nproc) + 1)) releases
|
||||||
|
|
||||||
|
- name: Delete current release assets
|
||||||
|
uses: andreaswilli/delete-release-assets-action@v2.0.0
|
||||||
|
with:
|
||||||
|
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
tag: Prerelease-${{ github.ref_name }}
|
||||||
|
deleteOnlyFromDrafts: false
|
||||||
|
|
||||||
|
- name: Tag Repo
|
||||||
|
uses: richardsimko/update-tag@v1
|
||||||
|
with:
|
||||||
|
tag_name: Prerelease-${{ github.ref_name }}
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Upload Alpha
|
||||||
|
uses: softprops/action-gh-release@v1
|
||||||
|
if: ${{ success() }}
|
||||||
|
with:
|
||||||
|
tag: ${{ github.ref_name }}
|
||||||
|
tag_name: Prerelease-${{ github.ref_name }}
|
||||||
|
files: bin/*
|
||||||
|
prerelease: true
|
||||||
|
generate_release_notes: true
|
44
.github/workflows/release.yaml
vendored
Normal file
44
.github/workflows/release.yaml
vendored
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
name: Release
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- "v*"
|
||||||
|
jobs:
|
||||||
|
Build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Get latest go version
|
||||||
|
id: version
|
||||||
|
run: |
|
||||||
|
echo ::set-output name=go_version::$(curl -s https://raw.githubusercontent.com/actions/go-versions/main/versions-manifest.json | grep -oE '"version": "[0-9]{1}.[0-9]{1,}(.[0-9]{1,})?"' | head -1 | cut -d':' -f2 | sed 's/ //g; s/"//g')
|
||||||
|
- name: Setup Go
|
||||||
|
uses: actions/setup-go@v2
|
||||||
|
with:
|
||||||
|
go-version: ${{ steps.version.outputs.go_version }}
|
||||||
|
|
||||||
|
- name: Check out code into the Go module directory
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
- name: Cache go module
|
||||||
|
uses: actions/cache@v2
|
||||||
|
with:
|
||||||
|
path: ~/go/pkg/mod
|
||||||
|
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-go-
|
||||||
|
- name: Test
|
||||||
|
run: |
|
||||||
|
go test ./...
|
||||||
|
- name: Build
|
||||||
|
if: success()
|
||||||
|
env:
|
||||||
|
NAME: Clash.Meta
|
||||||
|
BINDIR: bin
|
||||||
|
run: make -j$(($(nproc) + 1)) releases
|
||||||
|
|
||||||
|
- name: Upload Release
|
||||||
|
uses: softprops/action-gh-release@v1
|
||||||
|
if: ${{ success() && startsWith(github.ref, 'refs/tags/')}}
|
||||||
|
with:
|
||||||
|
tag: ${{ github.ref }}
|
||||||
|
files: bin/*
|
||||||
|
generate_release_notes: true
|
3
.gitignore
vendored
3
.gitignore
vendored
@ -24,5 +24,4 @@ vendor
|
|||||||
# test suite
|
# test suite
|
||||||
test/config/cache*
|
test/config/cache*
|
||||||
/output
|
/output
|
||||||
.vscode/
|
/.vscode
|
||||||
.fleet/
|
|
@ -8,10 +8,9 @@ linters:
|
|||||||
|
|
||||||
linters-settings:
|
linters-settings:
|
||||||
gci:
|
gci:
|
||||||
custom-order: true
|
|
||||||
sections:
|
sections:
|
||||||
- standard
|
- standard
|
||||||
- prefix(github.com/Dreamacro/clash)
|
- prefix(github.com/Dreamacro/clash)
|
||||||
- default
|
- default
|
||||||
staticcheck:
|
staticcheck:
|
||||||
go: '1.19'
|
go: '1.18'
|
||||||
|
23
Dockerfile
23
Dockerfile
@ -1,27 +1,26 @@
|
|||||||
FROM alpine:latest as builder
|
FROM golang:alpine as builder
|
||||||
ARG TARGETPLATFORM
|
|
||||||
RUN echo "I'm building for $TARGETPLATFORM"
|
|
||||||
|
|
||||||
RUN apk add --no-cache gzip && \
|
RUN apk add --no-cache make git && \
|
||||||
mkdir /clash-config && \
|
mkdir /clash-config && \
|
||||||
wget -O /clash-config/Country.mmdb https://raw.githubusercontent.com/Loyalsoldier/geoip/release/Country.mmdb && \
|
wget -O /clash-config/Country.mmdb https://raw.githubusercontent.com/Loyalsoldier/geoip/release/Country.mmdb && \
|
||||||
wget -O /clash-config/geosite.dat https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat && \
|
wget -O /clash-config/geosite.dat https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat && \
|
||||||
wget -O /clash-config/geoip.dat https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat
|
wget -O /clash-config/geoip.dat https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat
|
||||||
|
|
||||||
COPY docker/file-name.sh /clash/file-name.sh
|
|
||||||
WORKDIR /clash
|
COPY . /clash-src
|
||||||
COPY bin/ bin/
|
WORKDIR /clash-src
|
||||||
RUN FILE_NAME=`sh file-name.sh` && echo $FILE_NAME && \
|
RUN go mod download &&\
|
||||||
FILE_NAME=`ls bin/ | egrep "$FILE_NAME.*"|awk NR==1` && echo $FILE_NAME && \
|
make docker &&\
|
||||||
mv bin/$FILE_NAME clash.gz && gzip -d clash.gz && echo "$FILE_NAME" > /clash-config/test
|
mv ./bin/Clash.Meta-docker /clash
|
||||||
|
|
||||||
FROM alpine:latest
|
FROM alpine:latest
|
||||||
LABEL org.opencontainers.image.source="https://github.com/MetaCubeX/Clash.Meta"
|
LABEL org.opencontainers.image.source="https://github.com/MetaCubeX/Clash.Meta"
|
||||||
|
|
||||||
RUN apk add --no-cache ca-certificates tzdata iptables
|
RUN apk add --no-cache ca-certificates tzdata
|
||||||
|
|
||||||
VOLUME ["/root/.config/clash/"]
|
VOLUME ["/root/.config/clash/"]
|
||||||
|
|
||||||
COPY --from=builder /clash-config/ /root/.config/clash/
|
COPY --from=builder /clash-config/ /root/.config/clash/
|
||||||
COPY --from=builder /clash/clash /clash
|
COPY --from=builder /clash /clash
|
||||||
RUN chmod +x /clash
|
RUN chmod +x /clash
|
||||||
ENTRYPOINT [ "/clash" ]
|
ENTRYPOINT [ "/clash" ]
|
26
Makefile
26
Makefile
@ -1,4 +1,4 @@
|
|||||||
NAME=clash.meta
|
NAME=Clash.Meta
|
||||||
BINDIR=bin
|
BINDIR=bin
|
||||||
BRANCH=$(shell git branch --show-current)
|
BRANCH=$(shell git branch --show-current)
|
||||||
ifeq ($(BRANCH),Alpha)
|
ifeq ($(BRANCH),Alpha)
|
||||||
@ -12,7 +12,7 @@ VERSION=$(shell git rev-parse --short HEAD)
|
|||||||
endif
|
endif
|
||||||
|
|
||||||
BUILDTIME=$(shell date -u)
|
BUILDTIME=$(shell date -u)
|
||||||
GOBUILD=CGO_ENABLED=0 go build -tags with_gvisor -trimpath -ldflags '-X "github.com/Dreamacro/clash/constant.Version=$(VERSION)" \
|
GOBUILD=CGO_ENABLED=0 go build -trimpath -ldflags '-X "github.com/Dreamacro/clash/constant.Version=$(VERSION)" \
|
||||||
-X "github.com/Dreamacro/clash/constant.BuildTime=$(BUILDTIME)" \
|
-X "github.com/Dreamacro/clash/constant.BuildTime=$(BUILDTIME)" \
|
||||||
-w -s -buildid='
|
-w -s -buildid='
|
||||||
|
|
||||||
@ -47,17 +47,14 @@ all:linux-amd64 linux-arm64\
|
|||||||
darwin-amd64 darwin-arm64\
|
darwin-amd64 darwin-arm64\
|
||||||
windows-amd64 windows-arm64\
|
windows-amd64 windows-arm64\
|
||||||
|
|
||||||
|
|
||||||
darwin-all: darwin-amd64 darwin-arm64
|
|
||||||
|
|
||||||
docker:
|
docker:
|
||||||
GOAMD64=v1 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
GOAMD64=v3 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
||||||
|
|
||||||
darwin-amd64:
|
darwin-amd64:
|
||||||
GOARCH=amd64 GOOS=darwin GOAMD64=v3 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
GOARCH=amd64 GOOS=darwin GOAMD64=v3 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
||||||
|
|
||||||
darwin-amd64-compatible:
|
darwin-amd64-compatible:
|
||||||
GOARCH=amd64 GOOS=darwin GOAMD64=v1 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
GOARCH=amd64 GOOS=darwin GOAMD64=v2 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
||||||
|
|
||||||
darwin-arm64:
|
darwin-arm64:
|
||||||
GOARCH=arm64 GOOS=darwin $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
GOARCH=arm64 GOOS=darwin $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
||||||
@ -69,7 +66,7 @@ linux-amd64:
|
|||||||
GOARCH=amd64 GOOS=linux GOAMD64=v3 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
GOARCH=amd64 GOOS=linux GOAMD64=v3 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
||||||
|
|
||||||
linux-amd64-compatible:
|
linux-amd64-compatible:
|
||||||
GOARCH=amd64 GOOS=linux GOAMD64=v1 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
GOARCH=amd64 GOOS=linux GOAMD64=v2 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
||||||
|
|
||||||
linux-arm64:
|
linux-arm64:
|
||||||
GOARCH=arm64 GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
GOARCH=arm64 GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
||||||
@ -101,9 +98,6 @@ linux-mips64:
|
|||||||
linux-mips64le:
|
linux-mips64le:
|
||||||
GOARCH=mips64le GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
GOARCH=mips64le GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
||||||
|
|
||||||
linux-riscv64:
|
|
||||||
GOARCH=riscv64 GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
|
||||||
|
|
||||||
android-arm64:
|
android-arm64:
|
||||||
GOARCH=arm64 GOOS=android $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
GOARCH=arm64 GOOS=android $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
||||||
|
|
||||||
@ -123,7 +117,7 @@ windows-amd64:
|
|||||||
GOARCH=amd64 GOOS=windows GOAMD64=v3 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.exe
|
GOARCH=amd64 GOOS=windows GOAMD64=v3 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.exe
|
||||||
|
|
||||||
windows-amd64-compatible:
|
windows-amd64-compatible:
|
||||||
GOARCH=amd64 GOOS=windows GOAMD64=v1 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.exe
|
GOARCH=amd64 GOOS=windows GOAMD64=v2 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.exe
|
||||||
|
|
||||||
windows-arm64:
|
windows-arm64:
|
||||||
GOARCH=arm64 GOOS=windows $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.exe
|
GOARCH=arm64 GOOS=windows $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.exe
|
||||||
@ -153,11 +147,3 @@ lint:
|
|||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm $(BINDIR)/*
|
rm $(BINDIR)/*
|
||||||
|
|
||||||
CLANG ?= clang-14
|
|
||||||
CFLAGS := -O2 -g -Wall -Werror $(CFLAGS)
|
|
||||||
|
|
||||||
ebpf: export BPF_CLANG := $(CLANG)
|
|
||||||
ebpf: export BPF_CFLAGS := $(CFLAGS)
|
|
||||||
ebpf:
|
|
||||||
cd component/ebpf/ && go generate ./...
|
|
||||||
|
137
README.md
137
README.md
@ -29,41 +29,12 @@
|
|||||||
- Netfilter TCP redirecting. Deploy Clash on your Internet gateway with `iptables`.
|
- Netfilter TCP redirecting. Deploy Clash on your Internet gateway with `iptables`.
|
||||||
- Comprehensive HTTP RESTful API controller
|
- Comprehensive HTTP RESTful API controller
|
||||||
|
|
||||||
## Wiki
|
## Getting Started
|
||||||
Configuration examples can be found at [/docs/config.yaml](https://github.com/MetaCubeX/Clash.Meta/blob/Alpha/docs/config.yaml), while documentation can be found [Clash.Meta Wiki](https://clash-meta.wiki).
|
Documentations are now moved to [GitHub Wiki](https://github.com/Dreamacro/clash/wiki).
|
||||||
|
|
||||||
## Build
|
## Advanced usage for this branch
|
||||||
|
|
||||||
You should install [golang](https://go.dev) first.
|
### DNS configuration
|
||||||
|
|
||||||
Then get the source code of Clash.Meta:
|
|
||||||
|
|
||||||
```shell
|
|
||||||
git clone https://github.com/MetaCubeX/Clash.Meta.git
|
|
||||||
cd Clash.Meta && go mod download
|
|
||||||
```
|
|
||||||
|
|
||||||
If you can't visit github,you should set proxy first:
|
|
||||||
|
|
||||||
```shell
|
|
||||||
go env -w GOPROXY=https://goproxy.io,direct
|
|
||||||
```
|
|
||||||
|
|
||||||
Now you can build it:
|
|
||||||
|
|
||||||
```shell
|
|
||||||
go build
|
|
||||||
```
|
|
||||||
|
|
||||||
If you need gvisor for tun stack, build with:
|
|
||||||
|
|
||||||
```shell
|
|
||||||
go build -tags with_gvisor
|
|
||||||
```
|
|
||||||
|
|
||||||
<!-- ## Advanced usage of this fork -->
|
|
||||||
|
|
||||||
<!-- ### DNS configuration
|
|
||||||
|
|
||||||
Support `geosite` with `fallback-filter`.
|
Support `geosite` with `fallback-filter`.
|
||||||
|
|
||||||
@ -73,6 +44,7 @@ Support resolve ip with a `Proxy Tunnel`.
|
|||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
proxy-groups:
|
proxy-groups:
|
||||||
|
|
||||||
- name: DNS
|
- name: DNS
|
||||||
type: url-test
|
type: url-test
|
||||||
use:
|
use:
|
||||||
@ -81,7 +53,6 @@ proxy-groups:
|
|||||||
interval: 180
|
interval: 180
|
||||||
lazy: true
|
lazy: true
|
||||||
```
|
```
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
dns:
|
dns:
|
||||||
enable: true
|
enable: true
|
||||||
@ -97,8 +68,8 @@ dns:
|
|||||||
- https://doh.pub/dns-query
|
- https://doh.pub/dns-query
|
||||||
- tls://223.5.5.5:853
|
- tls://223.5.5.5:853
|
||||||
fallback:
|
fallback:
|
||||||
- "https://1.0.0.1/dns-query#DNS" # append the proxy adapter name or group name to the end of DNS URL with '#' prefix.
|
- 'https://1.0.0.1/dns-query#DNS' # append the proxy adapter name or group name to the end of DNS URL with '#' prefix.
|
||||||
- "tls://8.8.4.4:853#DNS"
|
- 'tls://8.8.4.4:853#DNS'
|
||||||
fallback-filter:
|
fallback-filter:
|
||||||
geoip: false
|
geoip: false
|
||||||
geosite:
|
geosite:
|
||||||
@ -119,23 +90,21 @@ Built-in [Wintun](https://www.wintun.net) driver.
|
|||||||
# Enable the TUN listener
|
# Enable the TUN listener
|
||||||
tun:
|
tun:
|
||||||
enable: true
|
enable: true
|
||||||
stack: system # system/gvisor
|
stack: gvisor # only gvisor
|
||||||
dns-hijack:
|
dns-hijack:
|
||||||
- 0.0.0.0:53 # additional dns server listen on TUN
|
- 0.0.0.0:53 # additional dns server listen on TUN
|
||||||
auto-route: true # auto set global route
|
auto-route: true # auto set global route
|
||||||
```
|
```
|
||||||
|
|
||||||
### Rules configuration
|
### Rules configuration
|
||||||
|
|
||||||
- Support rule `GEOSITE`.
|
- Support rule `GEOSITE`.
|
||||||
- Support rule-providers `RULE-SET`.
|
- Support rule-providers `RULE-SET`.
|
||||||
- Support `multiport` condition for rule `SRC-PORT` and `DST-PORT`.
|
- Support `multiport` condition for rule `SRC-PORT` and `DST-PORT`.
|
||||||
- Support `network` condition for all rules.
|
- Support `network` condition for all rules.
|
||||||
- Support source IPCIDR condition for all rules, just append to the end.
|
- Support source IPCIDR condition for all rules, just append to the end.
|
||||||
- The `GEOSITE` databases via https://github.com/Loyalsoldier/v2ray-rules-dat.
|
- The `GEOSITE` databases via https://github.com/Loyalsoldier/v2ray-rules-dat.
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
rules:
|
rules:
|
||||||
|
|
||||||
# network(tcp/udp) condition for all rules
|
# network(tcp/udp) condition for all rules
|
||||||
- DOMAIN-SUFFIX,bilibili.com,DIRECT,tcp
|
- DOMAIN-SUFFIX,bilibili.com,DIRECT,tcp
|
||||||
- DOMAIN-SUFFIX,bilibili.com,REJECT,udp
|
- DOMAIN-SUFFIX,bilibili.com,REJECT,udp
|
||||||
@ -164,6 +133,7 @@ rules:
|
|||||||
- MATCH,PROXY
|
- MATCH,PROXY
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
### Proxies configuration
|
### Proxies configuration
|
||||||
|
|
||||||
Active health detection `urltest / fallback` (based on tcp handshake, multiple failures within a limited time will actively trigger health detection to use the node)
|
Active health detection `urltest / fallback` (based on tcp handshake, multiple failures within a limited time will actively trigger health detection to use the node)
|
||||||
@ -172,17 +142,18 @@ Support `Policy Group Filter`
|
|||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
proxy-groups:
|
proxy-groups:
|
||||||
|
|
||||||
- name: 🚀 HK Group
|
- name: 🚀 HK Group
|
||||||
type: select
|
type: select
|
||||||
use:
|
use:
|
||||||
- ALL
|
- ALL
|
||||||
filter: "HK"
|
filter: 'HK'
|
||||||
|
|
||||||
- name: 🚀 US Group
|
- name: 🚀 US Group
|
||||||
type: select
|
type: select
|
||||||
use:
|
use:
|
||||||
- ALL
|
- ALL
|
||||||
filter: "US"
|
filter: 'US'
|
||||||
|
|
||||||
proxy-providers:
|
proxy-providers:
|
||||||
ALL:
|
ALL:
|
||||||
@ -194,12 +165,14 @@ proxy-providers:
|
|||||||
enable: true
|
enable: true
|
||||||
interval: 600
|
interval: 600
|
||||||
url: http://www.gstatic.com/generate_204
|
url: http://www.gstatic.com/generate_204
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Support outbound transport protocol `VLESS`.
|
Support outbound transport protocol `VLESS`.
|
||||||
|
|
||||||
The XTLS support (TCP/UDP) transport by the XRAY-CORE.
|
The XTLS support (TCP/UDP) transport by the XRAY-CORE.
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
proxies:
|
proxies:
|
||||||
- name: "vless"
|
- name: "vless"
|
||||||
@ -239,46 +212,8 @@ proxies:
|
|||||||
grpc-service-name: grpcname
|
grpc-service-name: grpcname
|
||||||
```
|
```
|
||||||
|
|
||||||
Support outbound transport protocol `Wireguard`
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
proxies:
|
|
||||||
- name: "wg"
|
|
||||||
type: wireguard
|
|
||||||
server: 162.159.192.1
|
|
||||||
port: 2480
|
|
||||||
ip: 172.16.0.2
|
|
||||||
ipv6: fd01:5ca1:ab1e:80fa:ab85:6eea:213f:f4a5
|
|
||||||
private-key: eCtXsJZ27+4PbhDkHnB923tkUn2Gj59wZw5wFA75MnU=
|
|
||||||
public-key: Cr8hWlKvtDt7nrvf+f0brNQQzabAqrjfBvas9pmowjo=
|
|
||||||
udp: true
|
|
||||||
```
|
|
||||||
|
|
||||||
Support outbound transport protocol `Tuic`
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
proxies:
|
|
||||||
- name: "tuic"
|
|
||||||
server: www.example.com
|
|
||||||
port: 10443
|
|
||||||
type: tuic
|
|
||||||
token: TOKEN
|
|
||||||
# ip: 127.0.0.1 # for overwriting the DNS lookup result of the server address set in option 'server'
|
|
||||||
# heartbeat-interval: 10000
|
|
||||||
# alpn: [h3]
|
|
||||||
# disable-sni: true
|
|
||||||
reduce-rtt: true
|
|
||||||
# request-timeout: 8000
|
|
||||||
udp-relay-mode: native # Available: "native", "quic". Default: "native"
|
|
||||||
# congestion-controller: bbr # Available: "cubic", "new_reno", "bbr". Default: "cubic"
|
|
||||||
# max-udp-relay-packet-size: 1500
|
|
||||||
# fast-open: true
|
|
||||||
# skip-cert-verify: true
|
|
||||||
``` -->
|
|
||||||
|
|
||||||
### IPTABLES configuration
|
### IPTABLES configuration
|
||||||
|
Work on Linux OS who's supported `iptables`
|
||||||
Work on Linux OS which supported `iptables`
|
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
# Enable the TPROXY listener
|
# Enable the TPROXY listener
|
||||||
@ -289,15 +224,17 @@ iptables:
|
|||||||
inbound-interface: eth0 # detect the inbound interface, default is 'lo'
|
inbound-interface: eth0 # detect the inbound interface, default is 'lo'
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
### General installation guide for Linux
|
### General installation guide for Linux
|
||||||
|
+ Create user given name `clash-meta`
|
||||||
|
|
||||||
- Create user given name `clash-meta`
|
+ Download and decompress pre-built binaries from [releases](https://github.com/MetaCubeX/Clash.Meta/releases)
|
||||||
|
|
||||||
- Download and decompress pre-built binaries from [releases](https://github.com/MetaCubeX/Clash.Meta/releases)
|
+ Rename executable file to `Clash-Meta` and move to `/usr/local/bin/`
|
||||||
|
|
||||||
|
+ Create folder `/etc/Clash-Meta/` as working directory
|
||||||
|
|
||||||
- Rename executable file to `Clash-Meta` and move to `/usr/local/bin/`
|
|
||||||
|
|
||||||
- Create folder `/etc/Clash-Meta/` as working directory
|
|
||||||
|
|
||||||
Run Meta Kernel by user `clash-meta` as a daemon.
|
Run Meta Kernel by user `clash-meta` as a daemon.
|
||||||
|
|
||||||
@ -314,8 +251,8 @@ User=clash-meta
|
|||||||
Group=clash-meta
|
Group=clash-meta
|
||||||
LimitNPROC=500
|
LimitNPROC=500
|
||||||
LimitNOFILE=1000000
|
LimitNOFILE=1000000
|
||||||
CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_RAW CAP_NET_BIND_SERVICE
|
CapabilityBoundingSet=cap_net_admin
|
||||||
AmbientCapabilities=CAP_NET_ADMIN CAP_NET_RAW CAP_NET_BIND_SERVICE
|
AmbientCapabilities=cap_net_admin
|
||||||
Restart=always
|
Restart=always
|
||||||
ExecStartPre=/usr/bin/sleep 1s
|
ExecStartPre=/usr/bin/sleep 1s
|
||||||
ExecStart=/usr/local/bin/Clash-Meta -d /etc/Clash-Meta
|
ExecStart=/usr/local/bin/Clash-Meta -d /etc/Clash-Meta
|
||||||
@ -323,13 +260,10 @@ ExecStart=/usr/local/bin/Clash-Meta -d /etc/Clash-Meta
|
|||||||
[Install]
|
[Install]
|
||||||
WantedBy=multi-user.target
|
WantedBy=multi-user.target
|
||||||
```
|
```
|
||||||
|
|
||||||
Launch clashd on system startup with:
|
Launch clashd on system startup with:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
$ systemctl enable Clash-Meta
|
$ systemctl enable Clash-Meta
|
||||||
```
|
```
|
||||||
|
|
||||||
Launch clashd immediately with:
|
Launch clashd immediately with:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
@ -340,29 +274,22 @@ $ systemctl start Clash-Meta
|
|||||||
|
|
||||||
Clash add field `Process` to `Metadata` and prepare to get process name for Restful API `GET /connections`.
|
Clash add field `Process` to `Metadata` and prepare to get process name for Restful API `GET /connections`.
|
||||||
|
|
||||||
To display process name in GUI please use [Razord-meta](https://github.com/MetaCubeX/Razord-meta).
|
To display process name in GUI please use [Dashboard For Meta](https://github.com/Clash-Mini/Dashboard).
|
||||||
|
|
||||||
### Dashboard
|

|
||||||
|
|
||||||
We also made a custom fork of yacd provide better support for this project, check it out at [Yacd-meta](https://github.com/MetaCubeX/Yacd-meta)
|
|
||||||
|
|
||||||
## Development
|
## Development
|
||||||
|
|
||||||
If you want to build an application that uses clash as a library, check out the
|
If you want to build an application that uses clash as a library, check out the
|
||||||
the [GitHub Wiki](https://github.com/Dreamacro/clash/wiki/use-clash-as-a-library)
|
the [GitHub Wiki](https://github.com/Dreamacro/clash/wiki/use-clash-as-a-library)
|
||||||
|
|
||||||
## Debugging
|
|
||||||
Check [wiki](https://github.com/MetaCubeX/Clash.Meta/wiki/How-to-use-debug-api) to get an instruction on using debug API.
|
|
||||||
|
|
||||||
|
|
||||||
## Credits
|
## Credits
|
||||||
|
|
||||||
- [Dreamacro/clash](https://github.com/Dreamacro/clash)
|
* [Dreamacro/clash](https://github.com/Dreamacro/clash)
|
||||||
- [SagerNet/sing-box](https://github.com/SagerNet/sing-box)
|
* [riobard/go-shadowsocks2](https://github.com/riobard/go-shadowsocks2)
|
||||||
- [riobard/go-shadowsocks2](https://github.com/riobard/go-shadowsocks2)
|
* [v2ray/v2ray-core](https://github.com/v2ray/v2ray-core)
|
||||||
- [v2ray/v2ray-core](https://github.com/v2ray/v2ray-core)
|
* [WireGuard/wireguard-go](https://github.com/WireGuard/wireguard-go)
|
||||||
- [WireGuard/wireguard-go](https://github.com/WireGuard/wireguard-go)
|
* [yaling888/clash-plus-pro](https://github.com/yaling888/clash)
|
||||||
- [yaling888/clash-plus-pro](https://github.com/yaling888/clash)
|
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
|
@ -4,15 +4,16 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/Dreamacro/clash/common/queue"
|
|
||||||
"github.com/Dreamacro/clash/component/dialer"
|
|
||||||
C "github.com/Dreamacro/clash/constant"
|
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/Dreamacro/clash/common/queue"
|
||||||
|
"github.com/Dreamacro/clash/component/dialer"
|
||||||
|
C "github.com/Dreamacro/clash/constant"
|
||||||
"go.uber.org/atomic"
|
"go.uber.org/atomic"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -39,6 +40,11 @@ func (p *Proxy) Dial(metadata *C.Metadata) (C.Conn, error) {
|
|||||||
// DialContext implements C.ProxyAdapter
|
// DialContext implements C.ProxyAdapter
|
||||||
func (p *Proxy) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.Conn, error) {
|
func (p *Proxy) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.Conn, error) {
|
||||||
conn, err := p.ProxyAdapter.DialContext(ctx, metadata, opts...)
|
conn, err := p.ProxyAdapter.DialContext(ctx, metadata, opts...)
|
||||||
|
wasCancel := false
|
||||||
|
if err != nil {
|
||||||
|
wasCancel = strings.Contains(err.Error(), "operation was canceled")
|
||||||
|
}
|
||||||
|
p.alive.Store(err == nil || wasCancel)
|
||||||
return conn, err
|
return conn, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -52,6 +58,7 @@ func (p *Proxy) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
|
|||||||
// ListenPacketContext implements C.ProxyAdapter
|
// ListenPacketContext implements C.ProxyAdapter
|
||||||
func (p *Proxy) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) {
|
func (p *Proxy) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) {
|
||||||
pc, err := p.ProxyAdapter.ListenPacketContext(ctx, metadata, opts...)
|
pc, err := p.ProxyAdapter.ListenPacketContext(ctx, metadata, opts...)
|
||||||
|
p.alive.Store(err == nil)
|
||||||
return pc, err
|
return pc, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -92,8 +99,6 @@ func (p *Proxy) MarshalJSON() ([]byte, error) {
|
|||||||
mapping["history"] = p.DelayHistory()
|
mapping["history"] = p.DelayHistory()
|
||||||
mapping["name"] = p.Name()
|
mapping["name"] = p.Name()
|
||||||
mapping["udp"] = p.SupportUDP()
|
mapping["udp"] = p.SupportUDP()
|
||||||
mapping["xudp"] = p.SupportXUDP()
|
|
||||||
mapping["tfo"] = p.SupportTFO()
|
|
||||||
return json.Marshal(mapping)
|
return json.Marshal(mapping)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -146,32 +151,25 @@ func (p *Proxy) URLTest(ctx context.Context, url string) (t uint16, err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
client := http.Client{
|
client := http.Client{
|
||||||
Timeout: 30 * time.Second,
|
|
||||||
Transport: transport,
|
Transport: transport,
|
||||||
CheckRedirect: func(req *http.Request, via []*http.Request) error {
|
CheckRedirect: func(req *http.Request, via []*http.Request) error {
|
||||||
return http.ErrUseLastResponse
|
return http.ErrUseLastResponse
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
defer client.CloseIdleConnections()
|
defer client.CloseIdleConnections()
|
||||||
|
|
||||||
resp, err := client.Do(req)
|
resp, err := client.Do(req)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
_ = resp.Body.Close()
|
|
||||||
|
|
||||||
if unifiedDelay {
|
if unifiedDelay {
|
||||||
second := time.Now()
|
start = time.Now()
|
||||||
resp, err = client.Do(req)
|
resp, err = client.Do(req)
|
||||||
if err == nil {
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
_ = resp.Body.Close()
|
_ = resp.Body.Close()
|
||||||
start = second
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
t = uint16(time.Since(start) / time.Millisecond)
|
t = uint16(time.Since(start) / time.Millisecond)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -200,6 +198,7 @@ func urlToMetadata(rawURL string) (addr C.Metadata, err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
addr = C.Metadata{
|
addr = C.Metadata{
|
||||||
|
AddrType: C.AtypDomainName,
|
||||||
Host: u.Hostname(),
|
Host: u.Hostname(),
|
||||||
DstIP: netip.Addr{},
|
DstIP: netip.Addr{},
|
||||||
DstPort: port,
|
DstPort: port,
|
||||||
|
@ -1,29 +0,0 @@
|
|||||||
package inbound
|
|
||||||
|
|
||||||
import (
|
|
||||||
C "github.com/Dreamacro/clash/constant"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Addition func(metadata *C.Metadata)
|
|
||||||
|
|
||||||
func (a Addition) Apply(metadata *C.Metadata) {
|
|
||||||
a(metadata)
|
|
||||||
}
|
|
||||||
|
|
||||||
func WithInName(name string) Addition {
|
|
||||||
return func(metadata *C.Metadata) {
|
|
||||||
metadata.InName = name
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func WithSpecialRules(specialRules string) Addition {
|
|
||||||
return func(metadata *C.Metadata) {
|
|
||||||
metadata.SpecialRules = specialRules
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func WithSpecialProxy(specialProxy string) Addition {
|
|
||||||
return func(metadata *C.Metadata) {
|
|
||||||
metadata.SpecialProxy = specialProxy
|
|
||||||
}
|
|
||||||
}
|
|
@ -9,20 +9,13 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// NewHTTP receive normal http request and return HTTPContext
|
// NewHTTP receive normal http request and return HTTPContext
|
||||||
func NewHTTP(target socks5.Addr, source net.Addr, conn net.Conn, additions ...Addition) *context.ConnContext {
|
func NewHTTP(target socks5.Addr, source net.Addr, conn net.Conn) *context.ConnContext {
|
||||||
metadata := parseSocksAddr(target)
|
metadata := parseSocksAddr(target)
|
||||||
metadata.NetWork = C.TCP
|
metadata.NetWork = C.TCP
|
||||||
metadata.Type = C.HTTP
|
metadata.Type = C.HTTP
|
||||||
for _, addition := range additions {
|
if ip, port, err := parseAddr(source.String()); err == nil {
|
||||||
addition.Apply(metadata)
|
|
||||||
}
|
|
||||||
if ip, port, err := parseAddr(source); err == nil {
|
|
||||||
metadata.SrcIP = ip
|
metadata.SrcIP = ip
|
||||||
metadata.SrcPort = port
|
metadata.SrcPort = port
|
||||||
}
|
}
|
||||||
if ip, port, err := parseAddr(conn.LocalAddr()); err == nil {
|
|
||||||
metadata.InIP = ip
|
|
||||||
metadata.InPort = port
|
|
||||||
}
|
|
||||||
return context.NewConnContext(conn, metadata)
|
return context.NewConnContext(conn, metadata)
|
||||||
}
|
}
|
||||||
|
@ -9,19 +9,12 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// NewHTTPS receive CONNECT request and return ConnContext
|
// NewHTTPS receive CONNECT request and return ConnContext
|
||||||
func NewHTTPS(request *http.Request, conn net.Conn, additions ...Addition) *context.ConnContext {
|
func NewHTTPS(request *http.Request, conn net.Conn) *context.ConnContext {
|
||||||
metadata := parseHTTPAddr(request)
|
metadata := parseHTTPAddr(request)
|
||||||
metadata.Type = C.HTTPS
|
metadata.Type = C.HTTPS
|
||||||
for _, addition := range additions {
|
if ip, port, err := parseAddr(conn.RemoteAddr().String()); err == nil {
|
||||||
addition.Apply(metadata)
|
|
||||||
}
|
|
||||||
if ip, port, err := parseAddr(conn.RemoteAddr()); err == nil {
|
|
||||||
metadata.SrcIP = ip
|
metadata.SrcIP = ip
|
||||||
metadata.SrcPort = port
|
metadata.SrcPort = port
|
||||||
}
|
}
|
||||||
if ip, port, err := parseAddr(conn.LocalAddr()); err == nil {
|
|
||||||
metadata.InIP = ip
|
|
||||||
metadata.InPort = port
|
|
||||||
}
|
|
||||||
return context.NewConnContext(conn, metadata)
|
return context.NewConnContext(conn, metadata)
|
||||||
}
|
}
|
||||||
|
@ -1,26 +0,0 @@
|
|||||||
package inbound
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"net"
|
|
||||||
|
|
||||||
"github.com/sagernet/tfo-go"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
lc = tfo.ListenConfig{
|
|
||||||
DisableTFO: true,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
func SetTfo(open bool) {
|
|
||||||
lc.DisableTFO = !open
|
|
||||||
}
|
|
||||||
|
|
||||||
func ListenContext(ctx context.Context, network, address string) (net.Listener, error) {
|
|
||||||
return lc.Listen(ctx, network, address)
|
|
||||||
}
|
|
||||||
|
|
||||||
func Listen(network, address string) (net.Listener, error) {
|
|
||||||
return ListenContext(context.Background(), network, address)
|
|
||||||
}
|
|
@ -2,7 +2,7 @@ package inbound
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
"github.com/Dreamacro/clash/transport/socks5"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
)
|
)
|
||||||
|
|
||||||
// PacketAdapter is a UDP Packet adapter for socks/redir/tun
|
// PacketAdapter is a UDP Packet adapter for socks/redir/tun
|
||||||
@ -17,26 +17,17 @@ func (s *PacketAdapter) Metadata() *C.Metadata {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewPacket is PacketAdapter generator
|
// NewPacket is PacketAdapter generator
|
||||||
func NewPacket(target socks5.Addr, packet C.UDPPacket, source C.Type, additions ...Addition) C.PacketAdapter {
|
func NewPacket(target M.Socksaddr, packet C.UDPPacket, source C.Type) *PacketAdapter {
|
||||||
metadata := parseSocksAddr(target)
|
metadata := socksAddrToMetadata(target)
|
||||||
metadata.NetWork = C.UDP
|
metadata.NetWork = C.UDP
|
||||||
metadata.Type = source
|
metadata.Type = source
|
||||||
for _, addition := range additions {
|
if ip, port, err := parseAddr(packet.LocalAddr().String()); err == nil {
|
||||||
addition.Apply(metadata)
|
|
||||||
}
|
|
||||||
if ip, port, err := parseAddr(packet.LocalAddr()); err == nil {
|
|
||||||
metadata.SrcIP = ip
|
metadata.SrcIP = ip
|
||||||
metadata.SrcPort = port
|
metadata.SrcPort = port
|
||||||
}
|
}
|
||||||
if p, ok := packet.(C.UDPPacketInAddr); ok {
|
|
||||||
if ip, port, err := parseAddr(p.InAddr()); err == nil {
|
|
||||||
metadata.InIP = ip
|
|
||||||
metadata.InPort = port
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return &PacketAdapter{
|
return &PacketAdapter{
|
||||||
packet,
|
UDPPacket: packet,
|
||||||
metadata,
|
metadata: metadata,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,21 +10,17 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// NewSocket receive TCP inbound and return ConnContext
|
// NewSocket receive TCP inbound and return ConnContext
|
||||||
func NewSocket(target socks5.Addr, conn net.Conn, source C.Type, additions ...Addition) *context.ConnContext {
|
func NewSocket(target socks5.Addr, conn net.Conn, source C.Type) *context.ConnContext {
|
||||||
metadata := parseSocksAddr(target)
|
metadata := parseSocksAddr(target)
|
||||||
metadata.NetWork = C.TCP
|
metadata.NetWork = C.TCP
|
||||||
metadata.Type = source
|
metadata.Type = source
|
||||||
for _, addition := range additions {
|
remoteAddr := conn.RemoteAddr()
|
||||||
addition.Apply(metadata)
|
// Filter when net.Addr interface is nil
|
||||||
}
|
if remoteAddr != nil {
|
||||||
|
if ip, port, err := parseAddr(remoteAddr.String()); err == nil {
|
||||||
if ip, port, err := parseAddr(conn.RemoteAddr()); err == nil {
|
|
||||||
metadata.SrcIP = ip
|
metadata.SrcIP = ip
|
||||||
metadata.SrcPort = port
|
metadata.SrcPort = port
|
||||||
}
|
}
|
||||||
if ip, port, err := parseAddr(conn.LocalAddr()); err == nil {
|
|
||||||
metadata.InIP = ip
|
|
||||||
metadata.InPort = port
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return context.NewConnContext(conn, metadata)
|
return context.NewConnContext(conn, metadata)
|
||||||
@ -34,16 +30,19 @@ func NewInner(conn net.Conn, dst string, host string) *context.ConnContext {
|
|||||||
metadata := &C.Metadata{}
|
metadata := &C.Metadata{}
|
||||||
metadata.NetWork = C.TCP
|
metadata.NetWork = C.TCP
|
||||||
metadata.Type = C.INNER
|
metadata.Type = C.INNER
|
||||||
metadata.DNSMode = C.DNSNormal
|
metadata.DNSMode = C.DNSMapping
|
||||||
metadata.Host = host
|
metadata.Host = host
|
||||||
|
metadata.AddrType = C.AtypDomainName
|
||||||
metadata.Process = C.ClashName
|
metadata.Process = C.ClashName
|
||||||
if h, port, err := net.SplitHostPort(dst); err == nil {
|
if h, port, err := net.SplitHostPort(dst); err == nil {
|
||||||
metadata.DstPort = port
|
metadata.DstPort = port
|
||||||
if host == "" {
|
if host == "" {
|
||||||
if ip, err := netip.ParseAddr(h); err == nil {
|
if ip, err := netip.ParseAddr(h); err == nil {
|
||||||
metadata.DstIP = ip
|
metadata.DstIP = ip
|
||||||
} else {
|
metadata.AddrType = C.AtypIPv4
|
||||||
metadata.Host = h
|
if ip.Is6() {
|
||||||
|
metadata.AddrType = C.AtypIPv6
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
package inbound
|
package inbound
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
@ -11,10 +10,30 @@ import (
|
|||||||
"github.com/Dreamacro/clash/common/nnip"
|
"github.com/Dreamacro/clash/common/nnip"
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
"github.com/Dreamacro/clash/transport/socks5"
|
"github.com/Dreamacro/clash/transport/socks5"
|
||||||
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
)
|
)
|
||||||
|
|
||||||
func parseSocksAddr(target socks5.Addr) *C.Metadata {
|
func socksAddrToMetadata(addr M.Socksaddr) *C.Metadata {
|
||||||
metadata := &C.Metadata{}
|
metadata := &C.Metadata{}
|
||||||
|
switch addr.Family() {
|
||||||
|
case M.AddressFamilyIPv4:
|
||||||
|
metadata.AddrType = C.AtypIPv4
|
||||||
|
metadata.DstIP = addr.Addr
|
||||||
|
case M.AddressFamilyIPv6:
|
||||||
|
metadata.AddrType = C.AtypIPv6
|
||||||
|
metadata.DstIP = addr.Addr
|
||||||
|
case M.AddressFamilyFqdn:
|
||||||
|
metadata.AddrType = C.AtypDomainName
|
||||||
|
metadata.Host = addr.Fqdn
|
||||||
|
}
|
||||||
|
metadata.DstPort = strconv.Itoa(int(addr.Port))
|
||||||
|
return metadata
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseSocksAddr(target socks5.Addr) *C.Metadata {
|
||||||
|
metadata := &C.Metadata{
|
||||||
|
AddrType: int(target[0]),
|
||||||
|
}
|
||||||
|
|
||||||
switch target[0] {
|
switch target[0] {
|
||||||
case socks5.AtypDomainName:
|
case socks5.AtypDomainName:
|
||||||
@ -25,8 +44,7 @@ func parseSocksAddr(target socks5.Addr) *C.Metadata {
|
|||||||
metadata.DstIP = nnip.IpToAddr(net.IP(target[1 : 1+net.IPv4len]))
|
metadata.DstIP = nnip.IpToAddr(net.IP(target[1 : 1+net.IPv4len]))
|
||||||
metadata.DstPort = strconv.Itoa((int(target[1+net.IPv4len]) << 8) | int(target[1+net.IPv4len+1]))
|
metadata.DstPort = strconv.Itoa((int(target[1+net.IPv4len]) << 8) | int(target[1+net.IPv4len+1]))
|
||||||
case socks5.AtypIPv6:
|
case socks5.AtypIPv6:
|
||||||
ip6, _ := netip.AddrFromSlice(target[1 : 1+net.IPv6len])
|
metadata.DstIP = nnip.IpToAddr(net.IP(target[1 : 1+net.IPv6len]))
|
||||||
metadata.DstIP = ip6.Unmap()
|
|
||||||
metadata.DstPort = strconv.Itoa((int(target[1+net.IPv6len]) << 8) | int(target[1+net.IPv6len+1]))
|
metadata.DstPort = strconv.Itoa((int(target[1+net.IPv6len]) << 8) | int(target[1+net.IPv6len+1]))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -45,6 +63,7 @@ func parseHTTPAddr(request *http.Request) *C.Metadata {
|
|||||||
|
|
||||||
metadata := &C.Metadata{
|
metadata := &C.Metadata{
|
||||||
NetWork: C.TCP,
|
NetWork: C.TCP,
|
||||||
|
AddrType: C.AtypDomainName,
|
||||||
Host: host,
|
Host: host,
|
||||||
DstIP: netip.Addr{},
|
DstIP: netip.Addr{},
|
||||||
DstPort: port,
|
DstPort: port,
|
||||||
@ -52,25 +71,20 @@ func parseHTTPAddr(request *http.Request) *C.Metadata {
|
|||||||
|
|
||||||
ip, err := netip.ParseAddr(host)
|
ip, err := netip.ParseAddr(host)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
switch {
|
||||||
|
case ip.Is6():
|
||||||
|
metadata.AddrType = C.AtypIPv6
|
||||||
|
default:
|
||||||
|
metadata.AddrType = C.AtypIPv4
|
||||||
|
}
|
||||||
metadata.DstIP = ip
|
metadata.DstIP = ip
|
||||||
}
|
}
|
||||||
|
|
||||||
return metadata
|
return metadata
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseAddr(addr net.Addr) (netip.Addr, string, error) {
|
func parseAddr(addr string) (netip.Addr, string, error) {
|
||||||
// Filter when net.Addr interface is nil
|
host, port, err := net.SplitHostPort(addr)
|
||||||
if addr == nil {
|
|
||||||
return netip.Addr{}, "", errors.New("nil addr")
|
|
||||||
}
|
|
||||||
if rawAddr, ok := addr.(interface{ RawAddr() net.Addr }); ok {
|
|
||||||
ip, port, err := parseAddr(rawAddr.RawAddr())
|
|
||||||
if err == nil {
|
|
||||||
return ip, port, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
addrStr := addr.String()
|
|
||||||
host, port, err := net.SplitHostPort(addrStr)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return netip.Addr{}, "", err
|
return netip.Addr{}, "", err
|
||||||
}
|
}
|
||||||
|
@ -7,10 +7,13 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
N "github.com/Dreamacro/clash/common/net"
|
|
||||||
"github.com/Dreamacro/clash/common/utils"
|
|
||||||
"github.com/Dreamacro/clash/component/dialer"
|
"github.com/Dreamacro/clash/component/dialer"
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
|
"github.com/gofrs/uuid"
|
||||||
|
"github.com/sagernet/sing/common/buf"
|
||||||
|
"github.com/sagernet/sing/common/bufio"
|
||||||
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
|
N "github.com/sagernet/sing/common/network"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Base struct {
|
type Base struct {
|
||||||
@ -19,11 +22,8 @@ type Base struct {
|
|||||||
iface string
|
iface string
|
||||||
tp C.AdapterType
|
tp C.AdapterType
|
||||||
udp bool
|
udp bool
|
||||||
xudp bool
|
|
||||||
tfo bool
|
|
||||||
rmark int
|
rmark int
|
||||||
id string
|
id string
|
||||||
prefer C.DNSPrefer
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Name implements C.ProxyAdapter
|
// Name implements C.ProxyAdapter
|
||||||
@ -34,7 +34,12 @@ func (b *Base) Name() string {
|
|||||||
// Id implements C.ProxyAdapter
|
// Id implements C.ProxyAdapter
|
||||||
func (b *Base) Id() string {
|
func (b *Base) Id() string {
|
||||||
if b.id == "" {
|
if b.id == "" {
|
||||||
b.id = utils.NewUUIDV6().String()
|
id, err := uuid.NewV6()
|
||||||
|
if err != nil {
|
||||||
|
b.id = b.name
|
||||||
|
} else {
|
||||||
|
b.id = id.String()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return b.id
|
return b.id
|
||||||
@ -54,26 +59,16 @@ func (b *Base) DialContext(ctx context.Context, metadata *C.Metadata, opts ...di
|
|||||||
return nil, errors.New("no support")
|
return nil, errors.New("no support")
|
||||||
}
|
}
|
||||||
|
|
||||||
// DialContextWithDialer implements C.ProxyAdapter
|
|
||||||
func (b *Base) DialContextWithDialer(ctx context.Context, dialer C.Dialer, metadata *C.Metadata) (_ C.Conn, err error) {
|
|
||||||
return nil, errors.New("no support")
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListenPacketContext implements C.ProxyAdapter
|
// ListenPacketContext implements C.ProxyAdapter
|
||||||
func (b *Base) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) {
|
func (b *Base) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) {
|
||||||
return nil, errors.New("no support")
|
return nil, errors.New("no support")
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListenPacketWithDialer implements C.ProxyAdapter
|
// ListenPacketOnStreamConn implements C.ProxyAdapter
|
||||||
func (b *Base) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, metadata *C.Metadata) (_ C.PacketConn, err error) {
|
func (b *Base) ListenPacketOnStreamConn(c net.Conn, metadata *C.Metadata) (_ C.PacketConn, err error) {
|
||||||
return nil, errors.New("no support")
|
return nil, errors.New("no support")
|
||||||
}
|
}
|
||||||
|
|
||||||
// SupportWithDialer implements C.ProxyAdapter
|
|
||||||
func (b *Base) SupportWithDialer() bool {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// SupportUOT implements C.ProxyAdapter
|
// SupportUOT implements C.ProxyAdapter
|
||||||
func (b *Base) SupportUOT() bool {
|
func (b *Base) SupportUOT() bool {
|
||||||
return false
|
return false
|
||||||
@ -84,16 +79,6 @@ func (b *Base) SupportUDP() bool {
|
|||||||
return b.udp
|
return b.udp
|
||||||
}
|
}
|
||||||
|
|
||||||
// SupportXUDP implements C.ProxyAdapter
|
|
||||||
func (b *Base) SupportXUDP() bool {
|
|
||||||
return b.xudp
|
|
||||||
}
|
|
||||||
|
|
||||||
// SupportTFO implements C.ProxyAdapter
|
|
||||||
func (b *Base) SupportTFO() bool {
|
|
||||||
return b.tfo
|
|
||||||
}
|
|
||||||
|
|
||||||
// MarshalJSON implements C.ProxyAdapter
|
// MarshalJSON implements C.ProxyAdapter
|
||||||
func (b *Base) MarshalJSON() ([]byte, error) {
|
func (b *Base) MarshalJSON() ([]byte, error) {
|
||||||
return json.Marshal(map[string]string{
|
return json.Marshal(map[string]string{
|
||||||
@ -108,7 +93,7 @@ func (b *Base) Addr() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Unwrap implements C.ProxyAdapter
|
// Unwrap implements C.ProxyAdapter
|
||||||
func (b *Base) Unwrap(metadata *C.Metadata, touch bool) C.Proxy {
|
func (b *Base) Unwrap(metadata *C.Metadata) C.Proxy {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -122,30 +107,12 @@ func (b *Base) DialOptions(opts ...dialer.Option) []dialer.Option {
|
|||||||
opts = append(opts, dialer.WithRoutingMark(b.rmark))
|
opts = append(opts, dialer.WithRoutingMark(b.rmark))
|
||||||
}
|
}
|
||||||
|
|
||||||
switch b.prefer {
|
|
||||||
case C.IPv4Only:
|
|
||||||
opts = append(opts, dialer.WithOnlySingleStack(true))
|
|
||||||
case C.IPv6Only:
|
|
||||||
opts = append(opts, dialer.WithOnlySingleStack(false))
|
|
||||||
case C.IPv4Prefer:
|
|
||||||
opts = append(opts, dialer.WithPreferIPv4())
|
|
||||||
case C.IPv6Prefer:
|
|
||||||
opts = append(opts, dialer.WithPreferIPv6())
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
|
|
||||||
if b.tfo {
|
|
||||||
opts = append(opts, dialer.WithTFO(true))
|
|
||||||
}
|
|
||||||
|
|
||||||
return opts
|
return opts
|
||||||
}
|
}
|
||||||
|
|
||||||
type BasicOption struct {
|
type BasicOption struct {
|
||||||
TFO bool `proxy:"tfo,omitempty" group:"tfo,omitempty"`
|
|
||||||
Interface string `proxy:"interface-name,omitempty" group:"interface-name,omitempty"`
|
Interface string `proxy:"interface-name,omitempty" group:"interface-name,omitempty"`
|
||||||
RoutingMark int `proxy:"routing-mark,omitempty" group:"routing-mark,omitempty"`
|
RoutingMark int `proxy:"routing-mark,omitempty" group:"routing-mark,omitempty"`
|
||||||
IPVersion string `proxy:"ip-version,omitempty" group:"ip-version,omitempty"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type BaseOption struct {
|
type BaseOption struct {
|
||||||
@ -153,11 +120,8 @@ type BaseOption struct {
|
|||||||
Addr string
|
Addr string
|
||||||
Type C.AdapterType
|
Type C.AdapterType
|
||||||
UDP bool
|
UDP bool
|
||||||
XUDP bool
|
|
||||||
TFO bool
|
|
||||||
Interface string
|
Interface string
|
||||||
RoutingMark int
|
RoutingMark int
|
||||||
Prefer C.DNSPrefer
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewBase(opt BaseOption) *Base {
|
func NewBase(opt BaseOption) *Base {
|
||||||
@ -166,16 +130,13 @@ func NewBase(opt BaseOption) *Base {
|
|||||||
addr: opt.Addr,
|
addr: opt.Addr,
|
||||||
tp: opt.Type,
|
tp: opt.Type,
|
||||||
udp: opt.UDP,
|
udp: opt.UDP,
|
||||||
xudp: opt.XUDP,
|
|
||||||
tfo: opt.TFO,
|
|
||||||
iface: opt.Interface,
|
iface: opt.Interface,
|
||||||
rmark: opt.RoutingMark,
|
rmark: opt.RoutingMark,
|
||||||
prefer: opt.Prefer,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type conn struct {
|
type conn struct {
|
||||||
N.ExtendedConn
|
net.Conn
|
||||||
chain C.Chain
|
chain C.Chain
|
||||||
actualRemoteDestination string
|
actualRemoteDestination string
|
||||||
}
|
}
|
||||||
@ -194,19 +155,14 @@ func (c *conn) AppendToChains(a C.ProxyAdapter) {
|
|||||||
c.chain = append(c.chain, a.Name())
|
c.chain = append(c.chain, a.Name())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *conn) Upstream() any {
|
|
||||||
return c.ExtendedConn
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewConn(c net.Conn, a C.ProxyAdapter) C.Conn {
|
func NewConn(c net.Conn, a C.ProxyAdapter) C.Conn {
|
||||||
return &conn{N.NewExtendedConn(c), []string{a.Name()}, parseRemoteDestination(a.Addr())}
|
return &conn{c, []string{a.Name()}, parseRemoteDestination(a.Addr())}
|
||||||
}
|
}
|
||||||
|
|
||||||
type packetConn struct {
|
type packetConn struct {
|
||||||
net.PacketConn
|
net.PacketConn
|
||||||
|
nc N.PacketConn
|
||||||
chain C.Chain
|
chain C.Chain
|
||||||
adapterName string
|
|
||||||
connID string
|
|
||||||
actualRemoteDestination string
|
actualRemoteDestination string
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -224,13 +180,22 @@ func (c *packetConn) AppendToChains(a C.ProxyAdapter) {
|
|||||||
c.chain = append(c.chain, a.Name())
|
c.chain = append(c.chain, a.Name())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *packetConn) LocalAddr() net.Addr {
|
func (c *packetConn) ReadPacket(buffer *buf.Buffer) (addr M.Socksaddr, err error) {
|
||||||
lAddr := c.PacketConn.LocalAddr()
|
return c.nc.ReadPacket(buffer)
|
||||||
return N.NewCustomAddr(c.adapterName, c.connID, lAddr) // make quic-go's connMultiplexer happy
|
}
|
||||||
|
|
||||||
|
func (c *packetConn) WritePacket(buffer *buf.Buffer, addr M.Socksaddr) error {
|
||||||
|
return c.nc.WritePacket(buffer, addr)
|
||||||
}
|
}
|
||||||
|
|
||||||
func newPacketConn(pc net.PacketConn, a C.ProxyAdapter) C.PacketConn {
|
func newPacketConn(pc net.PacketConn, a C.ProxyAdapter) C.PacketConn {
|
||||||
return &packetConn{pc, []string{a.Name()}, a.Name(), utils.NewUUIDV4().String(), parseRemoteDestination(a.Addr())}
|
var nc N.PacketConn
|
||||||
|
if n, isNc := pc.(N.PacketConn); isNc {
|
||||||
|
nc = n
|
||||||
|
} else {
|
||||||
|
nc = &bufio.PacketConnWrapper{PacketConn: pc}
|
||||||
|
}
|
||||||
|
return &packetConn{pc, nc, []string{a.Name()}, parseRemoteDestination(a.Addr())}
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseRemoteDestination(addr string) string {
|
func parseRemoteDestination(addr string) string {
|
||||||
|
@ -5,7 +5,6 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/component/dialer"
|
"github.com/Dreamacro/clash/component/dialer"
|
||||||
"github.com/Dreamacro/clash/component/resolver"
|
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -15,7 +14,7 @@ type Direct struct {
|
|||||||
|
|
||||||
// DialContext implements C.ProxyAdapter
|
// DialContext implements C.ProxyAdapter
|
||||||
func (d *Direct) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.Conn, error) {
|
func (d *Direct) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.Conn, error) {
|
||||||
opts = append(opts, dialer.WithResolver(resolver.DefaultResolver))
|
opts = append(opts, dialer.WithDirect())
|
||||||
c, err := dialer.DialContext(ctx, "tcp", metadata.RemoteAddress(), d.Base.DialOptions(opts...)...)
|
c, err := dialer.DialContext(ctx, "tcp", metadata.RemoteAddress(), d.Base.DialOptions(opts...)...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -26,8 +25,8 @@ func (d *Direct) DialContext(ctx context.Context, metadata *C.Metadata, opts ...
|
|||||||
|
|
||||||
// ListenPacketContext implements C.ProxyAdapter
|
// ListenPacketContext implements C.ProxyAdapter
|
||||||
func (d *Direct) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) {
|
func (d *Direct) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) {
|
||||||
opts = append(opts, dialer.WithResolver(resolver.DefaultResolver))
|
opts = append(opts, dialer.WithDirect())
|
||||||
pc, err := dialer.ListenPacket(ctx, dialer.ParseNetwork("udp", metadata.DstIP), "", d.Base.DialOptions(opts...)...)
|
pc, err := dialer.ListenPacket(ctx, "udp", "", d.Base.DialOptions(opts...)...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -44,7 +43,6 @@ func NewDirect() *Direct {
|
|||||||
name: "DIRECT",
|
name: "DIRECT",
|
||||||
tp: C.Direct,
|
tp: C.Direct,
|
||||||
udp: true,
|
udp: true,
|
||||||
prefer: C.DualStack,
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -55,7 +53,6 @@ func NewCompatible() *Direct {
|
|||||||
name: "COMPATIBLE",
|
name: "COMPATIBLE",
|
||||||
tp: C.Compatible,
|
tp: C.Compatible,
|
||||||
udp: true,
|
udp: true,
|
||||||
prefer: C.DualStack,
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -14,7 +14,6 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/component/dialer"
|
"github.com/Dreamacro/clash/component/dialer"
|
||||||
tlsC "github.com/Dreamacro/clash/component/tls"
|
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -36,7 +35,6 @@ type HttpOption struct {
|
|||||||
TLS bool `proxy:"tls,omitempty"`
|
TLS bool `proxy:"tls,omitempty"`
|
||||||
SNI string `proxy:"sni,omitempty"`
|
SNI string `proxy:"sni,omitempty"`
|
||||||
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
|
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
|
||||||
Fingerprint string `proxy:"fingerprint,omitempty"`
|
|
||||||
Headers map[string]string `proxy:"headers,omitempty"`
|
Headers map[string]string `proxy:"headers,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -44,9 +42,7 @@ type HttpOption struct {
|
|||||||
func (h *Http) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
func (h *Http) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
||||||
if h.tlsConfig != nil {
|
if h.tlsConfig != nil {
|
||||||
cc := tls.Client(c, h.tlsConfig)
|
cc := tls.Client(c, h.tlsConfig)
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), C.DefaultTLSTimeout)
|
err := cc.Handshake()
|
||||||
defer cancel()
|
|
||||||
err := cc.HandshakeContext(ctx)
|
|
||||||
c = cc
|
c = cc
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("%s connect error: %w", h.addr, err)
|
return nil, fmt.Errorf("%s connect error: %w", h.addr, err)
|
||||||
@ -61,20 +57,13 @@ func (h *Http) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
|||||||
|
|
||||||
// DialContext implements C.ProxyAdapter
|
// DialContext implements C.ProxyAdapter
|
||||||
func (h *Http) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) {
|
func (h *Http) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) {
|
||||||
return h.DialContextWithDialer(ctx, dialer.NewDialer(h.Base.DialOptions(opts...)...), metadata)
|
c, err := dialer.DialContext(ctx, "tcp", h.addr, h.Base.DialOptions(opts...)...)
|
||||||
}
|
|
||||||
|
|
||||||
// DialContextWithDialer implements C.ProxyAdapter
|
|
||||||
func (h *Http) DialContextWithDialer(ctx context.Context, dialer C.Dialer, metadata *C.Metadata) (_ C.Conn, err error) {
|
|
||||||
c, err := dialer.DialContext(ctx, "tcp", h.addr)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("%s connect error: %w", h.addr, err)
|
return nil, fmt.Errorf("%s connect error: %w", h.addr, err)
|
||||||
}
|
}
|
||||||
tcpKeepAlive(c)
|
tcpKeepAlive(c)
|
||||||
|
|
||||||
defer func(c net.Conn) {
|
defer safeConnClose(c, err)
|
||||||
safeConnClose(c, err)
|
|
||||||
}(c)
|
|
||||||
|
|
||||||
c, err = h.StreamConn(c, metadata)
|
c, err = h.StreamConn(c, metadata)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -84,11 +73,6 @@ func (h *Http) DialContextWithDialer(ctx context.Context, dialer C.Dialer, metad
|
|||||||
return NewConn(c, h), nil
|
return NewConn(c, h), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// SupportWithDialer implements C.ProxyAdapter
|
|
||||||
func (h *Http) SupportWithDialer() bool {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *Http) shakeHand(metadata *C.Metadata, rw io.ReadWriter) error {
|
func (h *Http) shakeHand(metadata *C.Metadata, rw io.ReadWriter) error {
|
||||||
addr := metadata.RemoteAddress()
|
addr := metadata.RemoteAddress()
|
||||||
req := &http.Request{
|
req := &http.Request{
|
||||||
@ -102,7 +86,7 @@ func (h *Http) shakeHand(metadata *C.Metadata, rw io.ReadWriter) error {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
//增加headers
|
// 增加headers
|
||||||
if len(h.option.Headers) != 0 {
|
if len(h.option.Headers) != 0 {
|
||||||
for key, value := range h.option.Headers {
|
for key, value := range h.option.Headers {
|
||||||
req.Header.Add(key, value)
|
req.Header.Add(key, value)
|
||||||
@ -142,26 +126,16 @@ func (h *Http) shakeHand(metadata *C.Metadata, rw io.ReadWriter) error {
|
|||||||
return fmt.Errorf("can not connect remote err code: %d", resp.StatusCode)
|
return fmt.Errorf("can not connect remote err code: %d", resp.StatusCode)
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewHttp(option HttpOption) (*Http, error) {
|
func NewHttp(option HttpOption) *Http {
|
||||||
var tlsConfig *tls.Config
|
var tlsConfig *tls.Config
|
||||||
if option.TLS {
|
if option.TLS {
|
||||||
sni := option.Server
|
sni := option.Server
|
||||||
if option.SNI != "" {
|
if option.SNI != "" {
|
||||||
sni = option.SNI
|
sni = option.SNI
|
||||||
}
|
}
|
||||||
if len(option.Fingerprint) == 0 {
|
tlsConfig = &tls.Config{
|
||||||
tlsConfig = tlsC.GetGlobalTLSConfig(&tls.Config{
|
|
||||||
InsecureSkipVerify: option.SkipCertVerify,
|
InsecureSkipVerify: option.SkipCertVerify,
|
||||||
ServerName: sni,
|
ServerName: sni,
|
||||||
})
|
|
||||||
} else {
|
|
||||||
var err error
|
|
||||||
if tlsConfig, err = tlsC.GetSpecifiedFingerprintTLSConfig(&tls.Config{
|
|
||||||
InsecureSkipVerify: option.SkipCertVerify,
|
|
||||||
ServerName: sni,
|
|
||||||
}, option.Fingerprint); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -170,14 +144,12 @@ func NewHttp(option HttpOption) (*Http, error) {
|
|||||||
name: option.Name,
|
name: option.Name,
|
||||||
addr: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)),
|
addr: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)),
|
||||||
tp: C.Http,
|
tp: C.Http,
|
||||||
tfo: option.TFO,
|
|
||||||
iface: option.Interface,
|
iface: option.Interface,
|
||||||
rmark: option.RoutingMark,
|
rmark: option.RoutingMark,
|
||||||
prefer: C.NewDNSPrefer(option.IPVersion),
|
|
||||||
},
|
},
|
||||||
user: option.UserName,
|
user: option.UserName,
|
||||||
pass: option.Password,
|
pass: option.Password,
|
||||||
tlsConfig: tlsConfig,
|
tlsConfig: tlsConfig,
|
||||||
option: &option,
|
option: &option,
|
||||||
}, nil
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,43 +2,40 @@ package outbound
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/sha256"
|
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
|
"crypto/x509"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/hex"
|
"errors"
|
||||||
"encoding/pem"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
"net"
|
"net"
|
||||||
"net/netip"
|
|
||||||
"os"
|
|
||||||
"regexp"
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/metacubex/quic-go"
|
|
||||||
"github.com/metacubex/quic-go/congestion"
|
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/component/dialer"
|
"github.com/Dreamacro/clash/component/dialer"
|
||||||
tlsC "github.com/Dreamacro/clash/component/tls"
|
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
"github.com/Dreamacro/clash/log"
|
"github.com/Dreamacro/clash/log"
|
||||||
hyCongestion "github.com/Dreamacro/clash/transport/hysteria/congestion"
|
"github.com/lucas-clemente/quic-go"
|
||||||
"github.com/Dreamacro/clash/transport/hysteria/core"
|
"github.com/lucas-clemente/quic-go/congestion"
|
||||||
"github.com/Dreamacro/clash/transport/hysteria/obfs"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
"github.com/Dreamacro/clash/transport/hysteria/pmtud_fix"
|
hyCongestion "github.com/tobyxdd/hysteria/pkg/congestion"
|
||||||
"github.com/Dreamacro/clash/transport/hysteria/transport"
|
"github.com/tobyxdd/hysteria/pkg/core"
|
||||||
|
"github.com/tobyxdd/hysteria/pkg/obfs"
|
||||||
|
"github.com/tobyxdd/hysteria/pkg/pmtud_fix"
|
||||||
|
"github.com/tobyxdd/hysteria/pkg/transport"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
mbpsToBps = 125000
|
mbpsToBps = 125000
|
||||||
|
minSpeedBPS = 16384
|
||||||
|
|
||||||
DefaultStreamReceiveWindow = 15728640 // 15 MB/s
|
DefaultStreamReceiveWindow = 15728640 // 15 MB/s
|
||||||
DefaultConnectionReceiveWindow = 67108864 // 64 MB/s
|
DefaultConnectionReceiveWindow = 67108864 // 64 MB/s
|
||||||
|
DefaultMaxIncomingStreams = 1024
|
||||||
|
|
||||||
DefaultALPN = "hysteria"
|
DefaultALPN = "hysteria"
|
||||||
DefaultProtocol = "udp"
|
DefaultProtocol = "udp"
|
||||||
DefaultHopInterval = 10
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var rateStringRegexp = regexp.MustCompile(`^(\d+)\s*([KMGT]?)([Bb])ps$`)
|
var rateStringRegexp = regexp.MustCompile(`^(\d+)\s*([KMGT]?)([Bb])ps$`)
|
||||||
@ -47,38 +44,23 @@ type Hysteria struct {
|
|||||||
*Base
|
*Base
|
||||||
|
|
||||||
client *core.Client
|
client *core.Client
|
||||||
|
clientTransport *transport.ClientTransport
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Hysteria) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.Conn, error) {
|
func (h *Hysteria) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.Conn, error) {
|
||||||
hdc := hyDialerWithContext{
|
tcpConn, err := h.client.DialTCP(metadata.RemoteAddress(), hyDialer(func() (net.PacketConn, error) {
|
||||||
ctx: context.Background(),
|
return dialer.ListenPacket(ctx, "udp", "", h.Base.DialOptions(opts...)...)
|
||||||
hyDialer: func(network string) (net.PacketConn, error) {
|
}))
|
||||||
return dialer.ListenPacket(ctx, network, "", h.Base.DialOptions(opts...)...)
|
|
||||||
},
|
|
||||||
remoteAddr: func(addr string) (net.Addr, error) {
|
|
||||||
return resolveUDPAddrWithPrefer(ctx, "udp", addr, h.prefer)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
tcpConn, err := h.client.DialTCP(metadata.RemoteAddress(), &hdc)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return NewConn(tcpConn, h), nil
|
return NewConn(tcpConn, h), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Hysteria) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) {
|
func (h *Hysteria) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) {
|
||||||
hdc := hyDialerWithContext{
|
udpConn, err := h.client.DialUDP(hyDialer(func() (net.PacketConn, error) {
|
||||||
ctx: context.Background(),
|
return dialer.ListenPacket(ctx, "udp", "", h.Base.DialOptions(opts...)...)
|
||||||
hyDialer: func(network string) (net.PacketConn, error) {
|
}))
|
||||||
return dialer.ListenPacket(ctx, network, "", h.Base.DialOptions(opts...)...)
|
|
||||||
},
|
|
||||||
remoteAddr: func(addr string) (net.Addr, error) {
|
|
||||||
return resolveUDPAddrWithPrefer(ctx, "udp", addr, h.prefer)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
udpConn, err := h.client.DialUDP(&hdc)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -89,42 +71,43 @@ type HysteriaOption struct {
|
|||||||
BasicOption
|
BasicOption
|
||||||
Name string `proxy:"name"`
|
Name string `proxy:"name"`
|
||||||
Server string `proxy:"server"`
|
Server string `proxy:"server"`
|
||||||
Port int `proxy:"port,omitempty"`
|
Port int `proxy:"port"`
|
||||||
Ports string `proxy:"ports,omitempty"`
|
|
||||||
Protocol string `proxy:"protocol,omitempty"`
|
Protocol string `proxy:"protocol,omitempty"`
|
||||||
ObfsProtocol string `proxy:"obfs-protocol,omitempty"` // compatible with Stash
|
Up string `proxy:"up,omitempty"`
|
||||||
Up string `proxy:"up"`
|
UpMbps int `proxy:"up_mbps,omitempty"`
|
||||||
UpSpeed int `proxy:"up-speed,omitempty"` // compatible with Stash
|
Down string `proxy:"down,omitempty"`
|
||||||
Down string `proxy:"down"`
|
DownMbps int `proxy:"down_mbps,omitempty"`
|
||||||
DownSpeed int `proxy:"down-speed,omitempty"` // compatible with Stash
|
|
||||||
Auth string `proxy:"auth,omitempty"`
|
Auth string `proxy:"auth,omitempty"`
|
||||||
AuthString string `proxy:"auth-str,omitempty"`
|
AuthString string `proxy:"auth_str,omitempty"`
|
||||||
Obfs string `proxy:"obfs,omitempty"`
|
Obfs string `proxy:"obfs,omitempty"`
|
||||||
SNI string `proxy:"sni,omitempty"`
|
SNI string `proxy:"sni,omitempty"`
|
||||||
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
|
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
|
||||||
Fingerprint string `proxy:"fingerprint,omitempty"`
|
ALPN string `proxy:"alpn,omitempty"`
|
||||||
ALPN []string `proxy:"alpn,omitempty"`
|
|
||||||
CustomCA string `proxy:"ca,omitempty"`
|
CustomCA string `proxy:"ca,omitempty"`
|
||||||
CustomCAString string `proxy:"ca-str,omitempty"`
|
CustomCAString string `proxy:"ca_str,omitempty"`
|
||||||
ReceiveWindowConn int `proxy:"recv-window-conn,omitempty"`
|
ReceiveWindowConn int `proxy:"recv_window_conn,omitempty"`
|
||||||
ReceiveWindow int `proxy:"recv-window,omitempty"`
|
ReceiveWindow int `proxy:"recv_window,omitempty"`
|
||||||
DisableMTUDiscovery bool `proxy:"disable-mtu-discovery,omitempty"`
|
DisableMTUDiscovery bool `proxy:"disable_mtu_discovery,omitempty"`
|
||||||
FastOpen bool `proxy:"fast-open,omitempty"`
|
|
||||||
HopInterval int `proxy:"hop-interval,omitempty"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *HysteriaOption) Speed() (uint64, uint64, error) {
|
func (c *HysteriaOption) Speed() (uint64, uint64, error) {
|
||||||
var up, down uint64
|
var up, down uint64
|
||||||
|
if len(c.Up) > 0 {
|
||||||
up = stringToBps(c.Up)
|
up = stringToBps(c.Up)
|
||||||
if up == 0 {
|
if up == 0 {
|
||||||
return 0, 0, fmt.Errorf("invaild upload speed: %s", c.Up)
|
return 0, 0, errors.New("invalid speed format")
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
up = uint64(c.UpMbps) * mbpsToBps
|
||||||
|
}
|
||||||
|
if len(c.Down) > 0 {
|
||||||
down = stringToBps(c.Down)
|
down = stringToBps(c.Down)
|
||||||
if down == 0 {
|
if down == 0 {
|
||||||
return 0, 0, fmt.Errorf("invaild download speed: %s", c.Down)
|
return 0, 0, errors.New("invalid speed format")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
down = uint64(c.DownMbps) * mbpsToBps
|
||||||
}
|
}
|
||||||
|
|
||||||
return up, down, nil
|
return up, down, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -134,115 +117,81 @@ func NewHysteria(option HysteriaOption) (*Hysteria, error) {
|
|||||||
Timeout: 8 * time.Second,
|
Timeout: 8 * time.Second,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port))
|
|
||||||
ports := option.Ports
|
|
||||||
|
|
||||||
|
addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port))
|
||||||
serverName := option.Server
|
serverName := option.Server
|
||||||
if option.SNI != "" {
|
if option.SNI != "" {
|
||||||
serverName = option.SNI
|
serverName = option.Server
|
||||||
}
|
}
|
||||||
|
|
||||||
tlsConfig := &tls.Config{
|
tlsConfig := &tls.Config{
|
||||||
ServerName: serverName,
|
ServerName: serverName,
|
||||||
InsecureSkipVerify: option.SkipCertVerify,
|
InsecureSkipVerify: option.SkipCertVerify,
|
||||||
MinVersion: tls.VersionTLS13,
|
MinVersion: tls.VersionTLS13,
|
||||||
}
|
}
|
||||||
|
if len(option.ALPN) > 0 {
|
||||||
var bs []byte
|
tlsConfig.NextProtos = []string{option.ALPN}
|
||||||
var err error
|
} else {
|
||||||
|
tlsConfig.NextProtos = []string{DefaultALPN}
|
||||||
|
}
|
||||||
if len(option.CustomCA) > 0 {
|
if len(option.CustomCA) > 0 {
|
||||||
bs, err = os.ReadFile(option.CustomCA)
|
bs, err := ioutil.ReadFile(option.CustomCA)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("hysteria %s load ca error: %w", addr, err)
|
return nil, fmt.Errorf("hysteria %s load ca error: %w", addr, err)
|
||||||
}
|
}
|
||||||
|
cp := x509.NewCertPool()
|
||||||
|
if !cp.AppendCertsFromPEM(bs) {
|
||||||
|
return nil, fmt.Errorf("hysteria %s failed to parse ca_str", addr)
|
||||||
|
}
|
||||||
|
tlsConfig.RootCAs = cp
|
||||||
} else if option.CustomCAString != "" {
|
} else if option.CustomCAString != "" {
|
||||||
bs = []byte(option.CustomCAString)
|
cp := x509.NewCertPool()
|
||||||
|
if !cp.AppendCertsFromPEM([]byte(option.CustomCAString)) {
|
||||||
|
return nil, fmt.Errorf("hysteria %s failed to parse ca_str", addr)
|
||||||
}
|
}
|
||||||
|
tlsConfig.RootCAs = cp
|
||||||
if len(bs) > 0 {
|
|
||||||
block, _ := pem.Decode(bs)
|
|
||||||
if block == nil {
|
|
||||||
return nil, fmt.Errorf("CA cert is not PEM")
|
|
||||||
}
|
|
||||||
|
|
||||||
fpBytes := sha256.Sum256(block.Bytes)
|
|
||||||
if len(option.Fingerprint) == 0 {
|
|
||||||
option.Fingerprint = hex.EncodeToString(fpBytes[:])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(option.Fingerprint) != 0 {
|
|
||||||
var err error
|
|
||||||
tlsConfig, err = tlsC.GetSpecifiedFingerprintTLSConfig(tlsConfig, option.Fingerprint)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
tlsConfig = tlsC.GetGlobalTLSConfig(tlsConfig)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(option.ALPN) > 0 {
|
|
||||||
tlsConfig.NextProtos = option.ALPN
|
|
||||||
} else {
|
|
||||||
tlsConfig.NextProtos = []string{DefaultALPN}
|
|
||||||
}
|
}
|
||||||
quicConfig := &quic.Config{
|
quicConfig := &quic.Config{
|
||||||
InitialStreamReceiveWindow: uint64(option.ReceiveWindowConn),
|
InitialStreamReceiveWindow: uint64(option.ReceiveWindowConn),
|
||||||
MaxStreamReceiveWindow: uint64(option.ReceiveWindowConn),
|
MaxStreamReceiveWindow: uint64(option.ReceiveWindowConn),
|
||||||
InitialConnectionReceiveWindow: uint64(option.ReceiveWindow),
|
InitialConnectionReceiveWindow: uint64(option.ReceiveWindow),
|
||||||
MaxConnectionReceiveWindow: uint64(option.ReceiveWindow),
|
MaxConnectionReceiveWindow: uint64(option.ReceiveWindow),
|
||||||
KeepAlivePeriod: 10 * time.Second,
|
KeepAlive: true,
|
||||||
DisablePathMTUDiscovery: option.DisableMTUDiscovery,
|
DisablePathMTUDiscovery: option.DisableMTUDiscovery,
|
||||||
EnableDatagrams: true,
|
EnableDatagrams: true,
|
||||||
}
|
}
|
||||||
if option.ObfsProtocol != "" {
|
|
||||||
option.Protocol = option.ObfsProtocol
|
|
||||||
}
|
|
||||||
if option.Protocol == "" {
|
if option.Protocol == "" {
|
||||||
option.Protocol = DefaultProtocol
|
option.Protocol = DefaultProtocol
|
||||||
}
|
}
|
||||||
if option.HopInterval == 0 {
|
if option.ReceiveWindowConn == 0 {
|
||||||
option.HopInterval = DefaultHopInterval
|
quicConfig.InitialStreamReceiveWindow = DefaultStreamReceiveWindow
|
||||||
}
|
|
||||||
hopInterval := time.Duration(int64(option.HopInterval)) * time.Second
|
|
||||||
if option.ReceiveWindow == 0 {
|
|
||||||
quicConfig.InitialStreamReceiveWindow = DefaultStreamReceiveWindow / 10
|
|
||||||
quicConfig.MaxStreamReceiveWindow = DefaultStreamReceiveWindow
|
quicConfig.MaxStreamReceiveWindow = DefaultStreamReceiveWindow
|
||||||
}
|
}
|
||||||
if option.ReceiveWindow == 0 {
|
if option.ReceiveWindow == 0 {
|
||||||
quicConfig.InitialConnectionReceiveWindow = DefaultConnectionReceiveWindow / 10
|
quicConfig.InitialConnectionReceiveWindow = DefaultConnectionReceiveWindow
|
||||||
quicConfig.MaxConnectionReceiveWindow = DefaultConnectionReceiveWindow
|
quicConfig.MaxConnectionReceiveWindow = DefaultConnectionReceiveWindow
|
||||||
}
|
}
|
||||||
if !quicConfig.DisablePathMTUDiscovery && pmtud_fix.DisablePathMTUDiscovery {
|
if !quicConfig.DisablePathMTUDiscovery && pmtud_fix.DisablePathMTUDiscovery {
|
||||||
log.Infoln("hysteria: Path MTU Discovery is not yet supported on this platform")
|
log.Infoln("hysteria: Path MTU Discovery is not yet supported on this platform")
|
||||||
}
|
}
|
||||||
|
var auth []byte
|
||||||
var auth = []byte(option.AuthString)
|
|
||||||
if option.Auth != "" {
|
if option.Auth != "" {
|
||||||
auth, err = base64.StdEncoding.DecodeString(option.Auth)
|
authBytes, err := base64.StdEncoding.DecodeString(option.Auth)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, fmt.Errorf("hysteria %s parse auth error: %w", addr, err)
|
||||||
}
|
}
|
||||||
|
auth = authBytes
|
||||||
|
} else {
|
||||||
|
auth = []byte(option.AuthString)
|
||||||
}
|
}
|
||||||
var obfuscator obfs.Obfuscator
|
var obfuscator obfs.Obfuscator
|
||||||
if len(option.Obfs) > 0 {
|
if len(option.Obfs) > 0 {
|
||||||
obfuscator = obfs.NewXPlusObfuscator([]byte(option.Obfs))
|
obfuscator = obfs.NewXPlusObfuscator([]byte(option.Obfs))
|
||||||
}
|
}
|
||||||
|
up, down, _ := option.Speed()
|
||||||
up, down, err := option.Speed()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if option.UpSpeed != 0 {
|
|
||||||
up = uint64(option.UpSpeed * mbpsToBps)
|
|
||||||
}
|
|
||||||
if option.DownSpeed != 0 {
|
|
||||||
down = uint64(option.DownSpeed * mbpsToBps)
|
|
||||||
}
|
|
||||||
client, err := core.NewClient(
|
client, err := core.NewClient(
|
||||||
addr, ports, option.Protocol, auth, tlsConfig, quicConfig, clientTransport, up, down, func(refBPS uint64) congestion.CongestionControl {
|
addr, option.Protocol, auth, tlsConfig, quicConfig, clientTransport, up, down, func(refBPS uint64) congestion.CongestionControl {
|
||||||
return hyCongestion.NewBrutalSender(congestion.ByteCount(refBPS))
|
return hyCongestion.NewBrutalSender(congestion.ByteCount(refBPS))
|
||||||
}, obfuscator, hopInterval, option.FastOpen,
|
}, obfuscator,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("hysteria %s create error: %w", addr, err)
|
return nil, fmt.Errorf("hysteria %s create error: %w", addr, err)
|
||||||
@ -253,12 +202,11 @@ func NewHysteria(option HysteriaOption) (*Hysteria, error) {
|
|||||||
addr: addr,
|
addr: addr,
|
||||||
tp: C.Hysteria,
|
tp: C.Hysteria,
|
||||||
udp: true,
|
udp: true,
|
||||||
tfo: option.FastOpen,
|
|
||||||
iface: option.Interface,
|
iface: option.Interface,
|
||||||
rmark: option.RoutingMark,
|
rmark: option.RoutingMark,
|
||||||
prefer: C.NewDNSPrefer(option.IPVersion),
|
|
||||||
},
|
},
|
||||||
client: client,
|
client: client,
|
||||||
|
clientTransport: clientTransport,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -266,12 +214,6 @@ func stringToBps(s string) uint64 {
|
|||||||
if s == "" {
|
if s == "" {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
// when have not unit, use Mbps
|
|
||||||
if v, err := strconv.Atoi(s); err == nil {
|
|
||||||
return stringToBps(fmt.Sprintf("%d Mbps", v))
|
|
||||||
}
|
|
||||||
|
|
||||||
m := rateStringRegexp.FindStringSubmatch(s)
|
m := rateStringRegexp.FindStringSubmatch(s)
|
||||||
if m == nil {
|
if m == nil {
|
||||||
return 0
|
return 0
|
||||||
@ -321,24 +263,8 @@ func (c *hyPacketConn) WriteTo(p []byte, addr net.Addr) (n int, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
type hyDialerWithContext struct {
|
type hyDialer func() (net.PacketConn, error)
|
||||||
hyDialer func(network string) (net.PacketConn, error)
|
|
||||||
ctx context.Context
|
|
||||||
remoteAddr func(host string) (net.Addr, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *hyDialerWithContext) ListenPacket(rAddr net.Addr) (net.PacketConn, error) {
|
func (h hyDialer) ListenPacket() (net.PacketConn, error) {
|
||||||
network := "udp"
|
return h()
|
||||||
if addrPort, err := netip.ParseAddrPort(rAddr.String()); err == nil {
|
|
||||||
network = dialer.ParseNetwork(network, addrPort.Addr())
|
|
||||||
}
|
|
||||||
return h.hyDialer(network)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *hyDialerWithContext) Context() context.Context {
|
|
||||||
return h.ctx
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *hyDialerWithContext) RemoteAddr(host string) (net.Addr, error) {
|
|
||||||
return h.remoteAddr(host)
|
|
||||||
}
|
}
|
||||||
|
@ -1,35 +0,0 @@
|
|||||||
package outbound
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/base64"
|
|
||||||
"encoding/hex"
|
|
||||||
"errors"
|
|
||||||
|
|
||||||
tlsC "github.com/Dreamacro/clash/component/tls"
|
|
||||||
|
|
||||||
"golang.org/x/crypto/curve25519"
|
|
||||||
)
|
|
||||||
|
|
||||||
type RealityOptions struct {
|
|
||||||
PublicKey string `proxy:"public-key"`
|
|
||||||
ShortID string `proxy:"short-id"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o RealityOptions) Parse() (*tlsC.RealityConfig, error) {
|
|
||||||
if o.PublicKey != "" {
|
|
||||||
config := new(tlsC.RealityConfig)
|
|
||||||
|
|
||||||
n, err := base64.RawURLEncoding.Decode(config.PublicKey[:], []byte(o.PublicKey))
|
|
||||||
if err != nil || n != curve25519.ScalarSize {
|
|
||||||
return nil, errors.New("invalid REALITY public key")
|
|
||||||
}
|
|
||||||
|
|
||||||
n, err = hex.Decode(config.ShortID[:], []byte(o.ShortID))
|
|
||||||
if err != nil || n > tlsC.RealityMaxShortIDLen {
|
|
||||||
return nil, errors.New("invalid REALITY short ID")
|
|
||||||
}
|
|
||||||
|
|
||||||
return config, nil
|
|
||||||
}
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
@ -6,7 +6,6 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/common/buf"
|
|
||||||
"github.com/Dreamacro/clash/component/dialer"
|
"github.com/Dreamacro/clash/component/dialer"
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
)
|
)
|
||||||
@ -17,12 +16,12 @@ type Reject struct {
|
|||||||
|
|
||||||
// DialContext implements C.ProxyAdapter
|
// DialContext implements C.ProxyAdapter
|
||||||
func (r *Reject) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.Conn, error) {
|
func (r *Reject) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.Conn, error) {
|
||||||
return NewConn(nopConn{}, r), nil
|
return NewConn(&nopConn{}, r), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListenPacketContext implements C.ProxyAdapter
|
// ListenPacketContext implements C.ProxyAdapter
|
||||||
func (r *Reject) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) {
|
func (r *Reject) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) {
|
||||||
return newPacketConn(nopPacketConn{}, r), nil
|
return newPacketConn(&nopPacketConn{}, r), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewReject() *Reject {
|
func NewReject() *Reject {
|
||||||
@ -31,7 +30,6 @@ func NewReject() *Reject {
|
|||||||
name: "REJECT",
|
name: "REJECT",
|
||||||
tp: C.Reject,
|
tp: C.Reject,
|
||||||
udp: true,
|
udp: true,
|
||||||
prefer: C.DualStack,
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -42,44 +40,33 @@ func NewPass() *Reject {
|
|||||||
name: "PASS",
|
name: "PASS",
|
||||||
tp: C.Pass,
|
tp: C.Pass,
|
||||||
udp: true,
|
udp: true,
|
||||||
prefer: C.DualStack,
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type nopConn struct{}
|
type nopConn struct{}
|
||||||
|
|
||||||
func (rw nopConn) Read(b []byte) (int, error) {
|
func (rw *nopConn) Read(b []byte) (int, error) {
|
||||||
return 0, io.EOF
|
return 0, io.EOF
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rw nopConn) ReadBuffer(buffer *buf.Buffer) error {
|
func (rw *nopConn) Write(b []byte) (int, error) {
|
||||||
return io.EOF
|
|
||||||
}
|
|
||||||
|
|
||||||
func (rw nopConn) Write(b []byte) (int, error) {
|
|
||||||
return 0, io.EOF
|
return 0, io.EOF
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rw nopConn) WriteBuffer(buffer *buf.Buffer) error {
|
func (rw *nopConn) Close() error { return nil }
|
||||||
return io.EOF
|
func (rw *nopConn) LocalAddr() net.Addr { return nil }
|
||||||
}
|
func (rw *nopConn) RemoteAddr() net.Addr { return nil }
|
||||||
|
func (rw *nopConn) SetDeadline(time.Time) error { return nil }
|
||||||
func (rw nopConn) Close() error { return nil }
|
func (rw *nopConn) SetReadDeadline(time.Time) error { return nil }
|
||||||
func (rw nopConn) LocalAddr() net.Addr { return nil }
|
func (rw *nopConn) SetWriteDeadline(time.Time) error { return nil }
|
||||||
func (rw nopConn) RemoteAddr() net.Addr { return nil }
|
|
||||||
func (rw nopConn) SetDeadline(time.Time) error { return nil }
|
|
||||||
func (rw nopConn) SetReadDeadline(time.Time) error { return nil }
|
|
||||||
func (rw nopConn) SetWriteDeadline(time.Time) error { return nil }
|
|
||||||
|
|
||||||
var udpAddrIPv4Unspecified = &net.UDPAddr{IP: net.IPv4zero, Port: 0}
|
|
||||||
|
|
||||||
type nopPacketConn struct{}
|
type nopPacketConn struct{}
|
||||||
|
|
||||||
func (npc nopPacketConn) WriteTo(b []byte, addr net.Addr) (n int, err error) { return len(b), nil }
|
func (npc *nopPacketConn) WriteTo(b []byte, addr net.Addr) (n int, err error) { return len(b), nil }
|
||||||
func (npc nopPacketConn) ReadFrom(b []byte) (int, net.Addr, error) { return 0, nil, io.EOF }
|
func (npc *nopPacketConn) ReadFrom(b []byte) (int, net.Addr, error) { return 0, nil, io.EOF }
|
||||||
func (npc nopPacketConn) Close() error { return nil }
|
func (npc *nopPacketConn) Close() error { return nil }
|
||||||
func (npc nopPacketConn) LocalAddr() net.Addr { return udpAddrIPv4Unspecified }
|
func (npc *nopPacketConn) LocalAddr() net.Addr { return &net.UDPAddr{IP: net.IPv4zero, Port: 0} }
|
||||||
func (npc nopPacketConn) SetDeadline(time.Time) error { return nil }
|
func (npc *nopPacketConn) SetDeadline(time.Time) error { return nil }
|
||||||
func (npc nopPacketConn) SetReadDeadline(time.Time) error { return nil }
|
func (npc *nopPacketConn) SetReadDeadline(time.Time) error { return nil }
|
||||||
func (npc nopPacketConn) SetWriteDeadline(time.Time) error { return nil }
|
func (npc *nopPacketConn) SetWriteDeadline(time.Time) error { return nil }
|
||||||
|
@ -6,37 +6,27 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
|
||||||
|
|
||||||
N "github.com/Dreamacro/clash/common/net"
|
|
||||||
"github.com/Dreamacro/clash/common/structure"
|
"github.com/Dreamacro/clash/common/structure"
|
||||||
"github.com/Dreamacro/clash/component/dialer"
|
"github.com/Dreamacro/clash/component/dialer"
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
"github.com/Dreamacro/clash/transport/restls"
|
|
||||||
obfs "github.com/Dreamacro/clash/transport/simple-obfs"
|
obfs "github.com/Dreamacro/clash/transport/simple-obfs"
|
||||||
shadowtls "github.com/Dreamacro/clash/transport/sing-shadowtls"
|
|
||||||
"github.com/Dreamacro/clash/transport/socks5"
|
"github.com/Dreamacro/clash/transport/socks5"
|
||||||
v2rayObfs "github.com/Dreamacro/clash/transport/v2ray-plugin"
|
v2rayObfs "github.com/Dreamacro/clash/transport/v2ray-plugin"
|
||||||
|
"github.com/sagernet/sing-shadowsocks"
|
||||||
restlsC "github.com/3andne/restls-client-go"
|
"github.com/sagernet/sing-shadowsocks/shadowimpl"
|
||||||
shadowsocks "github.com/metacubex/sing-shadowsocks"
|
|
||||||
"github.com/metacubex/sing-shadowsocks/shadowimpl"
|
|
||||||
"github.com/sagernet/sing/common/bufio"
|
"github.com/sagernet/sing/common/bufio"
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
"github.com/sagernet/sing/common/uot"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type ShadowSocks struct {
|
type ShadowSocks struct {
|
||||||
*Base
|
*Base
|
||||||
method shadowsocks.Method
|
method shadowsocks.Method
|
||||||
|
|
||||||
option *ShadowSocksOption
|
|
||||||
// obfs
|
// obfs
|
||||||
obfsMode string
|
obfsMode string
|
||||||
obfsOption *simpleObfsOption
|
obfsOption *simpleObfsOption
|
||||||
v2rayOption *v2rayObfs.Option
|
v2rayOption *v2rayObfs.Option
|
||||||
shadowTLSOption *shadowtls.ShadowTLSOption
|
|
||||||
restlsConfig *restlsC.Config
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type ShadowSocksOption struct {
|
type ShadowSocksOption struct {
|
||||||
@ -49,9 +39,6 @@ type ShadowSocksOption struct {
|
|||||||
UDP bool `proxy:"udp,omitempty"`
|
UDP bool `proxy:"udp,omitempty"`
|
||||||
Plugin string `proxy:"plugin,omitempty"`
|
Plugin string `proxy:"plugin,omitempty"`
|
||||||
PluginOpts map[string]any `proxy:"plugin-opts,omitempty"`
|
PluginOpts map[string]any `proxy:"plugin-opts,omitempty"`
|
||||||
UDPOverTCP bool `proxy:"udp-over-tcp,omitempty"`
|
|
||||||
UDPOverTCPVersion int `proxy:"udp-over-tcp-version,omitempty"`
|
|
||||||
ClientFingerprint string `proxy:"client-fingerprint,omitempty"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type simpleObfsOption struct {
|
type simpleObfsOption struct {
|
||||||
@ -64,37 +51,13 @@ type v2rayObfsOption struct {
|
|||||||
Host string `obfs:"host,omitempty"`
|
Host string `obfs:"host,omitempty"`
|
||||||
Path string `obfs:"path,omitempty"`
|
Path string `obfs:"path,omitempty"`
|
||||||
TLS bool `obfs:"tls,omitempty"`
|
TLS bool `obfs:"tls,omitempty"`
|
||||||
Fingerprint string `obfs:"fingerprint,omitempty"`
|
|
||||||
Headers map[string]string `obfs:"headers,omitempty"`
|
Headers map[string]string `obfs:"headers,omitempty"`
|
||||||
SkipCertVerify bool `obfs:"skip-cert-verify,omitempty"`
|
SkipCertVerify bool `obfs:"skip-cert-verify,omitempty"`
|
||||||
Mux bool `obfs:"mux,omitempty"`
|
Mux bool `obfs:"mux,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type shadowTLSOption struct {
|
|
||||||
Password string `obfs:"password"`
|
|
||||||
Host string `obfs:"host"`
|
|
||||||
Fingerprint string `obfs:"fingerprint,omitempty"`
|
|
||||||
SkipCertVerify bool `obfs:"skip-cert-verify,omitempty"`
|
|
||||||
Version int `obfs:"version,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type restlsOption struct {
|
|
||||||
Password string `obfs:"password"`
|
|
||||||
Host string `obfs:"host"`
|
|
||||||
VersionHint string `obfs:"version-hint"`
|
|
||||||
RestlsScript string `obfs:"restls-script,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// StreamConn implements C.ProxyAdapter
|
// StreamConn implements C.ProxyAdapter
|
||||||
func (ss *ShadowSocks) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
func (ss *ShadowSocks) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
||||||
// fix tls handshake not timeout
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), C.DefaultTLSTimeout)
|
|
||||||
defer cancel()
|
|
||||||
return ss.StreamConnContext(ctx, c, metadata)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ss *ShadowSocks) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
|
||||||
useEarly := false
|
|
||||||
switch ss.obfsMode {
|
switch ss.obfsMode {
|
||||||
case "tls":
|
case "tls":
|
||||||
c = obfs.NewTLSObfs(c, ss.obfsOption.Host)
|
c = obfs.NewTLSObfs(c, ss.obfsOption.Host)
|
||||||
@ -107,124 +70,49 @@ func (ss *ShadowSocks) StreamConnContext(ctx context.Context, c net.Conn, metada
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("%s connect error: %w", ss.addr, err)
|
return nil, fmt.Errorf("%s connect error: %w", ss.addr, err)
|
||||||
}
|
}
|
||||||
case shadowtls.Mode:
|
|
||||||
var err error
|
|
||||||
c, err = shadowtls.NewShadowTLS(ctx, c, ss.shadowTLSOption)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
}
|
||||||
useEarly = true
|
|
||||||
case restls.Mode:
|
|
||||||
var err error
|
|
||||||
c, err = restls.NewRestls(ctx, c, ss.restlsConfig)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("%s (restls) connect error: %w", ss.addr, err)
|
|
||||||
}
|
|
||||||
useEarly = true
|
|
||||||
}
|
|
||||||
useEarly = useEarly || N.NeedHandshake(c)
|
|
||||||
if metadata.NetWork == C.UDP && ss.option.UDPOverTCP {
|
|
||||||
uotDestination := uot.RequestDestination(uint8(ss.option.UDPOverTCPVersion))
|
|
||||||
if useEarly {
|
|
||||||
return ss.method.DialEarlyConn(c, uotDestination), nil
|
|
||||||
} else {
|
|
||||||
return ss.method.DialConn(c, uotDestination)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if useEarly {
|
|
||||||
return ss.method.DialEarlyConn(c, M.ParseSocksaddr(metadata.RemoteAddress())), nil
|
|
||||||
} else {
|
|
||||||
return ss.method.DialConn(c, M.ParseSocksaddr(metadata.RemoteAddress()))
|
return ss.method.DialConn(c, M.ParseSocksaddr(metadata.RemoteAddress()))
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// DialContext implements C.ProxyAdapter
|
// DialContext implements C.ProxyAdapter
|
||||||
func (ss *ShadowSocks) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) {
|
func (ss *ShadowSocks) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) {
|
||||||
return ss.DialContextWithDialer(ctx, dialer.NewDialer(ss.Base.DialOptions(opts...)...), metadata)
|
c, err := dialer.DialContext(ctx, "tcp", ss.addr, ss.Base.DialOptions(opts...)...)
|
||||||
}
|
|
||||||
|
|
||||||
// DialContextWithDialer implements C.ProxyAdapter
|
|
||||||
func (ss *ShadowSocks) DialContextWithDialer(ctx context.Context, dialer C.Dialer, metadata *C.Metadata) (_ C.Conn, err error) {
|
|
||||||
c, err := dialer.DialContext(ctx, "tcp", ss.addr)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("%s connect error: %w", ss.addr, err)
|
return nil, fmt.Errorf("%s connect error: %w", ss.addr, err)
|
||||||
}
|
}
|
||||||
tcpKeepAlive(c)
|
tcpKeepAlive(c)
|
||||||
|
|
||||||
defer func(c net.Conn) {
|
defer safeConnClose(c, err)
|
||||||
safeConnClose(c, err)
|
|
||||||
}(c)
|
|
||||||
|
|
||||||
c, err = ss.StreamConnContext(ctx, c, metadata)
|
c, err = ss.StreamConn(c, metadata)
|
||||||
return NewConn(c, ss), err
|
return NewConn(c, ss), err
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListenPacketContext implements C.ProxyAdapter
|
// ListenPacketContext implements C.ProxyAdapter
|
||||||
func (ss *ShadowSocks) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) {
|
func (ss *ShadowSocks) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) {
|
||||||
return ss.ListenPacketWithDialer(ctx, dialer.NewDialer(ss.Base.DialOptions(opts...)...), metadata)
|
pc, err := dialer.ListenPacket(ctx, "udp", "", ss.Base.DialOptions(opts...)...)
|
||||||
}
|
|
||||||
|
|
||||||
// ListenPacketWithDialer implements C.ProxyAdapter
|
|
||||||
func (ss *ShadowSocks) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, metadata *C.Metadata) (_ C.PacketConn, err error) {
|
|
||||||
if ss.option.UDPOverTCP {
|
|
||||||
tcpConn, err := ss.DialContextWithDialer(ctx, dialer, metadata)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
destination := M.ParseSocksaddr(metadata.RemoteAddress())
|
|
||||||
if ss.option.UDPOverTCPVersion == 1 {
|
|
||||||
return newPacketConn(uot.NewConn(tcpConn, uot.Request{Destination: destination}), ss), nil
|
|
||||||
} else {
|
|
||||||
return newPacketConn(uot.NewLazyConn(tcpConn, uot.Request{Destination: destination}), ss), nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
addr, err := resolveUDPAddrWithPrefer(ctx, "udp", ss.addr, ss.prefer)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
pc, err := dialer.ListenPacket(ctx, "udp", "", addr.AddrPort())
|
addr, err := resolveUDPAddr("udp", ss.addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
pc.Close()
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
pc = ss.method.DialPacketConn(&bufio.BindPacketConn{PacketConn: pc, Addr: addr})
|
pc = ss.method.DialPacketConn(&bufio.BindPacketConn{PacketConn: pc, Addr: addr})
|
||||||
return newPacketConn(pc, ss), nil
|
return newPacketConn(pc, ss), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// SupportWithDialer implements C.ProxyAdapter
|
|
||||||
func (ss *ShadowSocks) SupportWithDialer() bool {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListenPacketOnStreamConn implements C.ProxyAdapter
|
|
||||||
func (ss *ShadowSocks) ListenPacketOnStreamConn(c net.Conn, metadata *C.Metadata) (_ C.PacketConn, err error) {
|
|
||||||
if ss.option.UDPOverTCP {
|
|
||||||
destination := M.ParseSocksaddr(metadata.RemoteAddress())
|
|
||||||
if ss.option.UDPOverTCPVersion == uot.LegacyVersion {
|
|
||||||
return newPacketConn(uot.NewConn(c, uot.Request{Destination: destination}), ss), nil
|
|
||||||
} else {
|
|
||||||
return newPacketConn(uot.NewLazyConn(c, uot.Request{Destination: destination}), ss), nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil, errors.New("no support")
|
|
||||||
}
|
|
||||||
|
|
||||||
// SupportUOT implements C.ProxyAdapter
|
|
||||||
func (ss *ShadowSocks) SupportUOT() bool {
|
|
||||||
return ss.option.UDPOverTCP
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) {
|
func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) {
|
||||||
addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port))
|
addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port))
|
||||||
method, err := shadowimpl.FetchMethod(option.Cipher, option.Password, time.Now)
|
method, err := shadowimpl.FetchMethod(option.Cipher, option.Password)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("ss %s initialize error: %w", addr, err)
|
return nil, fmt.Errorf("ss %s initialize error: %w", addr, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var v2rayOption *v2rayObfs.Option
|
var v2rayOption *v2rayObfs.Option
|
||||||
var obfsOption *simpleObfsOption
|
var obfsOption *simpleObfsOption
|
||||||
var shadowTLSOpt *shadowtls.ShadowTLSOption
|
|
||||||
var restlsConfig *restlsC.Config
|
|
||||||
obfsMode := ""
|
obfsMode := ""
|
||||||
|
|
||||||
decoder := structure.NewDecoder(structure.Option{TagName: "obfs", WeaklyTypedInput: true})
|
decoder := structure.NewDecoder(structure.Option{TagName: "obfs", WeaklyTypedInput: true})
|
||||||
@ -260,43 +148,6 @@ func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) {
|
|||||||
v2rayOption.TLS = true
|
v2rayOption.TLS = true
|
||||||
v2rayOption.SkipCertVerify = opts.SkipCertVerify
|
v2rayOption.SkipCertVerify = opts.SkipCertVerify
|
||||||
}
|
}
|
||||||
} else if option.Plugin == shadowtls.Mode {
|
|
||||||
obfsMode = shadowtls.Mode
|
|
||||||
opt := &shadowTLSOption{
|
|
||||||
Version: 2,
|
|
||||||
}
|
|
||||||
if err := decoder.Decode(option.PluginOpts, opt); err != nil {
|
|
||||||
return nil, fmt.Errorf("ss %s initialize shadow-tls-plugin error: %w", addr, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
shadowTLSOpt = &shadowtls.ShadowTLSOption{
|
|
||||||
Password: opt.Password,
|
|
||||||
Host: opt.Host,
|
|
||||||
Fingerprint: opt.Fingerprint,
|
|
||||||
ClientFingerprint: option.ClientFingerprint,
|
|
||||||
SkipCertVerify: opt.SkipCertVerify,
|
|
||||||
Version: opt.Version,
|
|
||||||
}
|
|
||||||
} else if option.Plugin == restls.Mode {
|
|
||||||
obfsMode = restls.Mode
|
|
||||||
restlsOpt := &restlsOption{}
|
|
||||||
if err := decoder.Decode(option.PluginOpts, restlsOpt); err != nil {
|
|
||||||
return nil, fmt.Errorf("ss %s initialize restls-plugin error: %w", addr, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
restlsConfig, err = restlsC.NewRestlsConfig(restlsOpt.Host, restlsOpt.Password, restlsOpt.VersionHint, restlsOpt.RestlsScript, option.ClientFingerprint)
|
|
||||||
restlsConfig.SessionTicketsDisabled = true
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("ss %s initialize restls-plugin error: %w", addr, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
switch option.UDPOverTCPVersion {
|
|
||||||
case uot.Version, uot.LegacyVersion:
|
|
||||||
case 0:
|
|
||||||
option.UDPOverTCPVersion = uot.Version
|
|
||||||
default:
|
|
||||||
return nil, fmt.Errorf("ss %s unknown udp over tcp protocol version: %d", addr, option.UDPOverTCPVersion)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return &ShadowSocks{
|
return &ShadowSocks{
|
||||||
@ -305,19 +156,14 @@ func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) {
|
|||||||
addr: addr,
|
addr: addr,
|
||||||
tp: C.Shadowsocks,
|
tp: C.Shadowsocks,
|
||||||
udp: option.UDP,
|
udp: option.UDP,
|
||||||
tfo: option.TFO,
|
|
||||||
iface: option.Interface,
|
iface: option.Interface,
|
||||||
rmark: option.RoutingMark,
|
rmark: option.RoutingMark,
|
||||||
prefer: C.NewDNSPrefer(option.IPVersion),
|
|
||||||
},
|
},
|
||||||
method: method,
|
method: method,
|
||||||
|
|
||||||
option: &option,
|
|
||||||
obfsMode: obfsMode,
|
obfsMode: obfsMode,
|
||||||
v2rayOption: v2rayOption,
|
v2rayOption: v2rayOption,
|
||||||
obfsOption: obfsOption,
|
obfsOption: obfsOption,
|
||||||
shadowTLSOption: shadowTLSOpt,
|
|
||||||
restlsConfig: restlsConfig,
|
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -60,20 +60,13 @@ func (ssr *ShadowSocksR) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn,
|
|||||||
|
|
||||||
// DialContext implements C.ProxyAdapter
|
// DialContext implements C.ProxyAdapter
|
||||||
func (ssr *ShadowSocksR) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) {
|
func (ssr *ShadowSocksR) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) {
|
||||||
return ssr.DialContextWithDialer(ctx, dialer.NewDialer(ssr.Base.DialOptions(opts...)...), metadata)
|
c, err := dialer.DialContext(ctx, "tcp", ssr.addr, ssr.Base.DialOptions(opts...)...)
|
||||||
}
|
|
||||||
|
|
||||||
// DialContextWithDialer implements C.ProxyAdapter
|
|
||||||
func (ssr *ShadowSocksR) DialContextWithDialer(ctx context.Context, dialer C.Dialer, metadata *C.Metadata) (_ C.Conn, err error) {
|
|
||||||
c, err := dialer.DialContext(ctx, "tcp", ssr.addr)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("%s connect error: %w", ssr.addr, err)
|
return nil, fmt.Errorf("%s connect error: %w", ssr.addr, err)
|
||||||
}
|
}
|
||||||
tcpKeepAlive(c)
|
tcpKeepAlive(c)
|
||||||
|
|
||||||
defer func(c net.Conn) {
|
defer safeConnClose(c, err)
|
||||||
safeConnClose(c, err)
|
|
||||||
}(c)
|
|
||||||
|
|
||||||
c, err = ssr.StreamConn(c, metadata)
|
c, err = ssr.StreamConn(c, metadata)
|
||||||
return NewConn(c, ssr), err
|
return NewConn(c, ssr), err
|
||||||
@ -81,18 +74,14 @@ func (ssr *ShadowSocksR) DialContextWithDialer(ctx context.Context, dialer C.Dia
|
|||||||
|
|
||||||
// ListenPacketContext implements C.ProxyAdapter
|
// ListenPacketContext implements C.ProxyAdapter
|
||||||
func (ssr *ShadowSocksR) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) {
|
func (ssr *ShadowSocksR) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) {
|
||||||
return ssr.ListenPacketWithDialer(ctx, dialer.NewDialer(ssr.Base.DialOptions(opts...)...), metadata)
|
pc, err := dialer.ListenPacket(ctx, "udp", "", ssr.Base.DialOptions(opts...)...)
|
||||||
}
|
|
||||||
|
|
||||||
// ListenPacketWithDialer implements C.ProxyAdapter
|
|
||||||
func (ssr *ShadowSocksR) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, metadata *C.Metadata) (_ C.PacketConn, err error) {
|
|
||||||
addr, err := resolveUDPAddrWithPrefer(ctx, "udp", ssr.addr, ssr.prefer)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
pc, err := dialer.ListenPacket(ctx, "udp", "", addr.AddrPort())
|
addr, err := resolveUDPAddr("udp", ssr.addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
pc.Close()
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -101,11 +90,6 @@ func (ssr *ShadowSocksR) ListenPacketWithDialer(ctx context.Context, dialer C.Di
|
|||||||
return newPacketConn(&ssPacketConn{PacketConn: pc, rAddr: addr}, ssr), nil
|
return newPacketConn(&ssPacketConn{PacketConn: pc, rAddr: addr}, ssr), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// SupportWithDialer implements C.ProxyAdapter
|
|
||||||
func (ssr *ShadowSocksR) SupportWithDialer() bool {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewShadowSocksR(option ShadowSocksROption) (*ShadowSocksR, error) {
|
func NewShadowSocksR(option ShadowSocksROption) (*ShadowSocksR, error) {
|
||||||
// SSR protocol compatibility
|
// SSR protocol compatibility
|
||||||
// https://github.com/Dreamacro/clash/pull/2056
|
// https://github.com/Dreamacro/clash/pull/2056
|
||||||
@ -163,10 +147,8 @@ func NewShadowSocksR(option ShadowSocksROption) (*ShadowSocksR, error) {
|
|||||||
addr: addr,
|
addr: addr,
|
||||||
tp: C.ShadowsocksR,
|
tp: C.ShadowsocksR,
|
||||||
udp: option.UDP,
|
udp: option.UDP,
|
||||||
tfo: option.TFO,
|
|
||||||
iface: option.Interface,
|
iface: option.Interface,
|
||||||
rmark: option.RoutingMark,
|
rmark: option.RoutingMark,
|
||||||
prefer: C.NewDNSPrefer(option.IPVersion),
|
|
||||||
},
|
},
|
||||||
cipher: coreCiph,
|
cipher: coreCiph,
|
||||||
obfs: obfs,
|
obfs: obfs,
|
||||||
|
@ -78,20 +78,13 @@ func (s *Snell) DialContext(ctx context.Context, metadata *C.Metadata, opts ...d
|
|||||||
return NewConn(c, s), err
|
return NewConn(c, s), err
|
||||||
}
|
}
|
||||||
|
|
||||||
return s.DialContextWithDialer(ctx, dialer.NewDialer(s.Base.DialOptions(opts...)...), metadata)
|
c, err := dialer.DialContext(ctx, "tcp", s.addr, s.Base.DialOptions(opts...)...)
|
||||||
}
|
|
||||||
|
|
||||||
// DialContextWithDialer implements C.ProxyAdapter
|
|
||||||
func (s *Snell) DialContextWithDialer(ctx context.Context, dialer C.Dialer, metadata *C.Metadata) (_ C.Conn, err error) {
|
|
||||||
c, err := dialer.DialContext(ctx, "tcp", s.addr)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("%s connect error: %w", s.addr, err)
|
return nil, fmt.Errorf("%s connect error: %w", s.addr, err)
|
||||||
}
|
}
|
||||||
tcpKeepAlive(c)
|
tcpKeepAlive(c)
|
||||||
|
|
||||||
defer func(c net.Conn) {
|
defer safeConnClose(c, err)
|
||||||
safeConnClose(c, err)
|
|
||||||
}(c)
|
|
||||||
|
|
||||||
c, err = s.StreamConn(c, metadata)
|
c, err = s.StreamConn(c, metadata)
|
||||||
return NewConn(c, s), err
|
return NewConn(c, s), err
|
||||||
@ -99,12 +92,7 @@ func (s *Snell) DialContextWithDialer(ctx context.Context, dialer C.Dialer, meta
|
|||||||
|
|
||||||
// ListenPacketContext implements C.ProxyAdapter
|
// ListenPacketContext implements C.ProxyAdapter
|
||||||
func (s *Snell) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) {
|
func (s *Snell) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) {
|
||||||
return s.ListenPacketWithDialer(ctx, dialer.NewDialer(s.Base.DialOptions(opts...)...), metadata)
|
c, err := dialer.DialContext(ctx, "tcp", s.addr, s.Base.DialOptions(opts...)...)
|
||||||
}
|
|
||||||
|
|
||||||
// ListenPacketWithDialer implements C.ProxyAdapter
|
|
||||||
func (s *Snell) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, metadata *C.Metadata) (C.PacketConn, error) {
|
|
||||||
c, err := dialer.DialContext(ctx, "tcp", s.addr)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -120,9 +108,10 @@ func (s *Snell) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, met
|
|||||||
return newPacketConn(pc, s), nil
|
return newPacketConn(pc, s), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// SupportWithDialer implements C.ProxyAdapter
|
// ListenPacketOnStreamConn implements C.ProxyAdapter
|
||||||
func (s *Snell) SupportWithDialer() bool {
|
func (s *Snell) ListenPacketOnStreamConn(c net.Conn, metadata *C.Metadata) (_ C.PacketConn, err error) {
|
||||||
return true
|
pc := snell.PacketConn(c)
|
||||||
|
return newPacketConn(pc, s), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// SupportUOT implements C.ProxyAdapter
|
// SupportUOT implements C.ProxyAdapter
|
||||||
@ -167,10 +156,8 @@ func NewSnell(option SnellOption) (*Snell, error) {
|
|||||||
addr: addr,
|
addr: addr,
|
||||||
tp: C.Snell,
|
tp: C.Snell,
|
||||||
udp: option.UDP,
|
udp: option.UDP,
|
||||||
tfo: option.TFO,
|
|
||||||
iface: option.Interface,
|
iface: option.Interface,
|
||||||
rmark: option.RoutingMark,
|
rmark: option.RoutingMark,
|
||||||
prefer: C.NewDNSPrefer(option.IPVersion),
|
|
||||||
},
|
},
|
||||||
psk: psk,
|
psk: psk,
|
||||||
obfsOption: obfsOption,
|
obfsOption: obfsOption,
|
||||||
|
@ -10,7 +10,6 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/component/dialer"
|
"github.com/Dreamacro/clash/component/dialer"
|
||||||
tlsC "github.com/Dreamacro/clash/component/tls"
|
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
"github.com/Dreamacro/clash/transport/socks5"
|
"github.com/Dreamacro/clash/transport/socks5"
|
||||||
)
|
)
|
||||||
@ -34,16 +33,13 @@ type Socks5Option struct {
|
|||||||
TLS bool `proxy:"tls,omitempty"`
|
TLS bool `proxy:"tls,omitempty"`
|
||||||
UDP bool `proxy:"udp,omitempty"`
|
UDP bool `proxy:"udp,omitempty"`
|
||||||
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
|
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
|
||||||
Fingerprint string `proxy:"fingerprint,omitempty"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// StreamConn implements C.ProxyAdapter
|
// StreamConn implements C.ProxyAdapter
|
||||||
func (ss *Socks5) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
func (ss *Socks5) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
||||||
if ss.tls {
|
if ss.tls {
|
||||||
cc := tls.Client(c, ss.tlsConfig)
|
cc := tls.Client(c, ss.tlsConfig)
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), C.DefaultTLSTimeout)
|
err := cc.Handshake()
|
||||||
defer cancel()
|
|
||||||
err := cc.HandshakeContext(ctx)
|
|
||||||
c = cc
|
c = cc
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("%s connect error: %w", ss.addr, err)
|
return nil, fmt.Errorf("%s connect error: %w", ss.addr, err)
|
||||||
@ -65,20 +61,13 @@ func (ss *Socks5) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error)
|
|||||||
|
|
||||||
// DialContext implements C.ProxyAdapter
|
// DialContext implements C.ProxyAdapter
|
||||||
func (ss *Socks5) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) {
|
func (ss *Socks5) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) {
|
||||||
return ss.DialContextWithDialer(ctx, dialer.NewDialer(ss.Base.DialOptions(opts...)...), metadata)
|
c, err := dialer.DialContext(ctx, "tcp", ss.addr, ss.Base.DialOptions(opts...)...)
|
||||||
}
|
|
||||||
|
|
||||||
// DialContextWithDialer implements C.ProxyAdapter
|
|
||||||
func (ss *Socks5) DialContextWithDialer(ctx context.Context, dialer C.Dialer, metadata *C.Metadata) (_ C.Conn, err error) {
|
|
||||||
c, err := dialer.DialContext(ctx, "tcp", ss.addr)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("%s connect error: %w", ss.addr, err)
|
return nil, fmt.Errorf("%s connect error: %w", ss.addr, err)
|
||||||
}
|
}
|
||||||
tcpKeepAlive(c)
|
tcpKeepAlive(c)
|
||||||
|
|
||||||
defer func(c net.Conn) {
|
defer safeConnClose(c, err)
|
||||||
safeConnClose(c, err)
|
|
||||||
}(c)
|
|
||||||
|
|
||||||
c, err = ss.StreamConn(c, metadata)
|
c, err = ss.StreamConn(c, metadata)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -88,11 +77,6 @@ func (ss *Socks5) DialContextWithDialer(ctx context.Context, dialer C.Dialer, me
|
|||||||
return NewConn(c, ss), nil
|
return NewConn(c, ss), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// SupportWithDialer implements C.ProxyAdapter
|
|
||||||
func (ss *Socks5) SupportWithDialer() bool {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListenPacketContext implements C.ProxyAdapter
|
// ListenPacketContext implements C.ProxyAdapter
|
||||||
func (ss *Socks5) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.PacketConn, err error) {
|
func (ss *Socks5) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.PacketConn, err error) {
|
||||||
c, err := dialer.DialContext(ctx, "tcp", ss.addr, ss.Base.DialOptions(opts...)...)
|
c, err := dialer.DialContext(ctx, "tcp", ss.addr, ss.Base.DialOptions(opts...)...)
|
||||||
@ -103,15 +87,11 @@ func (ss *Socks5) ListenPacketContext(ctx context.Context, metadata *C.Metadata,
|
|||||||
|
|
||||||
if ss.tls {
|
if ss.tls {
|
||||||
cc := tls.Client(c, ss.tlsConfig)
|
cc := tls.Client(c, ss.tlsConfig)
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), C.DefaultTLSTimeout)
|
err = cc.Handshake()
|
||||||
defer cancel()
|
|
||||||
err = cc.HandshakeContext(ctx)
|
|
||||||
c = cc
|
c = cc
|
||||||
}
|
}
|
||||||
|
|
||||||
defer func(c net.Conn) {
|
defer safeConnClose(c, err)
|
||||||
safeConnClose(c, err)
|
|
||||||
}(c)
|
|
||||||
|
|
||||||
tcpKeepAlive(c)
|
tcpKeepAlive(c)
|
||||||
var user *socks5.User
|
var user *socks5.User
|
||||||
@ -128,21 +108,7 @@ func (ss *Socks5) ListenPacketContext(ctx context.Context, metadata *C.Metadata,
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Support unspecified UDP bind address.
|
pc, err := dialer.ListenPacket(ctx, "udp", "", ss.Base.DialOptions(opts...)...)
|
||||||
bindUDPAddr := bindAddr.UDPAddr()
|
|
||||||
if bindUDPAddr == nil {
|
|
||||||
err = errors.New("invalid UDP bind address")
|
|
||||||
return
|
|
||||||
} else if bindUDPAddr.IP.IsUnspecified() {
|
|
||||||
serverAddr, err := resolveUDPAddr(ctx, "udp", ss.Addr())
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
bindUDPAddr.IP = serverAddr.IP
|
|
||||||
}
|
|
||||||
|
|
||||||
pc, err := dialer.ListenPacket(ctx, dialer.ParseNetwork("udp", bindUDPAddr.AddrPort().Addr()), "", ss.Base.DialOptions(opts...)...)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -155,25 +121,30 @@ func (ss *Socks5) ListenPacketContext(ctx context.Context, metadata *C.Metadata,
|
|||||||
pc.Close()
|
pc.Close()
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
// Support unspecified UDP bind address.
|
||||||
|
bindUDPAddr := bindAddr.UDPAddr()
|
||||||
|
if bindUDPAddr == nil {
|
||||||
|
err = errors.New("invalid UDP bind address")
|
||||||
|
return
|
||||||
|
} else if bindUDPAddr.IP.IsUnspecified() {
|
||||||
|
serverAddr, err := resolveUDPAddr("udp", ss.Addr())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
bindUDPAddr.IP = serverAddr.IP
|
||||||
|
}
|
||||||
|
|
||||||
return newPacketConn(&socksPacketConn{PacketConn: pc, rAddr: bindUDPAddr, tcpConn: c}, ss), nil
|
return newPacketConn(&socksPacketConn{PacketConn: pc, rAddr: bindUDPAddr, tcpConn: c}, ss), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewSocks5(option Socks5Option) (*Socks5, error) {
|
func NewSocks5(option Socks5Option) *Socks5 {
|
||||||
var tlsConfig *tls.Config
|
var tlsConfig *tls.Config
|
||||||
if option.TLS {
|
if option.TLS {
|
||||||
tlsConfig = &tls.Config{
|
tlsConfig = &tls.Config{
|
||||||
InsecureSkipVerify: option.SkipCertVerify,
|
InsecureSkipVerify: option.SkipCertVerify,
|
||||||
ServerName: option.Server,
|
ServerName: option.Server,
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(option.Fingerprint) == 0 {
|
|
||||||
tlsConfig = tlsC.GetGlobalTLSConfig(tlsConfig)
|
|
||||||
} else {
|
|
||||||
var err error
|
|
||||||
if tlsConfig, err = tlsC.GetSpecifiedFingerprintTLSConfig(tlsConfig, option.Fingerprint); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return &Socks5{
|
return &Socks5{
|
||||||
@ -182,17 +153,15 @@ func NewSocks5(option Socks5Option) (*Socks5, error) {
|
|||||||
addr: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)),
|
addr: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)),
|
||||||
tp: C.Socks5,
|
tp: C.Socks5,
|
||||||
udp: option.UDP,
|
udp: option.UDP,
|
||||||
tfo: option.TFO,
|
|
||||||
iface: option.Interface,
|
iface: option.Interface,
|
||||||
rmark: option.RoutingMark,
|
rmark: option.RoutingMark,
|
||||||
prefer: C.NewDNSPrefer(option.IPVersion),
|
|
||||||
},
|
},
|
||||||
user: option.UserName,
|
user: option.UserName,
|
||||||
pass: option.Password,
|
pass: option.Password,
|
||||||
tls: option.TLS,
|
tls: option.TLS,
|
||||||
skipCertVerify: option.SkipCertVerify,
|
skipCertVerify: option.SkipCertVerify,
|
||||||
tlsConfig: tlsConfig,
|
tlsConfig: tlsConfig,
|
||||||
}, nil
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type socksPacketConn struct {
|
type socksPacketConn struct {
|
||||||
|
@ -8,9 +8,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
N "github.com/Dreamacro/clash/common/net"
|
|
||||||
"github.com/Dreamacro/clash/component/dialer"
|
"github.com/Dreamacro/clash/component/dialer"
|
||||||
tlsC "github.com/Dreamacro/clash/component/tls"
|
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
"github.com/Dreamacro/clash/transport/gun"
|
"github.com/Dreamacro/clash/transport/gun"
|
||||||
"github.com/Dreamacro/clash/transport/trojan"
|
"github.com/Dreamacro/clash/transport/trojan"
|
||||||
@ -26,8 +24,6 @@ type Trojan struct {
|
|||||||
gunTLSConfig *tls.Config
|
gunTLSConfig *tls.Config
|
||||||
gunConfig *gun.Config
|
gunConfig *gun.Config
|
||||||
transport *gun.TransportWrap
|
transport *gun.TransportWrap
|
||||||
|
|
||||||
realityConfig *tlsC.RealityConfig
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type TrojanOption struct {
|
type TrojanOption struct {
|
||||||
@ -39,15 +35,12 @@ type TrojanOption struct {
|
|||||||
ALPN []string `proxy:"alpn,omitempty"`
|
ALPN []string `proxy:"alpn,omitempty"`
|
||||||
SNI string `proxy:"sni,omitempty"`
|
SNI string `proxy:"sni,omitempty"`
|
||||||
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
|
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
|
||||||
Fingerprint string `proxy:"fingerprint,omitempty"`
|
|
||||||
UDP bool `proxy:"udp,omitempty"`
|
UDP bool `proxy:"udp,omitempty"`
|
||||||
Network string `proxy:"network,omitempty"`
|
Network string `proxy:"network,omitempty"`
|
||||||
RealityOpts RealityOptions `proxy:"reality-opts,omitempty"`
|
|
||||||
GrpcOpts GrpcOptions `proxy:"grpc-opts,omitempty"`
|
GrpcOpts GrpcOptions `proxy:"grpc-opts,omitempty"`
|
||||||
WSOpts WSOptions `proxy:"ws-opts,omitempty"`
|
WSOpts WSOptions `proxy:"ws-opts,omitempty"`
|
||||||
Flow string `proxy:"flow,omitempty"`
|
Flow string `proxy:"flow,omitempty"`
|
||||||
FlowShow bool `proxy:"flow-show,omitempty"`
|
FlowShow bool `proxy:"flow-show,omitempty"`
|
||||||
ClientFingerprint string `proxy:"client-fingerprint,omitempty"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Trojan) plainStream(c net.Conn) (net.Conn, error) {
|
func (t *Trojan) plainStream(c net.Conn) (net.Conn, error) {
|
||||||
@ -80,13 +73,8 @@ func (t *Trojan) plainStream(c net.Conn) (net.Conn, error) {
|
|||||||
// StreamConn implements C.ProxyAdapter
|
// StreamConn implements C.ProxyAdapter
|
||||||
func (t *Trojan) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
func (t *Trojan) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
if tlsC.HaveGlobalFingerprint() && len(t.option.ClientFingerprint) == 0 {
|
|
||||||
t.option.ClientFingerprint = tlsC.GetGlobalFingerprint()
|
|
||||||
}
|
|
||||||
|
|
||||||
if t.transport != nil {
|
if t.transport != nil {
|
||||||
c, err = gun.StreamGunWithConn(c, t.gunTLSConfig, t.gunConfig, t.realityConfig)
|
c, err = gun.StreamGunWithConn(c, t.gunTLSConfig, t.gunConfig)
|
||||||
} else {
|
} else {
|
||||||
c, err = t.plainStream(c)
|
c, err = t.plainStream(c)
|
||||||
}
|
}
|
||||||
@ -105,7 +93,7 @@ func (t *Trojan) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error)
|
|||||||
return c, err
|
return c, err
|
||||||
}
|
}
|
||||||
err = t.instance.WriteHeader(c, trojan.CommandTCP, serializesSocksAddr(metadata))
|
err = t.instance.WriteHeader(c, trojan.CommandTCP, serializesSocksAddr(metadata))
|
||||||
return N.NewExtendedConn(c), err
|
return c, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// DialContext implements C.ProxyAdapter
|
// DialContext implements C.ProxyAdapter
|
||||||
@ -130,20 +118,14 @@ func (t *Trojan) DialContext(ctx context.Context, metadata *C.Metadata, opts ...
|
|||||||
|
|
||||||
return NewConn(c, t), nil
|
return NewConn(c, t), nil
|
||||||
}
|
}
|
||||||
return t.DialContextWithDialer(ctx, dialer.NewDialer(t.Base.DialOptions(opts...)...), metadata)
|
|
||||||
}
|
|
||||||
|
|
||||||
// DialContextWithDialer implements C.ProxyAdapter
|
c, err := dialer.DialContext(ctx, "tcp", t.addr, t.Base.DialOptions(opts...)...)
|
||||||
func (t *Trojan) DialContextWithDialer(ctx context.Context, dialer C.Dialer, metadata *C.Metadata) (_ C.Conn, err error) {
|
|
||||||
c, err := dialer.DialContext(ctx, "tcp", t.addr)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("%s connect error: %w", t.addr, err)
|
return nil, fmt.Errorf("%s connect error: %w", t.addr, err)
|
||||||
}
|
}
|
||||||
tcpKeepAlive(c)
|
tcpKeepAlive(c)
|
||||||
|
|
||||||
defer func(c net.Conn) {
|
defer safeConnClose(c, err)
|
||||||
safeConnClose(c, err)
|
|
||||||
}(c)
|
|
||||||
|
|
||||||
c, err = t.StreamConn(c, metadata)
|
c, err = t.StreamConn(c, metadata)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -163,34 +145,19 @@ func (t *Trojan) ListenPacketContext(ctx context.Context, metadata *C.Metadata,
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("%s connect error: %w", t.addr, err)
|
return nil, fmt.Errorf("%s connect error: %w", t.addr, err)
|
||||||
}
|
}
|
||||||
defer func(c net.Conn) {
|
defer safeConnClose(c, err)
|
||||||
safeConnClose(c, err)
|
} else {
|
||||||
}(c)
|
c, err = dialer.DialContext(ctx, "tcp", t.addr, t.Base.DialOptions(opts...)...)
|
||||||
err = t.instance.WriteHeader(c, trojan.CommandUDP, serializesSocksAddr(metadata))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
pc := t.instance.PacketConn(c)
|
|
||||||
return newPacketConn(pc, t), err
|
|
||||||
}
|
|
||||||
return t.ListenPacketWithDialer(ctx, dialer.NewDialer(t.Base.DialOptions(opts...)...), metadata)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListenPacketWithDialer implements C.ProxyAdapter
|
|
||||||
func (t *Trojan) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, metadata *C.Metadata) (_ C.PacketConn, err error) {
|
|
||||||
c, err := dialer.DialContext(ctx, "tcp", t.addr)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("%s connect error: %w", t.addr, err)
|
return nil, fmt.Errorf("%s connect error: %w", t.addr, err)
|
||||||
}
|
}
|
||||||
defer func(c net.Conn) {
|
defer safeConnClose(c, err)
|
||||||
safeConnClose(c, err)
|
|
||||||
}(c)
|
|
||||||
tcpKeepAlive(c)
|
tcpKeepAlive(c)
|
||||||
c, err = t.plainStream(c)
|
c, err = t.plainStream(c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("%s connect error: %w", t.addr, err)
|
return nil, fmt.Errorf("%s connect error: %w", t.addr, err)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
err = t.instance.WriteHeader(c, trojan.CommandUDP, serializesSocksAddr(metadata))
|
err = t.instance.WriteHeader(c, trojan.CommandUDP, serializesSocksAddr(metadata))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -201,11 +168,6 @@ func (t *Trojan) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, me
|
|||||||
return newPacketConn(pc, t), err
|
return newPacketConn(pc, t), err
|
||||||
}
|
}
|
||||||
|
|
||||||
// SupportWithDialer implements C.ProxyAdapter
|
|
||||||
func (t *Trojan) SupportWithDialer() bool {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListenPacketOnStreamConn implements C.ProxyAdapter
|
// ListenPacketOnStreamConn implements C.ProxyAdapter
|
||||||
func (t *Trojan) ListenPacketOnStreamConn(c net.Conn, metadata *C.Metadata) (_ C.PacketConn, err error) {
|
func (t *Trojan) ListenPacketOnStreamConn(c net.Conn, metadata *C.Metadata) (_ C.PacketConn, err error) {
|
||||||
pc := t.instance.PacketConn(c)
|
pc := t.instance.PacketConn(c)
|
||||||
@ -226,13 +188,9 @@ func NewTrojan(option TrojanOption) (*Trojan, error) {
|
|||||||
ServerName: option.Server,
|
ServerName: option.Server,
|
||||||
SkipCertVerify: option.SkipCertVerify,
|
SkipCertVerify: option.SkipCertVerify,
|
||||||
FlowShow: option.FlowShow,
|
FlowShow: option.FlowShow,
|
||||||
Fingerprint: option.Fingerprint,
|
|
||||||
ClientFingerprint: option.ClientFingerprint,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
switch option.Network {
|
if option.Network != "ws" && len(option.Flow) >= 16 {
|
||||||
case "", "tcp":
|
|
||||||
if len(option.Flow) >= 16 {
|
|
||||||
option.Flow = option.Flow[:16]
|
option.Flow = option.Flow[:16]
|
||||||
switch option.Flow {
|
switch option.Flow {
|
||||||
case vless.XRO, vless.XRD, vless.XRS:
|
case vless.XRO, vless.XRD, vless.XRS:
|
||||||
@ -241,7 +199,6 @@ func NewTrojan(option TrojanOption) (*Trojan, error) {
|
|||||||
return nil, fmt.Errorf("unsupported xtls flow type: %s", option.Flow)
|
return nil, fmt.Errorf("unsupported xtls flow type: %s", option.Flow)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if option.SNI != "" {
|
if option.SNI != "" {
|
||||||
tOption.ServerName = option.SNI
|
tOption.ServerName = option.SNI
|
||||||
@ -253,22 +210,13 @@ func NewTrojan(option TrojanOption) (*Trojan, error) {
|
|||||||
addr: addr,
|
addr: addr,
|
||||||
tp: C.Trojan,
|
tp: C.Trojan,
|
||||||
udp: option.UDP,
|
udp: option.UDP,
|
||||||
tfo: option.TFO,
|
|
||||||
iface: option.Interface,
|
iface: option.Interface,
|
||||||
rmark: option.RoutingMark,
|
rmark: option.RoutingMark,
|
||||||
prefer: C.NewDNSPrefer(option.IPVersion),
|
|
||||||
},
|
},
|
||||||
instance: trojan.New(tOption),
|
instance: trojan.New(tOption),
|
||||||
option: &option,
|
option: &option,
|
||||||
}
|
}
|
||||||
|
|
||||||
var err error
|
|
||||||
t.realityConfig, err = option.RealityOpts.Parse()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
tOption.Reality = t.realityConfig
|
|
||||||
|
|
||||||
if option.Network == "grpc" {
|
if option.Network == "grpc" {
|
||||||
dialFn := func(network, addr string) (net.Conn, error) {
|
dialFn := func(network, addr string) (net.Conn, error) {
|
||||||
c, err := dialer.DialContext(context.Background(), "tcp", t.addr, t.Base.DialOptions()...)
|
c, err := dialer.DialContext(context.Background(), "tcp", t.addr, t.Base.DialOptions()...)
|
||||||
@ -286,16 +234,11 @@ func NewTrojan(option TrojanOption) (*Trojan, error) {
|
|||||||
ServerName: tOption.ServerName,
|
ServerName: tOption.ServerName,
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(option.Fingerprint) == 0 {
|
if t.option.Flow != "" {
|
||||||
tlsConfig = tlsC.GetGlobalTLSConfig(tlsConfig)
|
t.transport = gun.NewHTTP2XTLSClient(dialFn, tlsConfig)
|
||||||
} else {
|
} else {
|
||||||
var err error
|
t.transport = gun.NewHTTP2Client(dialFn, tlsConfig)
|
||||||
if tlsConfig, err = tlsC.GetSpecifiedFingerprintTLSConfig(tlsConfig, option.Fingerprint); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
t.transport = gun.NewHTTP2Client(dialFn, tlsConfig, tOption.ClientFingerprint, t.realityConfig)
|
|
||||||
|
|
||||||
t.gunTLSConfig = tlsConfig
|
t.gunTLSConfig = tlsConfig
|
||||||
t.gunConfig = &gun.Config{
|
t.gunConfig = &gun.Config{
|
||||||
|
@ -1,262 +0,0 @@
|
|||||||
package outbound
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"crypto/sha256"
|
|
||||||
"crypto/tls"
|
|
||||||
"encoding/hex"
|
|
||||||
"encoding/pem"
|
|
||||||
"fmt"
|
|
||||||
"math"
|
|
||||||
"net"
|
|
||||||
"os"
|
|
||||||
"strconv"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/metacubex/quic-go"
|
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/component/dialer"
|
|
||||||
tlsC "github.com/Dreamacro/clash/component/tls"
|
|
||||||
C "github.com/Dreamacro/clash/constant"
|
|
||||||
"github.com/Dreamacro/clash/transport/tuic"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Tuic struct {
|
|
||||||
*Base
|
|
||||||
client *tuic.PoolClient
|
|
||||||
}
|
|
||||||
|
|
||||||
type TuicOption struct {
|
|
||||||
BasicOption
|
|
||||||
Name string `proxy:"name"`
|
|
||||||
Server string `proxy:"server"`
|
|
||||||
Port int `proxy:"port"`
|
|
||||||
Token string `proxy:"token"`
|
|
||||||
Ip string `proxy:"ip,omitempty"`
|
|
||||||
HeartbeatInterval int `proxy:"heartbeat-interval,omitempty"`
|
|
||||||
ALPN []string `proxy:"alpn,omitempty"`
|
|
||||||
ReduceRtt bool `proxy:"reduce-rtt,omitempty"`
|
|
||||||
RequestTimeout int `proxy:"request-timeout,omitempty"`
|
|
||||||
UdpRelayMode string `proxy:"udp-relay-mode,omitempty"`
|
|
||||||
CongestionController string `proxy:"congestion-controller,omitempty"`
|
|
||||||
DisableSni bool `proxy:"disable-sni,omitempty"`
|
|
||||||
MaxUdpRelayPacketSize int `proxy:"max-udp-relay-packet-size,omitempty"`
|
|
||||||
|
|
||||||
FastOpen bool `proxy:"fast-open,omitempty"`
|
|
||||||
MaxOpenStreams int `proxy:"max-open-streams,omitempty"`
|
|
||||||
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
|
|
||||||
Fingerprint string `proxy:"fingerprint,omitempty"`
|
|
||||||
CustomCA string `proxy:"ca,omitempty"`
|
|
||||||
CustomCAString string `proxy:"ca-str,omitempty"`
|
|
||||||
ReceiveWindowConn int `proxy:"recv-window-conn,omitempty"`
|
|
||||||
ReceiveWindow int `proxy:"recv-window,omitempty"`
|
|
||||||
DisableMTUDiscovery bool `proxy:"disable-mtu-discovery,omitempty"`
|
|
||||||
MaxDatagramFrameSize int `proxy:"max-datagram-frame-size,omitempty"`
|
|
||||||
SNI string `proxy:"sni,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// DialContext implements C.ProxyAdapter
|
|
||||||
func (t *Tuic) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.Conn, error) {
|
|
||||||
return t.DialContextWithDialer(ctx, dialer.NewDialer(t.Base.DialOptions(opts...)...), metadata)
|
|
||||||
}
|
|
||||||
|
|
||||||
// DialContextWithDialer implements C.ProxyAdapter
|
|
||||||
func (t *Tuic) DialContextWithDialer(ctx context.Context, dialer C.Dialer, metadata *C.Metadata) (C.Conn, error) {
|
|
||||||
conn, err := t.client.DialContextWithDialer(ctx, metadata, dialer, t.dialWithDialer)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return NewConn(conn, t), err
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListenPacketContext implements C.ProxyAdapter
|
|
||||||
func (t *Tuic) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.PacketConn, err error) {
|
|
||||||
return t.ListenPacketWithDialer(ctx, dialer.NewDialer(t.Base.DialOptions(opts...)...), metadata)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListenPacketWithDialer implements C.ProxyAdapter
|
|
||||||
func (t *Tuic) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, metadata *C.Metadata) (_ C.PacketConn, err error) {
|
|
||||||
pc, err := t.client.ListenPacketWithDialer(ctx, metadata, dialer, t.dialWithDialer)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return newPacketConn(pc, t), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// SupportWithDialer implements C.ProxyAdapter
|
|
||||||
func (t *Tuic) SupportWithDialer() bool {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *Tuic) dial(ctx context.Context, opts ...dialer.Option) (pc net.PacketConn, addr net.Addr, err error) {
|
|
||||||
return t.dialWithDialer(ctx, dialer.NewDialer(opts...))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *Tuic) dialWithDialer(ctx context.Context, dialer C.Dialer) (pc net.PacketConn, addr net.Addr, err error) {
|
|
||||||
udpAddr, err := resolveUDPAddrWithPrefer(ctx, "udp", t.addr, t.prefer)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
addr = udpAddr
|
|
||||||
pc, err = dialer.ListenPacket(ctx, "udp", "", udpAddr.AddrPort())
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewTuic(option TuicOption) (*Tuic, error) {
|
|
||||||
addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port))
|
|
||||||
serverName := option.Server
|
|
||||||
tlsConfig := &tls.Config{
|
|
||||||
ServerName: serverName,
|
|
||||||
InsecureSkipVerify: option.SkipCertVerify,
|
|
||||||
MinVersion: tls.VersionTLS13,
|
|
||||||
}
|
|
||||||
if option.SNI != "" {
|
|
||||||
tlsConfig.ServerName = option.SNI
|
|
||||||
}
|
|
||||||
|
|
||||||
var bs []byte
|
|
||||||
var err error
|
|
||||||
if len(option.CustomCA) > 0 {
|
|
||||||
bs, err = os.ReadFile(option.CustomCA)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("tuic %s load ca error: %w", addr, err)
|
|
||||||
}
|
|
||||||
} else if option.CustomCAString != "" {
|
|
||||||
bs = []byte(option.CustomCAString)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(bs) > 0 {
|
|
||||||
block, _ := pem.Decode(bs)
|
|
||||||
if block == nil {
|
|
||||||
return nil, fmt.Errorf("CA cert is not PEM")
|
|
||||||
}
|
|
||||||
|
|
||||||
fpBytes := sha256.Sum256(block.Bytes)
|
|
||||||
if len(option.Fingerprint) == 0 {
|
|
||||||
option.Fingerprint = hex.EncodeToString(fpBytes[:])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(option.Fingerprint) != 0 {
|
|
||||||
var err error
|
|
||||||
tlsConfig, err = tlsC.GetSpecifiedFingerprintTLSConfig(tlsConfig, option.Fingerprint)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
tlsConfig = tlsC.GetGlobalTLSConfig(tlsConfig)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(option.ALPN) > 0 {
|
|
||||||
tlsConfig.NextProtos = option.ALPN
|
|
||||||
} else {
|
|
||||||
tlsConfig.NextProtos = []string{"h3"}
|
|
||||||
}
|
|
||||||
|
|
||||||
if option.RequestTimeout == 0 {
|
|
||||||
option.RequestTimeout = 8000
|
|
||||||
}
|
|
||||||
|
|
||||||
if option.HeartbeatInterval <= 0 {
|
|
||||||
option.HeartbeatInterval = 10000
|
|
||||||
}
|
|
||||||
|
|
||||||
if option.UdpRelayMode != "quic" {
|
|
||||||
option.UdpRelayMode = "native"
|
|
||||||
}
|
|
||||||
|
|
||||||
if option.MaxUdpRelayPacketSize == 0 {
|
|
||||||
option.MaxUdpRelayPacketSize = 1252
|
|
||||||
}
|
|
||||||
|
|
||||||
if option.MaxOpenStreams == 0 {
|
|
||||||
option.MaxOpenStreams = 100
|
|
||||||
}
|
|
||||||
|
|
||||||
if option.MaxDatagramFrameSize == 0 {
|
|
||||||
option.MaxDatagramFrameSize = option.MaxUdpRelayPacketSize + tuic.PacketOverHead
|
|
||||||
}
|
|
||||||
|
|
||||||
if option.MaxDatagramFrameSize > 1400 {
|
|
||||||
option.MaxDatagramFrameSize = 1400
|
|
||||||
}
|
|
||||||
option.MaxUdpRelayPacketSize = option.MaxDatagramFrameSize - tuic.PacketOverHead
|
|
||||||
|
|
||||||
// ensure server's incoming stream can handle correctly, increase to 1.1x
|
|
||||||
quicMaxOpenStreams := int64(option.MaxOpenStreams)
|
|
||||||
quicMaxOpenStreams = quicMaxOpenStreams + int64(math.Ceil(float64(quicMaxOpenStreams)/10.0))
|
|
||||||
quicConfig := &quic.Config{
|
|
||||||
InitialStreamReceiveWindow: uint64(option.ReceiveWindowConn),
|
|
||||||
MaxStreamReceiveWindow: uint64(option.ReceiveWindowConn),
|
|
||||||
InitialConnectionReceiveWindow: uint64(option.ReceiveWindow),
|
|
||||||
MaxConnectionReceiveWindow: uint64(option.ReceiveWindow),
|
|
||||||
MaxIncomingStreams: quicMaxOpenStreams,
|
|
||||||
MaxIncomingUniStreams: quicMaxOpenStreams,
|
|
||||||
KeepAlivePeriod: time.Duration(option.HeartbeatInterval) * time.Millisecond,
|
|
||||||
DisablePathMTUDiscovery: option.DisableMTUDiscovery,
|
|
||||||
MaxDatagramFrameSize: int64(option.MaxDatagramFrameSize),
|
|
||||||
EnableDatagrams: true,
|
|
||||||
}
|
|
||||||
if option.ReceiveWindowConn == 0 {
|
|
||||||
quicConfig.InitialStreamReceiveWindow = tuic.DefaultStreamReceiveWindow / 10
|
|
||||||
quicConfig.MaxStreamReceiveWindow = tuic.DefaultStreamReceiveWindow
|
|
||||||
}
|
|
||||||
if option.ReceiveWindow == 0 {
|
|
||||||
quicConfig.InitialConnectionReceiveWindow = tuic.DefaultConnectionReceiveWindow / 10
|
|
||||||
quicConfig.MaxConnectionReceiveWindow = tuic.DefaultConnectionReceiveWindow
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(option.Ip) > 0 {
|
|
||||||
addr = net.JoinHostPort(option.Ip, strconv.Itoa(option.Port))
|
|
||||||
}
|
|
||||||
host := option.Server
|
|
||||||
if option.DisableSni {
|
|
||||||
host = ""
|
|
||||||
tlsConfig.ServerName = ""
|
|
||||||
}
|
|
||||||
tkn := tuic.GenTKN(option.Token)
|
|
||||||
|
|
||||||
t := &Tuic{
|
|
||||||
Base: &Base{
|
|
||||||
name: option.Name,
|
|
||||||
addr: addr,
|
|
||||||
tp: C.Tuic,
|
|
||||||
udp: true,
|
|
||||||
tfo: option.FastOpen,
|
|
||||||
iface: option.Interface,
|
|
||||||
rmark: option.RoutingMark,
|
|
||||||
prefer: C.NewDNSPrefer(option.IPVersion),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
clientMaxOpenStreams := int64(option.MaxOpenStreams)
|
|
||||||
|
|
||||||
// to avoid tuic's "too many open streams", decrease to 0.9x
|
|
||||||
if clientMaxOpenStreams == 100 {
|
|
||||||
clientMaxOpenStreams = clientMaxOpenStreams - int64(math.Ceil(float64(clientMaxOpenStreams)/10.0))
|
|
||||||
}
|
|
||||||
|
|
||||||
if clientMaxOpenStreams < 1 {
|
|
||||||
clientMaxOpenStreams = 1
|
|
||||||
}
|
|
||||||
clientOption := &tuic.ClientOption{
|
|
||||||
TlsConfig: tlsConfig,
|
|
||||||
QuicConfig: quicConfig,
|
|
||||||
Host: host,
|
|
||||||
Token: tkn,
|
|
||||||
UdpRelayMode: option.UdpRelayMode,
|
|
||||||
CongestionController: option.CongestionController,
|
|
||||||
ReduceRtt: option.ReduceRtt,
|
|
||||||
RequestTimeout: time.Duration(option.RequestTimeout) * time.Millisecond,
|
|
||||||
MaxUdpRelayPacketSize: option.MaxUdpRelayPacketSize,
|
|
||||||
FastOpen: option.FastOpen,
|
|
||||||
MaxOpenStreams: clientMaxOpenStreams,
|
|
||||||
}
|
|
||||||
|
|
||||||
t.client = tuic.NewPoolClient(clientOption)
|
|
||||||
|
|
||||||
return t, nil
|
|
||||||
}
|
|
@ -2,11 +2,8 @@ package outbound
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
xtls "github.com/xtls/go"
|
|
||||||
"net"
|
"net"
|
||||||
"net/netip"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
@ -14,6 +11,7 @@ import (
|
|||||||
"github.com/Dreamacro/clash/component/resolver"
|
"github.com/Dreamacro/clash/component/resolver"
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
"github.com/Dreamacro/clash/transport/socks5"
|
"github.com/Dreamacro/clash/transport/socks5"
|
||||||
|
xtls "github.com/xtls/go"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -45,11 +43,10 @@ func getClientXSessionCache() xtls.ClientSessionCache {
|
|||||||
|
|
||||||
func serializesSocksAddr(metadata *C.Metadata) []byte {
|
func serializesSocksAddr(metadata *C.Metadata) []byte {
|
||||||
var buf [][]byte
|
var buf [][]byte
|
||||||
addrType := metadata.AddrType()
|
aType := uint8(metadata.AddrType)
|
||||||
aType := uint8(addrType)
|
|
||||||
p, _ := strconv.ParseUint(metadata.DstPort, 10, 16)
|
p, _ := strconv.ParseUint(metadata.DstPort, 10, 16)
|
||||||
port := []byte{uint8(p >> 8), uint8(p & 0xff)}
|
port := []byte{uint8(p >> 8), uint8(p & 0xff)}
|
||||||
switch addrType {
|
switch metadata.AddrType {
|
||||||
case socks5.AtypDomainName:
|
case socks5.AtypDomainName:
|
||||||
lenM := uint8(len(metadata.Host))
|
lenM := uint8(len(metadata.Host))
|
||||||
host := []byte(metadata.Host)
|
host := []byte(metadata.Host)
|
||||||
@ -64,69 +61,13 @@ func serializesSocksAddr(metadata *C.Metadata) []byte {
|
|||||||
return bytes.Join(buf, nil)
|
return bytes.Join(buf, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func resolveUDPAddr(ctx context.Context, network, address string) (*net.UDPAddr, error) {
|
func resolveUDPAddr(network, address string) (*net.UDPAddr, error) {
|
||||||
host, port, err := net.SplitHostPort(address)
|
host, port, err := net.SplitHostPort(address)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
ip, err := resolver.ResolveProxyServerHost(ctx, host)
|
ip, err := resolver.ResolveProxyServerHost(host)
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return net.ResolveUDPAddr(network, net.JoinHostPort(ip.String(), port))
|
|
||||||
}
|
|
||||||
|
|
||||||
func resolveUDPAddrWithPrefer(ctx context.Context, network, address string, prefer C.DNSPrefer) (*net.UDPAddr, error) {
|
|
||||||
host, port, err := net.SplitHostPort(address)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
var ip netip.Addr
|
|
||||||
var fallback netip.Addr
|
|
||||||
switch prefer {
|
|
||||||
case C.IPv4Only:
|
|
||||||
ip, err = resolver.ResolveIPv4ProxyServerHost(ctx, host)
|
|
||||||
case C.IPv6Only:
|
|
||||||
ip, err = resolver.ResolveIPv6ProxyServerHost(ctx, host)
|
|
||||||
case C.IPv6Prefer:
|
|
||||||
var ips []netip.Addr
|
|
||||||
ips, err = resolver.LookupIPProxyServerHost(ctx, host)
|
|
||||||
if err == nil {
|
|
||||||
for _, addr := range ips {
|
|
||||||
if addr.Is6() {
|
|
||||||
ip = addr
|
|
||||||
break
|
|
||||||
} else {
|
|
||||||
if !fallback.IsValid() {
|
|
||||||
fallback = addr
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
// C.IPv4Prefer, C.DualStack and other
|
|
||||||
var ips []netip.Addr
|
|
||||||
ips, err = resolver.LookupIPProxyServerHost(ctx, host)
|
|
||||||
if err == nil {
|
|
||||||
for _, addr := range ips {
|
|
||||||
if addr.Is4() {
|
|
||||||
ip = addr
|
|
||||||
break
|
|
||||||
} else {
|
|
||||||
if !fallback.IsValid() {
|
|
||||||
fallback = addr
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !ip.IsValid() && fallback.IsValid() {
|
|
||||||
ip = fallback
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -134,7 +75,7 @@ func resolveUDPAddrWithPrefer(ctx context.Context, network, address string, pref
|
|||||||
}
|
}
|
||||||
|
|
||||||
func safeConnClose(c net.Conn, err error) {
|
func safeConnClose(c net.Conn, err error) {
|
||||||
if err != nil && c != nil {
|
if err != nil {
|
||||||
_ = c.Close()
|
_ = c.Close()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,17 +15,10 @@ import (
|
|||||||
"github.com/Dreamacro/clash/common/convert"
|
"github.com/Dreamacro/clash/common/convert"
|
||||||
"github.com/Dreamacro/clash/component/dialer"
|
"github.com/Dreamacro/clash/component/dialer"
|
||||||
"github.com/Dreamacro/clash/component/resolver"
|
"github.com/Dreamacro/clash/component/resolver"
|
||||||
tlsC "github.com/Dreamacro/clash/component/tls"
|
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
"github.com/Dreamacro/clash/log"
|
|
||||||
"github.com/Dreamacro/clash/transport/gun"
|
"github.com/Dreamacro/clash/transport/gun"
|
||||||
"github.com/Dreamacro/clash/transport/socks5"
|
|
||||||
"github.com/Dreamacro/clash/transport/vless"
|
"github.com/Dreamacro/clash/transport/vless"
|
||||||
"github.com/Dreamacro/clash/transport/vmess"
|
"github.com/Dreamacro/clash/transport/vmess"
|
||||||
|
|
||||||
vmessSing "github.com/sagernet/sing-vmess"
|
|
||||||
"github.com/sagernet/sing-vmess/packetaddr"
|
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -42,8 +35,6 @@ type Vless struct {
|
|||||||
gunTLSConfig *tls.Config
|
gunTLSConfig *tls.Config
|
||||||
gunConfig *gun.Config
|
gunConfig *gun.Config
|
||||||
transport *gun.TransportWrap
|
transport *gun.TransportWrap
|
||||||
|
|
||||||
realityConfig *tlsC.RealityConfig
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type VlessOption struct {
|
type VlessOption struct {
|
||||||
@ -56,11 +47,7 @@ type VlessOption struct {
|
|||||||
FlowShow bool `proxy:"flow-show,omitempty"`
|
FlowShow bool `proxy:"flow-show,omitempty"`
|
||||||
TLS bool `proxy:"tls,omitempty"`
|
TLS bool `proxy:"tls,omitempty"`
|
||||||
UDP bool `proxy:"udp,omitempty"`
|
UDP bool `proxy:"udp,omitempty"`
|
||||||
PacketAddr bool `proxy:"packet-addr,omitempty"`
|
|
||||||
XUDP bool `proxy:"xudp,omitempty"`
|
|
||||||
PacketEncoding string `proxy:"packet-encoding,omitempty"`
|
|
||||||
Network string `proxy:"network,omitempty"`
|
Network string `proxy:"network,omitempty"`
|
||||||
RealityOpts RealityOptions `proxy:"reality-opts,omitempty"`
|
|
||||||
HTTPOpts HTTPOptions `proxy:"http-opts,omitempty"`
|
HTTPOpts HTTPOptions `proxy:"http-opts,omitempty"`
|
||||||
HTTP2Opts HTTP2Options `proxy:"h2-opts,omitempty"`
|
HTTP2Opts HTTP2Options `proxy:"h2-opts,omitempty"`
|
||||||
GrpcOpts GrpcOptions `proxy:"grpc-opts,omitempty"`
|
GrpcOpts GrpcOptions `proxy:"grpc-opts,omitempty"`
|
||||||
@ -68,20 +55,14 @@ type VlessOption struct {
|
|||||||
WSPath string `proxy:"ws-path,omitempty"`
|
WSPath string `proxy:"ws-path,omitempty"`
|
||||||
WSHeaders map[string]string `proxy:"ws-headers,omitempty"`
|
WSHeaders map[string]string `proxy:"ws-headers,omitempty"`
|
||||||
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
|
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
|
||||||
Fingerprint string `proxy:"fingerprint,omitempty"`
|
|
||||||
ServerName string `proxy:"servername,omitempty"`
|
ServerName string `proxy:"servername,omitempty"`
|
||||||
ClientFingerprint string `proxy:"client-fingerprint,omitempty"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *Vless) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
func (v *Vless) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
if tlsC.HaveGlobalFingerprint() && len(v.option.ClientFingerprint) == 0 {
|
|
||||||
v.option.ClientFingerprint = tlsC.GetGlobalFingerprint()
|
|
||||||
}
|
|
||||||
|
|
||||||
switch v.option.Network {
|
switch v.option.Network {
|
||||||
case "ws":
|
case "ws":
|
||||||
|
|
||||||
host, port, _ := net.SplitHostPort(v.addr)
|
host, port, _ := net.SplitHostPort(v.addr)
|
||||||
wsOpts := &vmess.WebsocketConfig{
|
wsOpts := &vmess.WebsocketConfig{
|
||||||
Host: host,
|
Host: host,
|
||||||
@ -89,44 +70,31 @@ func (v *Vless) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
|||||||
Path: v.option.WSOpts.Path,
|
Path: v.option.WSOpts.Path,
|
||||||
MaxEarlyData: v.option.WSOpts.MaxEarlyData,
|
MaxEarlyData: v.option.WSOpts.MaxEarlyData,
|
||||||
EarlyDataHeaderName: v.option.WSOpts.EarlyDataHeaderName,
|
EarlyDataHeaderName: v.option.WSOpts.EarlyDataHeaderName,
|
||||||
ClientFingerprint: v.option.ClientFingerprint,
|
|
||||||
Headers: http.Header{},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(v.option.WSOpts.Headers) != 0 {
|
if len(v.option.WSOpts.Headers) != 0 {
|
||||||
|
header := http.Header{}
|
||||||
for key, value := range v.option.WSOpts.Headers {
|
for key, value := range v.option.WSOpts.Headers {
|
||||||
wsOpts.Headers.Add(key, value)
|
header.Add(key, value)
|
||||||
}
|
}
|
||||||
|
wsOpts.Headers = header
|
||||||
}
|
}
|
||||||
if v.option.TLS {
|
|
||||||
wsOpts.TLS = true
|
wsOpts.TLS = true
|
||||||
tlsConfig := &tls.Config{
|
wsOpts.TLSConfig = &tls.Config{
|
||||||
MinVersion: tls.VersionTLS12,
|
MinVersion: tls.VersionTLS12,
|
||||||
ServerName: host,
|
ServerName: host,
|
||||||
InsecureSkipVerify: v.option.SkipCertVerify,
|
InsecureSkipVerify: v.option.SkipCertVerify,
|
||||||
NextProtos: []string{"http/1.1"},
|
NextProtos: []string{"http/1.1"},
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(v.option.Fingerprint) == 0 {
|
|
||||||
wsOpts.TLSConfig = tlsC.GetGlobalTLSConfig(tlsConfig)
|
|
||||||
} else {
|
|
||||||
wsOpts.TLSConfig, err = tlsC.GetSpecifiedFingerprintTLSConfig(tlsConfig, v.option.Fingerprint)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if v.option.ServerName != "" {
|
if v.option.ServerName != "" {
|
||||||
wsOpts.TLSConfig.ServerName = v.option.ServerName
|
wsOpts.TLSConfig.ServerName = v.option.ServerName
|
||||||
} else if host := wsOpts.Headers.Get("Host"); host != "" {
|
} else if host := wsOpts.Headers.Get("Host"); host != "" {
|
||||||
wsOpts.TLSConfig.ServerName = host
|
wsOpts.TLSConfig.ServerName = host
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
if host := wsOpts.Headers.Get("Host"); host == "" {
|
|
||||||
wsOpts.Headers.Set("Host", convert.RandHost())
|
wsOpts.Headers.Set("Host", convert.RandHost())
|
||||||
convert.SetUserAgent(wsOpts.Headers)
|
convert.SetUserAgent(wsOpts.Headers)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
c, err = vmess.StreamWebsocketConn(c, wsOpts)
|
c, err = vmess.StreamWebsocketConn(c, wsOpts)
|
||||||
case "http":
|
case "http":
|
||||||
// readability first, so just copy default TLS logic
|
// readability first, so just copy default TLS logic
|
||||||
@ -157,7 +125,11 @@ func (v *Vless) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
|||||||
|
|
||||||
c, err = vmess.StreamH2Conn(c, h2Opts)
|
c, err = vmess.StreamH2Conn(c, h2Opts)
|
||||||
case "grpc":
|
case "grpc":
|
||||||
c, err = gun.StreamGunWithConn(c, v.gunTLSConfig, v.gunConfig, v.realityConfig)
|
if v.isXTLSEnabled() {
|
||||||
|
c, err = gun.StreamGunWithXTLSConn(c, v.gunTLSConfig, v.gunConfig)
|
||||||
|
} else {
|
||||||
|
c, err = gun.StreamGunWithConn(c, v.gunTLSConfig, v.gunConfig)
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
// default tcp network
|
// default tcp network
|
||||||
// handle TLS And XTLS
|
// handle TLS And XTLS
|
||||||
@ -168,17 +140,20 @@ func (v *Vless) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return v.client.StreamConn(c, parseVlessAddr(metadata, v.option.XUDP))
|
return v.client.StreamConn(c, parseVlessAddr(metadata))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *Vless) streamTLSOrXTLSConn(conn net.Conn, isH2 bool) (net.Conn, error) {
|
func (v *Vless) streamTLSOrXTLSConn(conn net.Conn, isH2 bool) (net.Conn, error) {
|
||||||
host, _, _ := net.SplitHostPort(v.addr)
|
host, _, _ := net.SplitHostPort(v.addr)
|
||||||
|
|
||||||
if v.isLegacyXTLSEnabled() && !isH2 {
|
if v.isXTLSEnabled() {
|
||||||
xtlsOpts := vless.XTLSConfig{
|
xtlsOpts := vless.XTLSConfig{
|
||||||
Host: host,
|
Host: host,
|
||||||
SkipCertVerify: v.option.SkipCertVerify,
|
SkipCertVerify: v.option.SkipCertVerify,
|
||||||
Fingerprint: v.option.Fingerprint,
|
}
|
||||||
|
|
||||||
|
if isH2 {
|
||||||
|
xtlsOpts.NextProtos = []string{"h2"}
|
||||||
}
|
}
|
||||||
|
|
||||||
if v.option.ServerName != "" {
|
if v.option.ServerName != "" {
|
||||||
@ -191,9 +166,6 @@ func (v *Vless) streamTLSOrXTLSConn(conn net.Conn, isH2 bool) (net.Conn, error)
|
|||||||
tlsOpts := vmess.TLSConfig{
|
tlsOpts := vmess.TLSConfig{
|
||||||
Host: host,
|
Host: host,
|
||||||
SkipCertVerify: v.option.SkipCertVerify,
|
SkipCertVerify: v.option.SkipCertVerify,
|
||||||
FingerPrint: v.option.Fingerprint,
|
|
||||||
ClientFingerprint: v.option.ClientFingerprint,
|
|
||||||
Reality: v.realityConfig,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if isH2 {
|
if isH2 {
|
||||||
@ -210,8 +182,8 @@ func (v *Vless) streamTLSOrXTLSConn(conn net.Conn, isH2 bool) (net.Conn, error)
|
|||||||
return conn, nil
|
return conn, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *Vless) isLegacyXTLSEnabled() bool {
|
func (v *Vless) isXTLSEnabled() bool {
|
||||||
return v.client.Addons != nil && v.client.Addons.Flow != vless.XRV
|
return v.client.Addons != nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// DialContext implements C.ProxyAdapter
|
// DialContext implements C.ProxyAdapter
|
||||||
@ -222,43 +194,32 @@ func (v *Vless) DialContext(ctx context.Context, metadata *C.Metadata, opts ...d
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
defer func(c net.Conn) {
|
defer safeConnClose(c, err)
|
||||||
safeConnClose(c, err)
|
|
||||||
}(c)
|
|
||||||
|
|
||||||
c, err = v.client.StreamConn(c, parseVlessAddr(metadata, v.option.XUDP))
|
c, err = v.client.StreamConn(c, parseVlessAddr(metadata))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return NewConn(c, v), nil
|
return NewConn(c, v), nil
|
||||||
}
|
}
|
||||||
return v.DialContextWithDialer(ctx, dialer.NewDialer(v.Base.DialOptions(opts...)...), metadata)
|
|
||||||
}
|
|
||||||
|
|
||||||
// DialContextWithDialer implements C.ProxyAdapter
|
c, err := dialer.DialContext(ctx, "tcp", v.addr, v.Base.DialOptions(opts...)...)
|
||||||
func (v *Vless) DialContextWithDialer(ctx context.Context, dialer C.Dialer, metadata *C.Metadata) (_ C.Conn, err error) {
|
|
||||||
c, err := dialer.DialContext(ctx, "tcp", v.addr)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error())
|
return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error())
|
||||||
}
|
}
|
||||||
tcpKeepAlive(c)
|
tcpKeepAlive(c)
|
||||||
defer func(c net.Conn) {
|
defer safeConnClose(c, err)
|
||||||
safeConnClose(c, err)
|
|
||||||
}(c)
|
|
||||||
|
|
||||||
c, err = v.StreamConn(c, metadata)
|
c, err = v.StreamConn(c, metadata)
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error())
|
|
||||||
}
|
|
||||||
return NewConn(c, v), err
|
return NewConn(c, v), err
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListenPacketContext implements C.ProxyAdapter
|
// ListenPacketContext implements C.ProxyAdapter
|
||||||
func (v *Vless) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.PacketConn, err error) {
|
func (v *Vless) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.PacketConn, err error) {
|
||||||
// vless use stream-oriented udp with a special address, so we need a net.UDPAddr
|
// vless use stream-oriented udp with a special address, so we needs a net.UDPAddr
|
||||||
if !metadata.Resolved() {
|
if !metadata.Resolved() {
|
||||||
ip, err := resolver.ResolveIP(ctx, metadata.Host)
|
ip, err := resolver.ResolveIP(metadata.Host)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.New("can't resolve ip")
|
return nil, errors.New("can't resolve ip")
|
||||||
}
|
}
|
||||||
@ -272,55 +233,17 @@ func (v *Vless) ListenPacketContext(ctx context.Context, metadata *C.Metadata, o
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
defer func(c net.Conn) {
|
defer safeConnClose(c, err)
|
||||||
safeConnClose(c, err)
|
|
||||||
}(c)
|
|
||||||
|
|
||||||
if v.option.PacketAddr {
|
c, err = v.client.StreamConn(c, parseVlessAddr(metadata))
|
||||||
packetAddrMetadata := *metadata // make a copy
|
|
||||||
packetAddrMetadata.Host = packetaddr.SeqPacketMagicAddress
|
|
||||||
packetAddrMetadata.DstPort = "443"
|
|
||||||
|
|
||||||
c, err = v.client.StreamConn(c, parseVlessAddr(&packetAddrMetadata, false))
|
|
||||||
} else {
|
} else {
|
||||||
c, err = v.client.StreamConn(c, parseVlessAddr(metadata, v.option.XUDP))
|
c, err = dialer.DialContext(ctx, "tcp", v.addr, v.Base.DialOptions(opts...)...)
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("new vless client error: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return v.ListenPacketOnStreamConn(c, metadata)
|
|
||||||
}
|
|
||||||
return v.ListenPacketWithDialer(ctx, dialer.NewDialer(v.Base.DialOptions(opts...)...), metadata)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListenPacketWithDialer implements C.ProxyAdapter
|
|
||||||
func (v *Vless) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, metadata *C.Metadata) (_ C.PacketConn, err error) {
|
|
||||||
// vless use stream-oriented udp with a special address, so we need a net.UDPAddr
|
|
||||||
if !metadata.Resolved() {
|
|
||||||
ip, err := resolver.ResolveIP(ctx, metadata.Host)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.New("can't resolve ip")
|
|
||||||
}
|
|
||||||
metadata.DstIP = ip
|
|
||||||
}
|
|
||||||
c, err := dialer.DialContext(ctx, "tcp", v.addr)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error())
|
return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error())
|
||||||
}
|
}
|
||||||
tcpKeepAlive(c)
|
tcpKeepAlive(c)
|
||||||
defer func(c net.Conn) {
|
defer safeConnClose(c, err)
|
||||||
safeConnClose(c, err)
|
|
||||||
}(c)
|
|
||||||
|
|
||||||
if v.option.PacketAddr {
|
|
||||||
packetAddrMetadata := *metadata // make a copy
|
|
||||||
packetAddrMetadata.Host = packetaddr.SeqPacketMagicAddress
|
|
||||||
packetAddrMetadata.DstPort = "443"
|
|
||||||
|
|
||||||
c, err = v.StreamConn(c, &packetAddrMetadata)
|
|
||||||
} else {
|
|
||||||
c, err = v.StreamConn(c, metadata)
|
c, err = v.StreamConn(c, metadata)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -331,24 +254,8 @@ func (v *Vless) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, met
|
|||||||
return v.ListenPacketOnStreamConn(c, metadata)
|
return v.ListenPacketOnStreamConn(c, metadata)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SupportWithDialer implements C.ProxyAdapter
|
|
||||||
func (v *Vless) SupportWithDialer() bool {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListenPacketOnStreamConn implements C.ProxyAdapter
|
// ListenPacketOnStreamConn implements C.ProxyAdapter
|
||||||
func (v *Vless) ListenPacketOnStreamConn(c net.Conn, metadata *C.Metadata) (_ C.PacketConn, err error) {
|
func (v *Vless) ListenPacketOnStreamConn(c net.Conn, metadata *C.Metadata) (_ C.PacketConn, err error) {
|
||||||
if v.option.XUDP {
|
|
||||||
return newPacketConn(&threadSafePacketConn{
|
|
||||||
PacketConn: vmessSing.NewXUDPConn(c, M.ParseSocksaddr(metadata.RemoteAddress())),
|
|
||||||
}, v), nil
|
|
||||||
} else if v.option.PacketAddr {
|
|
||||||
return newPacketConn(&threadSafePacketConn{
|
|
||||||
PacketConn: packetaddr.NewConn(&vlessPacketConn{
|
|
||||||
Conn: c, rAddr: metadata.UDPAddr(),
|
|
||||||
}, M.ParseSocksaddr(metadata.RemoteAddress())),
|
|
||||||
}, v), nil
|
|
||||||
}
|
|
||||||
return newPacketConn(&vlessPacketConn{Conn: c, rAddr: metadata.UDPAddr()}, v), nil
|
return newPacketConn(&vlessPacketConn{Conn: c, rAddr: metadata.UDPAddr()}, v), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -357,19 +264,19 @@ func (v *Vless) SupportUOT() bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseVlessAddr(metadata *C.Metadata, xudp bool) *vless.DstAddr {
|
func parseVlessAddr(metadata *C.Metadata) *vless.DstAddr {
|
||||||
var addrType byte
|
var addrType byte
|
||||||
var addr []byte
|
var addr []byte
|
||||||
switch metadata.AddrType() {
|
switch metadata.AddrType {
|
||||||
case socks5.AtypIPv4:
|
case C.AtypIPv4:
|
||||||
addrType = vless.AtypIPv4
|
addrType = vless.AtypIPv4
|
||||||
addr = make([]byte, net.IPv4len)
|
addr = make([]byte, net.IPv4len)
|
||||||
copy(addr[:], metadata.DstIP.AsSlice())
|
copy(addr[:], metadata.DstIP.AsSlice())
|
||||||
case socks5.AtypIPv6:
|
case C.AtypIPv6:
|
||||||
addrType = vless.AtypIPv6
|
addrType = vless.AtypIPv6
|
||||||
addr = make([]byte, net.IPv6len)
|
addr = make([]byte, net.IPv6len)
|
||||||
copy(addr[:], metadata.DstIP.AsSlice())
|
copy(addr[:], metadata.DstIP.AsSlice())
|
||||||
case socks5.AtypDomainName:
|
case C.AtypDomainName:
|
||||||
addrType = vless.AtypDomainName
|
addrType = vless.AtypDomainName
|
||||||
addr = make([]byte, len(metadata.Host)+1)
|
addr = make([]byte, len(metadata.Host)+1)
|
||||||
addr[0] = byte(len(metadata.Host))
|
addr[0] = byte(len(metadata.Host))
|
||||||
@ -381,8 +288,7 @@ func parseVlessAddr(metadata *C.Metadata, xudp bool) *vless.DstAddr {
|
|||||||
UDP: metadata.NetWork == C.UDP,
|
UDP: metadata.NetWork == C.UDP,
|
||||||
AddrType: addrType,
|
AddrType: addrType,
|
||||||
Addr: addr,
|
Addr: addr,
|
||||||
Port: uint16(port),
|
Port: uint(port),
|
||||||
Mux: metadata.NetWork == C.UDP && xudp,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -483,9 +389,6 @@ func NewVless(option VlessOption) (*Vless, error) {
|
|||||||
if option.Network != "ws" && len(option.Flow) >= 16 {
|
if option.Network != "ws" && len(option.Flow) >= 16 {
|
||||||
option.Flow = option.Flow[:16]
|
option.Flow = option.Flow[:16]
|
||||||
switch option.Flow {
|
switch option.Flow {
|
||||||
case vless.XRV:
|
|
||||||
log.Warnln("To use %s, ensure your server is upgrade to Xray-core v1.8.0+", vless.XRV)
|
|
||||||
fallthrough
|
|
||||||
case vless.XRO, vless.XRD, vless.XRS:
|
case vless.XRO, vless.XRD, vless.XRS:
|
||||||
addons = &vless.Addons{
|
addons = &vless.Addons{
|
||||||
Flow: option.Flow,
|
Flow: option.Flow,
|
||||||
@ -495,16 +398,6 @@ func NewVless(option VlessOption) (*Vless, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
switch option.PacketEncoding {
|
|
||||||
case "packetaddr", "packet":
|
|
||||||
option.PacketAddr = true
|
|
||||||
option.XUDP = false
|
|
||||||
default: // https://github.com/XTLS/Xray-core/pull/1567#issuecomment-1407305458
|
|
||||||
if !option.PacketAddr {
|
|
||||||
option.XUDP = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
client, err := vless.NewClient(option.UUID, addons, option.FlowShow)
|
client, err := vless.NewClient(option.UUID, addons, option.FlowShow)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -516,21 +409,12 @@ func NewVless(option VlessOption) (*Vless, error) {
|
|||||||
addr: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)),
|
addr: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)),
|
||||||
tp: C.Vless,
|
tp: C.Vless,
|
||||||
udp: option.UDP,
|
udp: option.UDP,
|
||||||
xudp: option.XUDP,
|
|
||||||
tfo: option.TFO,
|
|
||||||
iface: option.Interface,
|
iface: option.Interface,
|
||||||
rmark: option.RoutingMark,
|
|
||||||
prefer: C.NewDNSPrefer(option.IPVersion),
|
|
||||||
},
|
},
|
||||||
client: client,
|
client: client,
|
||||||
option: &option,
|
option: &option,
|
||||||
}
|
}
|
||||||
|
|
||||||
v.realityConfig, err = v.option.RealityOpts.Parse()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
switch option.Network {
|
switch option.Network {
|
||||||
case "h2":
|
case "h2":
|
||||||
if len(option.HTTP2Opts.Host) == 0 {
|
if len(option.HTTP2Opts.Host) == 0 {
|
||||||
@ -549,12 +433,11 @@ func NewVless(option VlessOption) (*Vless, error) {
|
|||||||
gunConfig := &gun.Config{
|
gunConfig := &gun.Config{
|
||||||
ServiceName: v.option.GrpcOpts.GrpcServiceName,
|
ServiceName: v.option.GrpcOpts.GrpcServiceName,
|
||||||
Host: v.option.ServerName,
|
Host: v.option.ServerName,
|
||||||
ClientFingerprint: v.option.ClientFingerprint,
|
|
||||||
}
|
}
|
||||||
tlsConfig := tlsC.GetGlobalTLSConfig(&tls.Config{
|
tlsConfig := &tls.Config{
|
||||||
InsecureSkipVerify: v.option.SkipCertVerify,
|
InsecureSkipVerify: v.option.SkipCertVerify,
|
||||||
ServerName: v.option.ServerName,
|
ServerName: v.option.ServerName,
|
||||||
})
|
}
|
||||||
|
|
||||||
if v.option.ServerName == "" {
|
if v.option.ServerName == "" {
|
||||||
host, _, _ := net.SplitHostPort(v.addr)
|
host, _, _ := net.SplitHostPort(v.addr)
|
||||||
@ -564,8 +447,11 @@ func NewVless(option VlessOption) (*Vless, error) {
|
|||||||
|
|
||||||
v.gunTLSConfig = tlsConfig
|
v.gunTLSConfig = tlsConfig
|
||||||
v.gunConfig = gunConfig
|
v.gunConfig = gunConfig
|
||||||
|
if v.isXTLSEnabled() {
|
||||||
v.transport = gun.NewHTTP2Client(dialFn, tlsConfig, v.option.ClientFingerprint, v.realityConfig)
|
v.transport = gun.NewHTTP2XTLSClient(dialFn, tlsConfig)
|
||||||
|
} else {
|
||||||
|
v.transport = gun.NewHTTP2Client(dialFn, tlsConfig)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return v, nil
|
return v, nil
|
||||||
|
@ -9,23 +9,15 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
|
||||||
|
|
||||||
N "github.com/Dreamacro/clash/common/net"
|
"github.com/Dreamacro/clash/common/convert"
|
||||||
"github.com/Dreamacro/clash/component/dialer"
|
"github.com/Dreamacro/clash/component/dialer"
|
||||||
"github.com/Dreamacro/clash/component/resolver"
|
"github.com/Dreamacro/clash/component/resolver"
|
||||||
tlsC "github.com/Dreamacro/clash/component/tls"
|
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
"github.com/Dreamacro/clash/transport/gun"
|
"github.com/Dreamacro/clash/transport/gun"
|
||||||
clashVMess "github.com/Dreamacro/clash/transport/vmess"
|
"github.com/Dreamacro/clash/transport/vmess"
|
||||||
|
|
||||||
vmess "github.com/sagernet/sing-vmess"
|
|
||||||
"github.com/sagernet/sing-vmess/packetaddr"
|
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var ErrUDPRemoteAddrMismatch = errors.New("udp packet dropped due to mismatched remote address")
|
|
||||||
|
|
||||||
type Vmess struct {
|
type Vmess struct {
|
||||||
*Base
|
*Base
|
||||||
client *vmess.Client
|
client *vmess.Client
|
||||||
@ -35,8 +27,6 @@ type Vmess struct {
|
|||||||
gunTLSConfig *tls.Config
|
gunTLSConfig *tls.Config
|
||||||
gunConfig *gun.Config
|
gunConfig *gun.Config
|
||||||
transport *gun.TransportWrap
|
transport *gun.TransportWrap
|
||||||
|
|
||||||
realityConfig *tlsC.RealityConfig
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type VmessOption struct {
|
type VmessOption struct {
|
||||||
@ -51,19 +41,15 @@ type VmessOption struct {
|
|||||||
Network string `proxy:"network,omitempty"`
|
Network string `proxy:"network,omitempty"`
|
||||||
TLS bool `proxy:"tls,omitempty"`
|
TLS bool `proxy:"tls,omitempty"`
|
||||||
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
|
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
|
||||||
Fingerprint string `proxy:"fingerprint,omitempty"`
|
|
||||||
ServerName string `proxy:"servername,omitempty"`
|
ServerName string `proxy:"servername,omitempty"`
|
||||||
RealityOpts RealityOptions `proxy:"reality-opts,omitempty"`
|
|
||||||
HTTPOpts HTTPOptions `proxy:"http-opts,omitempty"`
|
HTTPOpts HTTPOptions `proxy:"http-opts,omitempty"`
|
||||||
HTTP2Opts HTTP2Options `proxy:"h2-opts,omitempty"`
|
HTTP2Opts HTTP2Options `proxy:"h2-opts,omitempty"`
|
||||||
GrpcOpts GrpcOptions `proxy:"grpc-opts,omitempty"`
|
GrpcOpts GrpcOptions `proxy:"grpc-opts,omitempty"`
|
||||||
WSOpts WSOptions `proxy:"ws-opts,omitempty"`
|
WSOpts WSOptions `proxy:"ws-opts,omitempty"`
|
||||||
PacketAddr bool `proxy:"packet-addr,omitempty"`
|
|
||||||
XUDP bool `proxy:"xudp,omitempty"`
|
// TODO: compatible with VMESS WS older version configurations
|
||||||
PacketEncoding string `proxy:"packet-encoding,omitempty"`
|
WSHeaders map[string]string `proxy:"ws-headers,omitempty"`
|
||||||
GlobalPadding bool `proxy:"global-padding,omitempty"`
|
WSPath string `proxy:"ws-path,omitempty"`
|
||||||
AuthenticatedLength bool `proxy:"authenticated-length,omitempty"`
|
|
||||||
ClientFingerprint string `proxy:"client-fingerprint,omitempty"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type HTTPOptions struct {
|
type HTTPOptions struct {
|
||||||
@ -91,22 +77,17 @@ type WSOptions struct {
|
|||||||
// StreamConn implements C.ProxyAdapter
|
// StreamConn implements C.ProxyAdapter
|
||||||
func (v *Vmess) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
func (v *Vmess) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
if tlsC.HaveGlobalFingerprint() && (len(v.option.ClientFingerprint) == 0) {
|
|
||||||
v.option.ClientFingerprint = tlsC.GetGlobalFingerprint()
|
|
||||||
}
|
|
||||||
|
|
||||||
switch v.option.Network {
|
switch v.option.Network {
|
||||||
case "ws":
|
case "ws":
|
||||||
|
|
||||||
host, port, _ := net.SplitHostPort(v.addr)
|
host, port, _ := net.SplitHostPort(v.addr)
|
||||||
wsOpts := &clashVMess.WebsocketConfig{
|
wsOpts := &vmess.WebsocketConfig{
|
||||||
Host: host,
|
Host: host,
|
||||||
Port: port,
|
Port: port,
|
||||||
Path: v.option.WSOpts.Path,
|
Path: v.option.WSOpts.Path,
|
||||||
MaxEarlyData: v.option.WSOpts.MaxEarlyData,
|
MaxEarlyData: v.option.WSOpts.MaxEarlyData,
|
||||||
EarlyDataHeaderName: v.option.WSOpts.EarlyDataHeaderName,
|
EarlyDataHeaderName: v.option.WSOpts.EarlyDataHeaderName,
|
||||||
ClientFingerprint: v.option.ClientFingerprint,
|
Headers: make(http.Header),
|
||||||
Headers: http.Header{},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(v.option.WSOpts.Headers) != 0 {
|
if len(v.option.WSOpts.Headers) != 0 {
|
||||||
@ -117,126 +98,99 @@ func (v *Vmess) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
|||||||
|
|
||||||
if v.option.TLS {
|
if v.option.TLS {
|
||||||
wsOpts.TLS = true
|
wsOpts.TLS = true
|
||||||
tlsConfig := &tls.Config{
|
wsOpts.TLSConfig = &tls.Config{
|
||||||
ServerName: host,
|
ServerName: host,
|
||||||
InsecureSkipVerify: v.option.SkipCertVerify,
|
InsecureSkipVerify: v.option.SkipCertVerify,
|
||||||
NextProtos: []string{"http/1.1"},
|
NextProtos: []string{"http/1.1"},
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(v.option.Fingerprint) == 0 {
|
|
||||||
wsOpts.TLSConfig = tlsC.GetGlobalTLSConfig(tlsConfig)
|
|
||||||
} else {
|
|
||||||
if wsOpts.TLSConfig, err = tlsC.GetSpecifiedFingerprintTLSConfig(tlsConfig, v.option.Fingerprint); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if v.option.ServerName != "" {
|
if v.option.ServerName != "" {
|
||||||
wsOpts.TLSConfig.ServerName = v.option.ServerName
|
wsOpts.TLSConfig.ServerName = v.option.ServerName
|
||||||
} else if host := wsOpts.Headers.Get("Host"); host != "" {
|
} else if host := wsOpts.Headers.Get("Host"); host != "" {
|
||||||
wsOpts.TLSConfig.ServerName = host
|
wsOpts.TLSConfig.ServerName = host
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
wsOpts.Headers.Set("Host", convert.RandHost())
|
||||||
|
convert.SetUserAgent(wsOpts.Headers)
|
||||||
}
|
}
|
||||||
c, err = clashVMess.StreamWebsocketConn(c, wsOpts)
|
c, err = vmess.StreamWebsocketConn(c, wsOpts)
|
||||||
case "http":
|
case "http":
|
||||||
// readability first, so just copy default TLS logic
|
// readability first, so just copy default TLS logic
|
||||||
if v.option.TLS {
|
if v.option.TLS {
|
||||||
host, _, _ := net.SplitHostPort(v.addr)
|
host, _, _ := net.SplitHostPort(v.addr)
|
||||||
tlsOpts := &clashVMess.TLSConfig{
|
tlsOpts := &vmess.TLSConfig{
|
||||||
Host: host,
|
Host: host,
|
||||||
SkipCertVerify: v.option.SkipCertVerify,
|
SkipCertVerify: v.option.SkipCertVerify,
|
||||||
ClientFingerprint: v.option.ClientFingerprint,
|
|
||||||
Reality: v.realityConfig,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if v.option.ServerName != "" {
|
if v.option.ServerName != "" {
|
||||||
tlsOpts.Host = v.option.ServerName
|
tlsOpts.Host = v.option.ServerName
|
||||||
}
|
}
|
||||||
c, err = clashVMess.StreamTLSConn(c, tlsOpts)
|
|
||||||
|
c, err = vmess.StreamTLSConn(c, tlsOpts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
http.Header(v.option.HTTPOpts.Headers).Set("Host", convert.RandHost())
|
||||||
|
convert.SetUserAgent(v.option.HTTPOpts.Headers)
|
||||||
}
|
}
|
||||||
|
|
||||||
host, _, _ := net.SplitHostPort(v.addr)
|
host, _, _ := net.SplitHostPort(v.addr)
|
||||||
httpOpts := &clashVMess.HTTPConfig{
|
httpOpts := &vmess.HTTPConfig{
|
||||||
Host: host,
|
Host: host,
|
||||||
Method: v.option.HTTPOpts.Method,
|
Method: v.option.HTTPOpts.Method,
|
||||||
Path: v.option.HTTPOpts.Path,
|
Path: v.option.HTTPOpts.Path,
|
||||||
Headers: v.option.HTTPOpts.Headers,
|
Headers: v.option.HTTPOpts.Headers,
|
||||||
}
|
}
|
||||||
|
|
||||||
c = clashVMess.StreamHTTPConn(c, httpOpts)
|
c = vmess.StreamHTTPConn(c, httpOpts)
|
||||||
case "h2":
|
case "h2":
|
||||||
host, _, _ := net.SplitHostPort(v.addr)
|
host, _, _ := net.SplitHostPort(v.addr)
|
||||||
tlsOpts := clashVMess.TLSConfig{
|
tlsOpts := vmess.TLSConfig{
|
||||||
Host: host,
|
Host: host,
|
||||||
SkipCertVerify: v.option.SkipCertVerify,
|
SkipCertVerify: v.option.SkipCertVerify,
|
||||||
NextProtos: []string{"h2"},
|
NextProtos: []string{"h2"},
|
||||||
ClientFingerprint: v.option.ClientFingerprint,
|
|
||||||
Reality: v.realityConfig,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if v.option.ServerName != "" {
|
if v.option.ServerName != "" {
|
||||||
tlsOpts.Host = v.option.ServerName
|
tlsOpts.Host = v.option.ServerName
|
||||||
}
|
}
|
||||||
|
|
||||||
c, err = clashVMess.StreamTLSConn(c, &tlsOpts)
|
c, err = vmess.StreamTLSConn(c, &tlsOpts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
h2Opts := &clashVMess.H2Config{
|
h2Opts := &vmess.H2Config{
|
||||||
Hosts: v.option.HTTP2Opts.Host,
|
Hosts: v.option.HTTP2Opts.Host,
|
||||||
Path: v.option.HTTP2Opts.Path,
|
Path: v.option.HTTP2Opts.Path,
|
||||||
}
|
}
|
||||||
|
|
||||||
c, err = clashVMess.StreamH2Conn(c, h2Opts)
|
c, err = vmess.StreamH2Conn(c, h2Opts)
|
||||||
case "grpc":
|
case "grpc":
|
||||||
c, err = gun.StreamGunWithConn(c, v.gunTLSConfig, v.gunConfig, v.realityConfig)
|
c, err = gun.StreamGunWithConn(c, v.gunTLSConfig, v.gunConfig)
|
||||||
default:
|
default:
|
||||||
// handle TLS
|
// handle TLS
|
||||||
if v.option.TLS {
|
if v.option.TLS {
|
||||||
host, _, _ := net.SplitHostPort(v.addr)
|
host, _, _ := net.SplitHostPort(v.addr)
|
||||||
tlsOpts := &clashVMess.TLSConfig{
|
tlsOpts := &vmess.TLSConfig{
|
||||||
Host: host,
|
Host: host,
|
||||||
SkipCertVerify: v.option.SkipCertVerify,
|
SkipCertVerify: v.option.SkipCertVerify,
|
||||||
ClientFingerprint: v.option.ClientFingerprint,
|
|
||||||
Reality: v.realityConfig,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if v.option.ServerName != "" {
|
if v.option.ServerName != "" {
|
||||||
tlsOpts.Host = v.option.ServerName
|
tlsOpts.Host = v.option.ServerName
|
||||||
}
|
}
|
||||||
|
|
||||||
c, err = clashVMess.StreamTLSConn(c, tlsOpts)
|
c, err = vmess.StreamTLSConn(c, tlsOpts)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if metadata.NetWork == C.UDP {
|
|
||||||
if v.option.XUDP {
|
return v.client.StreamConn(c, parseVmessAddr(metadata))
|
||||||
if N.NeedHandshake(c) {
|
|
||||||
return v.client.DialEarlyXUDPPacketConn(c, M.ParseSocksaddr(metadata.RemoteAddress())), nil
|
|
||||||
} else {
|
|
||||||
return v.client.DialXUDPPacketConn(c, M.ParseSocksaddr(metadata.RemoteAddress()))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if N.NeedHandshake(c) {
|
|
||||||
return v.client.DialEarlyPacketConn(c, M.ParseSocksaddr(metadata.RemoteAddress())), nil
|
|
||||||
} else {
|
|
||||||
return v.client.DialPacketConn(c, M.ParseSocksaddr(metadata.RemoteAddress()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if N.NeedHandshake(c) {
|
|
||||||
return v.client.DialEarlyConn(c, M.ParseSocksaddr(metadata.RemoteAddress())), nil
|
|
||||||
} else {
|
|
||||||
return v.client.DialConn(c, M.ParseSocksaddr(metadata.RemoteAddress()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// DialContext implements C.ProxyAdapter
|
// DialContext implements C.ProxyAdapter
|
||||||
@ -247,30 +201,22 @@ func (v *Vmess) DialContext(ctx context.Context, metadata *C.Metadata, opts ...d
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
defer func(c net.Conn) {
|
defer safeConnClose(c, err)
|
||||||
safeConnClose(c, err)
|
|
||||||
}(c)
|
|
||||||
|
|
||||||
c, err = v.client.DialConn(c, M.ParseSocksaddr(metadata.RemoteAddress()))
|
c, err = v.client.StreamConn(c, parseVmessAddr(metadata))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return NewConn(c, v), nil
|
return NewConn(c, v), nil
|
||||||
}
|
}
|
||||||
return v.DialContextWithDialer(ctx, dialer.NewDialer(v.Base.DialOptions(opts...)...), metadata)
|
|
||||||
}
|
|
||||||
|
|
||||||
// DialContextWithDialer implements C.ProxyAdapter
|
c, err := dialer.DialContext(ctx, "tcp", v.addr, v.Base.DialOptions(opts...)...)
|
||||||
func (v *Vmess) DialContextWithDialer(ctx context.Context, dialer C.Dialer, metadata *C.Metadata) (_ C.Conn, err error) {
|
|
||||||
c, err := dialer.DialContext(ctx, "tcp", v.addr)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error())
|
return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error())
|
||||||
}
|
}
|
||||||
tcpKeepAlive(c)
|
tcpKeepAlive(c)
|
||||||
defer func(c net.Conn) {
|
defer safeConnClose(c, err)
|
||||||
safeConnClose(c, err)
|
|
||||||
}(c)
|
|
||||||
|
|
||||||
c, err = v.StreamConn(c, metadata)
|
c, err = v.StreamConn(c, metadata)
|
||||||
return NewConn(c, v), err
|
return NewConn(c, v), err
|
||||||
@ -278,22 +224,15 @@ func (v *Vmess) DialContextWithDialer(ctx context.Context, dialer C.Dialer, meta
|
|||||||
|
|
||||||
// ListenPacketContext implements C.ProxyAdapter
|
// ListenPacketContext implements C.ProxyAdapter
|
||||||
func (v *Vmess) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.PacketConn, err error) {
|
func (v *Vmess) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.PacketConn, err error) {
|
||||||
// vmess use stream-oriented udp with a special address, so we need a net.UDPAddr
|
// vmess use stream-oriented udp with a special address, so we needs a net.UDPAddr
|
||||||
if !metadata.Resolved() {
|
if !metadata.Resolved() {
|
||||||
ip, err := resolver.ResolveIP(ctx, metadata.Host)
|
ip, err := resolver.ResolveIP(metadata.Host)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.New("can't resolve ip")
|
return nil, errors.New("can't resolve ip")
|
||||||
}
|
}
|
||||||
metadata.DstIP = ip
|
metadata.DstIP = ip
|
||||||
}
|
}
|
||||||
|
|
||||||
if v.option.PacketAddr {
|
|
||||||
_metadata := *metadata // make a copy
|
|
||||||
metadata = &_metadata
|
|
||||||
metadata.Host = packetaddr.SeqPacketMagicAddress
|
|
||||||
metadata.DstPort = "443"
|
|
||||||
}
|
|
||||||
|
|
||||||
var c net.Conn
|
var c net.Conn
|
||||||
// gun transport
|
// gun transport
|
||||||
if v.transport != nil && len(opts) == 0 {
|
if v.transport != nil && len(opts) == 0 {
|
||||||
@ -301,71 +240,29 @@ func (v *Vmess) ListenPacketContext(ctx context.Context, metadata *C.Metadata, o
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
defer func(c net.Conn) {
|
defer safeConnClose(c, err)
|
||||||
safeConnClose(c, err)
|
|
||||||
}(c)
|
|
||||||
|
|
||||||
if v.option.XUDP {
|
c, err = v.client.StreamConn(c, parseVmessAddr(metadata))
|
||||||
if N.NeedHandshake(c) {
|
|
||||||
c = v.client.DialEarlyXUDPPacketConn(c, M.ParseSocksaddr(metadata.RemoteAddress()))
|
|
||||||
} else {
|
} else {
|
||||||
c, err = v.client.DialXUDPPacketConn(c, M.ParseSocksaddr(metadata.RemoteAddress()))
|
c, err = dialer.DialContext(ctx, "tcp", v.addr, v.Base.DialOptions(opts...)...)
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if N.NeedHandshake(c) {
|
|
||||||
c = v.client.DialEarlyPacketConn(c, M.ParseSocksaddr(metadata.RemoteAddress()))
|
|
||||||
} else {
|
|
||||||
c, err = v.client.DialPacketConn(c, M.ParseSocksaddr(metadata.RemoteAddress()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("new vmess client error: %v", err)
|
|
||||||
}
|
|
||||||
return v.ListenPacketOnStreamConn(c, metadata)
|
|
||||||
}
|
|
||||||
return v.ListenPacketWithDialer(ctx, dialer.NewDialer(v.Base.DialOptions(opts...)...), metadata)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListenPacketWithDialer implements C.ProxyAdapter
|
|
||||||
func (v *Vmess) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, metadata *C.Metadata) (_ C.PacketConn, err error) {
|
|
||||||
// vmess use stream-oriented udp with a special address, so we need a net.UDPAddr
|
|
||||||
if !metadata.Resolved() {
|
|
||||||
ip, err := resolver.ResolveIP(ctx, metadata.Host)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.New("can't resolve ip")
|
|
||||||
}
|
|
||||||
metadata.DstIP = ip
|
|
||||||
}
|
|
||||||
|
|
||||||
c, err := dialer.DialContext(ctx, "tcp", v.addr)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error())
|
return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error())
|
||||||
}
|
}
|
||||||
tcpKeepAlive(c)
|
tcpKeepAlive(c)
|
||||||
defer func(c net.Conn) {
|
defer safeConnClose(c, err)
|
||||||
safeConnClose(c, err)
|
|
||||||
}(c)
|
|
||||||
|
|
||||||
c, err = v.StreamConn(c, metadata)
|
c, err = v.StreamConn(c, metadata)
|
||||||
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("new vmess client error: %v", err)
|
return nil, fmt.Errorf("new vmess client error: %v", err)
|
||||||
}
|
}
|
||||||
return v.ListenPacketOnStreamConn(c, metadata)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SupportWithDialer implements C.ProxyAdapter
|
return v.ListenPacketOnStreamConn(c, metadata)
|
||||||
func (v *Vmess) SupportWithDialer() bool {
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListenPacketOnStreamConn implements C.ProxyAdapter
|
// ListenPacketOnStreamConn implements C.ProxyAdapter
|
||||||
func (v *Vmess) ListenPacketOnStreamConn(c net.Conn, metadata *C.Metadata) (_ C.PacketConn, err error) {
|
func (v *Vmess) ListenPacketOnStreamConn(c net.Conn, metadata *C.Metadata) (_ C.PacketConn, err error) {
|
||||||
if v.option.PacketAddr {
|
|
||||||
return newPacketConn(&threadSafePacketConn{PacketConn: packetaddr.NewBindConn(c)}, v), nil
|
|
||||||
} else if pc, ok := c.(net.PacketConn); ok {
|
|
||||||
return newPacketConn(&threadSafePacketConn{PacketConn: pc}, v), nil
|
|
||||||
}
|
|
||||||
return newPacketConn(&vmessPacketConn{Conn: c, rAddr: metadata.UDPAddr()}, v), nil
|
return newPacketConn(&vmessPacketConn{Conn: c, rAddr: metadata.UDPAddr()}, v), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -376,32 +273,22 @@ func (v *Vmess) SupportUOT() bool {
|
|||||||
|
|
||||||
func NewVmess(option VmessOption) (*Vmess, error) {
|
func NewVmess(option VmessOption) (*Vmess, error) {
|
||||||
security := strings.ToLower(option.Cipher)
|
security := strings.ToLower(option.Cipher)
|
||||||
var options []vmess.ClientOption
|
client, err := vmess.NewClient(vmess.Config{
|
||||||
if option.GlobalPadding {
|
UUID: option.UUID,
|
||||||
options = append(options, vmess.ClientWithGlobalPadding())
|
AlterID: uint16(option.AlterID),
|
||||||
}
|
Security: security,
|
||||||
if option.AuthenticatedLength {
|
HostName: option.Server,
|
||||||
options = append(options, vmess.ClientWithAuthenticatedLength())
|
Port: strconv.Itoa(option.Port),
|
||||||
}
|
IsAead: option.AlterID == 0,
|
||||||
client, err := vmess.NewClient(option.UUID, security, option.AlterID, options...)
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
switch option.PacketEncoding {
|
|
||||||
case "packetaddr", "packet":
|
|
||||||
option.PacketAddr = true
|
|
||||||
case "xudp":
|
|
||||||
option.XUDP = true
|
|
||||||
}
|
|
||||||
if option.XUDP {
|
|
||||||
option.PacketAddr = false
|
|
||||||
}
|
|
||||||
|
|
||||||
switch option.Network {
|
switch option.Network {
|
||||||
case "h2", "grpc":
|
case "h2", "grpc":
|
||||||
if !option.TLS {
|
if !option.TLS {
|
||||||
option.TLS = true
|
return nil, fmt.Errorf("TLS must be true with h2/grpc network")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -411,11 +298,8 @@ func NewVmess(option VmessOption) (*Vmess, error) {
|
|||||||
addr: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)),
|
addr: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)),
|
||||||
tp: C.Vmess,
|
tp: C.Vmess,
|
||||||
udp: option.UDP,
|
udp: option.UDP,
|
||||||
xudp: option.XUDP,
|
|
||||||
tfo: option.TFO,
|
|
||||||
iface: option.Interface,
|
iface: option.Interface,
|
||||||
rmark: option.RoutingMark,
|
rmark: option.RoutingMark,
|
||||||
prefer: C.NewDNSPrefer(option.IPVersion),
|
|
||||||
},
|
},
|
||||||
client: client,
|
client: client,
|
||||||
option: &option,
|
option: &option,
|
||||||
@ -439,7 +323,6 @@ func NewVmess(option VmessOption) (*Vmess, error) {
|
|||||||
gunConfig := &gun.Config{
|
gunConfig := &gun.Config{
|
||||||
ServiceName: v.option.GrpcOpts.GrpcServiceName,
|
ServiceName: v.option.GrpcOpts.GrpcServiceName,
|
||||||
Host: v.option.ServerName,
|
Host: v.option.ServerName,
|
||||||
ClientFingerprint: v.option.ClientFingerprint,
|
|
||||||
}
|
}
|
||||||
tlsConfig := &tls.Config{
|
tlsConfig := &tls.Config{
|
||||||
InsecureSkipVerify: v.option.SkipCertVerify,
|
InsecureSkipVerify: v.option.SkipCertVerify,
|
||||||
@ -454,45 +337,46 @@ func NewVmess(option VmessOption) (*Vmess, error) {
|
|||||||
|
|
||||||
v.gunTLSConfig = tlsConfig
|
v.gunTLSConfig = tlsConfig
|
||||||
v.gunConfig = gunConfig
|
v.gunConfig = gunConfig
|
||||||
|
v.transport = gun.NewHTTP2Client(dialFn, tlsConfig)
|
||||||
v.transport = gun.NewHTTP2Client(dialFn, tlsConfig, v.option.ClientFingerprint, v.realityConfig)
|
|
||||||
}
|
|
||||||
|
|
||||||
v.realityConfig, err = v.option.RealityOpts.Parse()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return v, nil
|
return v, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type threadSafePacketConn struct {
|
func parseVmessAddr(metadata *C.Metadata) *vmess.DstAddr {
|
||||||
net.PacketConn
|
var addrType byte
|
||||||
access sync.Mutex
|
var addr []byte
|
||||||
}
|
switch metadata.AddrType {
|
||||||
|
case C.AtypIPv4:
|
||||||
|
addrType = byte(vmess.AtypIPv4)
|
||||||
|
addr = make([]byte, net.IPv4len)
|
||||||
|
copy(addr[:], metadata.DstIP.AsSlice())
|
||||||
|
case C.AtypIPv6:
|
||||||
|
addrType = byte(vmess.AtypIPv6)
|
||||||
|
addr = make([]byte, net.IPv6len)
|
||||||
|
copy(addr[:], metadata.DstIP.AsSlice())
|
||||||
|
case C.AtypDomainName:
|
||||||
|
addrType = byte(vmess.AtypDomainName)
|
||||||
|
addr = make([]byte, len(metadata.Host)+1)
|
||||||
|
addr[0] = byte(len(metadata.Host))
|
||||||
|
copy(addr[1:], []byte(metadata.Host))
|
||||||
|
}
|
||||||
|
|
||||||
func (c *threadSafePacketConn) WriteTo(b []byte, addr net.Addr) (int, error) {
|
port, _ := strconv.ParseUint(metadata.DstPort, 10, 16)
|
||||||
c.access.Lock()
|
return &vmess.DstAddr{
|
||||||
defer c.access.Unlock()
|
UDP: metadata.NetWork == C.UDP,
|
||||||
return c.PacketConn.WriteTo(b, addr)
|
AddrType: addrType,
|
||||||
|
Addr: addr,
|
||||||
|
Port: uint(port),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type vmessPacketConn struct {
|
type vmessPacketConn struct {
|
||||||
net.Conn
|
net.Conn
|
||||||
rAddr net.Addr
|
rAddr net.Addr
|
||||||
access sync.Mutex
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// WriteTo implments C.PacketConn.WriteTo
|
|
||||||
// Since VMess doesn't support full cone NAT by design, we verify if addr matches uc.rAddr, and drop the packet if not.
|
|
||||||
func (uc *vmessPacketConn) WriteTo(b []byte, addr net.Addr) (int, error) {
|
func (uc *vmessPacketConn) WriteTo(b []byte, addr net.Addr) (int, error) {
|
||||||
allowedAddr := uc.rAddr.(*net.UDPAddr)
|
|
||||||
destAddr := addr.(*net.UDPAddr)
|
|
||||||
if !(allowedAddr.IP.Equal(destAddr.IP) && allowedAddr.Port == destAddr.Port) {
|
|
||||||
return 0, ErrUDPRemoteAddrMismatch
|
|
||||||
}
|
|
||||||
uc.access.Lock()
|
|
||||||
defer uc.access.Unlock()
|
|
||||||
return uc.Conn.Write(b)
|
return uc.Conn.Write(b)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,269 +0,0 @@
|
|||||||
package outbound
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"encoding/base64"
|
|
||||||
"encoding/hex"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"net"
|
|
||||||
"net/netip"
|
|
||||||
"runtime"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
CN "github.com/Dreamacro/clash/common/net"
|
|
||||||
"github.com/Dreamacro/clash/component/dialer"
|
|
||||||
"github.com/Dreamacro/clash/component/resolver"
|
|
||||||
C "github.com/Dreamacro/clash/constant"
|
|
||||||
"github.com/Dreamacro/clash/log"
|
|
||||||
|
|
||||||
wireguard "github.com/metacubex/sing-wireguard"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing/common"
|
|
||||||
"github.com/sagernet/sing/common/debug"
|
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
|
||||||
N "github.com/sagernet/sing/common/network"
|
|
||||||
"github.com/sagernet/wireguard-go/device"
|
|
||||||
)
|
|
||||||
|
|
||||||
type WireGuard struct {
|
|
||||||
*Base
|
|
||||||
bind *wireguard.ClientBind
|
|
||||||
device *device.Device
|
|
||||||
tunDevice wireguard.Device
|
|
||||||
dialer *wgSingDialer
|
|
||||||
startOnce sync.Once
|
|
||||||
startErr error
|
|
||||||
}
|
|
||||||
|
|
||||||
type WireGuardOption struct {
|
|
||||||
BasicOption
|
|
||||||
Name string `proxy:"name"`
|
|
||||||
Server string `proxy:"server"`
|
|
||||||
Port int `proxy:"port"`
|
|
||||||
Ip string `proxy:"ip,omitempty"`
|
|
||||||
Ipv6 string `proxy:"ipv6,omitempty"`
|
|
||||||
PrivateKey string `proxy:"private-key"`
|
|
||||||
PublicKey string `proxy:"public-key"`
|
|
||||||
PreSharedKey string `proxy:"pre-shared-key,omitempty"`
|
|
||||||
Reserved []uint8 `proxy:"reserved,omitempty"`
|
|
||||||
Workers int `proxy:"workers,omitempty"`
|
|
||||||
MTU int `proxy:"mtu,omitempty"`
|
|
||||||
UDP bool `proxy:"udp,omitempty"`
|
|
||||||
PersistentKeepalive int `proxy:"persistent-keepalive,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type wgSingDialer struct {
|
|
||||||
dialer dialer.Dialer
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ N.Dialer = &wgSingDialer{}
|
|
||||||
|
|
||||||
func (d *wgSingDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
|
|
||||||
return d.dialer.DialContext(ctx, network, destination.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *wgSingDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
|
|
||||||
return d.dialer.ListenPacket(ctx, "udp", "", destination.AddrPort())
|
|
||||||
}
|
|
||||||
|
|
||||||
type wgNetDialer struct {
|
|
||||||
tunDevice wireguard.Device
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ dialer.NetDialer = &wgNetDialer{}
|
|
||||||
|
|
||||||
func (d wgNetDialer) DialContext(ctx context.Context, network, address string) (net.Conn, error) {
|
|
||||||
return d.tunDevice.DialContext(ctx, network, M.ParseSocksaddr(address).Unwrap())
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewWireGuard(option WireGuardOption) (*WireGuard, error) {
|
|
||||||
outbound := &WireGuard{
|
|
||||||
Base: &Base{
|
|
||||||
name: option.Name,
|
|
||||||
addr: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)),
|
|
||||||
tp: C.WireGuard,
|
|
||||||
udp: option.UDP,
|
|
||||||
iface: option.Interface,
|
|
||||||
rmark: option.RoutingMark,
|
|
||||||
prefer: C.NewDNSPrefer(option.IPVersion),
|
|
||||||
},
|
|
||||||
dialer: &wgSingDialer{dialer: dialer.NewDialer()},
|
|
||||||
}
|
|
||||||
runtime.SetFinalizer(outbound, closeWireGuard)
|
|
||||||
|
|
||||||
var reserved [3]uint8
|
|
||||||
if len(option.Reserved) > 0 {
|
|
||||||
if len(option.Reserved) != 3 {
|
|
||||||
return nil, E.New("invalid reserved value, required 3 bytes, got ", len(option.Reserved))
|
|
||||||
}
|
|
||||||
reserved[0] = uint8(option.Reserved[0])
|
|
||||||
reserved[1] = uint8(option.Reserved[1])
|
|
||||||
reserved[2] = uint8(option.Reserved[2])
|
|
||||||
}
|
|
||||||
peerAddr := M.ParseSocksaddrHostPort(option.Server, uint16(option.Port))
|
|
||||||
outbound.bind = wireguard.NewClientBind(context.Background(), outbound.dialer, peerAddr, reserved)
|
|
||||||
localPrefixes := make([]netip.Prefix, 0, 2)
|
|
||||||
if len(option.Ip) > 0 {
|
|
||||||
if !strings.Contains(option.Ip, "/") {
|
|
||||||
option.Ip = option.Ip + "/32"
|
|
||||||
}
|
|
||||||
if prefix, err := netip.ParsePrefix(option.Ip); err == nil {
|
|
||||||
localPrefixes = append(localPrefixes, prefix)
|
|
||||||
} else {
|
|
||||||
return nil, E.Cause(err, "ip address parse error")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(option.Ipv6) > 0 {
|
|
||||||
if !strings.Contains(option.Ipv6, "/") {
|
|
||||||
option.Ipv6 = option.Ipv6 + "/128"
|
|
||||||
}
|
|
||||||
if prefix, err := netip.ParsePrefix(option.Ipv6); err == nil {
|
|
||||||
localPrefixes = append(localPrefixes, prefix)
|
|
||||||
} else {
|
|
||||||
return nil, E.Cause(err, "ipv6 address parse error")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(localPrefixes) == 0 {
|
|
||||||
return nil, E.New("missing local address")
|
|
||||||
}
|
|
||||||
var privateKey, peerPublicKey, preSharedKey string
|
|
||||||
{
|
|
||||||
bytes, err := base64.StdEncoding.DecodeString(option.PrivateKey)
|
|
||||||
if err != nil {
|
|
||||||
return nil, E.Cause(err, "decode private key")
|
|
||||||
}
|
|
||||||
privateKey = hex.EncodeToString(bytes)
|
|
||||||
}
|
|
||||||
{
|
|
||||||
bytes, err := base64.StdEncoding.DecodeString(option.PublicKey)
|
|
||||||
if err != nil {
|
|
||||||
return nil, E.Cause(err, "decode peer public key")
|
|
||||||
}
|
|
||||||
peerPublicKey = hex.EncodeToString(bytes)
|
|
||||||
}
|
|
||||||
if option.PreSharedKey != "" {
|
|
||||||
bytes, err := base64.StdEncoding.DecodeString(option.PreSharedKey)
|
|
||||||
if err != nil {
|
|
||||||
return nil, E.Cause(err, "decode pre shared key")
|
|
||||||
}
|
|
||||||
preSharedKey = hex.EncodeToString(bytes)
|
|
||||||
}
|
|
||||||
ipcConf := "private_key=" + privateKey
|
|
||||||
ipcConf += "\npublic_key=" + peerPublicKey
|
|
||||||
ipcConf += "\nendpoint=" + peerAddr.String()
|
|
||||||
if preSharedKey != "" {
|
|
||||||
ipcConf += "\npreshared_key=" + preSharedKey
|
|
||||||
}
|
|
||||||
var has4, has6 bool
|
|
||||||
for _, address := range localPrefixes {
|
|
||||||
if address.Addr().Is4() {
|
|
||||||
has4 = true
|
|
||||||
} else {
|
|
||||||
has6 = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if has4 {
|
|
||||||
ipcConf += "\nallowed_ip=0.0.0.0/0"
|
|
||||||
}
|
|
||||||
if has6 {
|
|
||||||
ipcConf += "\nallowed_ip=::/0"
|
|
||||||
}
|
|
||||||
if option.PersistentKeepalive != 0 {
|
|
||||||
ipcConf += fmt.Sprintf("\npersistent_keepalive_interval=%d", option.PersistentKeepalive)
|
|
||||||
}
|
|
||||||
mtu := option.MTU
|
|
||||||
if mtu == 0 {
|
|
||||||
mtu = 1408
|
|
||||||
}
|
|
||||||
var err error
|
|
||||||
outbound.tunDevice, err = wireguard.NewStackDevice(localPrefixes, uint32(mtu))
|
|
||||||
if err != nil {
|
|
||||||
return nil, E.Cause(err, "create WireGuard device")
|
|
||||||
}
|
|
||||||
outbound.device = device.NewDevice(outbound.tunDevice, outbound.bind, &device.Logger{
|
|
||||||
Verbosef: func(format string, args ...interface{}) {
|
|
||||||
log.SingLogger.Debug(fmt.Sprintf(strings.ToLower(format), args...))
|
|
||||||
},
|
|
||||||
Errorf: func(format string, args ...interface{}) {
|
|
||||||
log.SingLogger.Error(fmt.Sprintf(strings.ToLower(format), args...))
|
|
||||||
},
|
|
||||||
}, option.Workers)
|
|
||||||
if debug.Enabled {
|
|
||||||
log.SingLogger.Trace("created wireguard ipc conf: \n", ipcConf)
|
|
||||||
}
|
|
||||||
err = outbound.device.IpcSet(ipcConf)
|
|
||||||
if err != nil {
|
|
||||||
return nil, E.Cause(err, "setup wireguard")
|
|
||||||
}
|
|
||||||
//err = outbound.tunDevice.Start()
|
|
||||||
return outbound, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func closeWireGuard(w *WireGuard) {
|
|
||||||
if w.device != nil {
|
|
||||||
w.device.Close()
|
|
||||||
}
|
|
||||||
_ = common.Close(w.tunDevice)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *WireGuard) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) {
|
|
||||||
options := w.Base.DialOptions(opts...)
|
|
||||||
w.dialer.dialer = dialer.NewDialer(options...)
|
|
||||||
var conn net.Conn
|
|
||||||
w.startOnce.Do(func() {
|
|
||||||
w.startErr = w.tunDevice.Start()
|
|
||||||
})
|
|
||||||
if w.startErr != nil {
|
|
||||||
return nil, w.startErr
|
|
||||||
}
|
|
||||||
if !metadata.Resolved() {
|
|
||||||
options = append(options, dialer.WithResolver(resolver.DefaultResolver))
|
|
||||||
options = append(options, dialer.WithNetDialer(wgNetDialer{tunDevice: w.tunDevice}))
|
|
||||||
conn, err = dialer.NewDialer(options...).DialContext(ctx, "tcp", metadata.RemoteAddress())
|
|
||||||
} else {
|
|
||||||
port, _ := strconv.Atoi(metadata.DstPort)
|
|
||||||
conn, err = w.tunDevice.DialContext(ctx, "tcp", M.SocksaddrFrom(metadata.DstIP, uint16(port)).Unwrap())
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if conn == nil {
|
|
||||||
return nil, E.New("conn is nil")
|
|
||||||
}
|
|
||||||
return NewConn(CN.NewRefConn(conn, w), w), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *WireGuard) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.PacketConn, err error) {
|
|
||||||
options := w.Base.DialOptions(opts...)
|
|
||||||
w.dialer.dialer = dialer.NewDialer(options...)
|
|
||||||
var pc net.PacketConn
|
|
||||||
w.startOnce.Do(func() {
|
|
||||||
w.startErr = w.tunDevice.Start()
|
|
||||||
})
|
|
||||||
if w.startErr != nil {
|
|
||||||
return nil, w.startErr
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if !metadata.Resolved() {
|
|
||||||
ip, err := resolver.ResolveIP(ctx, metadata.Host)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.New("can't resolve ip")
|
|
||||||
}
|
|
||||||
metadata.DstIP = ip
|
|
||||||
}
|
|
||||||
port, _ := strconv.Atoi(metadata.DstPort)
|
|
||||||
pc, err = w.tunDevice.ListenPacket(ctx, M.SocksaddrFrom(metadata.DstIP, uint16(port)).Unwrap())
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if pc == nil {
|
|
||||||
return nil, E.New("packetConn is nil")
|
|
||||||
}
|
|
||||||
return newPacketConn(CN.NewRefPacketConn(pc, w), w), nil
|
|
||||||
}
|
|
@ -7,8 +7,6 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/adapter/outbound"
|
"github.com/Dreamacro/clash/adapter/outbound"
|
||||||
"github.com/Dreamacro/clash/common/callback"
|
|
||||||
N "github.com/Dreamacro/clash/common/net"
|
|
||||||
"github.com/Dreamacro/clash/component/dialer"
|
"github.com/Dreamacro/clash/component/dialer"
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
"github.com/Dreamacro/clash/constant/provider"
|
"github.com/Dreamacro/clash/constant/provider"
|
||||||
@ -32,21 +30,9 @@ func (f *Fallback) DialContext(ctx context.Context, metadata *C.Metadata, opts .
|
|||||||
c, err := proxy.DialContext(ctx, metadata, f.Base.DialOptions(opts...)...)
|
c, err := proxy.DialContext(ctx, metadata, f.Base.DialOptions(opts...)...)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
c.AppendToChains(f)
|
c.AppendToChains(f)
|
||||||
} else {
|
|
||||||
f.onDialFailed(proxy.Type(), err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if N.NeedHandshake(c) {
|
|
||||||
c = &callback.FirstWriteCallBackConn{
|
|
||||||
Conn: c,
|
|
||||||
Callback: func(err error) {
|
|
||||||
if err == nil {
|
|
||||||
f.onDialSuccess()
|
f.onDialSuccess()
|
||||||
} else {
|
} else {
|
||||||
f.onDialFailed(proxy.Type(), err)
|
f.onDialFailed()
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return c, err
|
return c, err
|
||||||
@ -87,30 +73,24 @@ func (f *Fallback) MarshalJSON() ([]byte, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Unwrap implements C.ProxyAdapter
|
// Unwrap implements C.ProxyAdapter
|
||||||
func (f *Fallback) Unwrap(metadata *C.Metadata, touch bool) C.Proxy {
|
func (f *Fallback) Unwrap(metadata *C.Metadata) C.Proxy {
|
||||||
proxy := f.findAliveProxy(touch)
|
proxy := f.findAliveProxy(true)
|
||||||
return proxy
|
return proxy
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Fallback) findAliveProxy(touch bool) C.Proxy {
|
func (f *Fallback) findAliveProxy(touch bool) C.Proxy {
|
||||||
proxies := f.GetProxies(touch)
|
proxies := f.GetProxies(touch)
|
||||||
for _, proxy := range proxies {
|
al := proxies[0]
|
||||||
if len(f.selected) == 0 {
|
for i := len(proxies) - 1; i > -1; i-- {
|
||||||
if proxy.Alive() {
|
proxy := proxies[i]
|
||||||
|
if proxy.Name() == f.selected && proxy.Alive() {
|
||||||
return proxy
|
return proxy
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
if proxy.Name() == f.selected {
|
|
||||||
if proxy.Alive() {
|
if proxy.Alive() {
|
||||||
return proxy
|
al = proxy
|
||||||
} else {
|
|
||||||
f.selected = ""
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
return al
|
||||||
}
|
|
||||||
|
|
||||||
return proxies[0]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Fallback) Set(name string) error {
|
func (f *Fallback) Set(name string) error {
|
||||||
@ -146,8 +126,6 @@ func NewFallback(option *GroupCommonOption, providers []provider.ProxyProvider)
|
|||||||
RoutingMark: option.RoutingMark,
|
RoutingMark: option.RoutingMark,
|
||||||
},
|
},
|
||||||
option.Filter,
|
option.Filter,
|
||||||
option.ExcludeFilter,
|
|
||||||
option.ExcludeType,
|
|
||||||
providers,
|
providers,
|
||||||
}),
|
}),
|
||||||
disableUDP: option.DisableUDP,
|
disableUDP: option.DisableUDP,
|
||||||
|
@ -3,192 +3,102 @@ package outboundgroup
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/adapter/outbound"
|
"github.com/Dreamacro/clash/adapter/outbound"
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
"github.com/Dreamacro/clash/constant/provider"
|
|
||||||
types "github.com/Dreamacro/clash/constant/provider"
|
types "github.com/Dreamacro/clash/constant/provider"
|
||||||
|
"github.com/Dreamacro/clash/constant/provider"
|
||||||
"github.com/Dreamacro/clash/log"
|
"github.com/Dreamacro/clash/log"
|
||||||
"github.com/Dreamacro/clash/tunnel"
|
"github.com/Dreamacro/clash/tunnel"
|
||||||
|
|
||||||
"github.com/dlclark/regexp2"
|
"github.com/dlclark/regexp2"
|
||||||
"go.uber.org/atomic"
|
"go.uber.org/atomic"
|
||||||
)
|
)
|
||||||
|
|
||||||
type GroupBase struct {
|
type GroupBase struct {
|
||||||
*outbound.Base
|
*outbound.Base
|
||||||
filterRegs []*regexp2.Regexp
|
filter *regexp2.Regexp
|
||||||
excludeFilterReg *regexp2.Regexp
|
|
||||||
excludeTypeArray []string
|
|
||||||
providers []provider.ProxyProvider
|
providers []provider.ProxyProvider
|
||||||
|
versions sync.Map // map[string]uint
|
||||||
|
proxies sync.Map // map[string][]C.Proxy
|
||||||
failedTestMux sync.Mutex
|
failedTestMux sync.Mutex
|
||||||
failedTimes int
|
failedTimes int
|
||||||
failedTime time.Time
|
failedTime time.Time
|
||||||
failedTesting *atomic.Bool
|
failedTesting *atomic.Bool
|
||||||
proxies [][]C.Proxy
|
|
||||||
versions []atomic.Uint32
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type GroupBaseOption struct {
|
type GroupBaseOption struct {
|
||||||
outbound.BaseOption
|
outbound.BaseOption
|
||||||
filter string
|
filter string
|
||||||
excludeFilter string
|
|
||||||
excludeType string
|
|
||||||
providers []provider.ProxyProvider
|
providers []provider.ProxyProvider
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewGroupBase(opt GroupBaseOption) *GroupBase {
|
func NewGroupBase(opt GroupBaseOption) *GroupBase {
|
||||||
var excludeFilterReg *regexp2.Regexp
|
var filter *regexp2.Regexp = nil
|
||||||
if opt.excludeFilter != "" {
|
|
||||||
excludeFilterReg = regexp2.MustCompile(opt.excludeFilter, 0)
|
|
||||||
}
|
|
||||||
var excludeTypeArray []string
|
|
||||||
if opt.excludeType != "" {
|
|
||||||
excludeTypeArray = strings.Split(opt.excludeType, "|")
|
|
||||||
}
|
|
||||||
|
|
||||||
var filterRegs []*regexp2.Regexp
|
|
||||||
if opt.filter != "" {
|
if opt.filter != "" {
|
||||||
for _, filter := range strings.Split(opt.filter, "`") {
|
filter = regexp2.MustCompile(opt.filter, 0)
|
||||||
filterReg := regexp2.MustCompile(filter, 0)
|
|
||||||
filterRegs = append(filterRegs, filterReg)
|
|
||||||
}
|
}
|
||||||
}
|
return &GroupBase{
|
||||||
|
|
||||||
gb := &GroupBase{
|
|
||||||
Base: outbound.NewBase(opt.BaseOption),
|
Base: outbound.NewBase(opt.BaseOption),
|
||||||
filterRegs: filterRegs,
|
filter: filter,
|
||||||
excludeFilterReg: excludeFilterReg,
|
|
||||||
excludeTypeArray: excludeTypeArray,
|
|
||||||
providers: opt.providers,
|
providers: opt.providers,
|
||||||
failedTesting: atomic.NewBool(false),
|
failedTesting: atomic.NewBool(false),
|
||||||
}
|
}
|
||||||
|
|
||||||
gb.proxies = make([][]C.Proxy, len(opt.providers))
|
|
||||||
gb.versions = make([]atomic.Uint32, len(opt.providers))
|
|
||||||
|
|
||||||
return gb
|
|
||||||
}
|
|
||||||
|
|
||||||
func (gb *GroupBase) Touch() {
|
|
||||||
for _, pd := range gb.providers {
|
|
||||||
pd.Touch()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gb *GroupBase) GetProxies(touch bool) []C.Proxy {
|
func (gb *GroupBase) GetProxies(touch bool) []C.Proxy {
|
||||||
|
if gb.filter == nil {
|
||||||
var proxies []C.Proxy
|
var proxies []C.Proxy
|
||||||
if len(gb.filterRegs) == 0 {
|
|
||||||
for _, pd := range gb.providers {
|
for _, pd := range gb.providers {
|
||||||
if touch {
|
if touch {
|
||||||
pd.Touch()
|
pd.Touch()
|
||||||
}
|
}
|
||||||
proxies = append(proxies, pd.Proxies()...)
|
proxies = append(proxies, pd.Proxies()...)
|
||||||
}
|
}
|
||||||
} else {
|
if len(proxies) == 0 {
|
||||||
for i, pd := range gb.providers {
|
return append(proxies, tunnel.Proxies()["COMPATIBLE"])
|
||||||
|
}
|
||||||
|
return proxies
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, pd := range gb.providers {
|
||||||
if touch {
|
if touch {
|
||||||
pd.Touch()
|
pd.Touch()
|
||||||
}
|
}
|
||||||
|
|
||||||
if pd.VehicleType() == types.Compatible {
|
if pd.VehicleType() == types.Compatible {
|
||||||
gb.versions[i].Store(pd.Version())
|
gb.proxies.Store(pd.Name(), pd.Proxies())
|
||||||
gb.proxies[i] = pd.Proxies()
|
gb.versions.Store(pd.Name(), pd.Version())
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
version := gb.versions[i].Load()
|
if version, ok := gb.versions.Load(pd.Name()); !ok || version != pd.Version() {
|
||||||
if version != pd.Version() && gb.versions[i].CompareAndSwap(version, pd.Version()) {
|
|
||||||
var (
|
var (
|
||||||
proxies []C.Proxy
|
proxies []C.Proxy
|
||||||
newProxies []C.Proxy
|
newProxies []C.Proxy
|
||||||
)
|
)
|
||||||
|
|
||||||
proxies = pd.Proxies()
|
proxies = pd.Proxies()
|
||||||
proxiesSet := map[string]struct{}{}
|
|
||||||
for _, filterReg := range gb.filterRegs {
|
|
||||||
for _, p := range proxies {
|
for _, p := range proxies {
|
||||||
name := p.Name()
|
if mat, _ := gb.filter.FindStringMatch(p.Name()); mat != nil {
|
||||||
if mat, _ := filterReg.FindStringMatch(name); mat != nil {
|
|
||||||
if _, ok := proxiesSet[name]; !ok {
|
|
||||||
proxiesSet[name] = struct{}{}
|
|
||||||
newProxies = append(newProxies, p)
|
newProxies = append(newProxies, p)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
gb.proxies[i] = newProxies
|
gb.proxies.Store(pd.Name(), newProxies)
|
||||||
|
gb.versions.Store(pd.Name(), pd.Version())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
var proxies []C.Proxy
|
||||||
for _, p := range gb.proxies {
|
gb.proxies.Range(func(key, value any) bool {
|
||||||
proxies = append(proxies, p...)
|
proxies = append(proxies, value.([]C.Proxy)...)
|
||||||
}
|
return true
|
||||||
}
|
})
|
||||||
|
|
||||||
if len(proxies) == 0 {
|
if len(proxies) == 0 {
|
||||||
return append(proxies, tunnel.Proxies()["COMPATIBLE"])
|
return append(proxies, tunnel.Proxies()["COMPATIBLE"])
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(gb.providers) > 1 && len(gb.filterRegs) > 1 {
|
|
||||||
var newProxies []C.Proxy
|
|
||||||
proxiesSet := map[string]struct{}{}
|
|
||||||
for _, filterReg := range gb.filterRegs {
|
|
||||||
for _, p := range proxies {
|
|
||||||
name := p.Name()
|
|
||||||
if mat, _ := filterReg.FindStringMatch(name); mat != nil {
|
|
||||||
if _, ok := proxiesSet[name]; !ok {
|
|
||||||
proxiesSet[name] = struct{}{}
|
|
||||||
newProxies = append(newProxies, p)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for _, p := range proxies { // add not matched proxies at the end
|
|
||||||
name := p.Name()
|
|
||||||
if _, ok := proxiesSet[name]; !ok {
|
|
||||||
proxiesSet[name] = struct{}{}
|
|
||||||
newProxies = append(newProxies, p)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
proxies = newProxies
|
|
||||||
}
|
|
||||||
if gb.excludeTypeArray != nil {
|
|
||||||
var newProxies []C.Proxy
|
|
||||||
for _, p := range proxies {
|
|
||||||
mType := p.Type().String()
|
|
||||||
flag := false
|
|
||||||
for i := range gb.excludeTypeArray {
|
|
||||||
if strings.EqualFold(mType, gb.excludeTypeArray[i]) {
|
|
||||||
flag = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
if flag {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
newProxies = append(newProxies, p)
|
|
||||||
}
|
|
||||||
proxies = newProxies
|
|
||||||
}
|
|
||||||
|
|
||||||
if gb.excludeFilterReg != nil {
|
|
||||||
var newProxies []C.Proxy
|
|
||||||
for _, p := range proxies {
|
|
||||||
name := p.Name()
|
|
||||||
if mat, _ := gb.excludeFilterReg.FindStringMatch(name); mat != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
newProxies = append(newProxies, p)
|
|
||||||
}
|
|
||||||
proxies = newProxies
|
|
||||||
}
|
|
||||||
|
|
||||||
return proxies
|
return proxies
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -202,11 +112,11 @@ func (gb *GroupBase) URLTest(ctx context.Context, url string) (map[string]uint16
|
|||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
go func() {
|
go func() {
|
||||||
delay, err := proxy.URLTest(ctx, url)
|
delay, err := proxy.URLTest(ctx, url)
|
||||||
if err == nil {
|
|
||||||
lock.Lock()
|
lock.Lock()
|
||||||
|
if err == nil {
|
||||||
mp[proxy.Name()] = delay
|
mp[proxy.Name()] = delay
|
||||||
lock.Unlock()
|
|
||||||
}
|
}
|
||||||
|
lock.Unlock()
|
||||||
|
|
||||||
wg.Done()
|
wg.Done()
|
||||||
}()
|
}()
|
||||||
@ -220,13 +130,8 @@ func (gb *GroupBase) URLTest(ctx context.Context, url string) (map[string]uint16
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gb *GroupBase) onDialFailed(adapterType C.AdapterType, err error) {
|
func (gb *GroupBase) onDialFailed() {
|
||||||
if adapterType == C.Direct || adapterType == C.Compatible || adapterType == C.Reject || adapterType == C.Pass {
|
if gb.failedTesting.Load() {
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if strings.Contains(err.Error(), "connection refused") {
|
|
||||||
go gb.healthCheck()
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -240,25 +145,13 @@ func (gb *GroupBase) onDialFailed(adapterType C.AdapterType, err error) {
|
|||||||
gb.failedTime = time.Now()
|
gb.failedTime = time.Now()
|
||||||
} else {
|
} else {
|
||||||
if time.Since(gb.failedTime) > gb.failedTimeoutInterval() {
|
if time.Since(gb.failedTime) > gb.failedTimeoutInterval() {
|
||||||
gb.failedTimes = 0
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debugln("ProxyGroup: %s failed count: %d", gb.Name(), gb.failedTimes)
|
log.Debugln("ProxyGroup: %s failed count: %d", gb.Name(), gb.failedTimes)
|
||||||
if gb.failedTimes >= gb.maxFailedTimes() {
|
if gb.failedTimes >= gb.maxFailedTimes() {
|
||||||
log.Warnln("because %s failed multiple times, active health check", gb.Name())
|
|
||||||
gb.healthCheck()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (gb *GroupBase) healthCheck() {
|
|
||||||
if gb.failedTesting.Load() {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
gb.failedTesting.Store(true)
|
gb.failedTesting.Store(true)
|
||||||
|
log.Warnln("because %s failed multiple times, active health check", gb.Name())
|
||||||
wg := sync.WaitGroup{}
|
wg := sync.WaitGroup{}
|
||||||
for _, proxyProvider := range gb.providers {
|
for _, proxyProvider := range gb.providers {
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
@ -272,6 +165,9 @@ func (gb *GroupBase) healthCheck() {
|
|||||||
wg.Wait()
|
wg.Wait()
|
||||||
gb.failedTesting.Store(false)
|
gb.failedTesting.Store(false)
|
||||||
gb.failedTimes = 0
|
gb.failedTimes = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gb *GroupBase) failedIntervalTime() int64 {
|
func (gb *GroupBase) failedIntervalTime() int64 {
|
||||||
|
@ -6,22 +6,18 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"sync"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/adapter/outbound"
|
"github.com/Dreamacro/clash/adapter/outbound"
|
||||||
"github.com/Dreamacro/clash/common/cache"
|
"github.com/Dreamacro/clash/common/cache"
|
||||||
"github.com/Dreamacro/clash/common/callback"
|
|
||||||
"github.com/Dreamacro/clash/common/murmur3"
|
"github.com/Dreamacro/clash/common/murmur3"
|
||||||
N "github.com/Dreamacro/clash/common/net"
|
|
||||||
"github.com/Dreamacro/clash/component/dialer"
|
"github.com/Dreamacro/clash/component/dialer"
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
"github.com/Dreamacro/clash/constant/provider"
|
"github.com/Dreamacro/clash/constant/provider"
|
||||||
|
|
||||||
"golang.org/x/net/publicsuffix"
|
"golang.org/x/net/publicsuffix"
|
||||||
)
|
)
|
||||||
|
|
||||||
type strategyFn = func(proxies []C.Proxy, metadata *C.Metadata, touch bool) C.Proxy
|
type strategyFn = func(proxies []C.Proxy, metadata *C.Metadata) C.Proxy
|
||||||
|
|
||||||
type LoadBalance struct {
|
type LoadBalance struct {
|
||||||
*GroupBase
|
*GroupBase
|
||||||
@ -32,9 +28,11 @@ type LoadBalance struct {
|
|||||||
var errStrategy = errors.New("unsupported strategy")
|
var errStrategy = errors.New("unsupported strategy")
|
||||||
|
|
||||||
func parseStrategy(config map[string]any) string {
|
func parseStrategy(config map[string]any) string {
|
||||||
if strategy, ok := config["strategy"].(string); ok {
|
if elm, ok := config["strategy"]; ok {
|
||||||
|
if strategy, ok := elm.(string); ok {
|
||||||
return strategy
|
return strategy
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return "consistent-hashing"
|
return "consistent-hashing"
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -85,28 +83,18 @@ func jumpHash(key uint64, buckets int32) int32 {
|
|||||||
|
|
||||||
// DialContext implements C.ProxyAdapter
|
// DialContext implements C.ProxyAdapter
|
||||||
func (lb *LoadBalance) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (c C.Conn, err error) {
|
func (lb *LoadBalance) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (c C.Conn, err error) {
|
||||||
proxy := lb.Unwrap(metadata, true)
|
defer func() {
|
||||||
c, err = proxy.DialContext(ctx, metadata, lb.Base.DialOptions(opts...)...)
|
|
||||||
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
c.AppendToChains(lb)
|
c.AppendToChains(lb)
|
||||||
} else {
|
|
||||||
lb.onDialFailed(proxy.Type(), err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if N.NeedHandshake(c) {
|
|
||||||
c = &callback.FirstWriteCallBackConn{
|
|
||||||
Conn: c,
|
|
||||||
Callback: func(err error) {
|
|
||||||
if err == nil {
|
|
||||||
lb.onDialSuccess()
|
lb.onDialSuccess()
|
||||||
} else {
|
} else {
|
||||||
lb.onDialFailed(proxy.Type(), err)
|
lb.onDialFailed()
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
proxy := lb.Unwrap(metadata)
|
||||||
|
|
||||||
|
c, err = proxy.DialContext(ctx, metadata, lb.Base.DialOptions(opts...)...)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -118,7 +106,7 @@ func (lb *LoadBalance) ListenPacketContext(ctx context.Context, metadata *C.Meta
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
proxy := lb.Unwrap(metadata, true)
|
proxy := lb.Unwrap(metadata)
|
||||||
return proxy.ListenPacketContext(ctx, metadata, lb.Base.DialOptions(opts...)...)
|
return proxy.ListenPacketContext(ctx, metadata, lb.Base.DialOptions(opts...)...)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -129,25 +117,12 @@ func (lb *LoadBalance) SupportUDP() bool {
|
|||||||
|
|
||||||
func strategyRoundRobin() strategyFn {
|
func strategyRoundRobin() strategyFn {
|
||||||
idx := 0
|
idx := 0
|
||||||
idxMutex := sync.Mutex{}
|
return func(proxies []C.Proxy, metadata *C.Metadata) C.Proxy {
|
||||||
return func(proxies []C.Proxy, metadata *C.Metadata, touch bool) C.Proxy {
|
|
||||||
idxMutex.Lock()
|
|
||||||
defer idxMutex.Unlock()
|
|
||||||
|
|
||||||
i := 0
|
|
||||||
length := len(proxies)
|
length := len(proxies)
|
||||||
|
for i := 0; i < length; i++ {
|
||||||
if touch {
|
idx = (idx + 1) % length
|
||||||
defer func() {
|
proxy := proxies[idx]
|
||||||
idx = (idx + i) % length
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
for ; i < length; i++ {
|
|
||||||
id := (idx + i) % length
|
|
||||||
proxy := proxies[id]
|
|
||||||
if proxy.Alive() {
|
if proxy.Alive() {
|
||||||
i++
|
|
||||||
return proxy
|
return proxy
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -158,7 +133,7 @@ func strategyRoundRobin() strategyFn {
|
|||||||
|
|
||||||
func strategyConsistentHashing() strategyFn {
|
func strategyConsistentHashing() strategyFn {
|
||||||
maxRetry := 5
|
maxRetry := 5
|
||||||
return func(proxies []C.Proxy, metadata *C.Metadata, touch bool) C.Proxy {
|
return func(proxies []C.Proxy, metadata *C.Metadata) C.Proxy {
|
||||||
key := uint64(murmur3.Sum32([]byte(getKey(metadata))))
|
key := uint64(murmur3.Sum32([]byte(getKey(metadata))))
|
||||||
buckets := int32(len(proxies))
|
buckets := int32(len(proxies))
|
||||||
for i := 0; i < maxRetry; i, key = i+1, key+1 {
|
for i := 0; i < maxRetry; i, key = i+1, key+1 {
|
||||||
@ -169,13 +144,6 @@ func strategyConsistentHashing() strategyFn {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// when availability is poor, traverse the entire list to get the available nodes
|
|
||||||
for _, proxy := range proxies {
|
|
||||||
if proxy.Alive() {
|
|
||||||
return proxy
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return proxies[0]
|
return proxies[0]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -183,10 +151,10 @@ func strategyConsistentHashing() strategyFn {
|
|||||||
func strategyStickySessions() strategyFn {
|
func strategyStickySessions() strategyFn {
|
||||||
ttl := time.Minute * 10
|
ttl := time.Minute * 10
|
||||||
maxRetry := 5
|
maxRetry := 5
|
||||||
lruCache := cache.New[uint64, int](
|
lruCache := cache.NewLRUCache[uint64, int](
|
||||||
cache.WithAge[uint64, int](int64(ttl.Seconds())),
|
cache.WithAge[uint64, int](int64(ttl.Seconds())),
|
||||||
cache.WithSize[uint64, int](1000))
|
cache.WithSize[uint64, int](1000))
|
||||||
return func(proxies []C.Proxy, metadata *C.Metadata, touch bool) C.Proxy {
|
return func(proxies []C.Proxy, metadata *C.Metadata) C.Proxy {
|
||||||
key := uint64(murmur3.Sum32([]byte(getKeyWithSrcAndDst(metadata))))
|
key := uint64(murmur3.Sum32([]byte(getKeyWithSrcAndDst(metadata))))
|
||||||
length := len(proxies)
|
length := len(proxies)
|
||||||
idx, has := lruCache.Get(key)
|
idx, has := lruCache.Get(key)
|
||||||
@ -216,9 +184,9 @@ func strategyStickySessions() strategyFn {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Unwrap implements C.ProxyAdapter
|
// Unwrap implements C.ProxyAdapter
|
||||||
func (lb *LoadBalance) Unwrap(metadata *C.Metadata, touch bool) C.Proxy {
|
func (lb *LoadBalance) Unwrap(metadata *C.Metadata) C.Proxy {
|
||||||
proxies := lb.GetProxies(touch)
|
proxies := lb.GetProxies(true)
|
||||||
return lb.strategyFn(proxies, metadata, touch)
|
return lb.strategyFn(proxies, metadata)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MarshalJSON implements C.ProxyAdapter
|
// MarshalJSON implements C.ProxyAdapter
|
||||||
@ -254,8 +222,6 @@ func NewLoadBalance(option *GroupCommonOption, providers []provider.ProxyProvide
|
|||||||
RoutingMark: option.RoutingMark,
|
RoutingMark: option.RoutingMark,
|
||||||
},
|
},
|
||||||
option.Filter,
|
option.Filter,
|
||||||
option.ExcludeFilter,
|
|
||||||
option.ExcludeType,
|
|
||||||
providers,
|
providers,
|
||||||
}),
|
}),
|
||||||
strategyFn: strategyFn,
|
strategyFn: strategyFn,
|
||||||
|
@ -30,8 +30,6 @@ type GroupCommonOption struct {
|
|||||||
Lazy bool `group:"lazy,omitempty"`
|
Lazy bool `group:"lazy,omitempty"`
|
||||||
DisableUDP bool `group:"disable-udp,omitempty"`
|
DisableUDP bool `group:"disable-udp,omitempty"`
|
||||||
Filter string `group:"filter,omitempty"`
|
Filter string `group:"filter,omitempty"`
|
||||||
ExcludeFilter string `group:"exclude-filter,omitempty"`
|
|
||||||
ExcludeType string `group:"exclude-type,omitempty"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func ParseProxyGroup(config map[string]any, proxyMap map[string]C.Proxy, providersMap map[string]types.ProxyProvider) (C.ProxyAdapter, error) {
|
func ParseProxyGroup(config map[string]any, proxyMap map[string]C.Proxy, providersMap map[string]types.ProxyProvider) (C.ProxyAdapter, error) {
|
||||||
@ -78,7 +76,7 @@ func ParseProxyGroup(config map[string]any, proxyMap map[string]C.Proxy, provide
|
|||||||
providersMap[groupName] = pd
|
providersMap[groupName] = pd
|
||||||
} else {
|
} else {
|
||||||
if groupOption.URL == "" {
|
if groupOption.URL == "" {
|
||||||
groupOption.URL = "https://cp.cloudflare.com/generate_204"
|
groupOption.URL = "http://www.gstatic.com/generate_204"
|
||||||
}
|
}
|
||||||
|
|
||||||
if groupOption.Interval == 0 {
|
if groupOption.Interval == 0 {
|
||||||
|
@ -3,12 +3,9 @@ package outboundgroup
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"net"
|
"fmt"
|
||||||
"net/netip"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/adapter/outbound"
|
"github.com/Dreamacro/clash/adapter/outbound"
|
||||||
N "github.com/Dreamacro/clash/common/net"
|
|
||||||
"github.com/Dreamacro/clash/component/dialer"
|
"github.com/Dreamacro/clash/component/dialer"
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
"github.com/Dreamacro/clash/constant/provider"
|
"github.com/Dreamacro/clash/constant/provider"
|
||||||
@ -18,36 +15,6 @@ type Relay struct {
|
|||||||
*GroupBase
|
*GroupBase
|
||||||
}
|
}
|
||||||
|
|
||||||
type proxyDialer struct {
|
|
||||||
proxy C.Proxy
|
|
||||||
dialer C.Dialer
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p proxyDialer) DialContext(ctx context.Context, network, address string) (net.Conn, error) {
|
|
||||||
currentMeta, err := addrToMetadata(address)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if strings.Contains(network, "udp") { // should not support this operation
|
|
||||||
currentMeta.NetWork = C.UDP
|
|
||||||
pc, err := p.proxy.ListenPacketWithDialer(ctx, p.dialer, currentMeta)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return N.NewBindPacketConn(pc, currentMeta.UDPAddr()), nil
|
|
||||||
}
|
|
||||||
return p.proxy.DialContextWithDialer(ctx, p.dialer, currentMeta)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p proxyDialer) ListenPacket(ctx context.Context, network, address string, rAddrPort netip.AddrPort) (net.PacketConn, error) {
|
|
||||||
currentMeta, err := addrToMetadata(rAddrPort.String())
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
currentMeta.NetWork = C.UDP
|
|
||||||
return p.proxy.ListenPacketWithDialer(ctx, p.dialer, currentMeta)
|
|
||||||
}
|
|
||||||
|
|
||||||
// DialContext implements C.ProxyAdapter
|
// DialContext implements C.ProxyAdapter
|
||||||
func (r *Relay) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.Conn, error) {
|
func (r *Relay) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.Conn, error) {
|
||||||
proxies, chainProxies := r.proxies(metadata, true)
|
proxies, chainProxies := r.proxies(metadata, true)
|
||||||
@ -58,20 +25,38 @@ func (r *Relay) DialContext(ctx context.Context, metadata *C.Metadata, opts ...d
|
|||||||
case 1:
|
case 1:
|
||||||
return proxies[0].DialContext(ctx, metadata, r.Base.DialOptions(opts...)...)
|
return proxies[0].DialContext(ctx, metadata, r.Base.DialOptions(opts...)...)
|
||||||
}
|
}
|
||||||
var d C.Dialer
|
|
||||||
d = dialer.NewDialer(r.Base.DialOptions(opts...)...)
|
first := proxies[0]
|
||||||
for _, proxy := range proxies[:len(proxies)-1] {
|
|
||||||
d = proxyDialer{
|
|
||||||
proxy: proxy,
|
|
||||||
dialer: d,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
last := proxies[len(proxies)-1]
|
last := proxies[len(proxies)-1]
|
||||||
conn, err := last.DialContextWithDialer(ctx, d, metadata)
|
|
||||||
|
c, err := dialer.DialContext(ctx, "tcp", first.Addr(), r.Base.DialOptions(opts...)...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("%s connect error: %w", first.Addr(), err)
|
||||||
|
}
|
||||||
|
tcpKeepAlive(c)
|
||||||
|
|
||||||
|
var currentMeta *C.Metadata
|
||||||
|
for _, proxy := range proxies[1:] {
|
||||||
|
currentMeta, err = addrToMetadata(proxy.Addr())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
c, err = first.StreamConn(c, currentMeta)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("%s connect error: %w", first.Addr(), err)
|
||||||
|
}
|
||||||
|
|
||||||
|
first = proxy
|
||||||
|
}
|
||||||
|
|
||||||
|
c, err = last.StreamConn(c, metadata)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("%s connect error: %w", last.Addr(), err)
|
||||||
|
}
|
||||||
|
|
||||||
|
conn := outbound.NewConn(c, last)
|
||||||
|
|
||||||
for i := len(chainProxies) - 2; i >= 0; i-- {
|
for i := len(chainProxies) - 2; i >= 0; i-- {
|
||||||
conn.AppendToChains(chainProxies[i])
|
conn.AppendToChains(chainProxies[i])
|
||||||
}
|
}
|
||||||
@ -92,20 +77,41 @@ func (r *Relay) ListenPacketContext(ctx context.Context, metadata *C.Metadata, o
|
|||||||
return proxies[0].ListenPacketContext(ctx, metadata, r.Base.DialOptions(opts...)...)
|
return proxies[0].ListenPacketContext(ctx, metadata, r.Base.DialOptions(opts...)...)
|
||||||
}
|
}
|
||||||
|
|
||||||
var d C.Dialer
|
first := proxies[0]
|
||||||
d = dialer.NewDialer(r.Base.DialOptions(opts...)...)
|
|
||||||
for _, proxy := range proxies[:len(proxies)-1] {
|
|
||||||
d = proxyDialer{
|
|
||||||
proxy: proxy,
|
|
||||||
dialer: d,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
last := proxies[len(proxies)-1]
|
last := proxies[len(proxies)-1]
|
||||||
pc, err := last.ListenPacketWithDialer(ctx, d, metadata)
|
|
||||||
|
c, err := dialer.DialContext(ctx, "tcp", first.Addr(), r.Base.DialOptions(opts...)...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("%s connect error: %w", first.Addr(), err)
|
||||||
|
}
|
||||||
|
tcpKeepAlive(c)
|
||||||
|
|
||||||
|
var currentMeta *C.Metadata
|
||||||
|
for _, proxy := range proxies[1:] {
|
||||||
|
currentMeta, err = addrToMetadata(proxy.Addr())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
c, err = first.StreamConn(c, currentMeta)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("%s connect error: %w", first.Addr(), err)
|
||||||
|
}
|
||||||
|
|
||||||
|
first = proxy
|
||||||
|
}
|
||||||
|
|
||||||
|
c, err = last.StreamConn(c, metadata)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("%s connect error: %w", last.Addr(), err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var pc C.PacketConn
|
||||||
|
pc, err = last.ListenPacketOnStreamConn(c, metadata)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("%s connect error: %w", first.Addr(), err)
|
||||||
|
}
|
||||||
|
|
||||||
for i := len(chainProxies) - 2; i >= 0; i-- {
|
for i := len(chainProxies) - 2; i >= 0; i-- {
|
||||||
pc.AppendToChains(chainProxies[i])
|
pc.AppendToChains(chainProxies[i])
|
||||||
}
|
}
|
||||||
@ -121,19 +127,8 @@ func (r *Relay) SupportUDP() bool {
|
|||||||
if len(proxies) == 0 { // C.Direct
|
if len(proxies) == 0 { // C.Direct
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
for i := len(proxies) - 1; i >= 0; i-- {
|
last := proxies[len(proxies)-1]
|
||||||
proxy := proxies[i]
|
return last.SupportUDP() && last.SupportUOT()
|
||||||
if !proxy.SupportUDP() {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if proxy.SupportUOT() {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if !proxy.SupportWithDialer() {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MarshalJSON implements C.ProxyAdapter
|
// MarshalJSON implements C.ProxyAdapter
|
||||||
@ -158,11 +153,11 @@ func (r *Relay) proxies(metadata *C.Metadata, touch bool) ([]C.Proxy, []C.Proxy)
|
|||||||
for n, proxy := range rawProxies {
|
for n, proxy := range rawProxies {
|
||||||
proxies = append(proxies, proxy)
|
proxies = append(proxies, proxy)
|
||||||
chainProxies = append(chainProxies, proxy)
|
chainProxies = append(chainProxies, proxy)
|
||||||
subproxy := proxy.Unwrap(metadata, touch)
|
subproxy := proxy.Unwrap(metadata)
|
||||||
for subproxy != nil {
|
for subproxy != nil {
|
||||||
chainProxies = append(chainProxies, subproxy)
|
chainProxies = append(chainProxies, subproxy)
|
||||||
proxies[n] = subproxy
|
proxies[n] = subproxy
|
||||||
subproxy = subproxy.Unwrap(metadata, touch)
|
subproxy = subproxy.Unwrap(metadata)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -176,7 +171,7 @@ func (r *Relay) proxies(metadata *C.Metadata, touch bool) ([]C.Proxy, []C.Proxy)
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (r *Relay) Addr() string {
|
func (r *Relay) Addr() string {
|
||||||
proxies, _ := r.proxies(nil, false)
|
proxies, _ := r.proxies(nil, true)
|
||||||
return proxies[len(proxies)-1].Addr()
|
return proxies[len(proxies)-1].Addr()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -190,8 +185,6 @@ func NewRelay(option *GroupCommonOption, providers []provider.ProxyProvider) *Re
|
|||||||
RoutingMark: option.RoutingMark,
|
RoutingMark: option.RoutingMark,
|
||||||
},
|
},
|
||||||
"",
|
"",
|
||||||
"",
|
|
||||||
"",
|
|
||||||
providers,
|
providers,
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
|
@ -74,8 +74,8 @@ func (s *Selector) Set(name string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Unwrap implements C.ProxyAdapter
|
// Unwrap implements C.ProxyAdapter
|
||||||
func (s *Selector) Unwrap(metadata *C.Metadata, touch bool) C.Proxy {
|
func (s *Selector) Unwrap(*C.Metadata) C.Proxy {
|
||||||
return s.selectedProxy(touch)
|
return s.selectedProxy(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Selector) selectedProxy(touch bool) C.Proxy {
|
func (s *Selector) selectedProxy(touch bool) C.Proxy {
|
||||||
@ -99,8 +99,6 @@ func NewSelector(option *GroupCommonOption, providers []provider.ProxyProvider)
|
|||||||
RoutingMark: option.RoutingMark,
|
RoutingMark: option.RoutingMark,
|
||||||
},
|
},
|
||||||
option.Filter,
|
option.Filter,
|
||||||
option.ExcludeFilter,
|
|
||||||
option.ExcludeType,
|
|
||||||
providers,
|
providers,
|
||||||
}),
|
}),
|
||||||
selected: "COMPATIBLE",
|
selected: "COMPATIBLE",
|
||||||
|
@ -6,8 +6,6 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/adapter/outbound"
|
"github.com/Dreamacro/clash/adapter/outbound"
|
||||||
"github.com/Dreamacro/clash/common/callback"
|
|
||||||
N "github.com/Dreamacro/clash/common/net"
|
|
||||||
"github.com/Dreamacro/clash/common/singledo"
|
"github.com/Dreamacro/clash/common/singledo"
|
||||||
"github.com/Dreamacro/clash/component/dialer"
|
"github.com/Dreamacro/clash/component/dialer"
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
@ -36,27 +34,13 @@ func (u *URLTest) Now() string {
|
|||||||
|
|
||||||
// DialContext implements C.ProxyAdapter
|
// DialContext implements C.ProxyAdapter
|
||||||
func (u *URLTest) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (c C.Conn, err error) {
|
func (u *URLTest) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (c C.Conn, err error) {
|
||||||
proxy := u.fast(true)
|
c, err = u.fast(true).DialContext(ctx, metadata, u.Base.DialOptions(opts...)...)
|
||||||
c, err = proxy.DialContext(ctx, metadata, u.Base.DialOptions(opts...)...)
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
c.AppendToChains(u)
|
c.AppendToChains(u)
|
||||||
} else {
|
|
||||||
u.onDialFailed(proxy.Type(), err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if N.NeedHandshake(c) {
|
|
||||||
c = &callback.FirstWriteCallBackConn{
|
|
||||||
Conn: c,
|
|
||||||
Callback: func(err error) {
|
|
||||||
if err == nil {
|
|
||||||
u.onDialSuccess()
|
u.onDialSuccess()
|
||||||
} else {
|
} else {
|
||||||
u.onDialFailed(proxy.Type(), err)
|
u.onDialFailed()
|
||||||
}
|
}
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return c, err
|
return c, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -71,12 +55,12 @@ func (u *URLTest) ListenPacketContext(ctx context.Context, metadata *C.Metadata,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Unwrap implements C.ProxyAdapter
|
// Unwrap implements C.ProxyAdapter
|
||||||
func (u *URLTest) Unwrap(metadata *C.Metadata, touch bool) C.Proxy {
|
func (u *URLTest) Unwrap(*C.Metadata) C.Proxy {
|
||||||
return u.fast(touch)
|
return u.fast(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *URLTest) fast(touch bool) C.Proxy {
|
func (u *URLTest) fast(touch bool) C.Proxy {
|
||||||
elm, _, shared := u.fastSingle.Do(func() (C.Proxy, error) {
|
elm, _, _ := u.fastSingle.Do(func() (C.Proxy, error) {
|
||||||
proxies := u.GetProxies(touch)
|
proxies := u.GetProxies(touch)
|
||||||
fast := proxies[0]
|
fast := proxies[0]
|
||||||
min := fast.LastDelay()
|
min := fast.LastDelay()
|
||||||
@ -105,9 +89,6 @@ func (u *URLTest) fast(touch bool) C.Proxy {
|
|||||||
|
|
||||||
return u.fastNode, nil
|
return u.fastNode, nil
|
||||||
})
|
})
|
||||||
if shared && touch { // a shared fastSingle.Do() may cause providers untouched, so we touch them again
|
|
||||||
u.Touch()
|
|
||||||
}
|
|
||||||
|
|
||||||
return elm
|
return elm
|
||||||
}
|
}
|
||||||
@ -158,8 +139,6 @@ func NewURLTest(option *GroupCommonOption, providers []provider.ProxyProvider, o
|
|||||||
},
|
},
|
||||||
|
|
||||||
option.Filter,
|
option.Filter,
|
||||||
option.ExcludeFilter,
|
|
||||||
option.ExcludeType,
|
|
||||||
providers,
|
providers,
|
||||||
}),
|
}),
|
||||||
fastSingle: singledo.NewSingle[C.Proxy](time.Second * 10),
|
fastSingle: singledo.NewSingle[C.Proxy](time.Second * 10),
|
||||||
|
@ -16,19 +16,32 @@ func addrToMetadata(rawAddress string) (addr *C.Metadata, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if ip, err := netip.ParseAddr(host); err != nil {
|
ip, err := netip.ParseAddr(host)
|
||||||
|
if err != nil {
|
||||||
addr = &C.Metadata{
|
addr = &C.Metadata{
|
||||||
|
AddrType: C.AtypDomainName,
|
||||||
Host: host,
|
Host: host,
|
||||||
|
DstIP: netip.Addr{},
|
||||||
DstPort: port,
|
DstPort: port,
|
||||||
}
|
}
|
||||||
} else {
|
err = nil
|
||||||
|
return
|
||||||
|
} else if ip.Is4() {
|
||||||
addr = &C.Metadata{
|
addr = &C.Metadata{
|
||||||
|
AddrType: C.AtypIPv4,
|
||||||
Host: "",
|
Host: "",
|
||||||
DstIP: ip.Unmap(),
|
DstIP: ip,
|
||||||
DstPort: port,
|
DstPort: port,
|
||||||
}
|
}
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
addr = &C.Metadata{
|
||||||
|
AddrType: C.AtypIPv6,
|
||||||
|
Host: "",
|
||||||
|
DstIP: ip,
|
||||||
|
DstPort: port,
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,15 +3,13 @@ package adapter
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
tlsC "github.com/Dreamacro/clash/component/tls"
|
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/adapter/outbound"
|
"github.com/Dreamacro/clash/adapter/outbound"
|
||||||
"github.com/Dreamacro/clash/common/structure"
|
"github.com/Dreamacro/clash/common/structure"
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
)
|
)
|
||||||
|
|
||||||
func ParseProxy(mapping map[string]any) (C.Proxy, error) {
|
func ParseProxy(mapping map[string]any) (C.Proxy, error) {
|
||||||
decoder := structure.NewDecoder(structure.Option{TagName: "proxy", WeaklyTypedInput: true, KeyReplacer: structure.DefaultKeyReplacer})
|
decoder := structure.NewDecoder(structure.Option{TagName: "proxy", WeaklyTypedInput: true})
|
||||||
proxyType, existType := mapping["type"].(string)
|
proxyType, existType := mapping["type"].(string)
|
||||||
if !existType {
|
if !existType {
|
||||||
return nil, fmt.Errorf("missing type")
|
return nil, fmt.Errorf("missing type")
|
||||||
@ -23,7 +21,7 @@ func ParseProxy(mapping map[string]any) (C.Proxy, error) {
|
|||||||
)
|
)
|
||||||
switch proxyType {
|
switch proxyType {
|
||||||
case "ss":
|
case "ss":
|
||||||
ssOption := &outbound.ShadowSocksOption{ClientFingerprint: tlsC.GetGlobalFingerprint()}
|
ssOption := &outbound.ShadowSocksOption{}
|
||||||
err = decoder.Decode(mapping, ssOption)
|
err = decoder.Decode(mapping, ssOption)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
break
|
break
|
||||||
@ -42,30 +40,28 @@ func ParseProxy(mapping map[string]any) (C.Proxy, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
proxy, err = outbound.NewSocks5(*socksOption)
|
proxy = outbound.NewSocks5(*socksOption)
|
||||||
case "http":
|
case "http":
|
||||||
httpOption := &outbound.HttpOption{}
|
httpOption := &outbound.HttpOption{}
|
||||||
err = decoder.Decode(mapping, httpOption)
|
err = decoder.Decode(mapping, httpOption)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
proxy, err = outbound.NewHttp(*httpOption)
|
proxy = outbound.NewHttp(*httpOption)
|
||||||
case "vmess":
|
case "vmess":
|
||||||
vmessOption := &outbound.VmessOption{
|
vmessOption := &outbound.VmessOption{
|
||||||
HTTPOpts: outbound.HTTPOptions{
|
HTTPOpts: outbound.HTTPOptions{
|
||||||
Method: "GET",
|
Method: "GET",
|
||||||
Path: []string{"/"},
|
Path: []string{"/"},
|
||||||
},
|
},
|
||||||
ClientFingerprint: tlsC.GetGlobalFingerprint(),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
err = decoder.Decode(mapping, vmessOption)
|
err = decoder.Decode(mapping, vmessOption)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
proxy, err = outbound.NewVmess(*vmessOption)
|
proxy, err = outbound.NewVmess(*vmessOption)
|
||||||
case "vless":
|
case "vless":
|
||||||
vlessOption := &outbound.VlessOption{ClientFingerprint: tlsC.GetGlobalFingerprint()}
|
vlessOption := &outbound.VlessOption{}
|
||||||
err = decoder.Decode(mapping, vlessOption)
|
err = decoder.Decode(mapping, vlessOption)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
break
|
break
|
||||||
@ -79,7 +75,7 @@ func ParseProxy(mapping map[string]any) (C.Proxy, error) {
|
|||||||
}
|
}
|
||||||
proxy, err = outbound.NewSnell(*snellOption)
|
proxy, err = outbound.NewSnell(*snellOption)
|
||||||
case "trojan":
|
case "trojan":
|
||||||
trojanOption := &outbound.TrojanOption{ClientFingerprint: tlsC.GetGlobalFingerprint()}
|
trojanOption := &outbound.TrojanOption{}
|
||||||
err = decoder.Decode(mapping, trojanOption)
|
err = decoder.Decode(mapping, trojanOption)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
break
|
break
|
||||||
@ -92,20 +88,6 @@ func ParseProxy(mapping map[string]any) (C.Proxy, error) {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
proxy, err = outbound.NewHysteria(*hyOption)
|
proxy, err = outbound.NewHysteria(*hyOption)
|
||||||
case "wireguard":
|
|
||||||
wgOption := &outbound.WireGuardOption{}
|
|
||||||
err = decoder.Decode(mapping, wgOption)
|
|
||||||
if err != nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
proxy, err = outbound.NewWireGuard(*wgOption)
|
|
||||||
case "tuic":
|
|
||||||
tuicOption := &outbound.TuicOption{}
|
|
||||||
err = decoder.Decode(mapping, tuicOption)
|
|
||||||
if err != nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
proxy, err = outbound.NewTuic(*tuicOption)
|
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("unsupport proxy type: %s", proxyType)
|
return nil, fmt.Errorf("unsupport proxy type: %s", proxyType)
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
package resource
|
package provider
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
@ -16,49 +16,45 @@ var (
|
|||||||
dirMode os.FileMode = 0o755
|
dirMode os.FileMode = 0o755
|
||||||
)
|
)
|
||||||
|
|
||||||
type Parser[V any] func([]byte) (V, error)
|
type parser[V any] func([]byte) (V, error)
|
||||||
|
|
||||||
type Fetcher[V any] struct {
|
type fetcher[V any] struct {
|
||||||
resourceType string
|
|
||||||
name string
|
name string
|
||||||
vehicle types.Vehicle
|
vehicle types.Vehicle
|
||||||
UpdatedAt *time.Time
|
updatedAt *time.Time
|
||||||
ticker *time.Ticker
|
ticker *time.Ticker
|
||||||
done chan struct{}
|
done chan struct{}
|
||||||
hash [16]byte
|
hash [16]byte
|
||||||
parser Parser[V]
|
parser parser[V]
|
||||||
interval time.Duration
|
interval time.Duration
|
||||||
OnUpdate func(V)
|
onUpdate func(V)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Fetcher[V]) Name() string {
|
func (f *fetcher[V]) Name() string {
|
||||||
return f.name
|
return f.name
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Fetcher[V]) Vehicle() types.Vehicle {
|
func (f *fetcher[V]) VehicleType() types.VehicleType {
|
||||||
return f.vehicle
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *Fetcher[V]) VehicleType() types.VehicleType {
|
|
||||||
return f.vehicle.Type()
|
return f.vehicle.Type()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Fetcher[V]) Initial() (V, error) {
|
func (f *fetcher[V]) Initial() (V, error) {
|
||||||
var (
|
var (
|
||||||
buf []byte
|
buf []byte
|
||||||
err error
|
err error
|
||||||
isLocal bool
|
isLocal bool
|
||||||
forceUpdate bool
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if stat, fErr := os.Stat(f.vehicle.Path()); fErr == nil {
|
if stat, fErr := os.Stat(f.vehicle.Path()); fErr == nil {
|
||||||
buf, err = os.ReadFile(f.vehicle.Path())
|
buf, err = os.ReadFile(f.vehicle.Path())
|
||||||
modTime := stat.ModTime()
|
modTime := stat.ModTime()
|
||||||
f.UpdatedAt = &modTime
|
f.updatedAt = &modTime
|
||||||
isLocal = true
|
isLocal = true
|
||||||
if f.interval != 0 && modTime.Add(f.interval).Before(time.Now()) {
|
if f.interval != 0 && modTime.Add(f.interval).Before(time.Now()) {
|
||||||
log.Warnln("[Provider] %s not updated for a long time, force refresh", f.Name())
|
defer func() {
|
||||||
forceUpdate = true
|
log.Infoln("[Provider] %s's proxies not updated for a long time, force refresh", f.Name())
|
||||||
|
go f.Update()
|
||||||
|
}()
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
buf, err = f.vehicle.Read()
|
buf, err = f.vehicle.Read()
|
||||||
@ -68,21 +64,7 @@ func (f *Fetcher[V]) Initial() (V, error) {
|
|||||||
return getZero[V](), err
|
return getZero[V](), err
|
||||||
}
|
}
|
||||||
|
|
||||||
var contents V
|
proxies, err := f.parser(buf)
|
||||||
if forceUpdate {
|
|
||||||
var forceBuf []byte
|
|
||||||
if forceBuf, err = f.vehicle.Read(); err == nil {
|
|
||||||
if contents, err = f.parser(forceBuf); err == nil {
|
|
||||||
isLocal = false
|
|
||||||
buf = forceBuf
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil || !forceUpdate {
|
|
||||||
contents, err = f.parser(buf)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if !isLocal {
|
if !isLocal {
|
||||||
return getZero[V](), err
|
return getZero[V](), err
|
||||||
@ -94,7 +76,7 @@ func (f *Fetcher[V]) Initial() (V, error) {
|
|||||||
return getZero[V](), err
|
return getZero[V](), err
|
||||||
}
|
}
|
||||||
|
|
||||||
contents, err = f.parser(buf)
|
proxies, err = f.parser(buf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return getZero[V](), err
|
return getZero[V](), err
|
||||||
}
|
}
|
||||||
@ -110,15 +92,15 @@ func (f *Fetcher[V]) Initial() (V, error) {
|
|||||||
|
|
||||||
f.hash = md5.Sum(buf)
|
f.hash = md5.Sum(buf)
|
||||||
|
|
||||||
// pull contents automatically
|
// pull proxies automatically
|
||||||
if f.ticker != nil {
|
if f.ticker != nil {
|
||||||
go f.pullLoop()
|
go f.pullLoop()
|
||||||
}
|
}
|
||||||
|
|
||||||
return contents, nil
|
return proxies, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Fetcher[V]) Update() (V, bool, error) {
|
func (f *fetcher[V]) Update() (V, bool, error) {
|
||||||
buf, err := f.vehicle.Read()
|
buf, err := f.vehicle.Read()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return getZero[V](), false, err
|
return getZero[V](), false, err
|
||||||
@ -127,12 +109,12 @@ func (f *Fetcher[V]) Update() (V, bool, error) {
|
|||||||
now := time.Now()
|
now := time.Now()
|
||||||
hash := md5.Sum(buf)
|
hash := md5.Sum(buf)
|
||||||
if bytes.Equal(f.hash[:], hash[:]) {
|
if bytes.Equal(f.hash[:], hash[:]) {
|
||||||
f.UpdatedAt = &now
|
f.updatedAt = &now
|
||||||
_ = os.Chtimes(f.vehicle.Path(), now, now)
|
os.Chtimes(f.vehicle.Path(), now, now)
|
||||||
return getZero[V](), true, nil
|
return getZero[V](), true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
contents, err := f.parser(buf)
|
proxies, err := f.parser(buf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return getZero[V](), false, err
|
return getZero[V](), false, err
|
||||||
}
|
}
|
||||||
@ -143,37 +125,37 @@ func (f *Fetcher[V]) Update() (V, bool, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
f.UpdatedAt = &now
|
f.updatedAt = &now
|
||||||
f.hash = hash
|
f.hash = hash
|
||||||
|
|
||||||
return contents, false, nil
|
return proxies, false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Fetcher[V]) Destroy() error {
|
func (f *fetcher[V]) Destroy() error {
|
||||||
if f.ticker != nil {
|
if f.ticker != nil {
|
||||||
f.done <- struct{}{}
|
f.done <- struct{}{}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Fetcher[V]) pullLoop() {
|
func (f *fetcher[V]) pullLoop() {
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-f.ticker.C:
|
case <-f.ticker.C:
|
||||||
elm, same, err := f.Update()
|
elm, same, err := f.Update()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorln("[Provider] %s pull error: %s", f.Name(), err.Error())
|
log.Warnln("[Provider] %s pull error: %s", f.Name(), err.Error())
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if same {
|
if same {
|
||||||
log.Debugln("[Provider] %s's content doesn't change", f.Name())
|
log.Debugln("[Provider] %s's proxies doesn't change", f.Name())
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Infoln("[Provider] %s's content update", f.Name())
|
log.Infoln("[Provider] %s's proxies update", f.Name())
|
||||||
if f.OnUpdate != nil {
|
if f.onUpdate != nil {
|
||||||
f.OnUpdate(elm)
|
f.onUpdate(elm)
|
||||||
}
|
}
|
||||||
case <-f.done:
|
case <-f.done:
|
||||||
f.ticker.Stop()
|
f.ticker.Stop()
|
||||||
@ -194,20 +176,19 @@ func safeWrite(path string, buf []byte) error {
|
|||||||
return os.WriteFile(path, buf, fileMode)
|
return os.WriteFile(path, buf, fileMode)
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewFetcher[V any](name string, interval time.Duration, vehicle types.Vehicle, parser Parser[V], onUpdate func(V)) *Fetcher[V] {
|
func newFetcher[V any](name string, interval time.Duration, vehicle types.Vehicle, parser parser[V], onUpdate func(V)) *fetcher[V] {
|
||||||
var ticker *time.Ticker
|
var ticker *time.Ticker
|
||||||
if interval != 0 {
|
if interval != 0 {
|
||||||
ticker = time.NewTicker(interval)
|
ticker = time.NewTicker(interval)
|
||||||
}
|
}
|
||||||
|
|
||||||
return &Fetcher[V]{
|
return &fetcher[V]{
|
||||||
name: name,
|
name: name,
|
||||||
ticker: ticker,
|
ticker: ticker,
|
||||||
vehicle: vehicle,
|
vehicle: vehicle,
|
||||||
parser: parser,
|
parser: parser,
|
||||||
done: make(chan struct{}, 1),
|
done: make(chan struct{}, 1),
|
||||||
OnUpdate: onUpdate,
|
onUpdate: onUpdate,
|
||||||
interval: interval,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -5,11 +5,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/common/batch"
|
"github.com/Dreamacro/clash/common/batch"
|
||||||
"github.com/Dreamacro/clash/common/singledo"
|
|
||||||
"github.com/Dreamacro/clash/common/utils"
|
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
"github.com/Dreamacro/clash/log"
|
|
||||||
|
|
||||||
"go.uber.org/atomic"
|
"go.uber.org/atomic"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -29,19 +25,22 @@ type HealthCheck struct {
|
|||||||
lazy bool
|
lazy bool
|
||||||
lastTouch *atomic.Int64
|
lastTouch *atomic.Int64
|
||||||
done chan struct{}
|
done chan struct{}
|
||||||
singleDo *singledo.Single[struct{}]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (hc *HealthCheck) process() {
|
func (hc *HealthCheck) process() {
|
||||||
ticker := time.NewTicker(time.Duration(hc.interval) * time.Second)
|
ticker := time.NewTicker(time.Duration(hc.interval) * time.Second)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
time.Sleep(30 * time.Second)
|
||||||
|
hc.check()
|
||||||
|
}()
|
||||||
|
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-ticker.C:
|
case <-ticker.C:
|
||||||
now := time.Now().Unix()
|
now := time.Now().Unix()
|
||||||
if !hc.lazy || now-hc.lastTouch.Load() < int64(hc.interval) {
|
if !hc.lazy || now-hc.lastTouch.Load() < int64(hc.interval) {
|
||||||
hc.check()
|
hc.check()
|
||||||
} else {
|
|
||||||
log.Debugln("Skip once health check because we are lazy")
|
|
||||||
}
|
}
|
||||||
case <-hc.done:
|
case <-hc.done:
|
||||||
ticker.Stop()
|
ticker.Stop()
|
||||||
@ -63,26 +62,17 @@ func (hc *HealthCheck) touch() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (hc *HealthCheck) check() {
|
func (hc *HealthCheck) check() {
|
||||||
_, _, _ = hc.singleDo.Do(func() (struct{}, error) {
|
|
||||||
id := utils.NewUUIDV4().String()
|
|
||||||
log.Debugln("Start New Health Checking {%s}", id)
|
|
||||||
b, _ := batch.New[bool](context.Background(), batch.WithConcurrencyNum[bool](10))
|
b, _ := batch.New[bool](context.Background(), batch.WithConcurrencyNum[bool](10))
|
||||||
for _, proxy := range hc.proxies {
|
for _, proxy := range hc.proxies {
|
||||||
p := proxy
|
p := proxy
|
||||||
b.Go(p.Name(), func() (bool, error) {
|
b.Go(p.Name(), func() (bool, error) {
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), defaultURLTestTimeout)
|
ctx, cancel := context.WithTimeout(context.Background(), defaultURLTestTimeout)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
log.Debugln("Health Checking %s {%s}", p.Name(), id)
|
|
||||||
_, _ = p.URLTest(ctx, hc.url)
|
_, _ = p.URLTest(ctx, hc.url)
|
||||||
log.Debugln("Health Checked %s : %t %d ms {%s}", p.Name(), p.Alive(), p.LastDelay(), id)
|
|
||||||
return false, nil
|
return false, nil
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
b.Wait()
|
b.Wait()
|
||||||
log.Debugln("Finish A Health Checking {%s}", id)
|
|
||||||
return struct{}{}, nil
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (hc *HealthCheck) close() {
|
func (hc *HealthCheck) close() {
|
||||||
@ -97,6 +87,5 @@ func NewHealthCheck(proxies []C.Proxy, url string, interval uint, lazy bool) *He
|
|||||||
lazy: lazy,
|
lazy: lazy,
|
||||||
lastTouch: atomic.NewInt64(0),
|
lastTouch: atomic.NewInt64(0),
|
||||||
done: make(chan struct{}, 1),
|
done: make(chan struct{}, 1),
|
||||||
singleDo: singledo.NewSingle[struct{}](time.Second),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,7 +6,6 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/common/structure"
|
"github.com/Dreamacro/clash/common/structure"
|
||||||
"github.com/Dreamacro/clash/component/resource"
|
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
types "github.com/Dreamacro/clash/constant/provider"
|
types "github.com/Dreamacro/clash/constant/provider"
|
||||||
)
|
)
|
||||||
@ -26,8 +25,6 @@ type proxyProviderSchema struct {
|
|||||||
URL string `provider:"url,omitempty"`
|
URL string `provider:"url,omitempty"`
|
||||||
Interval int `provider:"interval,omitempty"`
|
Interval int `provider:"interval,omitempty"`
|
||||||
Filter string `provider:"filter,omitempty"`
|
Filter string `provider:"filter,omitempty"`
|
||||||
ExcludeFilter string `provider:"exclude-filter,omitempty"`
|
|
||||||
ExcludeType string `provider:"exclude-type,omitempty"`
|
|
||||||
HealthCheck healthCheckSchema `provider:"health-check,omitempty"`
|
HealthCheck healthCheckSchema `provider:"health-check,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -54,17 +51,14 @@ func ParseProxyProvider(name string, mapping map[string]any) (types.ProxyProvide
|
|||||||
var vehicle types.Vehicle
|
var vehicle types.Vehicle
|
||||||
switch schema.Type {
|
switch schema.Type {
|
||||||
case "file":
|
case "file":
|
||||||
vehicle = resource.NewFileVehicle(path)
|
vehicle = NewFileVehicle(path)
|
||||||
case "http":
|
case "http":
|
||||||
vehicle = resource.NewHTTPVehicle(schema.URL, path)
|
vehicle = NewHTTPVehicle(schema.URL, path)
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("%w: %s", errVehicleType, schema.Type)
|
return nil, fmt.Errorf("%w: %s", errVehicleType, schema.Type)
|
||||||
}
|
}
|
||||||
|
|
||||||
interval := time.Duration(uint(schema.Interval)) * time.Second
|
interval := time.Duration(uint(schema.Interval)) * time.Second
|
||||||
filter := schema.Filter
|
filter := schema.Filter
|
||||||
excludeFilter := schema.ExcludeFilter
|
return NewProxySetProvider(name, interval, filter, vehicle, hc)
|
||||||
excludeType := schema.ExcludeType
|
|
||||||
|
|
||||||
return NewProxySetProvider(name, interval, filter, excludeFilter, excludeType, vehicle, hc)
|
|
||||||
}
|
}
|
||||||
|
@ -1,23 +1,17 @@
|
|||||||
package provider
|
package provider
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"math"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/adapter"
|
"github.com/Dreamacro/clash/adapter"
|
||||||
"github.com/Dreamacro/clash/common/convert"
|
"github.com/Dreamacro/clash/common/convert"
|
||||||
clashHttp "github.com/Dreamacro/clash/component/http"
|
|
||||||
"github.com/Dreamacro/clash/component/resource"
|
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
types "github.com/Dreamacro/clash/constant/provider"
|
types "github.com/Dreamacro/clash/constant/provider"
|
||||||
"github.com/Dreamacro/clash/log"
|
|
||||||
|
|
||||||
"github.com/dlclark/regexp2"
|
"github.com/dlclark/regexp2"
|
||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
)
|
)
|
||||||
@ -36,11 +30,10 @@ type ProxySetProvider struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type proxySetProvider struct {
|
type proxySetProvider struct {
|
||||||
*resource.Fetcher[[]C.Proxy]
|
*fetcher[[]C.Proxy]
|
||||||
proxies []C.Proxy
|
proxies []C.Proxy
|
||||||
healthCheck *HealthCheck
|
healthCheck *HealthCheck
|
||||||
version uint32
|
version uint
|
||||||
subscriptionInfo *SubscriptionInfo
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pp *proxySetProvider) MarshalJSON() ([]byte, error) {
|
func (pp *proxySetProvider) MarshalJSON() ([]byte, error) {
|
||||||
@ -49,17 +42,16 @@ func (pp *proxySetProvider) MarshalJSON() ([]byte, error) {
|
|||||||
"type": pp.Type().String(),
|
"type": pp.Type().String(),
|
||||||
"vehicleType": pp.VehicleType().String(),
|
"vehicleType": pp.VehicleType().String(),
|
||||||
"proxies": pp.Proxies(),
|
"proxies": pp.Proxies(),
|
||||||
"updatedAt": pp.UpdatedAt,
|
"updatedAt": pp.updatedAt,
|
||||||
"subscriptionInfo": pp.subscriptionInfo,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pp *proxySetProvider) Version() uint32 {
|
func (pp *proxySetProvider) Version() uint {
|
||||||
return pp.version
|
return pp.version
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pp *proxySetProvider) Name() string {
|
func (pp *proxySetProvider) Name() string {
|
||||||
return pp.Fetcher.Name()
|
return pp.name
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pp *proxySetProvider) HealthCheck() {
|
func (pp *proxySetProvider) HealthCheck() {
|
||||||
@ -67,20 +59,19 @@ func (pp *proxySetProvider) HealthCheck() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (pp *proxySetProvider) Update() error {
|
func (pp *proxySetProvider) Update() error {
|
||||||
elm, same, err := pp.Fetcher.Update()
|
elm, same, err := pp.fetcher.Update()
|
||||||
if err == nil && !same {
|
if err == nil && !same {
|
||||||
pp.OnUpdate(elm)
|
pp.onUpdate(elm)
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pp *proxySetProvider) Initial() error {
|
func (pp *proxySetProvider) Initial() error {
|
||||||
elm, err := pp.Fetcher.Initial()
|
elm, err := pp.fetcher.Initial()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
pp.OnUpdate(elm)
|
pp.onUpdate(elm)
|
||||||
pp.getSubscriptionInfo()
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -100,67 +91,20 @@ func (pp *proxySetProvider) setProxies(proxies []C.Proxy) {
|
|||||||
pp.proxies = proxies
|
pp.proxies = proxies
|
||||||
pp.healthCheck.setProxy(proxies)
|
pp.healthCheck.setProxy(proxies)
|
||||||
if pp.healthCheck.auto() {
|
if pp.healthCheck.auto() {
|
||||||
go pp.healthCheck.check()
|
defer func() { go pp.healthCheck.check() }()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pp *proxySetProvider) getSubscriptionInfo() {
|
|
||||||
if pp.VehicleType() != types.HTTP {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
go func() {
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*90)
|
|
||||||
defer cancel()
|
|
||||||
resp, err := clashHttp.HttpRequest(ctx, pp.Vehicle().(*resource.HTTPVehicle).Url(),
|
|
||||||
http.MethodGet, http.Header{"User-Agent": {"clash"}}, nil)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
userInfoStr := strings.TrimSpace(resp.Header.Get("subscription-userinfo"))
|
|
||||||
if userInfoStr == "" {
|
|
||||||
resp2, err := clashHttp.HttpRequest(ctx, pp.Vehicle().(*resource.HTTPVehicle).Url(),
|
|
||||||
http.MethodGet, http.Header{"User-Agent": {"Quantumultx"}}, nil)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer resp2.Body.Close()
|
|
||||||
userInfoStr = strings.TrimSpace(resp2.Header.Get("subscription-userinfo"))
|
|
||||||
if userInfoStr == "" {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pp.subscriptionInfo, err = NewSubscriptionInfo(userInfoStr)
|
|
||||||
if err != nil {
|
|
||||||
log.Warnln("[Provider] get subscription-userinfo: %e", err)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
func stopProxyProvider(pd *ProxySetProvider) {
|
func stopProxyProvider(pd *ProxySetProvider) {
|
||||||
pd.healthCheck.close()
|
pd.healthCheck.close()
|
||||||
_ = pd.Fetcher.Destroy()
|
_ = pd.fetcher.Destroy()
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewProxySetProvider(name string, interval time.Duration, filter string, excludeFilter string, excludeType string, vehicle types.Vehicle, hc *HealthCheck) (*ProxySetProvider, error) {
|
func NewProxySetProvider(name string, interval time.Duration, filter string, vehicle types.Vehicle, hc *HealthCheck) (*ProxySetProvider, error) {
|
||||||
excludeFilterReg, err := regexp2.Compile(excludeFilter, 0)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("invalid excludeFilter regex: %w", err)
|
|
||||||
}
|
|
||||||
var excludeTypeArray []string
|
|
||||||
if excludeType != "" {
|
|
||||||
excludeTypeArray = strings.Split(excludeType, "|")
|
|
||||||
}
|
|
||||||
|
|
||||||
var filterRegs []*regexp2.Regexp
|
|
||||||
for _, filter := range strings.Split(filter, "`") {
|
|
||||||
filterReg, err := regexp2.Compile(filter, 0)
|
filterReg, err := regexp2.Compile(filter, 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("invalid filter regex: %w", err)
|
return nil, fmt.Errorf("invalid filter regex: %w", err)
|
||||||
}
|
}
|
||||||
filterRegs = append(filterRegs, filterReg)
|
|
||||||
}
|
|
||||||
|
|
||||||
if hc.auto() {
|
if hc.auto() {
|
||||||
go hc.process()
|
go hc.process()
|
||||||
@ -171,8 +115,9 @@ func NewProxySetProvider(name string, interval time.Duration, filter string, exc
|
|||||||
healthCheck: hc,
|
healthCheck: hc,
|
||||||
}
|
}
|
||||||
|
|
||||||
fetcher := resource.NewFetcher[[]C.Proxy](name, interval, vehicle, proxiesParseAndFilter(filter, excludeFilter, excludeTypeArray, filterRegs, excludeFilterReg), proxiesOnUpdate(pd))
|
fetcher := newFetcher[[]C.Proxy](name, interval, vehicle, proxiesParseAndFilter(filter, filterReg), proxiesOnUpdate(pd))
|
||||||
pd.Fetcher = fetcher
|
pd.fetcher = fetcher
|
||||||
|
|
||||||
wrapper := &ProxySetProvider{pd}
|
wrapper := &ProxySetProvider{pd}
|
||||||
runtime.SetFinalizer(wrapper, stopProxyProvider)
|
runtime.SetFinalizer(wrapper, stopProxyProvider)
|
||||||
return wrapper, nil
|
return wrapper, nil
|
||||||
@ -187,7 +132,7 @@ type compatibleProvider struct {
|
|||||||
name string
|
name string
|
||||||
healthCheck *HealthCheck
|
healthCheck *HealthCheck
|
||||||
proxies []C.Proxy
|
proxies []C.Proxy
|
||||||
version uint32
|
version uint
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cp *compatibleProvider) MarshalJSON() ([]byte, error) {
|
func (cp *compatibleProvider) MarshalJSON() ([]byte, error) {
|
||||||
@ -199,7 +144,7 @@ func (cp *compatibleProvider) MarshalJSON() ([]byte, error) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cp *compatibleProvider) Version() uint32 {
|
func (cp *compatibleProvider) Version() uint {
|
||||||
return cp.version
|
return cp.version
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -262,12 +207,15 @@ func NewCompatibleProvider(name string, proxies []C.Proxy, hc *HealthCheck) (*Co
|
|||||||
func proxiesOnUpdate(pd *proxySetProvider) func([]C.Proxy) {
|
func proxiesOnUpdate(pd *proxySetProvider) func([]C.Proxy) {
|
||||||
return func(elm []C.Proxy) {
|
return func(elm []C.Proxy) {
|
||||||
pd.setProxies(elm)
|
pd.setProxies(elm)
|
||||||
pd.version += 1
|
if pd.version == math.MaxUint {
|
||||||
pd.getSubscriptionInfo()
|
pd.version = 0
|
||||||
|
} else {
|
||||||
|
pd.version++
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func proxiesParseAndFilter(filter string, excludeFilter string, excludeTypeArray []string, filterRegs []*regexp2.Regexp, excludeFilterReg *regexp2.Regexp) resource.Parser[[]C.Proxy] {
|
func proxiesParseAndFilter(filter string, filterReg *regexp2.Regexp) parser[[]C.Proxy] {
|
||||||
return func(buf []byte) ([]C.Proxy, error) {
|
return func(buf []byte) ([]C.Proxy, error) {
|
||||||
schema := &ProxySchema{}
|
schema := &ProxySchema{}
|
||||||
|
|
||||||
@ -284,60 +232,18 @@ func proxiesParseAndFilter(filter string, excludeFilter string, excludeTypeArray
|
|||||||
}
|
}
|
||||||
|
|
||||||
proxies := []C.Proxy{}
|
proxies := []C.Proxy{}
|
||||||
proxiesSet := map[string]struct{}{}
|
|
||||||
for _, filterReg := range filterRegs {
|
|
||||||
for idx, mapping := range schema.Proxies {
|
for idx, mapping := range schema.Proxies {
|
||||||
if nil != excludeTypeArray && len(excludeTypeArray) > 0 {
|
name, ok := mapping["name"]
|
||||||
mType, ok := mapping["type"]
|
mat, _ := filterReg.FindStringMatch(name.(string))
|
||||||
if !ok {
|
if ok && len(filter) > 0 && mat == nil {
|
||||||
continue
|
|
||||||
}
|
|
||||||
pType, ok := mType.(string)
|
|
||||||
if !ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
flag := false
|
|
||||||
for i := range excludeTypeArray {
|
|
||||||
if strings.EqualFold(pType, excludeTypeArray[i]) {
|
|
||||||
flag = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
if flag {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
mName, ok := mapping["name"]
|
|
||||||
if !ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
name, ok := mName.(string)
|
|
||||||
if !ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if len(excludeFilter) > 0 {
|
|
||||||
if mat, _ := excludeFilterReg.FindStringMatch(name); mat != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(filter) > 0 {
|
|
||||||
if mat, _ := filterReg.FindStringMatch(name); mat == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if _, ok := proxiesSet[name]; ok {
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
proxy, err := adapter.ParseProxy(mapping)
|
proxy, err := adapter.ParseProxy(mapping)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("proxy %d error: %w", idx, err)
|
return nil, fmt.Errorf("proxy %d error: %w", idx, err)
|
||||||
}
|
}
|
||||||
proxiesSet[name] = struct{}{}
|
|
||||||
proxies = append(proxies, proxy)
|
proxies = append(proxies, proxy)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if len(proxies) == 0 {
|
if len(proxies) == 0 {
|
||||||
if len(filter) > 0 {
|
if len(filter) > 0 {
|
||||||
|
@ -1,57 +0,0 @@
|
|||||||
package provider
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/dlclark/regexp2"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
type SubscriptionInfo struct {
|
|
||||||
Upload int64
|
|
||||||
Download int64
|
|
||||||
Total int64
|
|
||||||
Expire int64
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewSubscriptionInfo(str string) (si *SubscriptionInfo, err error) {
|
|
||||||
si = &SubscriptionInfo{}
|
|
||||||
str = strings.ToLower(str)
|
|
||||||
reTraffic := regexp2.MustCompile("upload=(\\d+); download=(\\d+); total=(\\d+)", 0)
|
|
||||||
reExpire := regexp2.MustCompile("expire=(\\d+)", 0)
|
|
||||||
|
|
||||||
match, err := reTraffic.FindStringMatch(str)
|
|
||||||
if err != nil || match == nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
group := match.Groups()
|
|
||||||
si.Upload, err = str2uint64(group[1].String())
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
si.Download, err = str2uint64(group[2].String())
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
si.Total, err = str2uint64(group[3].String())
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
match, _ = reExpire.FindStringMatch(str)
|
|
||||||
if match != nil {
|
|
||||||
group = match.Groups()
|
|
||||||
si.Expire, err = str2uint64(group[1].String())
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func str2uint64(str string) (int64, error) {
|
|
||||||
i, err := strconv.ParseInt(str, 10, 64)
|
|
||||||
return i, err
|
|
||||||
}
|
|
@ -1,13 +1,14 @@
|
|||||||
package resource
|
package provider
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
clashHttp "github.com/Dreamacro/clash/component/http"
|
|
||||||
types "github.com/Dreamacro/clash/constant/provider"
|
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
netHttp "github.com/Dreamacro/clash/component/http"
|
||||||
|
types "github.com/Dreamacro/clash/constant/provider"
|
||||||
)
|
)
|
||||||
|
|
||||||
type FileVehicle struct {
|
type FileVehicle struct {
|
||||||
@ -35,10 +36,6 @@ type HTTPVehicle struct {
|
|||||||
path string
|
path string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *HTTPVehicle) Url() string {
|
|
||||||
return h.url
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *HTTPVehicle) Type() types.VehicleType {
|
func (h *HTTPVehicle) Type() types.VehicleType {
|
||||||
return types.HTTP
|
return types.HTTP
|
||||||
}
|
}
|
||||||
@ -50,7 +47,7 @@ func (h *HTTPVehicle) Path() string {
|
|||||||
func (h *HTTPVehicle) Read() ([]byte, error) {
|
func (h *HTTPVehicle) Read() ([]byte, error) {
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*20)
|
ctx, cancel := context.WithTimeout(context.Background(), time.Second*20)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
resp, err := clashHttp.HttpRequest(ctx, h.url, http.MethodGet, nil, nil)
|
resp, err := netHttp.HttpRequest(ctx, h.url, http.MethodGet, nil, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
@ -1,25 +0,0 @@
|
|||||||
package buf
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/sagernet/sing/common"
|
|
||||||
"github.com/sagernet/sing/common/buf"
|
|
||||||
)
|
|
||||||
|
|
||||||
const BufferSize = buf.BufferSize
|
|
||||||
|
|
||||||
type Buffer = buf.Buffer
|
|
||||||
|
|
||||||
var New = buf.New
|
|
||||||
var StackNew = buf.StackNew
|
|
||||||
var StackNewSize = buf.StackNewSize
|
|
||||||
var With = buf.With
|
|
||||||
|
|
||||||
var KeepAlive = common.KeepAlive
|
|
||||||
|
|
||||||
//go:norace
|
|
||||||
func Dup[T any](obj T) T {
|
|
||||||
return common.Dup(obj)
|
|
||||||
}
|
|
||||||
|
|
||||||
var Must = common.Must
|
|
||||||
var Error = common.Error
|
|
106
common/cache/cache.go
vendored
Normal file
106
common/cache/cache.go
vendored
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
package cache
|
||||||
|
|
||||||
|
import (
|
||||||
|
"runtime"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Cache store element with a expired time
|
||||||
|
type Cache[K comparable, V any] struct {
|
||||||
|
*cache[K, V]
|
||||||
|
}
|
||||||
|
|
||||||
|
type cache[K comparable, V any] struct {
|
||||||
|
mapping sync.Map
|
||||||
|
janitor *janitor[K, V]
|
||||||
|
}
|
||||||
|
|
||||||
|
type element[V any] struct {
|
||||||
|
Expired time.Time
|
||||||
|
Payload V
|
||||||
|
}
|
||||||
|
|
||||||
|
// Put element in Cache with its ttl
|
||||||
|
func (c *cache[K, V]) Put(key K, payload V, ttl time.Duration) {
|
||||||
|
c.mapping.Store(key, &element[V]{
|
||||||
|
Payload: payload,
|
||||||
|
Expired: time.Now().Add(ttl),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get element in Cache, and drop when it expired
|
||||||
|
func (c *cache[K, V]) Get(key K) V {
|
||||||
|
item, exist := c.mapping.Load(key)
|
||||||
|
if !exist {
|
||||||
|
return getZero[V]()
|
||||||
|
}
|
||||||
|
elm := item.(*element[V])
|
||||||
|
// expired
|
||||||
|
if time.Since(elm.Expired) > 0 {
|
||||||
|
c.mapping.Delete(key)
|
||||||
|
return getZero[V]()
|
||||||
|
}
|
||||||
|
return elm.Payload
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetWithExpire element in Cache with Expire Time
|
||||||
|
func (c *cache[K, V]) GetWithExpire(key K) (payload V, expired time.Time) {
|
||||||
|
item, exist := c.mapping.Load(key)
|
||||||
|
if !exist {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
elm := item.(*element[V])
|
||||||
|
// expired
|
||||||
|
if time.Since(elm.Expired) > 0 {
|
||||||
|
c.mapping.Delete(key)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return elm.Payload, elm.Expired
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *cache[K, V]) cleanup() {
|
||||||
|
c.mapping.Range(func(k, v any) bool {
|
||||||
|
key := k.(string)
|
||||||
|
elm := v.(*element[V])
|
||||||
|
if time.Since(elm.Expired) > 0 {
|
||||||
|
c.mapping.Delete(key)
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
type janitor[K comparable, V any] struct {
|
||||||
|
interval time.Duration
|
||||||
|
stop chan struct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j *janitor[K, V]) process(c *cache[K, V]) {
|
||||||
|
ticker := time.NewTicker(j.interval)
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ticker.C:
|
||||||
|
c.cleanup()
|
||||||
|
case <-j.stop:
|
||||||
|
ticker.Stop()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func stopJanitor[K comparable, V any](c *Cache[K, V]) {
|
||||||
|
c.janitor.stop <- struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// New return *Cache
|
||||||
|
func New[K comparable, V any](interval time.Duration) *Cache[K, V] {
|
||||||
|
j := &janitor[K, V]{
|
||||||
|
interval: interval,
|
||||||
|
stop: make(chan struct{}),
|
||||||
|
}
|
||||||
|
c := &cache[K, V]{janitor: j}
|
||||||
|
go j.process(c)
|
||||||
|
C := &Cache[K, V]{c}
|
||||||
|
runtime.SetFinalizer(C, stopJanitor[K, V])
|
||||||
|
return C
|
||||||
|
}
|
72
common/cache/cache_test.go
vendored
Normal file
72
common/cache/cache_test.go
vendored
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
package cache
|
||||||
|
|
||||||
|
import (
|
||||||
|
"runtime"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCache_Basic(t *testing.T) {
|
||||||
|
interval := 200 * time.Millisecond
|
||||||
|
ttl := 20 * time.Millisecond
|
||||||
|
c := New[string, int](interval)
|
||||||
|
c.Put("int", 1, ttl)
|
||||||
|
|
||||||
|
d := New[string, string](interval)
|
||||||
|
d.Put("string", "a", ttl)
|
||||||
|
|
||||||
|
i := c.Get("int")
|
||||||
|
assert.Equal(t, i, 1, "should recv 1")
|
||||||
|
|
||||||
|
s := d.Get("string")
|
||||||
|
assert.Equal(t, s, "a", "should recv 'a'")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCache_TTL(t *testing.T) {
|
||||||
|
interval := 200 * time.Millisecond
|
||||||
|
ttl := 20 * time.Millisecond
|
||||||
|
now := time.Now()
|
||||||
|
c := New[string, int](interval)
|
||||||
|
c.Put("int", 1, ttl)
|
||||||
|
c.Put("int2", 2, ttl)
|
||||||
|
|
||||||
|
i := c.Get("int")
|
||||||
|
_, expired := c.GetWithExpire("int2")
|
||||||
|
assert.Equal(t, i, 1, "should recv 1")
|
||||||
|
assert.True(t, now.Before(expired))
|
||||||
|
|
||||||
|
time.Sleep(ttl * 2)
|
||||||
|
i = c.Get("int")
|
||||||
|
j, _ := c.GetWithExpire("int2")
|
||||||
|
assert.True(t, i == 0, "should recv 0")
|
||||||
|
assert.True(t, j == 0, "should recv 0")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCache_AutoCleanup(t *testing.T) {
|
||||||
|
interval := 10 * time.Millisecond
|
||||||
|
ttl := 15 * time.Millisecond
|
||||||
|
c := New[string, int](interval)
|
||||||
|
c.Put("int", 1, ttl)
|
||||||
|
|
||||||
|
time.Sleep(ttl * 2)
|
||||||
|
i := c.Get("int")
|
||||||
|
j, _ := c.GetWithExpire("int")
|
||||||
|
assert.True(t, i == 0, "should recv 0")
|
||||||
|
assert.True(t, j == 0, "should recv 0")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCache_AutoGC(t *testing.T) {
|
||||||
|
sign := make(chan struct{})
|
||||||
|
go func() {
|
||||||
|
interval := 10 * time.Millisecond
|
||||||
|
ttl := 15 * time.Millisecond
|
||||||
|
c := New[string, int](interval)
|
||||||
|
c.Put("int", 1, ttl)
|
||||||
|
sign <- struct{}{}
|
||||||
|
}()
|
||||||
|
|
||||||
|
<-sign
|
||||||
|
runtime.GC()
|
||||||
|
}
|
4
common/cache/lrucache.go
vendored
4
common/cache/lrucache.go
vendored
@ -65,8 +65,8 @@ type LruCache[K comparable, V any] struct {
|
|||||||
onEvict EvictCallback[K, V]
|
onEvict EvictCallback[K, V]
|
||||||
}
|
}
|
||||||
|
|
||||||
// New creates an LruCache
|
// NewLRUCache creates an LruCache
|
||||||
func New[K comparable, V any](options ...Option[K, V]) *LruCache[K, V] {
|
func NewLRUCache[K comparable, V any](options ...Option[K, V]) *LruCache[K, V] {
|
||||||
lc := &LruCache[K, V]{
|
lc := &LruCache[K, V]{
|
||||||
lru: list.New[*entry[K, V]](),
|
lru: list.New[*entry[K, V]](),
|
||||||
cache: make(map[K]*list.Element[*entry[K, V]]),
|
cache: make(map[K]*list.Element[*entry[K, V]]),
|
||||||
|
20
common/cache/lrucache_test.go
vendored
20
common/cache/lrucache_test.go
vendored
@ -19,7 +19,7 @@ var entries = []struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestLRUCache(t *testing.T) {
|
func TestLRUCache(t *testing.T) {
|
||||||
c := New[string, string]()
|
c := NewLRUCache[string, string]()
|
||||||
|
|
||||||
for _, e := range entries {
|
for _, e := range entries {
|
||||||
c.Set(e.key, e.value)
|
c.Set(e.key, e.value)
|
||||||
@ -45,7 +45,7 @@ func TestLRUCache(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestLRUMaxAge(t *testing.T) {
|
func TestLRUMaxAge(t *testing.T) {
|
||||||
c := New[string, string](WithAge[string, string](86400))
|
c := NewLRUCache[string, string](WithAge[string, string](86400))
|
||||||
|
|
||||||
now := time.Now().Unix()
|
now := time.Now().Unix()
|
||||||
expected := now + 86400
|
expected := now + 86400
|
||||||
@ -88,7 +88,7 @@ func TestLRUMaxAge(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestLRUpdateOnGet(t *testing.T) {
|
func TestLRUpdateOnGet(t *testing.T) {
|
||||||
c := New[string, string](WithAge[string, string](86400), WithUpdateAgeOnGet[string, string]())
|
c := NewLRUCache[string, string](WithAge[string, string](86400), WithUpdateAgeOnGet[string, string]())
|
||||||
|
|
||||||
now := time.Now().Unix()
|
now := time.Now().Unix()
|
||||||
expires := now + 86400/2
|
expires := now + 86400/2
|
||||||
@ -103,7 +103,7 @@ func TestLRUpdateOnGet(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestMaxSize(t *testing.T) {
|
func TestMaxSize(t *testing.T) {
|
||||||
c := New[string, string](WithSize[string, string](2))
|
c := NewLRUCache[string, string](WithSize[string, string](2))
|
||||||
// Add one expired entry
|
// Add one expired entry
|
||||||
c.Set("foo", "bar")
|
c.Set("foo", "bar")
|
||||||
_, ok := c.Get("foo")
|
_, ok := c.Get("foo")
|
||||||
@ -117,7 +117,7 @@ func TestMaxSize(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestExist(t *testing.T) {
|
func TestExist(t *testing.T) {
|
||||||
c := New[int, int](WithSize[int, int](1))
|
c := NewLRUCache[int, int](WithSize[int, int](1))
|
||||||
c.Set(1, 2)
|
c.Set(1, 2)
|
||||||
assert.True(t, c.Exist(1))
|
assert.True(t, c.Exist(1))
|
||||||
c.Set(2, 3)
|
c.Set(2, 3)
|
||||||
@ -130,7 +130,7 @@ func TestEvict(t *testing.T) {
|
|||||||
temp = key + value
|
temp = key + value
|
||||||
}
|
}
|
||||||
|
|
||||||
c := New[int, int](WithEvict[int, int](evict), WithSize[int, int](1))
|
c := NewLRUCache[int, int](WithEvict[int, int](evict), WithSize[int, int](1))
|
||||||
c.Set(1, 2)
|
c.Set(1, 2)
|
||||||
c.Set(2, 3)
|
c.Set(2, 3)
|
||||||
|
|
||||||
@ -138,7 +138,7 @@ func TestEvict(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestSetWithExpire(t *testing.T) {
|
func TestSetWithExpire(t *testing.T) {
|
||||||
c := New[int, *struct{}](WithAge[int, *struct{}](1))
|
c := NewLRUCache[int, *struct{}](WithAge[int, *struct{}](1))
|
||||||
now := time.Now().Unix()
|
now := time.Now().Unix()
|
||||||
|
|
||||||
tenSecBefore := time.Unix(now-10, 0)
|
tenSecBefore := time.Unix(now-10, 0)
|
||||||
@ -153,7 +153,7 @@ func TestSetWithExpire(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestStale(t *testing.T) {
|
func TestStale(t *testing.T) {
|
||||||
c := New[int, int](WithAge[int, int](1), WithStale[int, int](true))
|
c := NewLRUCache[int, int](WithAge[int, int](1), WithStale[int, int](true))
|
||||||
now := time.Now().Unix()
|
now := time.Now().Unix()
|
||||||
|
|
||||||
tenSecBefore := time.Unix(now-10, 0)
|
tenSecBefore := time.Unix(now-10, 0)
|
||||||
@ -166,11 +166,11 @@ func TestStale(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestCloneTo(t *testing.T) {
|
func TestCloneTo(t *testing.T) {
|
||||||
o := New[string, int](WithSize[string, int](10))
|
o := NewLRUCache[string, int](WithSize[string, int](10))
|
||||||
o.Set("1", 1)
|
o.Set("1", 1)
|
||||||
o.Set("2", 2)
|
o.Set("2", 2)
|
||||||
|
|
||||||
n := New[string, int](WithSize[string, int](2))
|
n := NewLRUCache[string, int](WithSize[string, int](2))
|
||||||
n.Set("3", 3)
|
n.Set("3", 3)
|
||||||
n.Set("4", 4)
|
n.Set("4", 4)
|
||||||
|
|
||||||
|
@ -1,25 +0,0 @@
|
|||||||
package callback
|
|
||||||
|
|
||||||
import (
|
|
||||||
C "github.com/Dreamacro/clash/constant"
|
|
||||||
)
|
|
||||||
|
|
||||||
type FirstWriteCallBackConn struct {
|
|
||||||
C.Conn
|
|
||||||
Callback func(error)
|
|
||||||
written bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *FirstWriteCallBackConn) Write(b []byte) (n int, err error) {
|
|
||||||
defer func() {
|
|
||||||
if !c.written {
|
|
||||||
c.written = true
|
|
||||||
c.Callback(err)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
return c.Conn.Write(b)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *FirstWriteCallBackConn) Upstream() any {
|
|
||||||
return c.Conn
|
|
||||||
}
|
|
@ -14,7 +14,6 @@ func ExecCmd(cmdStr string) (string, error) {
|
|||||||
cmd = exec.Command(args[0])
|
cmd = exec.Command(args[0])
|
||||||
} else {
|
} else {
|
||||||
cmd = exec.Command(args[0], args[1:]...)
|
cmd = exec.Command(args[0], args[1:]...)
|
||||||
|
|
||||||
}
|
}
|
||||||
prepareBackgroundCommand(cmd)
|
prepareBackgroundCommand(cmd)
|
||||||
out, err := cmd.CombinedOutput()
|
out, err := cmd.CombinedOutput()
|
||||||
|
@ -7,5 +7,4 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func prepareBackgroundCommand(cmd *exec.Cmd) {
|
func prepareBackgroundCommand(cmd *exec.Cmd) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,45 +0,0 @@
|
|||||||
package convert
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/base64"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
encRaw = base64.RawStdEncoding
|
|
||||||
enc = base64.StdEncoding
|
|
||||||
)
|
|
||||||
|
|
||||||
// DecodeBase64 try to decode content from the given bytes,
|
|
||||||
// which can be in base64.RawStdEncoding, base64.StdEncoding or just plaintext.
|
|
||||||
func DecodeBase64(buf []byte) []byte {
|
|
||||||
result, err := tryDecodeBase64(buf)
|
|
||||||
if err != nil {
|
|
||||||
return buf
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
func tryDecodeBase64(buf []byte) ([]byte, error) {
|
|
||||||
dBuf := make([]byte, encRaw.DecodedLen(len(buf)))
|
|
||||||
n, err := encRaw.Decode(dBuf, buf)
|
|
||||||
if err != nil {
|
|
||||||
n, err = enc.Decode(dBuf, buf)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return dBuf[:n], nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func urlSafe(data string) string {
|
|
||||||
return strings.NewReplacer("+", "-", "/", "_").Replace(data)
|
|
||||||
}
|
|
||||||
|
|
||||||
func decodeUrlSafe(data string) string {
|
|
||||||
dcBuf, err := base64.RawURLEncoding.DecodeString(data)
|
|
||||||
if err != nil {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
return string(dcBuf)
|
|
||||||
}
|
|
@ -5,15 +5,39 @@ import (
|
|||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/Dreamacro/clash/log"
|
|
||||||
"net/url"
|
"net/url"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var enc = base64.StdEncoding
|
||||||
|
|
||||||
|
func DecodeBase64(buf []byte) ([]byte, error) {
|
||||||
|
dBuf := make([]byte, enc.DecodedLen(len(buf)))
|
||||||
|
n, err := enc.Decode(dBuf, buf)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return dBuf[:n], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecodeBase64StringToString decode base64 string to string
|
||||||
|
func DecodeBase64StringToString(s string) (string, error) {
|
||||||
|
dBuf, err := enc.DecodeString(s)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(dBuf), nil
|
||||||
|
}
|
||||||
|
|
||||||
// ConvertsV2Ray convert V2Ray subscribe proxies data to clash proxies config
|
// ConvertsV2Ray convert V2Ray subscribe proxies data to clash proxies config
|
||||||
func ConvertsV2Ray(buf []byte) ([]map[string]any, error) {
|
func ConvertsV2Ray(buf []byte) ([]map[string]any, error) {
|
||||||
data := DecodeBase64(buf)
|
data, err := DecodeBase64(buf)
|
||||||
|
if err != nil {
|
||||||
|
data = buf
|
||||||
|
}
|
||||||
|
|
||||||
arr := strings.Split(string(data), "\n")
|
arr := strings.Split(string(data), "\n")
|
||||||
|
|
||||||
@ -49,19 +73,11 @@ func ConvertsV2Ray(buf []byte) ([]map[string]any, error) {
|
|||||||
hysteria["port"] = urlHysteria.Port()
|
hysteria["port"] = urlHysteria.Port()
|
||||||
hysteria["sni"] = query.Get("peer")
|
hysteria["sni"] = query.Get("peer")
|
||||||
hysteria["obfs"] = query.Get("obfs")
|
hysteria["obfs"] = query.Get("obfs")
|
||||||
hysteria["alpn"] = []string{query.Get("alpn")}
|
hysteria["alpn"] = query.Get("alpn")
|
||||||
hysteria["auth_str"] = query.Get("auth")
|
hysteria["auth_str"] = query.Get("auth")
|
||||||
hysteria["protocol"] = query.Get("protocol")
|
hysteria["protocol"] = query.Get("protocol")
|
||||||
up := query.Get("up")
|
hysteria["down_mbps"], _ = strconv.Atoi(query.Get("downmbps"))
|
||||||
down := query.Get("down")
|
hysteria["up_mbps"], _ = strconv.Atoi(query.Get("upmbps"))
|
||||||
if up == "" {
|
|
||||||
up = query.Get("upmbps")
|
|
||||||
}
|
|
||||||
if down == "" {
|
|
||||||
down = query.Get("downmbps")
|
|
||||||
}
|
|
||||||
hysteria["down"] = down
|
|
||||||
hysteria["up"] = up
|
|
||||||
hysteria["skip-cert-verify"], _ = strconv.ParseBool(query.Get("insecure"))
|
hysteria["skip-cert-verify"], _ = strconv.ParseBool(query.Get("insecure"))
|
||||||
|
|
||||||
proxies = append(proxies, hysteria)
|
proxies = append(proxies, hysteria)
|
||||||
@ -83,7 +99,7 @@ func ConvertsV2Ray(buf []byte) ([]map[string]any, error) {
|
|||||||
trojan["port"] = urlTrojan.Port()
|
trojan["port"] = urlTrojan.Port()
|
||||||
trojan["password"] = urlTrojan.User.Username()
|
trojan["password"] = urlTrojan.User.Username()
|
||||||
trojan["udp"] = true
|
trojan["udp"] = true
|
||||||
trojan["skip-cert-verify"], _ = strconv.ParseBool(query.Get("allowInsecure"))
|
trojan["skip-cert-verify"] = false
|
||||||
|
|
||||||
sni := query.Get("sni")
|
sni := query.Get("sni")
|
||||||
if sni != "" {
|
if sni != "" {
|
||||||
@ -100,6 +116,7 @@ func ConvertsV2Ray(buf []byte) ([]map[string]any, error) {
|
|||||||
headers := make(map[string]any)
|
headers := make(map[string]any)
|
||||||
wsOpts := make(map[string]any)
|
wsOpts := make(map[string]any)
|
||||||
|
|
||||||
|
// headers["Host"] = RandHost()
|
||||||
headers["User-Agent"] = RandUserAgent()
|
headers["User-Agent"] = RandUserAgent()
|
||||||
|
|
||||||
wsOpts["path"] = query.Get("path")
|
wsOpts["path"] = query.Get("path")
|
||||||
@ -113,54 +130,97 @@ func ConvertsV2Ray(buf []byte) ([]map[string]any, error) {
|
|||||||
trojan["grpc-opts"] = grpcOpts
|
trojan["grpc-opts"] = grpcOpts
|
||||||
}
|
}
|
||||||
|
|
||||||
if fingerprint := query.Get("fp"); fingerprint == "" {
|
|
||||||
trojan["client-fingerprint"] = "chrome"
|
|
||||||
} else {
|
|
||||||
trojan["client-fingerprint"] = fingerprint
|
|
||||||
}
|
|
||||||
|
|
||||||
proxies = append(proxies, trojan)
|
proxies = append(proxies, trojan)
|
||||||
|
|
||||||
case "vless":
|
case "vless":
|
||||||
urlVLess, err := url.Parse(line)
|
urlVless, err := url.Parse(line)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
query := urlVLess.Query()
|
|
||||||
|
query := urlVless.Query()
|
||||||
|
|
||||||
|
name := uniqueName(names, urlVless.Fragment)
|
||||||
vless := make(map[string]any, 20)
|
vless := make(map[string]any, 20)
|
||||||
err = handleVShareLink(names, urlVLess, scheme, vless)
|
|
||||||
if err != nil {
|
vless["name"] = name
|
||||||
log.Warnln("error:%s line:%s", err.Error(), line)
|
vless["type"] = scheme
|
||||||
continue
|
vless["server"] = urlVless.Hostname()
|
||||||
|
vless["port"] = urlVless.Port()
|
||||||
|
vless["uuid"] = urlVless.User.Username()
|
||||||
|
vless["udp"] = true
|
||||||
|
vless["skip-cert-verify"] = false
|
||||||
|
|
||||||
|
sni := query.Get("sni")
|
||||||
|
if sni != "" {
|
||||||
|
vless["servername"] = sni
|
||||||
}
|
}
|
||||||
if flow := query.Get("flow"); flow != "" {
|
|
||||||
vless["flow"] = strings.ToLower(flow)
|
flow := strings.ToLower(query.Get("flow"))
|
||||||
|
if flow != "" {
|
||||||
|
vless["flow"] = flow
|
||||||
}
|
}
|
||||||
|
|
||||||
|
network := strings.ToLower(query.Get("type"))
|
||||||
|
if network != "" {
|
||||||
|
fakeType := strings.ToLower(query.Get("headerType"))
|
||||||
|
if network == "tcp" && fakeType == "http" {
|
||||||
|
network = "http"
|
||||||
|
}
|
||||||
|
if network == "http" {
|
||||||
|
network = "h2"
|
||||||
|
}
|
||||||
|
vless["network"] = network
|
||||||
|
}
|
||||||
|
|
||||||
|
switch network {
|
||||||
|
case "http":
|
||||||
|
headers := make(map[string]any)
|
||||||
|
httpOpts := make(map[string]any)
|
||||||
|
|
||||||
|
if query.Get("method") != "" {
|
||||||
|
httpOpts["method"] = query.Get("method")
|
||||||
|
}
|
||||||
|
if query.Get("path") != "" {
|
||||||
|
httpOpts["path"] = query.Get("path")
|
||||||
|
}
|
||||||
|
headers["User-Agent"] = RandUserAgent()
|
||||||
|
httpOpts["headers"] = headers
|
||||||
|
|
||||||
|
vless["http-opts"] = httpOpts
|
||||||
|
|
||||||
|
case "h2":
|
||||||
|
headers := make(map[string]any)
|
||||||
|
h2Opts := make(map[string]any)
|
||||||
|
|
||||||
|
headers["User-Agent"] = RandUserAgent()
|
||||||
|
h2Opts["path"] = query.Get("path")
|
||||||
|
h2Opts["headers"] = headers
|
||||||
|
|
||||||
|
vless["h2-opts"] = h2Opts
|
||||||
|
|
||||||
|
case "ws":
|
||||||
|
headers := make(map[string]any)
|
||||||
|
wsOpts := make(map[string]any)
|
||||||
|
|
||||||
|
// headers["Host"] = RandHost()
|
||||||
|
headers["User-Agent"] = RandUserAgent()
|
||||||
|
wsOpts["path"] = query.Get("path")
|
||||||
|
wsOpts["headers"] = headers
|
||||||
|
|
||||||
|
vless["ws-opts"] = wsOpts
|
||||||
|
|
||||||
|
case "grpc":
|
||||||
|
grpcOpts := make(map[string]any)
|
||||||
|
grpcOpts["grpc-service-name"] = query.Get("serviceName")
|
||||||
|
vless["grpc-opts"] = grpcOpts
|
||||||
|
}
|
||||||
|
|
||||||
proxies = append(proxies, vless)
|
proxies = append(proxies, vless)
|
||||||
|
|
||||||
case "vmess":
|
case "vmess":
|
||||||
// V2RayN-styled share link
|
dcBuf, err := enc.DecodeString(body)
|
||||||
// https://github.com/2dust/v2rayN/wiki/%E5%88%86%E4%BA%AB%E9%93%BE%E6%8E%A5%E6%A0%BC%E5%BC%8F%E8%AF%B4%E6%98%8E(ver-2)
|
|
||||||
dcBuf, err := tryDecodeBase64([]byte(body))
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// Xray VMessAEAD share link
|
|
||||||
urlVMess, err := url.Parse(line)
|
|
||||||
if err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
query := urlVMess.Query()
|
|
||||||
vmess := make(map[string]any, 20)
|
|
||||||
err = handleVShareLink(names, urlVMess, scheme, vmess)
|
|
||||||
if err != nil {
|
|
||||||
log.Warnln("error:%s line:%s", err.Error(), line)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
vmess["alterId"] = 0
|
|
||||||
vmess["cipher"] = "auto"
|
|
||||||
if encryption := query.Get("encryption"); encryption != "" {
|
|
||||||
vmess["cipher"] = encryption
|
|
||||||
}
|
|
||||||
proxies = append(proxies, vmess)
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -170,11 +230,8 @@ func ConvertsV2Ray(buf []byte) ([]map[string]any, error) {
|
|||||||
if jsonDc.Decode(&values) != nil {
|
if jsonDc.Decode(&values) != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
tempName, ok := values["ps"].(string)
|
|
||||||
if !ok {
|
name := uniqueName(names, values["ps"].(string))
|
||||||
continue
|
|
||||||
}
|
|
||||||
name := uniqueName(names, tempName)
|
|
||||||
vmess := make(map[string]any, 20)
|
vmess := make(map[string]any, 20)
|
||||||
|
|
||||||
vmess["name"] = name
|
vmess["name"] = name
|
||||||
@ -182,35 +239,26 @@ func ConvertsV2Ray(buf []byte) ([]map[string]any, error) {
|
|||||||
vmess["server"] = values["add"]
|
vmess["server"] = values["add"]
|
||||||
vmess["port"] = values["port"]
|
vmess["port"] = values["port"]
|
||||||
vmess["uuid"] = values["id"]
|
vmess["uuid"] = values["id"]
|
||||||
if alterId, ok := values["aid"]; ok {
|
vmess["alterId"] = values["aid"]
|
||||||
vmess["alterId"] = alterId
|
vmess["cipher"] = "auto"
|
||||||
} else {
|
|
||||||
vmess["alterId"] = 0
|
|
||||||
}
|
|
||||||
vmess["udp"] = true
|
vmess["udp"] = true
|
||||||
vmess["xudp"] = true
|
|
||||||
vmess["tls"] = false
|
|
||||||
vmess["skip-cert-verify"] = false
|
vmess["skip-cert-verify"] = false
|
||||||
|
|
||||||
vmess["cipher"] = "auto"
|
sni := values["sni"]
|
||||||
if cipher, ok := values["scy"]; ok && cipher != "" {
|
if sni != "" {
|
||||||
vmess["cipher"] = cipher
|
vmess["sni"] = sni
|
||||||
}
|
|
||||||
|
|
||||||
if sni, ok := values["sni"]; ok && sni != "" {
|
|
||||||
vmess["servername"] = sni
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
host := values["host"]
|
||||||
network := strings.ToLower(values["net"].(string))
|
network := strings.ToLower(values["net"].(string))
|
||||||
if values["type"] == "http" {
|
|
||||||
network = "http"
|
|
||||||
} else if network == "http" {
|
|
||||||
network = "h2"
|
|
||||||
}
|
|
||||||
vmess["network"] = network
|
vmess["network"] = network
|
||||||
|
|
||||||
tls := strings.ToLower(values["tls"].(string))
|
tls := strings.ToLower(values["tls"].(string))
|
||||||
if strings.HasSuffix(tls, "tls") {
|
if tls != "" && tls != "0" && tls != "null" {
|
||||||
|
if host != nil {
|
||||||
|
vmess["servername"] = host
|
||||||
|
}
|
||||||
vmess["tls"] = true
|
vmess["tls"] = true
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -218,13 +266,11 @@ func ConvertsV2Ray(buf []byte) ([]map[string]any, error) {
|
|||||||
case "http":
|
case "http":
|
||||||
headers := make(map[string]any)
|
headers := make(map[string]any)
|
||||||
httpOpts := make(map[string]any)
|
httpOpts := make(map[string]any)
|
||||||
if host, ok := values["host"]; ok && host != "" {
|
|
||||||
headers["Host"] = []string{host.(string)}
|
// headers["Host"] = RandHost()
|
||||||
}
|
headers["User-Agent"] = RandUserAgent()
|
||||||
httpOpts["path"] = []string{"/"}
|
httpOpts["method"] = values["method"]
|
||||||
if path, ok := values["path"]; ok && path != "" {
|
httpOpts["path"] = values["path"]
|
||||||
httpOpts["path"] = []string{path.(string)}
|
|
||||||
}
|
|
||||||
httpOpts["headers"] = headers
|
httpOpts["headers"] = headers
|
||||||
|
|
||||||
vmess["http-opts"] = httpOpts
|
vmess["http-opts"] = httpOpts
|
||||||
@ -232,10 +278,9 @@ func ConvertsV2Ray(buf []byte) ([]map[string]any, error) {
|
|||||||
case "h2":
|
case "h2":
|
||||||
headers := make(map[string]any)
|
headers := make(map[string]any)
|
||||||
h2Opts := make(map[string]any)
|
h2Opts := make(map[string]any)
|
||||||
if host, ok := values["host"]; ok && host != "" {
|
|
||||||
headers["Host"] = []string{host.(string)}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// headers["Host"] = RandHost()
|
||||||
|
headers["User-Agent"] = RandUserAgent()
|
||||||
h2Opts["path"] = values["path"]
|
h2Opts["path"] = values["path"]
|
||||||
h2Opts["headers"] = headers
|
h2Opts["headers"] = headers
|
||||||
|
|
||||||
@ -244,14 +289,15 @@ func ConvertsV2Ray(buf []byte) ([]map[string]any, error) {
|
|||||||
case "ws":
|
case "ws":
|
||||||
headers := make(map[string]any)
|
headers := make(map[string]any)
|
||||||
wsOpts := make(map[string]any)
|
wsOpts := make(map[string]any)
|
||||||
wsOpts["path"] = []string{"/"}
|
|
||||||
if host, ok := values["host"]; ok && host != "" {
|
headers["Host"] = RandHost()
|
||||||
headers["Host"] = host.(string)
|
headers["User-Agent"] = RandUserAgent()
|
||||||
}
|
|
||||||
if path, ok := values["path"]; ok && path != "" {
|
if values["path"] != nil {
|
||||||
wsOpts["path"] = path.(string)
|
wsOpts["path"] = values["path"]
|
||||||
}
|
}
|
||||||
wsOpts["headers"] = headers
|
wsOpts["headers"] = headers
|
||||||
|
|
||||||
vmess["ws-opts"] = wsOpts
|
vmess["ws-opts"] = wsOpts
|
||||||
|
|
||||||
case "grpc":
|
case "grpc":
|
||||||
@ -272,7 +318,7 @@ func ConvertsV2Ray(buf []byte) ([]map[string]any, error) {
|
|||||||
port := urlSS.Port()
|
port := urlSS.Port()
|
||||||
|
|
||||||
if port == "" {
|
if port == "" {
|
||||||
dcBuf, err := encRaw.DecodeString(urlSS.Host)
|
dcBuf, err := enc.DecodeString(urlSS.Host)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@ -284,28 +330,23 @@ func ConvertsV2Ray(buf []byte) ([]map[string]any, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
cipherRaw = urlSS.User.Username()
|
cipher = urlSS.User.Username()
|
||||||
cipher string
|
|
||||||
password string
|
password string
|
||||||
)
|
)
|
||||||
cipher = cipherRaw
|
|
||||||
if password, found = urlSS.User.Password(); !found {
|
if password, found = urlSS.User.Password(); !found {
|
||||||
dcBuf, err := base64.RawURLEncoding.DecodeString(cipherRaw)
|
dcBuf, err := enc.DecodeString(cipher)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
dcBuf, _ = enc.DecodeString(cipherRaw)
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
cipher, password, found = strings.Cut(string(dcBuf), ":")
|
cipher, password, found = strings.Cut(string(dcBuf), ":")
|
||||||
if !found {
|
if !found {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
err = VerifyMethod(cipher, password)
|
|
||||||
if err != nil {
|
|
||||||
dcBuf, _ = encRaw.DecodeString(cipherRaw)
|
|
||||||
cipher, password, found = strings.Cut(string(dcBuf), ":")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ss := make(map[string]any, 10)
|
ss := make(map[string]any, 20)
|
||||||
|
|
||||||
ss["name"] = name
|
ss["name"] = name
|
||||||
ss["type"] = scheme
|
ss["type"] = scheme
|
||||||
@ -313,22 +354,11 @@ func ConvertsV2Ray(buf []byte) ([]map[string]any, error) {
|
|||||||
ss["port"] = urlSS.Port()
|
ss["port"] = urlSS.Port()
|
||||||
ss["cipher"] = cipher
|
ss["cipher"] = cipher
|
||||||
ss["password"] = password
|
ss["password"] = password
|
||||||
query := urlSS.Query()
|
|
||||||
ss["udp"] = true
|
ss["udp"] = true
|
||||||
if query.Get("udp-over-tcp") == "true" || query.Get("uot") == "1" {
|
|
||||||
ss["udp-over-tcp"] = true
|
|
||||||
}
|
|
||||||
if strings.Contains(query.Get("plugin"), "obfs") {
|
|
||||||
obfsParams := strings.Split(query.Get("plugin"), ";")
|
|
||||||
ss["plugin"] = "obfs"
|
|
||||||
ss["plugin-opts"] = map[string]any{
|
|
||||||
"host": obfsParams[2][10:],
|
|
||||||
"mode": obfsParams[1][5:],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
proxies = append(proxies, ss)
|
proxies = append(proxies, ss)
|
||||||
case "ssr":
|
case "ssr":
|
||||||
dcBuf, err := encRaw.DecodeString(body)
|
dcBuf, err := enc.DecodeString(body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@ -395,6 +425,18 @@ func ConvertsV2Ray(buf []byte) ([]map[string]any, error) {
|
|||||||
return proxies, nil
|
return proxies, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func urlSafe(data string) string {
|
||||||
|
return strings.ReplaceAll(strings.ReplaceAll(data, "+", "-"), "/", "_")
|
||||||
|
}
|
||||||
|
|
||||||
|
func decodeUrlSafe(data string) string {
|
||||||
|
dcBuf, err := base64.URLEncoding.DecodeString(data)
|
||||||
|
if err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return string(dcBuf)
|
||||||
|
}
|
||||||
|
|
||||||
func uniqueName(names map[string]int, name string) string {
|
func uniqueName(names map[string]int, name string) string {
|
||||||
if index, ok := names[name]; ok {
|
if index, ok := names[name]; ok {
|
||||||
index++
|
index++
|
||||||
|
@ -2,14 +2,11 @@ package convert
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
|
"math/rand"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/common/utils"
|
"github.com/gofrs/uuid"
|
||||||
|
|
||||||
"github.com/metacubex/sing-shadowsocks/shadowimpl"
|
|
||||||
"github.com/zhangyunhao116/fastrand"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var hostsSuffix = []string{
|
var hostsSuffix = []string{
|
||||||
@ -294,7 +291,8 @@ var (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func RandHost() string {
|
func RandHost() string {
|
||||||
base := strings.ToLower(base64.RawURLEncoding.EncodeToString(utils.NewUUIDV4().Bytes()))
|
id, _ := uuid.NewV4()
|
||||||
|
base := strings.ToLower(base64.RawURLEncoding.EncodeToString(id.Bytes()))
|
||||||
base = strings.ReplaceAll(base, "-", "")
|
base = strings.ReplaceAll(base, "-", "")
|
||||||
base = strings.ReplaceAll(base, "_", "")
|
base = strings.ReplaceAll(base, "_", "")
|
||||||
buf := []byte(base)
|
buf := []byte(base)
|
||||||
@ -302,11 +300,11 @@ func RandHost() string {
|
|||||||
prefix += string(buf[6:8]) + "-"
|
prefix += string(buf[6:8]) + "-"
|
||||||
prefix += string(buf[len(buf)-8:])
|
prefix += string(buf[len(buf)-8:])
|
||||||
|
|
||||||
return prefix + hostsSuffix[fastrand.Intn(hostsLen)]
|
return prefix + hostsSuffix[rand.Intn(hostsLen)]
|
||||||
}
|
}
|
||||||
|
|
||||||
func RandUserAgent() string {
|
func RandUserAgent() string {
|
||||||
return userAgents[fastrand.Intn(uaLen)]
|
return userAgents[rand.Intn(uaLen)]
|
||||||
}
|
}
|
||||||
|
|
||||||
func SetUserAgent(header http.Header) {
|
func SetUserAgent(header http.Header) {
|
||||||
@ -316,8 +314,3 @@ func SetUserAgent(header http.Header) {
|
|||||||
userAgent := RandUserAgent()
|
userAgent := RandUserAgent()
|
||||||
header.Set("User-Agent", userAgent)
|
header.Set("User-Agent", userAgent)
|
||||||
}
|
}
|
||||||
|
|
||||||
func VerifyMethod(cipher, password string) (err error) {
|
|
||||||
_, err = shadowimpl.FetchMethod(cipher, password, time.Now)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
@ -1,129 +0,0 @@
|
|||||||
package convert
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"net/url"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
func handleVShareLink(names map[string]int, url *url.URL, scheme string, proxy map[string]any) error {
|
|
||||||
// Xray VMessAEAD / VLESS share link standard
|
|
||||||
// https://github.com/XTLS/Xray-core/discussions/716
|
|
||||||
query := url.Query()
|
|
||||||
proxy["name"] = uniqueName(names, url.Fragment)
|
|
||||||
if url.Hostname() == "" {
|
|
||||||
return errors.New("url.Hostname() is empty")
|
|
||||||
}
|
|
||||||
if url.Port() == "" {
|
|
||||||
return errors.New("url.Port() is empty")
|
|
||||||
}
|
|
||||||
proxy["type"] = scheme
|
|
||||||
proxy["server"] = url.Hostname()
|
|
||||||
proxy["port"] = url.Port()
|
|
||||||
proxy["uuid"] = url.User.Username()
|
|
||||||
proxy["udp"] = true
|
|
||||||
proxy["skip-cert-verify"] = false
|
|
||||||
proxy["tls"] = false
|
|
||||||
tls := strings.ToLower(query.Get("security"))
|
|
||||||
if strings.HasSuffix(tls, "tls") || tls == "reality" {
|
|
||||||
proxy["tls"] = true
|
|
||||||
if fingerprint := query.Get("fp"); fingerprint == "" {
|
|
||||||
proxy["client-fingerprint"] = "chrome"
|
|
||||||
} else {
|
|
||||||
proxy["client-fingerprint"] = fingerprint
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if sni := query.Get("sni"); sni != "" {
|
|
||||||
proxy["servername"] = sni
|
|
||||||
}
|
|
||||||
if realityPublicKey := query.Get("pbk"); realityPublicKey != "" {
|
|
||||||
proxy["reality-opts"] = map[string]any{
|
|
||||||
"public-key": realityPublicKey,
|
|
||||||
"short-id": query.Get("sid"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
switch query.Get("packetEncoding") {
|
|
||||||
case "none":
|
|
||||||
case "packet":
|
|
||||||
proxy["packet-addr"] = true
|
|
||||||
default:
|
|
||||||
proxy["xudp"] = true
|
|
||||||
}
|
|
||||||
|
|
||||||
network := strings.ToLower(query.Get("type"))
|
|
||||||
if network == "" {
|
|
||||||
network = "tcp"
|
|
||||||
}
|
|
||||||
fakeType := strings.ToLower(query.Get("headerType"))
|
|
||||||
if fakeType == "http" {
|
|
||||||
network = "http"
|
|
||||||
} else if network == "http" {
|
|
||||||
network = "h2"
|
|
||||||
}
|
|
||||||
proxy["network"] = network
|
|
||||||
switch network {
|
|
||||||
case "tcp":
|
|
||||||
if fakeType != "none" {
|
|
||||||
headers := make(map[string]any)
|
|
||||||
httpOpts := make(map[string]any)
|
|
||||||
httpOpts["path"] = []string{"/"}
|
|
||||||
|
|
||||||
if host := query.Get("host"); host != "" {
|
|
||||||
headers["Host"] = []string{host}
|
|
||||||
}
|
|
||||||
|
|
||||||
if method := query.Get("method"); method != "" {
|
|
||||||
httpOpts["method"] = method
|
|
||||||
}
|
|
||||||
|
|
||||||
if path := query.Get("path"); path != "" {
|
|
||||||
httpOpts["path"] = []string{path}
|
|
||||||
}
|
|
||||||
httpOpts["headers"] = headers
|
|
||||||
proxy["http-opts"] = httpOpts
|
|
||||||
}
|
|
||||||
|
|
||||||
case "http":
|
|
||||||
headers := make(map[string]any)
|
|
||||||
h2Opts := make(map[string]any)
|
|
||||||
h2Opts["path"] = []string{"/"}
|
|
||||||
if path := query.Get("path"); path != "" {
|
|
||||||
h2Opts["path"] = []string{path}
|
|
||||||
}
|
|
||||||
if host := query.Get("host"); host != "" {
|
|
||||||
h2Opts["host"] = []string{host}
|
|
||||||
}
|
|
||||||
h2Opts["headers"] = headers
|
|
||||||
proxy["h2-opts"] = h2Opts
|
|
||||||
|
|
||||||
case "ws":
|
|
||||||
headers := make(map[string]any)
|
|
||||||
wsOpts := make(map[string]any)
|
|
||||||
headers["User-Agent"] = RandUserAgent()
|
|
||||||
headers["Host"] = query.Get("host")
|
|
||||||
wsOpts["path"] = query.Get("path")
|
|
||||||
wsOpts["headers"] = headers
|
|
||||||
|
|
||||||
if earlyData := query.Get("ed"); earlyData != "" {
|
|
||||||
med, err := strconv.Atoi(earlyData)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("bad WebSocket max early data size: %v", err)
|
|
||||||
}
|
|
||||||
wsOpts["max-early-data"] = med
|
|
||||||
}
|
|
||||||
if earlyDataHeader := query.Get("eh"); earlyDataHeader != "" {
|
|
||||||
wsOpts["early-data-header-name"] = earlyDataHeader
|
|
||||||
}
|
|
||||||
|
|
||||||
proxy["ws-opts"] = wsOpts
|
|
||||||
|
|
||||||
case "grpc":
|
|
||||||
grpcOpts := make(map[string]any)
|
|
||||||
grpcOpts["grpc-service-name"] = query.Get("serviceName")
|
|
||||||
proxy["grpc-opts"] = grpcOpts
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
@ -5,10 +5,10 @@
|
|||||||
// Package list implements a doubly linked list.
|
// Package list implements a doubly linked list.
|
||||||
//
|
//
|
||||||
// To iterate over a list (where l is a *List):
|
// To iterate over a list (where l is a *List):
|
||||||
//
|
|
||||||
// for e := l.Front(); e != nil; e = e.Next() {
|
// for e := l.Front(); e != nil; e = e.Next() {
|
||||||
// // do something with e.Value
|
// // do something with e.Value
|
||||||
// }
|
// }
|
||||||
|
//
|
||||||
package list
|
package list
|
||||||
|
|
||||||
// Element is an element of a linked list.
|
// Element is an element of a linked list.
|
||||||
|
@ -1,36 +0,0 @@
|
|||||||
package net
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net"
|
|
||||||
)
|
|
||||||
|
|
||||||
type CustomAddr interface {
|
|
||||||
net.Addr
|
|
||||||
RawAddr() net.Addr
|
|
||||||
}
|
|
||||||
|
|
||||||
type customAddr struct {
|
|
||||||
networkStr string
|
|
||||||
addrStr string
|
|
||||||
rawAddr net.Addr
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a customAddr) Network() string {
|
|
||||||
return a.networkStr
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a customAddr) String() string {
|
|
||||||
return a.addrStr
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a customAddr) RawAddr() net.Addr {
|
|
||||||
return a.rawAddr
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewCustomAddr(networkStr string, addrStr string, rawAddr net.Addr) CustomAddr {
|
|
||||||
return customAddr{
|
|
||||||
networkStr: networkStr,
|
|
||||||
addrStr: addrStr,
|
|
||||||
rawAddr: rawAddr,
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,36 +0,0 @@
|
|||||||
package net
|
|
||||||
|
|
||||||
import "net"
|
|
||||||
|
|
||||||
type bindPacketConn struct {
|
|
||||||
net.PacketConn
|
|
||||||
rAddr net.Addr
|
|
||||||
}
|
|
||||||
|
|
||||||
func (wpc *bindPacketConn) Read(b []byte) (n int, err error) {
|
|
||||||
n, _, err = wpc.PacketConn.ReadFrom(b)
|
|
||||||
return n, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (wpc *bindPacketConn) Write(b []byte) (n int, err error) {
|
|
||||||
return wpc.PacketConn.WriteTo(b, wpc.rAddr)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (wpc *bindPacketConn) RemoteAddr() net.Addr {
|
|
||||||
return wpc.rAddr
|
|
||||||
}
|
|
||||||
|
|
||||||
func (wpc *bindPacketConn) LocalAddr() net.Addr {
|
|
||||||
if wpc.PacketConn.LocalAddr() == nil {
|
|
||||||
return &net.UDPAddr{IP: net.IPv4zero, Port: 0}
|
|
||||||
} else {
|
|
||||||
return wpc.PacketConn.LocalAddr()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewBindPacketConn(pc net.PacketConn, rAddr net.Addr) net.Conn {
|
|
||||||
return &bindPacketConn{
|
|
||||||
PacketConn: pc,
|
|
||||||
rAddr: rAddr,
|
|
||||||
}
|
|
||||||
}
|
|
@ -3,23 +3,18 @@ package net
|
|||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"net"
|
"net"
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/common/buf"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ ExtendedConn = (*BufferedConn)(nil)
|
|
||||||
|
|
||||||
type BufferedConn struct {
|
type BufferedConn struct {
|
||||||
r *bufio.Reader
|
r *bufio.Reader
|
||||||
ExtendedConn
|
net.Conn
|
||||||
peeked bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewBufferedConn(c net.Conn) *BufferedConn {
|
func NewBufferedConn(c net.Conn) *BufferedConn {
|
||||||
if bc, ok := c.(*BufferedConn); ok {
|
if bc, ok := c.(*BufferedConn); ok {
|
||||||
return bc
|
return bc
|
||||||
}
|
}
|
||||||
return &BufferedConn{bufio.NewReader(c), NewExtendedConn(c), false}
|
return &BufferedConn{bufio.NewReader(c), c}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reader returns the internal bufio.Reader.
|
// Reader returns the internal bufio.Reader.
|
||||||
@ -27,24 +22,11 @@ func (c *BufferedConn) Reader() *bufio.Reader {
|
|||||||
return c.r
|
return c.r
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *BufferedConn) ResetPeeked() {
|
|
||||||
c.peeked = false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *BufferedConn) Peeked() bool {
|
|
||||||
return c.peeked
|
|
||||||
}
|
|
||||||
|
|
||||||
// Peek returns the next n bytes without advancing the reader.
|
// Peek returns the next n bytes without advancing the reader.
|
||||||
func (c *BufferedConn) Peek(n int) ([]byte, error) {
|
func (c *BufferedConn) Peek(n int) ([]byte, error) {
|
||||||
c.peeked = true
|
|
||||||
return c.r.Peek(n)
|
return c.r.Peek(n)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *BufferedConn) Discard(n int) (discarded int, err error) {
|
|
||||||
return c.r.Discard(n)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *BufferedConn) Read(p []byte) (int, error) {
|
func (c *BufferedConn) Read(p []byte) (int, error) {
|
||||||
return c.r.Read(p)
|
return c.r.Read(p)
|
||||||
}
|
}
|
||||||
@ -60,22 +42,3 @@ func (c *BufferedConn) UnreadByte() error {
|
|||||||
func (c *BufferedConn) Buffered() int {
|
func (c *BufferedConn) Buffered() int {
|
||||||
return c.r.Buffered()
|
return c.r.Buffered()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *BufferedConn) ReadBuffer(buffer *buf.Buffer) (err error) {
|
|
||||||
if c.r.Buffered() > 0 {
|
|
||||||
_, err = buffer.ReadOnceFrom(c.r)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
return c.ExtendedConn.ReadBuffer(buffer)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *BufferedConn) Upstream() any {
|
|
||||||
return c.ExtendedConn
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *BufferedConn) ReaderReplaceable() bool {
|
|
||||||
if c.r.Buffered() > 0 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
@ -1,104 +0,0 @@
|
|||||||
package net
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net"
|
|
||||||
"runtime"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
type refConn struct {
|
|
||||||
conn net.Conn
|
|
||||||
ref any
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *refConn) Read(b []byte) (n int, err error) {
|
|
||||||
defer runtime.KeepAlive(c.ref)
|
|
||||||
return c.conn.Read(b)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *refConn) Write(b []byte) (n int, err error) {
|
|
||||||
defer runtime.KeepAlive(c.ref)
|
|
||||||
return c.conn.Write(b)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *refConn) Close() error {
|
|
||||||
defer runtime.KeepAlive(c.ref)
|
|
||||||
return c.conn.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *refConn) LocalAddr() net.Addr {
|
|
||||||
defer runtime.KeepAlive(c.ref)
|
|
||||||
return c.conn.LocalAddr()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *refConn) RemoteAddr() net.Addr {
|
|
||||||
defer runtime.KeepAlive(c.ref)
|
|
||||||
return c.conn.RemoteAddr()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *refConn) SetDeadline(t time.Time) error {
|
|
||||||
defer runtime.KeepAlive(c.ref)
|
|
||||||
return c.conn.SetDeadline(t)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *refConn) SetReadDeadline(t time.Time) error {
|
|
||||||
defer runtime.KeepAlive(c.ref)
|
|
||||||
return c.conn.SetReadDeadline(t)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *refConn) SetWriteDeadline(t time.Time) error {
|
|
||||||
defer runtime.KeepAlive(c.ref)
|
|
||||||
return c.conn.SetWriteDeadline(t)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *refConn) Upstream() any {
|
|
||||||
return c.conn
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewRefConn(conn net.Conn, ref any) net.Conn {
|
|
||||||
return &refConn{conn: conn, ref: ref}
|
|
||||||
}
|
|
||||||
|
|
||||||
type refPacketConn struct {
|
|
||||||
pc net.PacketConn
|
|
||||||
ref any
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pc *refPacketConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) {
|
|
||||||
defer runtime.KeepAlive(pc.ref)
|
|
||||||
return pc.pc.ReadFrom(p)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pc *refPacketConn) WriteTo(p []byte, addr net.Addr) (n int, err error) {
|
|
||||||
defer runtime.KeepAlive(pc.ref)
|
|
||||||
return pc.pc.WriteTo(p, addr)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pc *refPacketConn) Close() error {
|
|
||||||
defer runtime.KeepAlive(pc.ref)
|
|
||||||
return pc.pc.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pc *refPacketConn) LocalAddr() net.Addr {
|
|
||||||
defer runtime.KeepAlive(pc.ref)
|
|
||||||
return pc.pc.LocalAddr()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pc *refPacketConn) SetDeadline(t time.Time) error {
|
|
||||||
defer runtime.KeepAlive(pc.ref)
|
|
||||||
return pc.pc.SetDeadline(t)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pc *refPacketConn) SetReadDeadline(t time.Time) error {
|
|
||||||
defer runtime.KeepAlive(pc.ref)
|
|
||||||
return pc.pc.SetReadDeadline(t)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pc *refPacketConn) SetWriteDeadline(t time.Time) error {
|
|
||||||
defer runtime.KeepAlive(pc.ref)
|
|
||||||
return pc.pc.SetWriteDeadline(t)
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewRefPacketConn(pc net.PacketConn, ref any) net.PacketConn {
|
|
||||||
return &refPacketConn{pc: pc, ref: ref}
|
|
||||||
}
|
|
@ -1,24 +1,30 @@
|
|||||||
package net
|
package net
|
||||||
|
|
||||||
//import (
|
import (
|
||||||
// "io"
|
"io"
|
||||||
// "net"
|
"net"
|
||||||
// "time"
|
"time"
|
||||||
//)
|
|
||||||
//
|
"github.com/Dreamacro/clash/common/pool"
|
||||||
//// Relay copies between left and right bidirectionally.
|
)
|
||||||
//func Relay(leftConn, rightConn net.Conn) {
|
|
||||||
// ch := make(chan error)
|
// Relay copies between left and right bidirectionally.
|
||||||
//
|
func Relay(leftConn, rightConn net.Conn) {
|
||||||
// go func() {
|
ch := make(chan error)
|
||||||
// // Wrapping to avoid using *net.TCPConn.(ReadFrom)
|
|
||||||
// // See also https://github.com/Dreamacro/clash/pull/1209
|
go func() {
|
||||||
// _, err := io.Copy(WriteOnlyWriter{Writer: leftConn}, ReadOnlyReader{Reader: rightConn})
|
buf := pool.Get(pool.RelayBufferSize)
|
||||||
// leftConn.SetReadDeadline(time.Now())
|
// Wrapping to avoid using *net.TCPConn.(ReadFrom)
|
||||||
// ch <- err
|
// See also https://github.com/Dreamacro/clash/pull/1209
|
||||||
// }()
|
_, err := io.CopyBuffer(WriteOnlyWriter{Writer: leftConn}, ReadOnlyReader{Reader: rightConn}, buf)
|
||||||
//
|
pool.Put(buf)
|
||||||
// _, _ = io.Copy(WriteOnlyWriter{Writer: rightConn}, ReadOnlyReader{Reader: leftConn})
|
leftConn.SetReadDeadline(time.Now())
|
||||||
// rightConn.SetReadDeadline(time.Now())
|
ch <- err
|
||||||
// <-ch
|
}()
|
||||||
//}
|
|
||||||
|
buf := pool.Get(pool.RelayBufferSize)
|
||||||
|
io.CopyBuffer(WriteOnlyWriter{Writer: rightConn}, ReadOnlyReader{Reader: leftConn}, buf)
|
||||||
|
pool.Put(buf)
|
||||||
|
rightConn.SetReadDeadline(time.Now())
|
||||||
|
<-ch
|
||||||
|
}
|
||||||
|
@ -1,30 +0,0 @@
|
|||||||
package net
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"net"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing/common"
|
|
||||||
"github.com/sagernet/sing/common/bufio"
|
|
||||||
"github.com/sagernet/sing/common/network"
|
|
||||||
)
|
|
||||||
|
|
||||||
var NewExtendedConn = bufio.NewExtendedConn
|
|
||||||
var NewExtendedWriter = bufio.NewExtendedWriter
|
|
||||||
var NewExtendedReader = bufio.NewExtendedReader
|
|
||||||
|
|
||||||
type ExtendedConn = network.ExtendedConn
|
|
||||||
type ExtendedWriter = network.ExtendedWriter
|
|
||||||
type ExtendedReader = network.ExtendedReader
|
|
||||||
|
|
||||||
func NeedHandshake(conn any) bool {
|
|
||||||
if earlyConn, isEarlyConn := common.Cast[network.EarlyConn](conn); isEarlyConn && earlyConn.NeedHandshake() {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Relay copies between left and right bidirectionally.
|
|
||||||
func Relay(leftConn, rightConn net.Conn) {
|
|
||||||
_ = bufio.CopyConn(context.TODO(), leftConn, rightConn)
|
|
||||||
}
|
|
@ -1,52 +0,0 @@
|
|||||||
package net
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/rand"
|
|
||||||
"crypto/rsa"
|
|
||||||
"crypto/tls"
|
|
||||||
"crypto/x509"
|
|
||||||
"encoding/pem"
|
|
||||||
"fmt"
|
|
||||||
"math/big"
|
|
||||||
)
|
|
||||||
|
|
||||||
func ParseCert(certificate, privateKey string) (tls.Certificate, error) {
|
|
||||||
if certificate == "" || privateKey == "" {
|
|
||||||
return newRandomTLSKeyPair()
|
|
||||||
}
|
|
||||||
cert, painTextErr := tls.X509KeyPair([]byte(certificate), []byte(privateKey))
|
|
||||||
if painTextErr == nil {
|
|
||||||
return cert, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
cert, loadErr := tls.LoadX509KeyPair(certificate, privateKey)
|
|
||||||
if loadErr != nil {
|
|
||||||
return tls.Certificate{}, fmt.Errorf("parse certificate failed, maybe format error:%s, or path error: %s", painTextErr.Error(), loadErr.Error())
|
|
||||||
}
|
|
||||||
return cert, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func newRandomTLSKeyPair() (tls.Certificate, error) {
|
|
||||||
key, err := rsa.GenerateKey(rand.Reader, 2048)
|
|
||||||
if err != nil {
|
|
||||||
return tls.Certificate{}, err
|
|
||||||
}
|
|
||||||
template := x509.Certificate{SerialNumber: big.NewInt(1)}
|
|
||||||
certDER, err := x509.CreateCertificate(
|
|
||||||
rand.Reader,
|
|
||||||
&template,
|
|
||||||
&template,
|
|
||||||
&key.PublicKey,
|
|
||||||
key)
|
|
||||||
if err != nil {
|
|
||||||
return tls.Certificate{}, err
|
|
||||||
}
|
|
||||||
keyPEM := pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(key)})
|
|
||||||
certPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certDER})
|
|
||||||
|
|
||||||
tlsCert, err := tls.X509KeyPair(certPEM, keyPEM)
|
|
||||||
if err != nil {
|
|
||||||
return tls.Certificate{}, err
|
|
||||||
}
|
|
||||||
return tlsCert, nil
|
|
||||||
}
|
|
@ -1,131 +0,0 @@
|
|||||||
package net
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/binary"
|
|
||||||
"math/bits"
|
|
||||||
)
|
|
||||||
|
|
||||||
// kanged from https://github.com/nhooyr/websocket/blob/master/frame.go
|
|
||||||
// License: MIT
|
|
||||||
|
|
||||||
// MaskWebSocket applies the WebSocket masking algorithm to p
|
|
||||||
// with the given key.
|
|
||||||
// See https://tools.ietf.org/html/rfc6455#section-5.3
|
|
||||||
//
|
|
||||||
// The returned value is the correctly rotated key to
|
|
||||||
// to continue to mask/unmask the message.
|
|
||||||
//
|
|
||||||
// It is optimized for LittleEndian and expects the key
|
|
||||||
// to be in little endian.
|
|
||||||
//
|
|
||||||
// See https://github.com/golang/go/issues/31586
|
|
||||||
func MaskWebSocket(key uint32, b []byte) uint32 {
|
|
||||||
if len(b) >= 8 {
|
|
||||||
key64 := uint64(key)<<32 | uint64(key)
|
|
||||||
|
|
||||||
// At some point in the future we can clean these unrolled loops up.
|
|
||||||
// See https://github.com/golang/go/issues/31586#issuecomment-487436401
|
|
||||||
|
|
||||||
// Then we xor until b is less than 128 bytes.
|
|
||||||
for len(b) >= 128 {
|
|
||||||
v := binary.LittleEndian.Uint64(b)
|
|
||||||
binary.LittleEndian.PutUint64(b, v^key64)
|
|
||||||
v = binary.LittleEndian.Uint64(b[8:16])
|
|
||||||
binary.LittleEndian.PutUint64(b[8:16], v^key64)
|
|
||||||
v = binary.LittleEndian.Uint64(b[16:24])
|
|
||||||
binary.LittleEndian.PutUint64(b[16:24], v^key64)
|
|
||||||
v = binary.LittleEndian.Uint64(b[24:32])
|
|
||||||
binary.LittleEndian.PutUint64(b[24:32], v^key64)
|
|
||||||
v = binary.LittleEndian.Uint64(b[32:40])
|
|
||||||
binary.LittleEndian.PutUint64(b[32:40], v^key64)
|
|
||||||
v = binary.LittleEndian.Uint64(b[40:48])
|
|
||||||
binary.LittleEndian.PutUint64(b[40:48], v^key64)
|
|
||||||
v = binary.LittleEndian.Uint64(b[48:56])
|
|
||||||
binary.LittleEndian.PutUint64(b[48:56], v^key64)
|
|
||||||
v = binary.LittleEndian.Uint64(b[56:64])
|
|
||||||
binary.LittleEndian.PutUint64(b[56:64], v^key64)
|
|
||||||
v = binary.LittleEndian.Uint64(b[64:72])
|
|
||||||
binary.LittleEndian.PutUint64(b[64:72], v^key64)
|
|
||||||
v = binary.LittleEndian.Uint64(b[72:80])
|
|
||||||
binary.LittleEndian.PutUint64(b[72:80], v^key64)
|
|
||||||
v = binary.LittleEndian.Uint64(b[80:88])
|
|
||||||
binary.LittleEndian.PutUint64(b[80:88], v^key64)
|
|
||||||
v = binary.LittleEndian.Uint64(b[88:96])
|
|
||||||
binary.LittleEndian.PutUint64(b[88:96], v^key64)
|
|
||||||
v = binary.LittleEndian.Uint64(b[96:104])
|
|
||||||
binary.LittleEndian.PutUint64(b[96:104], v^key64)
|
|
||||||
v = binary.LittleEndian.Uint64(b[104:112])
|
|
||||||
binary.LittleEndian.PutUint64(b[104:112], v^key64)
|
|
||||||
v = binary.LittleEndian.Uint64(b[112:120])
|
|
||||||
binary.LittleEndian.PutUint64(b[112:120], v^key64)
|
|
||||||
v = binary.LittleEndian.Uint64(b[120:128])
|
|
||||||
binary.LittleEndian.PutUint64(b[120:128], v^key64)
|
|
||||||
b = b[128:]
|
|
||||||
}
|
|
||||||
|
|
||||||
// Then we xor until b is less than 64 bytes.
|
|
||||||
for len(b) >= 64 {
|
|
||||||
v := binary.LittleEndian.Uint64(b)
|
|
||||||
binary.LittleEndian.PutUint64(b, v^key64)
|
|
||||||
v = binary.LittleEndian.Uint64(b[8:16])
|
|
||||||
binary.LittleEndian.PutUint64(b[8:16], v^key64)
|
|
||||||
v = binary.LittleEndian.Uint64(b[16:24])
|
|
||||||
binary.LittleEndian.PutUint64(b[16:24], v^key64)
|
|
||||||
v = binary.LittleEndian.Uint64(b[24:32])
|
|
||||||
binary.LittleEndian.PutUint64(b[24:32], v^key64)
|
|
||||||
v = binary.LittleEndian.Uint64(b[32:40])
|
|
||||||
binary.LittleEndian.PutUint64(b[32:40], v^key64)
|
|
||||||
v = binary.LittleEndian.Uint64(b[40:48])
|
|
||||||
binary.LittleEndian.PutUint64(b[40:48], v^key64)
|
|
||||||
v = binary.LittleEndian.Uint64(b[48:56])
|
|
||||||
binary.LittleEndian.PutUint64(b[48:56], v^key64)
|
|
||||||
v = binary.LittleEndian.Uint64(b[56:64])
|
|
||||||
binary.LittleEndian.PutUint64(b[56:64], v^key64)
|
|
||||||
b = b[64:]
|
|
||||||
}
|
|
||||||
|
|
||||||
// Then we xor until b is less than 32 bytes.
|
|
||||||
for len(b) >= 32 {
|
|
||||||
v := binary.LittleEndian.Uint64(b)
|
|
||||||
binary.LittleEndian.PutUint64(b, v^key64)
|
|
||||||
v = binary.LittleEndian.Uint64(b[8:16])
|
|
||||||
binary.LittleEndian.PutUint64(b[8:16], v^key64)
|
|
||||||
v = binary.LittleEndian.Uint64(b[16:24])
|
|
||||||
binary.LittleEndian.PutUint64(b[16:24], v^key64)
|
|
||||||
v = binary.LittleEndian.Uint64(b[24:32])
|
|
||||||
binary.LittleEndian.PutUint64(b[24:32], v^key64)
|
|
||||||
b = b[32:]
|
|
||||||
}
|
|
||||||
|
|
||||||
// Then we xor until b is less than 16 bytes.
|
|
||||||
for len(b) >= 16 {
|
|
||||||
v := binary.LittleEndian.Uint64(b)
|
|
||||||
binary.LittleEndian.PutUint64(b, v^key64)
|
|
||||||
v = binary.LittleEndian.Uint64(b[8:16])
|
|
||||||
binary.LittleEndian.PutUint64(b[8:16], v^key64)
|
|
||||||
b = b[16:]
|
|
||||||
}
|
|
||||||
|
|
||||||
// Then we xor until b is less than 8 bytes.
|
|
||||||
for len(b) >= 8 {
|
|
||||||
v := binary.LittleEndian.Uint64(b)
|
|
||||||
binary.LittleEndian.PutUint64(b, v^key64)
|
|
||||||
b = b[8:]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Then we xor until b is less than 4 bytes.
|
|
||||||
for len(b) >= 4 {
|
|
||||||
v := binary.LittleEndian.Uint32(b)
|
|
||||||
binary.LittleEndian.PutUint32(b, v^key)
|
|
||||||
b = b[4:]
|
|
||||||
}
|
|
||||||
|
|
||||||
// xor remaining bytes.
|
|
||||||
for i := range b {
|
|
||||||
b[i] ^= byte(key)
|
|
||||||
key = bits.RotateLeft32(key, -8)
|
|
||||||
}
|
|
||||||
|
|
||||||
return key
|
|
||||||
}
|
|
@ -6,10 +6,16 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"math/bits"
|
"math/bits"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing/common/buf"
|
||||||
)
|
)
|
||||||
|
|
||||||
var defaultAllocator = NewAllocator()
|
var defaultAllocator = NewAllocator()
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
buf.DefaultAllocator = defaultAllocator
|
||||||
|
}
|
||||||
|
|
||||||
// Allocator for incoming frames, optimized to prevent overwriting after zeroing
|
// Allocator for incoming frames, optimized to prevent overwriting after zeroing
|
||||||
type Allocator struct {
|
type Allocator struct {
|
||||||
buffers []sync.Pool
|
buffers []sync.Pool
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
package pool
|
package pool
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"math/rand"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/zhangyunhao116/fastrand"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestAllocGet(t *testing.T) {
|
func TestAllocGet(t *testing.T) {
|
||||||
@ -43,6 +43,6 @@ func TestAllocPutThenGet(t *testing.T) {
|
|||||||
|
|
||||||
func BenchmarkMSB(b *testing.B) {
|
func BenchmarkMSB(b *testing.B) {
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
msb(fastrand.Int())
|
msb(rand.Int())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +0,0 @@
|
|||||||
package pool
|
|
||||||
|
|
||||||
import "github.com/sagernet/sing/common/buf"
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
buf.DefaultAllocator = defaultAllocator
|
|
||||||
}
|
|
@ -3,7 +3,6 @@ package structure
|
|||||||
// references: https://github.com/mitchellh/mapstructure
|
// references: https://github.com/mitchellh/mapstructure
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/base64"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strconv"
|
"strconv"
|
||||||
@ -14,11 +13,8 @@ import (
|
|||||||
type Option struct {
|
type Option struct {
|
||||||
TagName string
|
TagName string
|
||||||
WeaklyTypedInput bool
|
WeaklyTypedInput bool
|
||||||
KeyReplacer *strings.Replacer
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var DefaultKeyReplacer = strings.NewReplacer("_", "-")
|
|
||||||
|
|
||||||
// Decoder is the core of structure
|
// Decoder is the core of structure
|
||||||
type Decoder struct {
|
type Decoder struct {
|
||||||
option *Option
|
option *Option
|
||||||
@ -53,23 +49,6 @@ func (d *Decoder) Decode(src map[string]any, dst any) error {
|
|||||||
omitempty := found && omitKey == "omitempty"
|
omitempty := found && omitKey == "omitempty"
|
||||||
|
|
||||||
value, ok := src[key]
|
value, ok := src[key]
|
||||||
if !ok {
|
|
||||||
if d.option.KeyReplacer != nil {
|
|
||||||
key = d.option.KeyReplacer.Replace(key)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _strKey := range src {
|
|
||||||
strKey := _strKey
|
|
||||||
if d.option.KeyReplacer != nil {
|
|
||||||
strKey = d.option.KeyReplacer.Replace(strKey)
|
|
||||||
}
|
|
||||||
if strings.EqualFold(key, strKey) {
|
|
||||||
value = src[_strKey]
|
|
||||||
ok = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !ok || value == nil {
|
if !ok || value == nil {
|
||||||
if omitempty {
|
if omitempty {
|
||||||
continue
|
continue
|
||||||
@ -86,16 +65,9 @@ func (d *Decoder) Decode(src map[string]any, dst any) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (d *Decoder) decode(name string, data any, val reflect.Value) error {
|
func (d *Decoder) decode(name string, data any, val reflect.Value) error {
|
||||||
kind := val.Kind()
|
switch val.Kind() {
|
||||||
switch {
|
case reflect.Int:
|
||||||
case isInt(kind):
|
|
||||||
return d.decodeInt(name, data, val)
|
return d.decodeInt(name, data, val)
|
||||||
case isUint(kind):
|
|
||||||
return d.decodeUint(name, data, val)
|
|
||||||
case isFloat(kind):
|
|
||||||
return d.decodeFloat(name, data, val)
|
|
||||||
}
|
|
||||||
switch kind {
|
|
||||||
case reflect.String:
|
case reflect.String:
|
||||||
return d.decodeString(name, data, val)
|
return d.decodeString(name, data, val)
|
||||||
case reflect.Bool:
|
case reflect.Bool:
|
||||||
@ -113,42 +85,13 @@ func (d *Decoder) decode(name string, data any, val reflect.Value) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func isInt(kind reflect.Kind) bool {
|
|
||||||
switch kind {
|
|
||||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
|
||||||
return true
|
|
||||||
default:
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func isUint(kind reflect.Kind) bool {
|
|
||||||
switch kind {
|
|
||||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
|
||||||
return true
|
|
||||||
default:
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func isFloat(kind reflect.Kind) bool {
|
|
||||||
switch kind {
|
|
||||||
case reflect.Float32, reflect.Float64:
|
|
||||||
return true
|
|
||||||
default:
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Decoder) decodeInt(name string, data any, val reflect.Value) (err error) {
|
func (d *Decoder) decodeInt(name string, data any, val reflect.Value) (err error) {
|
||||||
dataVal := reflect.ValueOf(data)
|
dataVal := reflect.ValueOf(data)
|
||||||
kind := dataVal.Kind()
|
kind := dataVal.Kind()
|
||||||
switch {
|
switch {
|
||||||
case isInt(kind):
|
case kind == reflect.Int:
|
||||||
val.SetInt(dataVal.Int())
|
val.SetInt(dataVal.Int())
|
||||||
case isUint(kind) && d.option.WeaklyTypedInput:
|
case kind == reflect.Float64 && d.option.WeaklyTypedInput:
|
||||||
val.SetInt(int64(dataVal.Uint()))
|
|
||||||
case isFloat(kind) && d.option.WeaklyTypedInput:
|
|
||||||
val.SetInt(int64(dataVal.Float()))
|
val.SetInt(int64(dataVal.Float()))
|
||||||
case kind == reflect.String && d.option.WeaklyTypedInput:
|
case kind == reflect.String && d.option.WeaklyTypedInput:
|
||||||
var i int64
|
var i int64
|
||||||
@ -167,72 +110,14 @@ func (d *Decoder) decodeInt(name string, data any, val reflect.Value) (err error
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Decoder) decodeUint(name string, data any, val reflect.Value) (err error) {
|
|
||||||
dataVal := reflect.ValueOf(data)
|
|
||||||
kind := dataVal.Kind()
|
|
||||||
switch {
|
|
||||||
case isUint(kind):
|
|
||||||
val.SetUint(dataVal.Uint())
|
|
||||||
case isInt(kind) && d.option.WeaklyTypedInput:
|
|
||||||
val.SetUint(uint64(dataVal.Int()))
|
|
||||||
case isFloat(kind) && d.option.WeaklyTypedInput:
|
|
||||||
val.SetUint(uint64(dataVal.Float()))
|
|
||||||
case kind == reflect.String && d.option.WeaklyTypedInput:
|
|
||||||
var i uint64
|
|
||||||
i, err = strconv.ParseUint(dataVal.String(), 0, val.Type().Bits())
|
|
||||||
if err == nil {
|
|
||||||
val.SetUint(i)
|
|
||||||
} else {
|
|
||||||
err = fmt.Errorf("cannot parse '%s' as int: %s", name, err)
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
err = fmt.Errorf(
|
|
||||||
"'%s' expected type '%s', got unconvertible type '%s'",
|
|
||||||
name, val.Type(), dataVal.Type(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Decoder) decodeFloat(name string, data any, val reflect.Value) (err error) {
|
|
||||||
dataVal := reflect.ValueOf(data)
|
|
||||||
kind := dataVal.Kind()
|
|
||||||
switch {
|
|
||||||
case isFloat(kind):
|
|
||||||
val.SetFloat(dataVal.Float())
|
|
||||||
case isUint(kind):
|
|
||||||
val.SetFloat(float64(dataVal.Uint()))
|
|
||||||
case isInt(kind) && d.option.WeaklyTypedInput:
|
|
||||||
val.SetFloat(float64(dataVal.Int()))
|
|
||||||
case kind == reflect.String && d.option.WeaklyTypedInput:
|
|
||||||
var i float64
|
|
||||||
i, err = strconv.ParseFloat(dataVal.String(), val.Type().Bits())
|
|
||||||
if err == nil {
|
|
||||||
val.SetFloat(i)
|
|
||||||
} else {
|
|
||||||
err = fmt.Errorf("cannot parse '%s' as int: %s", name, err)
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
err = fmt.Errorf(
|
|
||||||
"'%s' expected type '%s', got unconvertible type '%s'",
|
|
||||||
name, val.Type(), dataVal.Type(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Decoder) decodeString(name string, data any, val reflect.Value) (err error) {
|
func (d *Decoder) decodeString(name string, data any, val reflect.Value) (err error) {
|
||||||
dataVal := reflect.ValueOf(data)
|
dataVal := reflect.ValueOf(data)
|
||||||
kind := dataVal.Kind()
|
kind := dataVal.Kind()
|
||||||
switch {
|
switch {
|
||||||
case kind == reflect.String:
|
case kind == reflect.String:
|
||||||
val.SetString(dataVal.String())
|
val.SetString(dataVal.String())
|
||||||
case isInt(kind) && d.option.WeaklyTypedInput:
|
case kind == reflect.Int && d.option.WeaklyTypedInput:
|
||||||
val.SetString(strconv.FormatInt(dataVal.Int(), 10))
|
val.SetString(strconv.FormatInt(dataVal.Int(), 10))
|
||||||
case isUint(kind) && d.option.WeaklyTypedInput:
|
|
||||||
val.SetString(strconv.FormatUint(dataVal.Uint(), 10))
|
|
||||||
case isFloat(kind) && d.option.WeaklyTypedInput:
|
|
||||||
val.SetString(strconv.FormatFloat(dataVal.Float(), 'E', -1, dataVal.Type().Bits()))
|
|
||||||
default:
|
default:
|
||||||
err = fmt.Errorf(
|
err = fmt.Errorf(
|
||||||
"'%s' expected type '%s', got unconvertible type '%s'",
|
"'%s' expected type '%s', got unconvertible type '%s'",
|
||||||
@ -248,10 +133,8 @@ func (d *Decoder) decodeBool(name string, data any, val reflect.Value) (err erro
|
|||||||
switch {
|
switch {
|
||||||
case kind == reflect.Bool:
|
case kind == reflect.Bool:
|
||||||
val.SetBool(dataVal.Bool())
|
val.SetBool(dataVal.Bool())
|
||||||
case isInt(kind) && d.option.WeaklyTypedInput:
|
case kind == reflect.Int && d.option.WeaklyTypedInput:
|
||||||
val.SetBool(dataVal.Int() != 0)
|
val.SetBool(dataVal.Int() != 0)
|
||||||
case isUint(kind) && d.option.WeaklyTypedInput:
|
|
||||||
val.SetString(strconv.FormatUint(dataVal.Uint(), 10))
|
|
||||||
default:
|
default:
|
||||||
err = fmt.Errorf(
|
err = fmt.Errorf(
|
||||||
"'%s' expected type '%s', got unconvertible type '%s'",
|
"'%s' expected type '%s', got unconvertible type '%s'",
|
||||||
@ -266,17 +149,6 @@ func (d *Decoder) decodeSlice(name string, data any, val reflect.Value) error {
|
|||||||
valType := val.Type()
|
valType := val.Type()
|
||||||
valElemType := valType.Elem()
|
valElemType := valType.Elem()
|
||||||
|
|
||||||
if dataVal.Kind() == reflect.String && valElemType.Kind() == reflect.Uint8 { // from encoding/json
|
|
||||||
s := []byte(dataVal.String())
|
|
||||||
b := make([]byte, base64.StdEncoding.DecodedLen(len(s)))
|
|
||||||
n, err := base64.StdEncoding.Decode(b, s)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("try decode '%s' by base64 error: %w", name, err)
|
|
||||||
}
|
|
||||||
val.SetBytes(b[:n])
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if dataVal.Kind() != reflect.Slice {
|
if dataVal.Kind() != reflect.Slice {
|
||||||
return fmt.Errorf("'%s' is not a slice", name)
|
return fmt.Errorf("'%s' is not a slice", name)
|
||||||
}
|
}
|
||||||
@ -481,18 +353,12 @@ func (d *Decoder) decodeStructFromMap(name string, dataVal, val reflect.Value) e
|
|||||||
if !rawMapVal.IsValid() {
|
if !rawMapVal.IsValid() {
|
||||||
// Do a slower search by iterating over each key and
|
// Do a slower search by iterating over each key and
|
||||||
// doing case-insensitive search.
|
// doing case-insensitive search.
|
||||||
if d.option.KeyReplacer != nil {
|
|
||||||
fieldName = d.option.KeyReplacer.Replace(fieldName)
|
|
||||||
}
|
|
||||||
for dataValKey := range dataValKeys {
|
for dataValKey := range dataValKeys {
|
||||||
mK, ok := dataValKey.Interface().(string)
|
mK, ok := dataValKey.Interface().(string)
|
||||||
if !ok {
|
if !ok {
|
||||||
// Not a string key
|
// Not a string key
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if d.option.KeyReplacer != nil {
|
|
||||||
mK = d.option.KeyReplacer.Replace(mK)
|
|
||||||
}
|
|
||||||
|
|
||||||
if strings.EqualFold(mK, fieldName) {
|
if strings.EqualFold(mK, fieldName) {
|
||||||
rawMapKey = dataValKey
|
rawMapKey = dataValKey
|
||||||
|
@ -137,45 +137,3 @@ func TestStructure_Nest(t *testing.T) {
|
|||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.Equal(t, s.BazOptional, goal)
|
assert.Equal(t, s.BazOptional, goal)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestStructure_SliceNilValue(t *testing.T) {
|
|
||||||
rawMap := map[string]any{
|
|
||||||
"foo": 1,
|
|
||||||
"bar": []any{"bar", nil},
|
|
||||||
}
|
|
||||||
|
|
||||||
goal := &BazSlice{
|
|
||||||
Foo: 1,
|
|
||||||
Bar: []string{"bar", ""},
|
|
||||||
}
|
|
||||||
|
|
||||||
s := &BazSlice{}
|
|
||||||
err := weakTypeDecoder.Decode(rawMap, s)
|
|
||||||
assert.Nil(t, err)
|
|
||||||
assert.Equal(t, goal.Bar, s.Bar)
|
|
||||||
|
|
||||||
s = &BazSlice{}
|
|
||||||
err = decoder.Decode(rawMap, s)
|
|
||||||
assert.NotNil(t, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestStructure_SliceNilValueComplex(t *testing.T) {
|
|
||||||
rawMap := map[string]any{
|
|
||||||
"bar": []any{map[string]any{"bar": "foo"}, nil},
|
|
||||||
}
|
|
||||||
|
|
||||||
s := &struct {
|
|
||||||
Bar []map[string]any `test:"bar"`
|
|
||||||
}{}
|
|
||||||
|
|
||||||
err := decoder.Decode(rawMap, s)
|
|
||||||
assert.Nil(t, err)
|
|
||||||
assert.Nil(t, s.Bar[1])
|
|
||||||
|
|
||||||
ss := &struct {
|
|
||||||
Bar []Baz `test:"bar"`
|
|
||||||
}{}
|
|
||||||
|
|
||||||
err = decoder.Decode(rawMap, ss)
|
|
||||||
assert.NotNil(t, err)
|
|
||||||
}
|
|
||||||
|
@ -1,8 +0,0 @@
|
|||||||
package utils
|
|
||||||
|
|
||||||
func MustOK[T any](result T, ok bool) T {
|
|
||||||
if ok {
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
panic("operation failed")
|
|
||||||
}
|
|
@ -1,34 +0,0 @@
|
|||||||
package utils
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"reflect"
|
|
||||||
)
|
|
||||||
|
|
||||||
func Filter[T comparable](tSlice []T, filter func(t T) bool) []T {
|
|
||||||
result := make([]T, 0)
|
|
||||||
for _, t := range tSlice {
|
|
||||||
if filter(t) {
|
|
||||||
result = append(result, t)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
func ToStringSlice(value any) ([]string, error) {
|
|
||||||
strArr := make([]string, 0)
|
|
||||||
switch reflect.TypeOf(value).Kind() {
|
|
||||||
case reflect.Slice, reflect.Array:
|
|
||||||
origin := reflect.ValueOf(value)
|
|
||||||
for i := 0; i < origin.Len(); i++ {
|
|
||||||
item := fmt.Sprintf("%v", origin.Index(i))
|
|
||||||
strArr = append(strArr, item)
|
|
||||||
}
|
|
||||||
case reflect.String:
|
|
||||||
strArr = append(strArr, fmt.Sprintf("%v", value))
|
|
||||||
default:
|
|
||||||
return nil, errors.New("value format error, must be string or array")
|
|
||||||
}
|
|
||||||
return strArr, nil
|
|
||||||
}
|
|
@ -2,50 +2,15 @@ package utils
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/gofrs/uuid"
|
"github.com/gofrs/uuid"
|
||||||
"github.com/zhangyunhao116/fastrand"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type fastRandReader struct{}
|
var uuidNamespace, _ = uuid.FromString("00000000-0000-0000-0000-000000000000")
|
||||||
|
|
||||||
func (r fastRandReader) Read(p []byte) (int, error) {
|
|
||||||
return fastrand.Read(p)
|
|
||||||
}
|
|
||||||
|
|
||||||
var UnsafeUUIDGenerator = uuid.NewGenWithOptions(uuid.WithRandomReader(fastRandReader{}))
|
|
||||||
|
|
||||||
func NewUUIDV1() uuid.UUID {
|
|
||||||
u, _ := UnsafeUUIDGenerator.NewV1() // fastrand.Read wouldn't cause error, so ignore err is safe
|
|
||||||
return u
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewUUIDV3(ns uuid.UUID, name string) uuid.UUID {
|
|
||||||
return UnsafeUUIDGenerator.NewV3(ns, name)
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewUUIDV4() uuid.UUID {
|
|
||||||
u, _ := UnsafeUUIDGenerator.NewV4() // fastrand.Read wouldn't cause error, so ignore err is safe
|
|
||||||
return u
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewUUIDV5(ns uuid.UUID, name string) uuid.UUID {
|
|
||||||
return UnsafeUUIDGenerator.NewV5(ns, name)
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewUUIDV6() uuid.UUID {
|
|
||||||
u, _ := UnsafeUUIDGenerator.NewV6() // fastrand.Read wouldn't cause error, so ignore err is safe
|
|
||||||
return u
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewUUIDV7() uuid.UUID {
|
|
||||||
u, _ := UnsafeUUIDGenerator.NewV7() // fastrand.Read wouldn't cause error, so ignore err is safe
|
|
||||||
return u
|
|
||||||
}
|
|
||||||
|
|
||||||
// UUIDMap https://github.com/XTLS/Xray-core/issues/158#issue-783294090
|
// UUIDMap https://github.com/XTLS/Xray-core/issues/158#issue-783294090
|
||||||
func UUIDMap(str string) (uuid.UUID, error) {
|
func UUIDMap(str string) (uuid.UUID, error) {
|
||||||
u, err := uuid.FromString(str)
|
u, err := uuid.FromString(str)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return NewUUIDV5(uuid.Nil, str), nil
|
return uuid.NewV5(uuidNamespace, str), nil
|
||||||
}
|
}
|
||||||
return u, nil
|
return u, nil
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
package utils
|
package utils
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/gofrs/uuid"
|
|
||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/gofrs/uuid"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestUUIDMap(t *testing.T) {
|
func TestUUIDMap(t *testing.T) {
|
||||||
|
@ -8,7 +8,6 @@ import (
|
|||||||
|
|
||||||
"github.com/Dreamacro/clash/common/nnip"
|
"github.com/Dreamacro/clash/common/nnip"
|
||||||
"github.com/Dreamacro/clash/component/iface"
|
"github.com/Dreamacro/clash/component/iface"
|
||||||
|
|
||||||
"github.com/insomniacslk/dhcp/dhcpv4"
|
"github.com/insomniacslk/dhcp/dhcpv4"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -1,18 +1,24 @@
|
|||||||
package dialer
|
package dialer
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"net"
|
"net"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/component/iface"
|
"github.com/Dreamacro/clash/component/iface"
|
||||||
|
|
||||||
"golang.org/x/sys/unix"
|
"golang.org/x/sys/unix"
|
||||||
)
|
)
|
||||||
|
|
||||||
func bindControl(ifaceIdx int) controlFn {
|
type controlFn = func(network, address string, c syscall.RawConn) error
|
||||||
return func(ctx context.Context, network, address string, c syscall.RawConn) (err error) {
|
|
||||||
|
func bindControl(ifaceIdx int, chain controlFn) controlFn {
|
||||||
|
return func(network, address string, c syscall.RawConn) (err error) {
|
||||||
|
defer func() {
|
||||||
|
if err == nil && chain != nil {
|
||||||
|
err = chain(network, address, c)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
addrPort, err := netip.ParseAddrPort(address)
|
addrPort, err := netip.ParseAddrPort(address)
|
||||||
if err == nil && !addrPort.Addr().IsGlobalUnicast() {
|
if err == nil && !addrPort.Addr().IsGlobalUnicast() {
|
||||||
return
|
return
|
||||||
@ -42,7 +48,7 @@ func bindIfaceToDialer(ifaceName string, dialer *net.Dialer, _ string, _ netip.A
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
addControlToDialer(dialer, bindControl(ifaceObj.Index))
|
dialer.Control = bindControl(ifaceObj.Index, dialer.Control)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -52,10 +58,6 @@ func bindIfaceToListenConfig(ifaceName string, lc *net.ListenConfig, _, address
|
|||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
addControlToListenConfig(lc, bindControl(ifaceObj.Index))
|
lc.Control = bindControl(ifaceObj.Index, lc.Control)
|
||||||
return address, nil
|
return address, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func ParseNetwork(network string, addr netip.Addr) string {
|
|
||||||
return network
|
|
||||||
}
|
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
package dialer
|
package dialer
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"net"
|
"net"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"syscall"
|
"syscall"
|
||||||
@ -9,8 +8,16 @@ import (
|
|||||||
"golang.org/x/sys/unix"
|
"golang.org/x/sys/unix"
|
||||||
)
|
)
|
||||||
|
|
||||||
func bindControl(ifaceName string) controlFn {
|
type controlFn = func(network, address string, c syscall.RawConn) error
|
||||||
return func(ctx context.Context, network, address string, c syscall.RawConn) (err error) {
|
|
||||||
|
func bindControl(ifaceName string, chain controlFn) controlFn {
|
||||||
|
return func(network, address string, c syscall.RawConn) (err error) {
|
||||||
|
defer func() {
|
||||||
|
if err == nil && chain != nil {
|
||||||
|
err = chain(network, address, c)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
addrPort, err := netip.ParseAddrPort(address)
|
addrPort, err := netip.ParseAddrPort(address)
|
||||||
if err == nil && !addrPort.Addr().IsGlobalUnicast() {
|
if err == nil && !addrPort.Addr().IsGlobalUnicast() {
|
||||||
return
|
return
|
||||||
@ -30,17 +37,13 @@ func bindControl(ifaceName string) controlFn {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func bindIfaceToDialer(ifaceName string, dialer *net.Dialer, _ string, _ netip.Addr) error {
|
func bindIfaceToDialer(ifaceName string, dialer *net.Dialer, _ string, _ netip.Addr) error {
|
||||||
addControlToDialer(dialer, bindControl(ifaceName))
|
dialer.Control = bindControl(ifaceName, dialer.Control)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func bindIfaceToListenConfig(ifaceName string, lc *net.ListenConfig, _, address string) (string, error) {
|
func bindIfaceToListenConfig(ifaceName string, lc *net.ListenConfig, _, address string) (string, error) {
|
||||||
addControlToListenConfig(lc, bindControl(ifaceName))
|
lc.Control = bindControl(ifaceName, lc.Control)
|
||||||
|
|
||||||
return address, nil
|
return address, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func ParseNetwork(network string, addr netip.Addr) string {
|
|
||||||
return network
|
|
||||||
}
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
//go:build !linux && !darwin && !windows
|
//go:build !linux && !darwin
|
||||||
|
|
||||||
package dialer
|
package dialer
|
||||||
|
|
||||||
@ -91,13 +91,3 @@ func bindIfaceToListenConfig(ifaceName string, _ *net.ListenConfig, network, add
|
|||||||
|
|
||||||
return addr.String(), nil
|
return addr.String(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func ParseNetwork(network string, addr netip.Addr) string {
|
|
||||||
// fix bindIfaceToListenConfig() force bind to an ipv4 address
|
|
||||||
if !strings.HasSuffix(network, "4") &&
|
|
||||||
!strings.HasSuffix(network, "6") &&
|
|
||||||
addr.Unmap().Is6() {
|
|
||||||
network += "6"
|
|
||||||
}
|
|
||||||
return network
|
|
||||||
}
|
|
||||||
|
@ -1,92 +0,0 @@
|
|||||||
package dialer
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"encoding/binary"
|
|
||||||
"net"
|
|
||||||
"net/netip"
|
|
||||||
"syscall"
|
|
||||||
"unsafe"
|
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/component/iface"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
IP_UNICAST_IF = 31
|
|
||||||
IPV6_UNICAST_IF = 31
|
|
||||||
)
|
|
||||||
|
|
||||||
func bind4(handle syscall.Handle, ifaceIdx int) error {
|
|
||||||
var bytes [4]byte
|
|
||||||
binary.BigEndian.PutUint32(bytes[:], uint32(ifaceIdx))
|
|
||||||
idx := *(*uint32)(unsafe.Pointer(&bytes[0]))
|
|
||||||
return syscall.SetsockoptInt(handle, syscall.IPPROTO_IP, IP_UNICAST_IF, int(idx))
|
|
||||||
}
|
|
||||||
|
|
||||||
func bind6(handle syscall.Handle, ifaceIdx int) error {
|
|
||||||
return syscall.SetsockoptInt(handle, syscall.IPPROTO_IPV6, IPV6_UNICAST_IF, ifaceIdx)
|
|
||||||
}
|
|
||||||
|
|
||||||
func bindControl(ifaceIdx int) controlFn {
|
|
||||||
return func(ctx context.Context, network, address string, c syscall.RawConn) (err error) {
|
|
||||||
addrPort, err := netip.ParseAddrPort(address)
|
|
||||||
if err == nil && !addrPort.Addr().IsGlobalUnicast() {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var innerErr error
|
|
||||||
err = c.Control(func(fd uintptr) {
|
|
||||||
handle := syscall.Handle(fd)
|
|
||||||
bind6err := bind6(handle, ifaceIdx)
|
|
||||||
bind4err := bind4(handle, ifaceIdx)
|
|
||||||
switch network {
|
|
||||||
case "ip6", "tcp6":
|
|
||||||
innerErr = bind6err
|
|
||||||
case "ip4", "tcp4", "udp4":
|
|
||||||
innerErr = bind4err
|
|
||||||
case "udp6":
|
|
||||||
// golang will set network to udp6 when listenUDP on wildcard ip (eg: ":0", "")
|
|
||||||
if (!addrPort.Addr().IsValid() || addrPort.Addr().IsUnspecified()) && bind6err != nil {
|
|
||||||
// try bind ipv6, if failed, ignore. it's a workaround for windows disable interface ipv6
|
|
||||||
if bind4err != nil {
|
|
||||||
innerErr = bind6err
|
|
||||||
} else {
|
|
||||||
innerErr = bind4err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
innerErr = bind6err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
if innerErr != nil {
|
|
||||||
err = innerErr
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func bindIfaceToDialer(ifaceName string, dialer *net.Dialer, _ string, _ netip.Addr) error {
|
|
||||||
ifaceObj, err := iface.ResolveInterface(ifaceName)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
addControlToDialer(dialer, bindControl(ifaceObj.Index))
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func bindIfaceToListenConfig(ifaceName string, lc *net.ListenConfig, _, address string) (string, error) {
|
|
||||||
ifaceObj, err := iface.ResolveInterface(ifaceName)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
addControlToListenConfig(lc, bindControl(ifaceObj.Index))
|
|
||||||
return address, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func ParseNetwork(network string, addr netip.Addr) string {
|
|
||||||
return network
|
|
||||||
}
|
|
@ -1,22 +0,0 @@
|
|||||||
package dialer
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"net"
|
|
||||||
"syscall"
|
|
||||||
)
|
|
||||||
|
|
||||||
type controlFn = func(ctx context.Context, network, address string, c syscall.RawConn) error
|
|
||||||
|
|
||||||
func addControlToListenConfig(lc *net.ListenConfig, fn controlFn) {
|
|
||||||
llc := *lc
|
|
||||||
lc.Control = func(network, address string, c syscall.RawConn) (err error) {
|
|
||||||
switch {
|
|
||||||
case llc.Control != nil:
|
|
||||||
if err = llc.Control(network, address, c); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return fn(context.Background(), network, address, c)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,22 +0,0 @@
|
|||||||
//go:build !go1.20
|
|
||||||
|
|
||||||
package dialer
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"net"
|
|
||||||
"syscall"
|
|
||||||
)
|
|
||||||
|
|
||||||
func addControlToDialer(d *net.Dialer, fn controlFn) {
|
|
||||||
ld := *d
|
|
||||||
d.Control = func(network, address string, c syscall.RawConn) (err error) {
|
|
||||||
switch {
|
|
||||||
case ld.Control != nil:
|
|
||||||
if err = ld.Control(network, address, c); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return fn(context.Background(), network, address, c)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,26 +0,0 @@
|
|||||||
//go:build go1.20
|
|
||||||
|
|
||||||
package dialer
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"net"
|
|
||||||
"syscall"
|
|
||||||
)
|
|
||||||
|
|
||||||
func addControlToDialer(d *net.Dialer, fn controlFn) {
|
|
||||||
ld := *d
|
|
||||||
d.ControlContext = func(ctx context.Context, network, address string, c syscall.RawConn) (err error) {
|
|
||||||
switch {
|
|
||||||
case ld.ControlContext != nil:
|
|
||||||
if err = ld.ControlContext(ctx, network, address, c); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
case ld.Control != nil:
|
|
||||||
if err = ld.Control(network, address, c); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return fn(ctx, network, address, c)
|
|
||||||
}
|
|
||||||
}
|
|
@ -2,28 +2,24 @@ package dialer
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/component/resolver"
|
"github.com/Dreamacro/clash/component/resolver"
|
||||||
)
|
)
|
||||||
|
|
||||||
type dialFunc func(ctx context.Context, network string, ips []netip.Addr, port string, opt *option) (net.Conn, error)
|
|
||||||
|
|
||||||
var (
|
var (
|
||||||
dialMux sync.Mutex
|
dialMux sync.Mutex
|
||||||
actualSingleStackDialContext = serialSingleStackDialContext
|
actualSingleDialContext = singleDialContext
|
||||||
actualDualStackDialContext = serialDualStackDialContext
|
actualDualStackDialContext = dualStackDialContext
|
||||||
tcpConcurrent = false
|
tcpConcurrent = false
|
||||||
fallbackTimeout = 300 * time.Millisecond
|
DisableIPv6 = false
|
||||||
)
|
)
|
||||||
|
|
||||||
func applyOptions(options ...Option) *option {
|
func DialContext(ctx context.Context, network, address string, options ...Option) (net.Conn, error) {
|
||||||
opt := &option{
|
opt := &option{
|
||||||
interfaceName: DefaultInterface.Load(),
|
interfaceName: DefaultInterface.Load(),
|
||||||
routingMark: int(DefaultRoutingMark.Load()),
|
routingMark: int(DefaultRoutingMark.Load()),
|
||||||
@ -37,39 +33,29 @@ func applyOptions(options ...Option) *option {
|
|||||||
o(opt)
|
o(opt)
|
||||||
}
|
}
|
||||||
|
|
||||||
return opt
|
|
||||||
}
|
|
||||||
|
|
||||||
func DialContext(ctx context.Context, network, address string, options ...Option) (net.Conn, error) {
|
|
||||||
opt := applyOptions(options...)
|
|
||||||
|
|
||||||
if opt.network == 4 || opt.network == 6 {
|
|
||||||
if strings.Contains(network, "tcp") {
|
|
||||||
network = "tcp"
|
|
||||||
} else {
|
|
||||||
network = "udp"
|
|
||||||
}
|
|
||||||
|
|
||||||
network = fmt.Sprintf("%s%d", network, opt.network)
|
|
||||||
}
|
|
||||||
|
|
||||||
ips, port, err := parseAddr(ctx, network, address, opt.resolver)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
switch network {
|
switch network {
|
||||||
case "tcp4", "tcp6", "udp4", "udp6":
|
case "tcp4", "tcp6", "udp4", "udp6":
|
||||||
return actualSingleStackDialContext(ctx, network, ips, port, opt)
|
return actualSingleDialContext(ctx, network, address, opt)
|
||||||
case "tcp", "udp":
|
case "tcp", "udp":
|
||||||
return actualDualStackDialContext(ctx, network, ips, port, opt)
|
return actualDualStackDialContext(ctx, network, address, opt)
|
||||||
default:
|
default:
|
||||||
return nil, ErrorInvalidedNetworkStack
|
return nil, errors.New("network invalid")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func ListenPacket(ctx context.Context, network, address string, options ...Option) (net.PacketConn, error) {
|
func ListenPacket(ctx context.Context, network, address string, options ...Option) (net.PacketConn, error) {
|
||||||
cfg := applyOptions(options...)
|
cfg := &option{
|
||||||
|
interfaceName: DefaultInterface.Load(),
|
||||||
|
routingMark: int(DefaultRoutingMark.Load()),
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, o := range DefaultOptions {
|
||||||
|
o(cfg)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, o := range options {
|
||||||
|
o(cfg)
|
||||||
|
}
|
||||||
|
|
||||||
lc := &net.ListenConfig{}
|
lc := &net.ListenConfig{}
|
||||||
if cfg.interfaceName != "" {
|
if cfg.interfaceName != "" {
|
||||||
@ -89,40 +75,26 @@ func ListenPacket(ctx context.Context, network, address string, options ...Optio
|
|||||||
return lc.ListenPacket(ctx, network, address)
|
return lc.ListenPacket(ctx, network, address)
|
||||||
}
|
}
|
||||||
|
|
||||||
func SetTcpConcurrent(concurrent bool) {
|
func SetDial(concurrent bool) {
|
||||||
dialMux.Lock()
|
dialMux.Lock()
|
||||||
defer dialMux.Unlock()
|
|
||||||
tcpConcurrent = concurrent
|
tcpConcurrent = concurrent
|
||||||
if concurrent {
|
if concurrent {
|
||||||
actualSingleStackDialContext = concurrentSingleStackDialContext
|
actualSingleDialContext = concurrentSingleDialContext
|
||||||
actualDualStackDialContext = concurrentDualStackDialContext
|
actualDualStackDialContext = concurrentDualStackDialContext
|
||||||
} else {
|
} else {
|
||||||
actualSingleStackDialContext = serialSingleStackDialContext
|
actualSingleDialContext = singleDialContext
|
||||||
actualDualStackDialContext = serialDualStackDialContext
|
actualDualStackDialContext = dualStackDialContext
|
||||||
}
|
}
|
||||||
|
|
||||||
|
dialMux.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetTcpConcurrent() bool {
|
func GetDial() bool {
|
||||||
dialMux.Lock()
|
|
||||||
defer dialMux.Unlock()
|
|
||||||
return tcpConcurrent
|
return tcpConcurrent
|
||||||
}
|
}
|
||||||
|
|
||||||
func dialContext(ctx context.Context, network string, destination netip.Addr, port string, opt *option) (net.Conn, error) {
|
func dialContext(ctx context.Context, network string, destination netip.Addr, port string, opt *option) (net.Conn, error) {
|
||||||
address := net.JoinHostPort(destination.String(), port)
|
dialer := &net.Dialer{}
|
||||||
|
|
||||||
netDialer := opt.netDialer
|
|
||||||
switch netDialer.(type) {
|
|
||||||
case nil:
|
|
||||||
netDialer = &net.Dialer{}
|
|
||||||
case *net.Dialer:
|
|
||||||
_netDialer := *netDialer.(*net.Dialer)
|
|
||||||
netDialer = &_netDialer // make a copy
|
|
||||||
default:
|
|
||||||
return netDialer.DialContext(ctx, network, address)
|
|
||||||
}
|
|
||||||
|
|
||||||
dialer := netDialer.(*net.Dialer)
|
|
||||||
if opt.interfaceName != "" {
|
if opt.interfaceName != "" {
|
||||||
if err := bindIfaceToDialer(opt.interfaceName, dialer, network, destination); err != nil {
|
if err := bindIfaceToDialer(opt.interfaceName, dialer, network, destination); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -131,213 +103,218 @@ func dialContext(ctx context.Context, network string, destination netip.Addr, po
|
|||||||
if opt.routingMark != 0 {
|
if opt.routingMark != 0 {
|
||||||
bindMarkToDialer(opt.routingMark, dialer, network, destination)
|
bindMarkToDialer(opt.routingMark, dialer, network, destination)
|
||||||
}
|
}
|
||||||
if opt.tfo {
|
|
||||||
return dialTFO(ctx, *dialer, network, address)
|
if DisableIPv6 && destination.Is6() {
|
||||||
|
return nil, fmt.Errorf("IPv6 is diabled, dialer cancel")
|
||||||
}
|
}
|
||||||
return dialer.DialContext(ctx, network, address)
|
|
||||||
|
return dialer.DialContext(ctx, network, net.JoinHostPort(destination.String(), port))
|
||||||
}
|
}
|
||||||
|
|
||||||
func serialSingleStackDialContext(ctx context.Context, network string, ips []netip.Addr, port string, opt *option) (net.Conn, error) {
|
func dualStackDialContext(ctx context.Context, network, address string, opt *option) (net.Conn, error) {
|
||||||
return serialDialContext(ctx, network, ips, port, opt)
|
host, port, err := net.SplitHostPort(address)
|
||||||
}
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
func serialDualStackDialContext(ctx context.Context, network string, ips []netip.Addr, port string, opt *option) (net.Conn, error) {
|
|
||||||
return dualStackDialContext(ctx, serialDialContext, network, ips, port, opt)
|
|
||||||
}
|
|
||||||
|
|
||||||
func concurrentSingleStackDialContext(ctx context.Context, network string, ips []netip.Addr, port string, opt *option) (net.Conn, error) {
|
|
||||||
return parallelDialContext(ctx, network, ips, port, opt)
|
|
||||||
}
|
|
||||||
|
|
||||||
func concurrentDualStackDialContext(ctx context.Context, network string, ips []netip.Addr, port string, opt *option) (net.Conn, error) {
|
|
||||||
if opt.prefer != 4 && opt.prefer != 6 {
|
|
||||||
return parallelDialContext(ctx, network, ips, port, opt)
|
|
||||||
}
|
}
|
||||||
return dualStackDialContext(ctx, parallelDialContext, network, ips, port, opt)
|
|
||||||
}
|
|
||||||
|
|
||||||
func dualStackDialContext(ctx context.Context, dialFn dialFunc, network string, ips []netip.Addr, port string, opt *option) (net.Conn, error) {
|
|
||||||
ipv4s, ipv6s := sortationAddr(ips)
|
|
||||||
preferIPVersion := opt.prefer
|
|
||||||
|
|
||||||
fallbackTicker := time.NewTicker(fallbackTimeout)
|
|
||||||
defer fallbackTicker.Stop()
|
|
||||||
results := make(chan dialResult)
|
|
||||||
returned := make(chan struct{})
|
returned := make(chan struct{})
|
||||||
defer close(returned)
|
defer close(returned)
|
||||||
racer := func(ips []netip.Addr, isPrimary bool) {
|
|
||||||
result := dialResult{isPrimary: isPrimary}
|
type dialResult struct {
|
||||||
|
net.Conn
|
||||||
|
error
|
||||||
|
resolved bool
|
||||||
|
ipv6 bool
|
||||||
|
done bool
|
||||||
|
}
|
||||||
|
results := make(chan dialResult)
|
||||||
|
var primary, fallback dialResult
|
||||||
|
|
||||||
|
startRacer := func(ctx context.Context, network, host string, direct bool, ipv6 bool) {
|
||||||
|
result := dialResult{ipv6: ipv6, done: true}
|
||||||
defer func() {
|
defer func() {
|
||||||
select {
|
select {
|
||||||
case results <- result:
|
case results <- result:
|
||||||
case <-returned:
|
case <-returned:
|
||||||
if result.Conn != nil && result.error == nil {
|
if result.Conn != nil {
|
||||||
_ = result.Conn.Close()
|
_ = result.Conn.Close()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
result.Conn, result.error = dialFn(ctx, network, ips, port, opt)
|
|
||||||
}
|
|
||||||
go racer(ipv4s, preferIPVersion != 6)
|
|
||||||
go racer(ipv6s, preferIPVersion != 4)
|
|
||||||
var fallback dialResult
|
|
||||||
var errs []error
|
|
||||||
for i := 0; i < 2; {
|
|
||||||
select {
|
|
||||||
case <-fallbackTicker.C:
|
|
||||||
if fallback.error == nil && fallback.Conn != nil {
|
|
||||||
return fallback.Conn, nil
|
|
||||||
}
|
|
||||||
case res := <-results:
|
|
||||||
i++
|
|
||||||
if res.error == nil {
|
|
||||||
if res.isPrimary {
|
|
||||||
return res.Conn, nil
|
|
||||||
}
|
|
||||||
fallback = res
|
|
||||||
} else {
|
|
||||||
if res.isPrimary {
|
|
||||||
errs = append([]error{fmt.Errorf("connect failed: %w", res.error)}, errs...)
|
|
||||||
} else {
|
|
||||||
errs = append(errs, fmt.Errorf("connect failed: %w", res.error))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if fallback.error == nil && fallback.Conn != nil {
|
|
||||||
return fallback.Conn, nil
|
|
||||||
}
|
|
||||||
return nil, errorsJoin(errs...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func parallelDialContext(ctx context.Context, network string, ips []netip.Addr, port string, opt *option) (net.Conn, error) {
|
var ip netip.Addr
|
||||||
if len(ips) == 0 {
|
if ipv6 {
|
||||||
return nil, ErrorNoIpAddress
|
if !direct {
|
||||||
|
ip, result.error = resolver.ResolveIPv6ProxyServerHost(host)
|
||||||
|
} else {
|
||||||
|
ip, result.error = resolver.ResolveIPv6(host)
|
||||||
}
|
}
|
||||||
results := make(chan dialResult)
|
} else {
|
||||||
returned := make(chan struct{})
|
if !direct {
|
||||||
defer close(returned)
|
ip, result.error = resolver.ResolveIPv4ProxyServerHost(host)
|
||||||
racer := func(ctx context.Context, ip netip.Addr) {
|
} else {
|
||||||
result := dialResult{isPrimary: true, ip: ip}
|
ip, result.error = resolver.ResolveIPv4(host)
|
||||||
defer func() {
|
|
||||||
select {
|
|
||||||
case results <- result:
|
|
||||||
case <-returned:
|
|
||||||
if result.Conn != nil && result.error == nil {
|
|
||||||
_ = result.Conn.Close()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}()
|
if result.error != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
result.resolved = true
|
||||||
|
|
||||||
result.Conn, result.error = dialContext(ctx, network, ip, port, opt)
|
result.Conn, result.error = dialContext(ctx, network, ip, port, opt)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, ip := range ips {
|
go startRacer(ctx, network+"4", host, opt.direct, false)
|
||||||
go racer(ctx, ip)
|
go startRacer(ctx, network+"6", host, opt.direct, true)
|
||||||
}
|
|
||||||
var errs []error
|
for res := range results {
|
||||||
for i := 0; i < len(ips); i++ {
|
|
||||||
res := <-results
|
|
||||||
if res.error == nil {
|
if res.error == nil {
|
||||||
return res.Conn, nil
|
return res.Conn, nil
|
||||||
}
|
}
|
||||||
errs = append(errs, res.error)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(errs) > 0 {
|
if !res.ipv6 {
|
||||||
return nil, errorsJoin(errs...)
|
primary = res
|
||||||
}
|
|
||||||
return nil, os.ErrDeadlineExceeded
|
|
||||||
}
|
|
||||||
|
|
||||||
func serialDialContext(ctx context.Context, network string, ips []netip.Addr, port string, opt *option) (net.Conn, error) {
|
|
||||||
if len(ips) == 0 {
|
|
||||||
return nil, ErrorNoIpAddress
|
|
||||||
}
|
|
||||||
var errs []error
|
|
||||||
for _, ip := range ips {
|
|
||||||
if conn, err := dialContext(ctx, network, ip, port, opt); err == nil {
|
|
||||||
return conn, nil
|
|
||||||
} else {
|
} else {
|
||||||
errs = append(errs, err)
|
fallback = res
|
||||||
|
}
|
||||||
|
|
||||||
|
if primary.done && fallback.done {
|
||||||
|
if primary.resolved {
|
||||||
|
return nil, primary.error
|
||||||
|
} else if fallback.resolved {
|
||||||
|
return nil, fallback.error
|
||||||
|
} else {
|
||||||
|
return nil, primary.error
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil, errorsJoin(errs...)
|
}
|
||||||
|
|
||||||
|
return nil, errors.New("never touched")
|
||||||
}
|
}
|
||||||
|
|
||||||
type dialResult struct {
|
func concurrentDualStackDialContext(ctx context.Context, network, address string, opt *option) (net.Conn, error) {
|
||||||
|
host, port, err := net.SplitHostPort(address)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var ips []netip.Addr
|
||||||
|
|
||||||
|
if opt.direct {
|
||||||
|
ips, err = resolver.ResolveAllIP(host)
|
||||||
|
} else {
|
||||||
|
ips, err = resolver.ResolveAllIPProxyServerHost(host)
|
||||||
|
}
|
||||||
|
|
||||||
|
return concurrentDialContext(ctx, network, ips, port, opt)
|
||||||
|
}
|
||||||
|
|
||||||
|
func concurrentDialContext(ctx context.Context, network string, ips []netip.Addr, port string, opt *option) (net.Conn, error) {
|
||||||
|
returned := make(chan struct{})
|
||||||
|
defer close(returned)
|
||||||
|
|
||||||
|
type dialResult struct {
|
||||||
ip netip.Addr
|
ip netip.Addr
|
||||||
net.Conn
|
net.Conn
|
||||||
error
|
error
|
||||||
isPrimary bool
|
resolved bool
|
||||||
|
}
|
||||||
|
|
||||||
|
results := make(chan dialResult)
|
||||||
|
|
||||||
|
tcpRacer := func(ctx context.Context, ip netip.Addr) {
|
||||||
|
result := dialResult{ip: ip}
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
select {
|
||||||
|
case results <- result:
|
||||||
|
case <-returned:
|
||||||
|
if result.Conn != nil {
|
||||||
|
result.Conn.Close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
v := "4"
|
||||||
|
if ip.Is6() {
|
||||||
|
v = "6"
|
||||||
|
}
|
||||||
|
|
||||||
|
result.Conn, result.error = dialContext(ctx, network+v, ip, port, opt)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, ip := range ips {
|
||||||
|
go tcpRacer(ctx, ip)
|
||||||
|
}
|
||||||
|
|
||||||
|
connCount := len(ips)
|
||||||
|
for res := range results {
|
||||||
|
connCount--
|
||||||
|
if res.error == nil {
|
||||||
|
return res.Conn, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if connCount == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, fmt.Errorf("all ips %v tcp shake hands failed", ips)
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseAddr(ctx context.Context, network, address string, preferResolver resolver.Resolver) ([]netip.Addr, string, error) {
|
func singleDialContext(ctx context.Context, network string, address string, opt *option) (net.Conn, error) {
|
||||||
host, port, err := net.SplitHostPort(address)
|
host, port, err := net.SplitHostPort(address)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, "-1", err
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var ip netip.Addr
|
||||||
|
switch network {
|
||||||
|
case "tcp4", "udp4":
|
||||||
|
if !opt.direct {
|
||||||
|
ip, err = resolver.ResolveIPv4ProxyServerHost(host)
|
||||||
|
} else {
|
||||||
|
ip, err = resolver.ResolveIPv4(host)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
if !opt.direct {
|
||||||
|
ip, err = resolver.ResolveIPv6ProxyServerHost(host)
|
||||||
|
} else {
|
||||||
|
ip, err = resolver.ResolveIPv6(host)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return dialContext(ctx, network, ip, port, opt)
|
||||||
|
}
|
||||||
|
|
||||||
|
func concurrentSingleDialContext(ctx context.Context, network string, address string, opt *option) (net.Conn, error) {
|
||||||
|
host, port, err := net.SplitHostPort(address)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var ips []netip.Addr
|
var ips []netip.Addr
|
||||||
switch network {
|
switch network {
|
||||||
case "tcp4", "udp4":
|
case "tcp4", "udp4":
|
||||||
if preferResolver == nil {
|
if !opt.direct {
|
||||||
ips, err = resolver.LookupIPv4ProxyServerHost(ctx, host)
|
ips, err = resolver.ResolveAllIPv4ProxyServerHost(host)
|
||||||
} else {
|
} else {
|
||||||
ips, err = resolver.LookupIPv4WithResolver(ctx, host, preferResolver)
|
ips, err = resolver.ResolveAllIPv4(host)
|
||||||
}
|
|
||||||
case "tcp6", "udp6":
|
|
||||||
if preferResolver == nil {
|
|
||||||
ips, err = resolver.LookupIPv6ProxyServerHost(ctx, host)
|
|
||||||
} else {
|
|
||||||
ips, err = resolver.LookupIPv6WithResolver(ctx, host, preferResolver)
|
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
if preferResolver == nil {
|
if !opt.direct {
|
||||||
ips, err = resolver.LookupIPProxyServerHost(ctx, host)
|
ips, err = resolver.ResolveAllIPv6ProxyServerHost(host)
|
||||||
} else {
|
} else {
|
||||||
ips, err = resolver.LookupIPWithResolver(ctx, host, preferResolver)
|
ips, err = resolver.ResolveAllIPv6(host)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, "-1", fmt.Errorf("dns resolve failed: %w", err)
|
return nil, err
|
||||||
}
|
}
|
||||||
for i, ip := range ips {
|
|
||||||
if ip.Is4In6() {
|
|
||||||
ips[i] = ip.Unmap()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ips, port, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func sortationAddr(ips []netip.Addr) (ipv4s, ipv6s []netip.Addr) {
|
return concurrentDialContext(ctx, network, ips, port, opt)
|
||||||
for _, v := range ips {
|
|
||||||
if v.Is4() { // 4in6 parse was in parseAddr
|
|
||||||
ipv4s = append(ipv4s, v)
|
|
||||||
} else {
|
|
||||||
ipv6s = append(ipv6s, v)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
type Dialer struct {
|
|
||||||
opt option
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d Dialer) DialContext(ctx context.Context, network, address string) (net.Conn, error) {
|
|
||||||
return DialContext(ctx, network, address, WithOption(d.opt))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d Dialer) ListenPacket(ctx context.Context, network, address string, rAddrPort netip.AddrPort) (net.PacketConn, error) {
|
|
||||||
opt := WithOption(d.opt)
|
|
||||||
if rAddrPort.Addr().Unmap().IsLoopback() {
|
|
||||||
// avoid "The requested address is not valid in its context."
|
|
||||||
opt = WithInterface("")
|
|
||||||
}
|
|
||||||
return ListenPacket(ctx, ParseNetwork(network, rAddrPort.Addr()), address, opt)
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewDialer(options ...Option) Dialer {
|
|
||||||
opt := applyOptions(options...)
|
|
||||||
return Dialer{opt: *opt}
|
|
||||||
}
|
}
|
||||||
|
@ -1,18 +0,0 @@
|
|||||||
package dialer
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
|
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
ErrorNoIpAddress = errors.New("no ip address")
|
|
||||||
ErrorInvalidedNetworkStack = errors.New("invalided network stack")
|
|
||||||
)
|
|
||||||
|
|
||||||
func errorsJoin(errs ...error) error {
|
|
||||||
// compatibility with golang<1.20
|
|
||||||
// maybe use errors.Join(errs...) is better after we drop the old version's support
|
|
||||||
return E.Errors(errs...)
|
|
||||||
}
|
|
@ -3,35 +3,39 @@
|
|||||||
package dialer
|
package dialer
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"net"
|
"net"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"syscall"
|
"syscall"
|
||||||
)
|
)
|
||||||
|
|
||||||
func bindMarkToDialer(mark int, dialer *net.Dialer, _ string, _ netip.Addr) {
|
func bindMarkToDialer(mark int, dialer *net.Dialer, _ string, _ netip.Addr) {
|
||||||
addControlToDialer(dialer, bindMarkToControl(mark))
|
dialer.Control = bindMarkToControl(mark, dialer.Control)
|
||||||
}
|
}
|
||||||
|
|
||||||
func bindMarkToListenConfig(mark int, lc *net.ListenConfig, _, _ string) {
|
func bindMarkToListenConfig(mark int, lc *net.ListenConfig, _, _ string) {
|
||||||
addControlToListenConfig(lc, bindMarkToControl(mark))
|
lc.Control = bindMarkToControl(mark, lc.Control)
|
||||||
}
|
}
|
||||||
|
|
||||||
func bindMarkToControl(mark int) controlFn {
|
func bindMarkToControl(mark int, chain controlFn) controlFn {
|
||||||
return func(ctx context.Context, network, address string, c syscall.RawConn) (err error) {
|
return func(network, address string, c syscall.RawConn) (err error) {
|
||||||
|
defer func() {
|
||||||
|
if err == nil && chain != nil {
|
||||||
|
err = chain(network, address, c)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
addrPort, err := netip.ParseAddrPort(address)
|
addrPort, err := netip.ParseAddrPort(address)
|
||||||
if err == nil && !addrPort.Addr().IsGlobalUnicast() {
|
if err == nil && !addrPort.Addr().IsGlobalUnicast() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var innerErr error
|
return c.Control(func(fd uintptr) {
|
||||||
err = c.Control(func(fd uintptr) {
|
switch network {
|
||||||
innerErr = syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, syscall.SO_MARK, mark)
|
case "tcp4", "udp4":
|
||||||
})
|
_ = syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, syscall.SO_MARK, mark)
|
||||||
if innerErr != nil {
|
case "tcp6", "udp6":
|
||||||
err = innerErr
|
_ = syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, syscall.SO_MARK, mark)
|
||||||
}
|
}
|
||||||
return
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,13 +1,6 @@
|
|||||||
package dialer
|
package dialer
|
||||||
|
|
||||||
import (
|
import "go.uber.org/atomic"
|
||||||
"context"
|
|
||||||
"net"
|
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/component/resolver"
|
|
||||||
|
|
||||||
"go.uber.org/atomic"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
var (
|
||||||
DefaultOptions []Option
|
DefaultOptions []Option
|
||||||
@ -15,19 +8,11 @@ var (
|
|||||||
DefaultRoutingMark = atomic.NewInt32(0)
|
DefaultRoutingMark = atomic.NewInt32(0)
|
||||||
)
|
)
|
||||||
|
|
||||||
type NetDialer interface {
|
|
||||||
DialContext(ctx context.Context, network, address string) (net.Conn, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
type option struct {
|
type option struct {
|
||||||
interfaceName string
|
interfaceName string
|
||||||
addrReuse bool
|
addrReuse bool
|
||||||
routingMark int
|
routingMark int
|
||||||
network int
|
direct bool
|
||||||
prefer int
|
|
||||||
tfo bool
|
|
||||||
resolver resolver.Resolver
|
|
||||||
netDialer NetDialer
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type Option func(opt *option)
|
type Option func(opt *option)
|
||||||
@ -50,48 +35,8 @@ func WithRoutingMark(mark int) Option {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func WithResolver(r resolver.Resolver) Option {
|
func WithDirect() Option {
|
||||||
return func(opt *option) {
|
return func(opt *option) {
|
||||||
opt.resolver = r
|
opt.direct = true
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func WithPreferIPv4() Option {
|
|
||||||
return func(opt *option) {
|
|
||||||
opt.prefer = 4
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func WithPreferIPv6() Option {
|
|
||||||
return func(opt *option) {
|
|
||||||
opt.prefer = 6
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func WithOnlySingleStack(isIPv4 bool) Option {
|
|
||||||
return func(opt *option) {
|
|
||||||
if isIPv4 {
|
|
||||||
opt.network = 4
|
|
||||||
} else {
|
|
||||||
opt.network = 6
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func WithTFO(tfo bool) Option {
|
|
||||||
return func(opt *option) {
|
|
||||||
opt.tfo = tfo
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func WithNetDialer(netDialer NetDialer) Option {
|
|
||||||
return func(opt *option) {
|
|
||||||
opt.netDialer = netDialer
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func WithOption(o option) Option {
|
|
||||||
return func(opt *option) {
|
|
||||||
*opt = o
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,6 @@
|
|||||||
package dialer
|
package dialer
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"net"
|
"net"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
@ -11,10 +10,18 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func addrReuseToListenConfig(lc *net.ListenConfig) {
|
func addrReuseToListenConfig(lc *net.ListenConfig) {
|
||||||
addControlToListenConfig(lc, func(ctx context.Context, network, address string, c syscall.RawConn) error {
|
chain := lc.Control
|
||||||
|
|
||||||
|
lc.Control = func(network, address string, c syscall.RawConn) (err error) {
|
||||||
|
defer func() {
|
||||||
|
if err == nil && chain != nil {
|
||||||
|
err = chain(network, address, c)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
return c.Control(func(fd uintptr) {
|
return c.Control(func(fd uintptr) {
|
||||||
unix.SetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_REUSEADDR, 1)
|
unix.SetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_REUSEADDR, 1)
|
||||||
unix.SetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_REUSEPORT, 1)
|
unix.SetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_REUSEPORT, 1)
|
||||||
})
|
})
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
package dialer
|
package dialer
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"net"
|
"net"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
@ -9,9 +8,17 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func addrReuseToListenConfig(lc *net.ListenConfig) {
|
func addrReuseToListenConfig(lc *net.ListenConfig) {
|
||||||
addControlToListenConfig(lc, func(ctx context.Context, network, address string, c syscall.RawConn) error {
|
chain := lc.Control
|
||||||
|
|
||||||
|
lc.Control = func(network, address string, c syscall.RawConn) (err error) {
|
||||||
|
defer func() {
|
||||||
|
if err == nil && chain != nil {
|
||||||
|
err = chain(network, address, c)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
return c.Control(func(fd uintptr) {
|
return c.Control(func(fd uintptr) {
|
||||||
windows.SetsockoptInt(windows.Handle(fd), windows.SOL_SOCKET, windows.SO_REUSEADDR, 1)
|
windows.SetsockoptInt(windows.Handle(fd), windows.SOL_SOCKET, windows.SO_REUSEADDR, 1)
|
||||||
})
|
})
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,123 +0,0 @@
|
|||||||
package dialer
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"github.com/sagernet/tfo-go"
|
|
||||||
"io"
|
|
||||||
"net"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
type tfoConn struct {
|
|
||||||
net.Conn
|
|
||||||
closed bool
|
|
||||||
dialed chan bool
|
|
||||||
cancel context.CancelFunc
|
|
||||||
ctx context.Context
|
|
||||||
dialFn func(ctx context.Context, earlyData []byte) (net.Conn, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *tfoConn) Dial(earlyData []byte) (err error) {
|
|
||||||
c.Conn, err = c.dialFn(c.ctx, earlyData)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
c.dialed <- true
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *tfoConn) Read(b []byte) (n int, err error) {
|
|
||||||
if c.closed {
|
|
||||||
return 0, io.ErrClosedPipe
|
|
||||||
}
|
|
||||||
if c.Conn == nil {
|
|
||||||
select {
|
|
||||||
case <-c.ctx.Done():
|
|
||||||
return 0, io.ErrUnexpectedEOF
|
|
||||||
case <-c.dialed:
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return c.Conn.Read(b)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *tfoConn) Write(b []byte) (n int, err error) {
|
|
||||||
if c.closed {
|
|
||||||
return 0, io.ErrClosedPipe
|
|
||||||
}
|
|
||||||
if c.Conn == nil {
|
|
||||||
if err := c.Dial(b); err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
return len(b), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return c.Conn.Write(b)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *tfoConn) Close() error {
|
|
||||||
c.closed = true
|
|
||||||
c.cancel()
|
|
||||||
if c.Conn == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return c.Conn.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *tfoConn) LocalAddr() net.Addr {
|
|
||||||
if c.Conn == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return c.Conn.LocalAddr()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *tfoConn) RemoteAddr() net.Addr {
|
|
||||||
if c.Conn == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return c.Conn.RemoteAddr()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *tfoConn) SetDeadline(t time.Time) error {
|
|
||||||
if err := c.SetReadDeadline(t); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return c.SetWriteDeadline(t)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *tfoConn) SetReadDeadline(t time.Time) error {
|
|
||||||
if c.Conn == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return c.Conn.SetReadDeadline(t)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *tfoConn) SetWriteDeadline(t time.Time) error {
|
|
||||||
if c.Conn == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return c.Conn.SetWriteDeadline(t)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *tfoConn) Upstream() any {
|
|
||||||
if c.Conn == nil { // ensure return a nil interface not an interface with nil value
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return c.Conn
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *tfoConn) NeedHandshake() bool {
|
|
||||||
return c.Conn == nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func dialTFO(ctx context.Context, netDialer net.Dialer, network, address string) (net.Conn, error) {
|
|
||||||
ctx, cancel := context.WithCancel(ctx)
|
|
||||||
dialer := tfo.Dialer{Dialer: netDialer, DisableTFO: false}
|
|
||||||
return &tfoConn{
|
|
||||||
dialed: make(chan bool, 1),
|
|
||||||
cancel: cancel,
|
|
||||||
ctx: ctx,
|
|
||||||
dialFn: func(ctx context.Context, earlyData []byte) (net.Conn, error) {
|
|
||||||
return dialer.DialContext(ctx, network, address, earlyData)
|
|
||||||
},
|
|
||||||
}, nil
|
|
||||||
}
|
|
@ -1,99 +0,0 @@
|
|||||||
/* SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) */
|
|
||||||
#ifndef __BPF_ENDIAN__
|
|
||||||
#define __BPF_ENDIAN__
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Isolate byte #n and put it into byte #m, for __u##b type.
|
|
||||||
* E.g., moving byte #6 (nnnnnnnn) into byte #1 (mmmmmmmm) for __u64:
|
|
||||||
* 1) xxxxxxxx nnnnnnnn xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx mmmmmmmm xxxxxxxx
|
|
||||||
* 2) nnnnnnnn xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx mmmmmmmm xxxxxxxx 00000000
|
|
||||||
* 3) 00000000 00000000 00000000 00000000 00000000 00000000 00000000 nnnnnnnn
|
|
||||||
* 4) 00000000 00000000 00000000 00000000 00000000 00000000 nnnnnnnn 00000000
|
|
||||||
*/
|
|
||||||
#define ___bpf_mvb(x, b, n, m) ((__u##b)(x) << (b-(n+1)*8) >> (b-8) << (m*8))
|
|
||||||
|
|
||||||
#define ___bpf_swab16(x) ((__u16)( \
|
|
||||||
___bpf_mvb(x, 16, 0, 1) | \
|
|
||||||
___bpf_mvb(x, 16, 1, 0)))
|
|
||||||
|
|
||||||
#define ___bpf_swab32(x) ((__u32)( \
|
|
||||||
___bpf_mvb(x, 32, 0, 3) | \
|
|
||||||
___bpf_mvb(x, 32, 1, 2) | \
|
|
||||||
___bpf_mvb(x, 32, 2, 1) | \
|
|
||||||
___bpf_mvb(x, 32, 3, 0)))
|
|
||||||
|
|
||||||
#define ___bpf_swab64(x) ((__u64)( \
|
|
||||||
___bpf_mvb(x, 64, 0, 7) | \
|
|
||||||
___bpf_mvb(x, 64, 1, 6) | \
|
|
||||||
___bpf_mvb(x, 64, 2, 5) | \
|
|
||||||
___bpf_mvb(x, 64, 3, 4) | \
|
|
||||||
___bpf_mvb(x, 64, 4, 3) | \
|
|
||||||
___bpf_mvb(x, 64, 5, 2) | \
|
|
||||||
___bpf_mvb(x, 64, 6, 1) | \
|
|
||||||
___bpf_mvb(x, 64, 7, 0)))
|
|
||||||
|
|
||||||
/* LLVM's BPF target selects the endianness of the CPU
|
|
||||||
* it compiles on, or the user specifies (bpfel/bpfeb),
|
|
||||||
* respectively. The used __BYTE_ORDER__ is defined by
|
|
||||||
* the compiler, we cannot rely on __BYTE_ORDER from
|
|
||||||
* libc headers, since it doesn't reflect the actual
|
|
||||||
* requested byte order.
|
|
||||||
*
|
|
||||||
* Note, LLVM's BPF target has different __builtin_bswapX()
|
|
||||||
* semantics. It does map to BPF_ALU | BPF_END | BPF_TO_BE
|
|
||||||
* in bpfel and bpfeb case, which means below, that we map
|
|
||||||
* to cpu_to_be16(). We could use it unconditionally in BPF
|
|
||||||
* case, but better not rely on it, so that this header here
|
|
||||||
* can be used from application and BPF program side, which
|
|
||||||
* use different targets.
|
|
||||||
*/
|
|
||||||
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
|
|
||||||
# define __bpf_ntohs(x) __builtin_bswap16(x)
|
|
||||||
# define __bpf_htons(x) __builtin_bswap16(x)
|
|
||||||
# define __bpf_constant_ntohs(x) ___bpf_swab16(x)
|
|
||||||
# define __bpf_constant_htons(x) ___bpf_swab16(x)
|
|
||||||
# define __bpf_ntohl(x) __builtin_bswap32(x)
|
|
||||||
# define __bpf_htonl(x) __builtin_bswap32(x)
|
|
||||||
# define __bpf_constant_ntohl(x) ___bpf_swab32(x)
|
|
||||||
# define __bpf_constant_htonl(x) ___bpf_swab32(x)
|
|
||||||
# define __bpf_be64_to_cpu(x) __builtin_bswap64(x)
|
|
||||||
# define __bpf_cpu_to_be64(x) __builtin_bswap64(x)
|
|
||||||
# define __bpf_constant_be64_to_cpu(x) ___bpf_swab64(x)
|
|
||||||
# define __bpf_constant_cpu_to_be64(x) ___bpf_swab64(x)
|
|
||||||
#elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
|
|
||||||
# define __bpf_ntohs(x) (x)
|
|
||||||
# define __bpf_htons(x) (x)
|
|
||||||
# define __bpf_constant_ntohs(x) (x)
|
|
||||||
# define __bpf_constant_htons(x) (x)
|
|
||||||
# define __bpf_ntohl(x) (x)
|
|
||||||
# define __bpf_htonl(x) (x)
|
|
||||||
# define __bpf_constant_ntohl(x) (x)
|
|
||||||
# define __bpf_constant_htonl(x) (x)
|
|
||||||
# define __bpf_be64_to_cpu(x) (x)
|
|
||||||
# define __bpf_cpu_to_be64(x) (x)
|
|
||||||
# define __bpf_constant_be64_to_cpu(x) (x)
|
|
||||||
# define __bpf_constant_cpu_to_be64(x) (x)
|
|
||||||
#else
|
|
||||||
# error "Fix your compiler's __BYTE_ORDER__?!"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#define bpf_htons(x) \
|
|
||||||
(__builtin_constant_p(x) ? \
|
|
||||||
__bpf_constant_htons(x) : __bpf_htons(x))
|
|
||||||
#define bpf_ntohs(x) \
|
|
||||||
(__builtin_constant_p(x) ? \
|
|
||||||
__bpf_constant_ntohs(x) : __bpf_ntohs(x))
|
|
||||||
#define bpf_htonl(x) \
|
|
||||||
(__builtin_constant_p(x) ? \
|
|
||||||
__bpf_constant_htonl(x) : __bpf_htonl(x))
|
|
||||||
#define bpf_ntohl(x) \
|
|
||||||
(__builtin_constant_p(x) ? \
|
|
||||||
__bpf_constant_ntohl(x) : __bpf_ntohl(x))
|
|
||||||
#define bpf_cpu_to_be64(x) \
|
|
||||||
(__builtin_constant_p(x) ? \
|
|
||||||
__bpf_constant_cpu_to_be64(x) : __bpf_cpu_to_be64(x))
|
|
||||||
#define bpf_be64_to_cpu(x) \
|
|
||||||
(__builtin_constant_p(x) ? \
|
|
||||||
__bpf_constant_be64_to_cpu(x) : __bpf_be64_to_cpu(x))
|
|
||||||
|
|
||||||
#endif /* __BPF_ENDIAN__ */
|
|
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user