Compare commits
36 Commits
v1.12.3_tu
...
plus_pro
Author | SHA1 | Date | |
---|---|---|---|
3610d3dd84 | |||
663017a775 | |||
05f0c1060b | |||
e03f1d0565 | |||
c8e2b30540 | |||
dd95d335d9 | |||
ea8a5409ad | |||
5b49414b49 | |||
178c70a320 | |||
6fe19944ad | |||
9f00907647 | |||
160e630f03 | |||
13d19ff101 | |||
934babca85 | |||
f23d1d5d7c | |||
4334b45e82 | |||
9ffcc9e352 | |||
392572d684 | |||
0321ddbb90 | |||
d74dd69329 | |||
c8bc4386dd | |||
1e7cbd6358 | |||
0a2701eef0 | |||
0d004bf6f3 | |||
053366c3e1 | |||
9d72bf2a36 | |||
571c34f140 | |||
05b4a326de | |||
d77ef6a525 | |||
4d9d8b28ec | |||
7973491625 | |||
edbc8ed972 | |||
bd123dddc6 | |||
ae493f1084 | |||
711b2bcf87 | |||
a45354fa08 |
142
.github/workflows/build-windows-amd.yml
vendored
Normal file
142
.github/workflows/build-windows-amd.yml
vendored
Normal file
@ -0,0 +1,142 @@
|
|||||||
|
name: Build-Windows
|
||||||
|
on: [push]
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: windows-latest
|
||||||
|
steps:
|
||||||
|
- name: Setup Go
|
||||||
|
uses: actions/setup-go@v2
|
||||||
|
with:
|
||||||
|
go-version: 1.18.x
|
||||||
|
|
||||||
|
- name: Check out code into the Go module directory
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- name: Go cache paths
|
||||||
|
id: go-cache-paths
|
||||||
|
run: |
|
||||||
|
echo "::set-output name=go-build::$(go env GOCACHE)"
|
||||||
|
echo "::set-output name=go-mod::$(go env GOMODCACHE)"
|
||||||
|
|
||||||
|
- name: Cache go module
|
||||||
|
uses: actions/cache@v2
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
${{ steps.go-cache-paths.outputs.go-mod }}
|
||||||
|
${{ steps.go-cache-paths.outputs.go-build }}
|
||||||
|
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-go-
|
||||||
|
|
||||||
|
- name: Setup Python
|
||||||
|
uses: actions/setup-python@v3
|
||||||
|
with:
|
||||||
|
python-version: '3.9'
|
||||||
|
architecture: 'x64'
|
||||||
|
|
||||||
|
- name: Get dependencies, run test
|
||||||
|
id: test
|
||||||
|
run: |
|
||||||
|
cmd /c mklink /J D:\python-amd64 $env:pythonLocation
|
||||||
|
|
||||||
|
echo "::set-output name=file_sha::$(git describe --tags --always)"
|
||||||
|
echo "::set-output name=file_date::$(Get-Date -Format 'yyyyMMdd')"
|
||||||
|
|
||||||
|
((Get-Content -path constant/version.go -Raw) -replace 'unknown version',$(Get-Date -Format 'yyyy.MM.dd')) | Set-Content -Path constant/version.go
|
||||||
|
((Get-Content -path constant/version.go -Raw) -replace 'unknown time',$(Get-Date)) | Set-Content -Path constant/version.go
|
||||||
|
|
||||||
|
# go test
|
||||||
|
go test -tags build_actions ./...
|
||||||
|
|
||||||
|
- name: Build
|
||||||
|
#if: startsWith(github.ref, 'refs/tags/')
|
||||||
|
run: |
|
||||||
|
$env:CGO_ENABLED=1; go build -tags build_actions -trimpath -ldflags '-w -s -buildid=' -o bin/clash-plus-pro-windows-amd64.exe
|
||||||
|
$env:GOAMD64="v3"; $env:CGO_ENABLED=1; go build -tags build_actions -trimpath -ldflags '-w -s -buildid=' -o bin/clash-plus-pro-windows-amd64-v3.exe
|
||||||
|
|
||||||
|
$version = Get-Date -Format 'yyyy.MM.dd'
|
||||||
|
|
||||||
|
cd bin/
|
||||||
|
Compress-Archive -Path clash-plus-pro-windows-amd64.exe -DestinationPath clash-plus-pro-windows-amd64-$version.zip
|
||||||
|
Compress-Archive -Path clash-plus-pro-windows-amd64-v3.exe -DestinationPath clash-plus-pro-windows-amd64-v3-$version.zip
|
||||||
|
Remove-Item -Force clash-plus-pro-windows-amd64.exe
|
||||||
|
Remove-Item -Force clash-plus-pro-windows-amd64-v3.exe
|
||||||
|
"$version" | Out-File version.txt -NoNewLine
|
||||||
|
|
||||||
|
- name: Upload files to tag
|
||||||
|
if: startsWith(github.ref, 'refs/tags/') == false
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
run: |
|
||||||
|
$version = Get-Date -Format 'yyyy.MM.dd'
|
||||||
|
|
||||||
|
$plus_pro = curl `
|
||||||
|
-H "Accept: application/vnd.github.v3+json" `
|
||||||
|
-H "Authorization: token $env:GITHUB_TOKEN" `
|
||||||
|
https://api.github.com/repos/yaling888/clash/releases/tags/plus_pro | ConvertFrom-Json
|
||||||
|
|
||||||
|
$plus_pro_url = $plus_pro.url
|
||||||
|
$upload_url = $plus_pro.upload_url
|
||||||
|
$plus_pro_upload_url = $upload_url.Substring(0,$upload_url.Length-13)
|
||||||
|
|
||||||
|
curl `
|
||||||
|
-X PATCH `
|
||||||
|
-H "Accept: application/vnd.github.v3+json" `
|
||||||
|
-H "Authorization: token $env:GITHUB_TOKEN" `
|
||||||
|
"$plus_pro_url" `
|
||||||
|
-d "{`"name`":`"Plus Pro $version`",`"draft`":true}" | Out-Null
|
||||||
|
|
||||||
|
foreach ($asset in $plus_pro.assets)
|
||||||
|
{
|
||||||
|
curl `
|
||||||
|
-X DELETE `
|
||||||
|
-H "Accept: application/vnd.github.v3+json" `
|
||||||
|
-H "Authorization: token $env:GITHUB_TOKEN" `
|
||||||
|
"$($asset.url)" | Out-Null
|
||||||
|
}
|
||||||
|
|
||||||
|
curl `
|
||||||
|
-X POST `
|
||||||
|
-H "Content-Type: application/zip" `
|
||||||
|
-T "bin/clash-plus-pro-windows-amd64-$version.zip" `
|
||||||
|
-H "Accept: application/vnd.github.v3+json" `
|
||||||
|
-H "Authorization: token $env:GITHUB_TOKEN" `
|
||||||
|
"$plus_pro_upload_url?name=clash-plus-pro-windows-amd64-$version.zip" | Out-Null
|
||||||
|
|
||||||
|
curl `
|
||||||
|
-X POST `
|
||||||
|
-H "Content-Type: application/zip" `
|
||||||
|
-T "bin/clash-plus-pro-windows-amd64-v3-$version.zip" `
|
||||||
|
-H "Accept: application/vnd.github.v3+json" `
|
||||||
|
-H "Authorization: token $env:GITHUB_TOKEN" `
|
||||||
|
"$plus_pro_upload_url?name=clash-plus-pro-windows-amd64-v3-$version.zip" | Out-Null
|
||||||
|
|
||||||
|
curl `
|
||||||
|
-X POST `
|
||||||
|
-H "Content-Type: text/plain" `
|
||||||
|
-T "bin/version.txt" `
|
||||||
|
-H "Accept: application/vnd.github.v3+json" `
|
||||||
|
-H "Authorization: token $env:GITHUB_TOKEN" `
|
||||||
|
"$plus_pro_upload_url?name=version.txt" | Out-Null
|
||||||
|
|
||||||
|
#- name: Upload files to Artifacts
|
||||||
|
# uses: actions/upload-artifact@v2
|
||||||
|
# with:
|
||||||
|
# name: clash-windows-amd64-${{ steps.test.outputs.file_sha }}-${{ steps.test.outputs.file_date }}
|
||||||
|
# path: |
|
||||||
|
# bin/*
|
||||||
|
|
||||||
|
- name: Upload Release
|
||||||
|
uses: softprops/action-gh-release@v1
|
||||||
|
if: startsWith(github.ref, 'refs/tags/')
|
||||||
|
with:
|
||||||
|
files: bin/*
|
||||||
|
draft: true
|
||||||
|
prerelease: false
|
||||||
|
generate_release_notes: false
|
||||||
|
|
||||||
|
#- name: Delete workflow runs
|
||||||
|
# uses: GitRML/delete-workflow-runs@main
|
||||||
|
# with:
|
||||||
|
# retain_days: 1
|
||||||
|
# keep_minimum_runs: 2
|
1
.github/workflows/linter.yml
vendored
1
.github/workflows/linter.yml
vendored
@ -20,3 +20,4 @@ jobs:
|
|||||||
uses: golangci/golangci-lint-action@v3
|
uses: golangci/golangci-lint-action@v3
|
||||||
with:
|
with:
|
||||||
version: latest
|
version: latest
|
||||||
|
args: --build-tags=build_local
|
||||||
|
79
.github/workflows/release.yml
vendored
79
.github/workflows/release.yml
vendored
@ -1,5 +1,8 @@
|
|||||||
name: Release
|
name: Release
|
||||||
on: [push]
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- rm
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
@ -33,29 +36,63 @@ jobs:
|
|||||||
restore-keys: |
|
restore-keys: |
|
||||||
${{ runner.os }}-go-
|
${{ runner.os }}-go-
|
||||||
|
|
||||||
|
- name: Setup Python
|
||||||
|
uses: actions/setup-python@v2
|
||||||
|
with:
|
||||||
|
python-version: '3.9'
|
||||||
|
|
||||||
- name: Get dependencies, run test
|
- name: Get dependencies, run test
|
||||||
run: |
|
run: |
|
||||||
go test ./...
|
# fetch python cross compile source files
|
||||||
|
mkdir -p bin/python/
|
||||||
|
cd bin/python/
|
||||||
|
curl -LO https://raw.githubusercontent.com/yaling888/snack/main/python-3.9.7-darwin-amd64.tar.xz
|
||||||
|
curl -LO https://raw.githubusercontent.com/yaling888/snack/main/python-3.9.7-darwin-arm64.tar.xz
|
||||||
|
curl -LO https://raw.githubusercontent.com/yaling888/snack/main/python-3.9.7-windows-amd64.tar.xz
|
||||||
|
curl -LO https://raw.githubusercontent.com/yaling888/snack/main/python-3.9.7-windows-386.tar.xz
|
||||||
|
#curl -LO https://raw.githubusercontent.com/yaling888/snack/main/python-3.9.7-linux-amd64.tar.xz
|
||||||
|
#curl -LO https://raw.githubusercontent.com/yaling888/snack/main/python-3.9.7-linux-arm64.tar.xz
|
||||||
|
#curl -LO https://raw.githubusercontent.com/yaling888/snack/main/python-3.9.7-linux-386.tar.xz
|
||||||
|
tar -Jxf python-3.9.7-darwin-amd64.tar.xz
|
||||||
|
tar -Jxf python-3.9.7-darwin-arm64.tar.xz
|
||||||
|
tar -Jxf python-3.9.7-windows-amd64.tar.xz
|
||||||
|
tar -Jxf python-3.9.7-windows-386.tar.xz
|
||||||
|
#tar -Jxf python-3.9.7-linux-amd64.tar.xz
|
||||||
|
#tar -Jxf python-3.9.7-linux-arm64.tar.xz
|
||||||
|
#tar -Jxf python-3.9.7-linux-386.tar.xz
|
||||||
|
rm python-3.9.7-*.tar.xz
|
||||||
|
cd ../../
|
||||||
|
|
||||||
|
# go test
|
||||||
|
go test -tags build_local ./...
|
||||||
|
|
||||||
|
# init xgo
|
||||||
|
docker pull techknowlogick/xgo:latest
|
||||||
|
go install src.techknowlogick.com/xgo@latest
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
if: startsWith(github.ref, 'refs/tags/')
|
#if: startsWith(github.ref, 'refs/tags/')
|
||||||
env:
|
env:
|
||||||
NAME: clash
|
NAME: clash
|
||||||
BINDIR: bin
|
BINDIR: bin
|
||||||
run: make -j releases
|
run: |
|
||||||
|
make -j releases
|
||||||
|
#ls -lahF bin/python/
|
||||||
|
|
||||||
#- name: Prepare upload
|
- name: Prepare upload
|
||||||
# run: |
|
if: startsWith(github.ref, 'refs/tags/') == false
|
||||||
# echo "FILE_DATE=_$(date +"%Y%m%d%H%M")" >> $GITHUB_ENV
|
run: |
|
||||||
# echo "FILE_SHA=$(git describe --tags --always 2>/dev/null)" >> $GITHUB_ENV
|
rm -rf bin/python/
|
||||||
#
|
echo "FILE_DATE=_$(date +"%Y%m%d%H%M")" >> $GITHUB_ENV
|
||||||
#- name: Upload files to Artifacts
|
echo "FILE_SHA=$(git describe --tags --always 2>/dev/null)" >> $GITHUB_ENV
|
||||||
# uses: actions/upload-artifact@v2
|
|
||||||
# if: startsWith(github.ref, 'refs/tags/') == false
|
- name: Upload files to Artifacts
|
||||||
# with:
|
uses: actions/upload-artifact@v2
|
||||||
# name: clash_${{ env.FILE_SHA }}${{ env.FILE_DATE }}
|
if: startsWith(github.ref, 'refs/tags/') == false
|
||||||
# path: |
|
with:
|
||||||
# bin/*
|
name: clash_${{ env.FILE_SHA }}${{ env.FILE_DATE }}
|
||||||
|
path: |
|
||||||
|
bin/*
|
||||||
|
|
||||||
- name: Upload Release
|
- name: Upload Release
|
||||||
uses: softprops/action-gh-release@v1
|
uses: softprops/action-gh-release@v1
|
||||||
@ -71,3 +108,13 @@ jobs:
|
|||||||
# with:
|
# with:
|
||||||
# retain_days: 1
|
# retain_days: 1
|
||||||
# keep_minimum_runs: 2
|
# keep_minimum_runs: 2
|
||||||
|
|
||||||
|
- name: Remove old Releases
|
||||||
|
uses: dev-drprasad/delete-older-releases@v0.2.0
|
||||||
|
if: startsWith(github.ref, 'refs/tags/') && !cancelled()
|
||||||
|
with:
|
||||||
|
keep_latest: 1
|
||||||
|
delete_tags: true
|
||||||
|
delete_tag_pattern: plus-pro
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
144
Makefile
144
Makefile
@ -1,119 +1,70 @@
|
|||||||
|
GOCMD=go
|
||||||
|
XGOCMD=xgo -go=go-1.18.x
|
||||||
|
GOBUILD=CGO_ENABLED=1 $(GOCMD) build -trimpath
|
||||||
|
GOCLEAN=$(GOCMD) clean
|
||||||
NAME=clash
|
NAME=clash
|
||||||
BINDIR=bin
|
BINDIR=$(shell pwd)/bin
|
||||||
VERSION=$(shell git describe --tags --always 2>/dev/null || echo "unknown version")
|
VERSION=$(shell git describe --tags --always 2>/dev/null || date +%F)
|
||||||
BUILDTIME=$(shell date -u)
|
BUILDTIME=$(shell date -u)
|
||||||
GOBUILD=CGO_ENABLED=0 go build -trimpath -ldflags '-X "github.com/Dreamacro/clash/constant.Version=$(VERSION)" \
|
BUILD_PACKAGE=.
|
||||||
|
RELEASE_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='
|
||||||
|
STATIC_LDFLAGS='-X "github.com/Dreamacro/clash/constant.Version=$(VERSION)" \
|
||||||
|
-X "github.com/Dreamacro/clash/constant.BuildTime=$(BUILDTIME)" \
|
||||||
|
-extldflags "-static" \
|
||||||
|
-w -s -buildid='
|
||||||
|
|
||||||
PLATFORM_LIST = \
|
PLATFORM_LIST = \
|
||||||
darwin-amd64 \
|
darwin-amd64 \
|
||||||
darwin-amd64-v3 \
|
|
||||||
darwin-arm64 \
|
darwin-arm64 \
|
||||||
linux-386 \
|
linux-amd64
|
||||||
linux-amd64 \
|
# linux-arm64
|
||||||
linux-amd64-v3 \
|
# linux-386
|
||||||
linux-armv5 \
|
|
||||||
linux-armv6 \
|
|
||||||
linux-armv7 \
|
|
||||||
linux-armv8 \
|
|
||||||
linux-mips-softfloat \
|
|
||||||
linux-mips-hardfloat \
|
|
||||||
linux-mipsle-softfloat \
|
|
||||||
linux-mipsle-hardfloat \
|
|
||||||
linux-mips64 \
|
|
||||||
linux-mips64le \
|
|
||||||
freebsd-386 \
|
|
||||||
freebsd-amd64 \
|
|
||||||
freebsd-amd64-v3 \
|
|
||||||
freebsd-arm64
|
|
||||||
|
|
||||||
WINDOWS_ARCH_LIST = \
|
WINDOWS_ARCH_LIST = \
|
||||||
windows-386 \
|
|
||||||
windows-amd64 \
|
windows-amd64 \
|
||||||
windows-amd64-v3 \
|
windows-386
|
||||||
windows-arm64 \
|
# windows-arm64
|
||||||
windows-arm32v7
|
|
||||||
|
|
||||||
all: linux-amd64 darwin-amd64 windows-amd64 # Most used
|
all: linux-amd64 darwin-amd64 windows-amd64 # Most used
|
||||||
|
|
||||||
docker:
|
local:
|
||||||
$(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
$(GOBUILD) -ldflags $(RELEASE_LDFLAGS) -tags build_local -o $(BINDIR)/$(NAME)-$@
|
||||||
|
|
||||||
|
local-v3:
|
||||||
|
GOAMD64=v3 $(GOBUILD) -ldflags $(RELEASE_LDFLAGS) -tags build_local -o $(BINDIR)/$(NAME)-$@
|
||||||
|
|
||||||
darwin-amd64:
|
darwin-amd64:
|
||||||
GOARCH=amd64 GOOS=darwin $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
$(XGOCMD) -dest=$(BINDIR) -out=$(NAME) -trimpath=true -ldflags=$(RELEASE_LDFLAGS) -targets=darwin-10.12/amd64 $(BUILD_PACKAGE) && \
|
||||||
|
mv $(BINDIR)/$(NAME)-darwin-10.12-amd64 $(BINDIR)/$(NAME)-darwin-amd64
|
||||||
darwin-amd64-v3:
|
|
||||||
GOARCH=amd64 GOOS=darwin GOAMD64=v3 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
|
||||||
|
|
||||||
darwin-arm64:
|
darwin-arm64:
|
||||||
GOARCH=arm64 GOOS=darwin $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
$(XGOCMD) -dest=$(BINDIR) -out=$(NAME) -trimpath=true -ldflags=$(RELEASE_LDFLAGS) -targets=darwin-11.1/arm64 $(BUILD_PACKAGE) && \
|
||||||
|
mv $(BINDIR)/$(NAME)-darwin-11.1-arm64 $(BINDIR)/$(NAME)-darwin-arm64
|
||||||
|
|
||||||
linux-386:
|
linux-386:
|
||||||
GOARCH=386 GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
$(XGOCMD) -dest=$(BINDIR) -out=$(NAME) -trimpath=true -ldflags=$(STATIC_LDFLAGS) -targets=linux/386 $(BUILD_PACKAGE)
|
||||||
|
|
||||||
linux-amd64:
|
linux-amd64:
|
||||||
GOARCH=amd64 GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
$(GOBUILD) -ldflags $(RELEASE_LDFLAGS) -o $(BINDIR)/$(NAME)-$@
|
||||||
|
#GOARCH=amd64 GOOS=linux $(GOBUILD) -ldflags $(RELEASE_LDFLAGS) -o $(BINDIR)/$(NAME)-$@
|
||||||
|
#$(XGOCMD) -dest=$(BINDIR) -out=$(NAME) -trimpath=true -ldflags=$(STATIC_LDFLAGS) -targets=linux/amd64 $(BUILD_PACKAGE)
|
||||||
|
|
||||||
linux-amd64-v3:
|
linux-arm64:
|
||||||
GOARCH=amd64 GOOS=linux GOAMD64=v3 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
$(XGOCMD) -dest=$(BINDIR) -out=$(NAME) -trimpath=true -ldflags=$(STATIC_LDFLAGS) -targets=linux/arm64 $(BUILD_PACKAGE)
|
||||||
|
|
||||||
linux-armv5:
|
|
||||||
GOARCH=arm GOOS=linux GOARM=5 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
|
||||||
|
|
||||||
linux-armv6:
|
|
||||||
GOARCH=arm GOOS=linux GOARM=6 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
|
||||||
|
|
||||||
linux-armv7:
|
|
||||||
GOARCH=arm GOOS=linux GOARM=7 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
|
||||||
|
|
||||||
linux-armv8:
|
|
||||||
GOARCH=arm64 GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
|
||||||
|
|
||||||
linux-mips-softfloat:
|
|
||||||
GOARCH=mips GOMIPS=softfloat GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
|
||||||
|
|
||||||
linux-mips-hardfloat:
|
|
||||||
GOARCH=mips GOMIPS=hardfloat GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
|
||||||
|
|
||||||
linux-mipsle-softfloat:
|
|
||||||
GOARCH=mipsle GOMIPS=softfloat GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
|
||||||
|
|
||||||
linux-mipsle-hardfloat:
|
|
||||||
GOARCH=mipsle GOMIPS=hardfloat GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
|
||||||
|
|
||||||
linux-mips64:
|
|
||||||
GOARCH=mips64 GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
|
||||||
|
|
||||||
linux-mips64le:
|
|
||||||
GOARCH=mips64le GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
|
||||||
|
|
||||||
freebsd-386:
|
|
||||||
GOARCH=386 GOOS=freebsd $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
|
||||||
|
|
||||||
freebsd-amd64:
|
|
||||||
GOARCH=amd64 GOOS=freebsd $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
|
||||||
|
|
||||||
freebsd-amd64-v3:
|
|
||||||
GOARCH=amd64 GOOS=freebsd GOAMD64=v3 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
|
||||||
|
|
||||||
freebsd-arm64:
|
|
||||||
GOARCH=arm64 GOOS=freebsd $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
|
||||||
|
|
||||||
windows-386:
|
windows-386:
|
||||||
GOARCH=386 GOOS=windows $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.exe
|
$(XGOCMD) -dest=$(BINDIR) -out=$(NAME) -trimpath=true -ldflags=$(RELEASE_LDFLAGS) -targets=windows-6.0/386 $(BUILD_PACKAGE) && \
|
||||||
|
mv $(BINDIR)/$(NAME)-windows-6.0-386.exe $(BINDIR)/$(NAME)-windows-386.exe
|
||||||
|
|
||||||
windows-amd64:
|
windows-amd64:
|
||||||
GOARCH=amd64 GOOS=windows $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.exe
|
$(XGOCMD) -dest=$(BINDIR) -out=$(NAME) -trimpath=true -ldflags=$(RELEASE_LDFLAGS) -targets=windows-6.0/amd64 $(BUILD_PACKAGE) && \
|
||||||
|
mv $(BINDIR)/$(NAME)-windows-6.0-amd64.exe $(BINDIR)/$(NAME)-windows-amd64.exe
|
||||||
|
|
||||||
windows-amd64-v3:
|
#windows-arm64:
|
||||||
GOARCH=amd64 GOOS=windows GOAMD64=v3 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.exe
|
# $(XGOCMD) -dest=$(BINDIR) -out=$(NAME) -trimpath=true -ldflags=$(RELEASE_LDFLAGS) -targets=windows/arm64 $(BUILD_PACKAGE)
|
||||||
|
# mv $(NAME)-windows-4.0-arm64.exe $(NAME)-windows-arm64.exe
|
||||||
windows-arm64:
|
|
||||||
GOARCH=arm64 GOOS=windows $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.exe
|
|
||||||
|
|
||||||
windows-arm32v7:
|
|
||||||
GOARCH=arm GOOS=windows GOARM=7 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.exe
|
|
||||||
|
|
||||||
gz_releases=$(addsuffix .gz, $(PLATFORM_LIST))
|
gz_releases=$(addsuffix .gz, $(PLATFORM_LIST))
|
||||||
zip_releases=$(addsuffix .zip, $(WINDOWS_ARCH_LIST))
|
zip_releases=$(addsuffix .zip, $(WINDOWS_ARCH_LIST))
|
||||||
@ -130,14 +81,17 @@ all-arch: $(PLATFORM_LIST) $(WINDOWS_ARCH_LIST)
|
|||||||
releases: $(gz_releases) $(zip_releases)
|
releases: $(gz_releases) $(zip_releases)
|
||||||
|
|
||||||
vet:
|
vet:
|
||||||
go test ./...
|
$(GOCMD) test -tags build_local ./...
|
||||||
|
|
||||||
lint:
|
lint:
|
||||||
GOOS=darwin golangci-lint run ./...
|
golangci-lint run --build-tags=build_local ./...
|
||||||
GOOS=windows golangci-lint run ./...
|
|
||||||
GOOS=linux golangci-lint run ./...
|
|
||||||
GOOS=freebsd golangci-lint run ./...
|
|
||||||
GOOS=openbsd golangci-lint run ./...
|
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm -rf $(BINDIR)/*
|
rm -rf $(BINDIR)/
|
||||||
|
mkdir -p $(BINDIR)
|
||||||
|
|
||||||
|
cleancache:
|
||||||
|
# go build cache may need to cleanup if changing C source code
|
||||||
|
$(GOCLEAN) -cache
|
||||||
|
rm -rf $(BINDIR)/
|
||||||
|
mkdir -p $(BINDIR)
|
114
README.md
114
README.md
@ -13,11 +13,11 @@
|
|||||||
<img src="https://goreportcard.com/badge/github.com/Dreamacro/clash?style=flat-square">
|
<img src="https://goreportcard.com/badge/github.com/Dreamacro/clash?style=flat-square">
|
||||||
</a>
|
</a>
|
||||||
<img src="https://img.shields.io/github/go-mod/go-version/Dreamacro/clash?style=flat-square">
|
<img src="https://img.shields.io/github/go-mod/go-version/Dreamacro/clash?style=flat-square">
|
||||||
<a href="https://github.com/yaling888/clash/releases">
|
<a href="https://github.com/Dreamacro/clash/releases">
|
||||||
<img src="https://img.shields.io/github/release/yaling888/clash/all.svg?style=flat-square">
|
<img src="https://img.shields.io/github/release/Dreamacro/clash/all.svg?style=flat-square">
|
||||||
</a>
|
</a>
|
||||||
<a href="https://github.com/yaling888/clash/releases/tag/plus_pro">
|
<a href="https://github.com/Dreamacro/clash/releases/tag/premium">
|
||||||
<img src="https://img.shields.io/badge/release-Plus Pro-00b4f0?style=flat-square">
|
<img src="https://img.shields.io/badge/release-Premium-00b4f0?style=flat-square">
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
@ -36,6 +36,29 @@
|
|||||||
Documentations are now moved to [GitHub Wiki](https://github.com/Dreamacro/clash/wiki).
|
Documentations are now moved to [GitHub Wiki](https://github.com/Dreamacro/clash/wiki).
|
||||||
|
|
||||||
## Advanced usage for this branch
|
## Advanced usage for this branch
|
||||||
|
### Build
|
||||||
|
This branch requires cgo and Python3.9, so make sure you set up Python3.9 before building.
|
||||||
|
|
||||||
|
For example, build on macOS:
|
||||||
|
```sh
|
||||||
|
brew update
|
||||||
|
brew install python@3.9
|
||||||
|
|
||||||
|
export PKG_CONFIG_PATH=$(find /usr/local/Cellar -name 'pkgconfig' -type d | grep lib/pkgconfig | tr '\n' ':' | sed s/.$//)
|
||||||
|
|
||||||
|
git clone -b plus-pro https://github.com/yaling888/clash.git
|
||||||
|
cd clash
|
||||||
|
|
||||||
|
# build
|
||||||
|
make local
|
||||||
|
# or make local-v3
|
||||||
|
|
||||||
|
ls bin/
|
||||||
|
|
||||||
|
# run
|
||||||
|
sudo bin/clash-local
|
||||||
|
```
|
||||||
|
|
||||||
### General configuration
|
### General configuration
|
||||||
```yaml
|
```yaml
|
||||||
sniffing: true # Sniff TLS SNI
|
sniffing: true # Sniff TLS SNI
|
||||||
@ -174,7 +197,20 @@ The `GEOIP` databases via [https://github.com/Loyalsoldier/geoip](https://raw.gi
|
|||||||
|
|
||||||
The `GEOSITE` databases via [https://github.com/Loyalsoldier/v2ray-rules-dat](https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat).
|
The `GEOSITE` databases via [https://github.com/Loyalsoldier/v2ray-rules-dat](https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat).
|
||||||
```yaml
|
```yaml
|
||||||
|
mode: rule
|
||||||
|
|
||||||
|
script:
|
||||||
|
shortcuts:
|
||||||
|
quic: 'network == "udp" and dst_port == 443'
|
||||||
|
privacy: '"analytics" in host or "adservice" in host or "firebase" in host or "safebrowsing" in host or "doubleclick" in host'
|
||||||
|
BilibiliUdp: |
|
||||||
|
network == "udp" and match_provider("geosite:bilibili")
|
||||||
rules:
|
rules:
|
||||||
|
# rule SCRIPT shortcuts
|
||||||
|
- SCRIPT,quic,REJECT # Disable QUIC, same as rule "DST-PORT,443,REJECT,udp"
|
||||||
|
- SCRIPT,privacy,REJECT
|
||||||
|
- SCRIPT,BilibiliUdp,REJECT # same as rule "GEOSITE,bilibili,REJECT,udp"
|
||||||
|
|
||||||
# network condition for all rules
|
# network condition for all rules
|
||||||
- DOMAIN-SUFFIX,example.com,DIRECT,tcp
|
- DOMAIN-SUFFIX,example.com,DIRECT,tcp
|
||||||
- DOMAIN-SUFFIX,example.com,REJECT,udp
|
- DOMAIN-SUFFIX,example.com,REJECT,udp
|
||||||
@ -209,6 +245,76 @@ rules:
|
|||||||
- MATCH,PROXY
|
- MATCH,PROXY
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Script configuration
|
||||||
|
Script enables users to programmatically select a policy for the packets with more flexibility.
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
mode: script
|
||||||
|
|
||||||
|
script:
|
||||||
|
# path: ./script.star
|
||||||
|
code: |
|
||||||
|
def main(ctx, metadata):
|
||||||
|
if metadata["process_name"] == 'apsd':
|
||||||
|
return "DIRECT"
|
||||||
|
|
||||||
|
if metadata["network"] == 'udp' and metadata["dst_port"] == 443:
|
||||||
|
return "REJECT"
|
||||||
|
|
||||||
|
host = metadata["host"]
|
||||||
|
for kw in ['analytics', 'adservice', 'firebase', 'bugly', 'safebrowsing', 'doubleclick']:
|
||||||
|
if kw in host:
|
||||||
|
return "REJECT"
|
||||||
|
|
||||||
|
now = time.now()
|
||||||
|
if (now.hour < 8 or now.hour > 17) and metadata["src_ip"] == '192.168.1.99':
|
||||||
|
return "REJECT"
|
||||||
|
|
||||||
|
if ctx.rule_providers["geosite:category-ads-all"].match(metadata):
|
||||||
|
return "REJECT"
|
||||||
|
|
||||||
|
if ctx.rule_providers["geosite:youtube"].match(metadata):
|
||||||
|
ctx.log('[Script] domain %s matched youtube' % host)
|
||||||
|
return "Proxy"
|
||||||
|
|
||||||
|
if ctx.rule_providers["geosite:geolocation-cn"].match(metadata):
|
||||||
|
ctx.log('[Script] domain %s matched geolocation-cn' % host)
|
||||||
|
return "DIRECT"
|
||||||
|
|
||||||
|
ip = metadata["dst_ip"]
|
||||||
|
if host != "":
|
||||||
|
ip = ctx.resolve_ip(host)
|
||||||
|
if ip == "":
|
||||||
|
return "Proxy"
|
||||||
|
|
||||||
|
code = ctx.geoip(ip)
|
||||||
|
if code == "LAN" or code == "CN":
|
||||||
|
return "DIRECT"
|
||||||
|
|
||||||
|
return "Proxy" # default policy for requests which are not matched by any other script
|
||||||
|
```
|
||||||
|
the context and metadata
|
||||||
|
```ts
|
||||||
|
interface Metadata {
|
||||||
|
type: string // socks5、http
|
||||||
|
network: string // tcp
|
||||||
|
host: string
|
||||||
|
process_name: string
|
||||||
|
process_path: string
|
||||||
|
src_ip: string
|
||||||
|
src_port: int
|
||||||
|
dst_ip: string
|
||||||
|
dst_port: int
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Context {
|
||||||
|
resolve_ip: (host: string) => string // ip string
|
||||||
|
geoip: (ip: string) => string // country code
|
||||||
|
log: (log: string) => void
|
||||||
|
rule_providers: Record<string, { match: (metadata: Metadata) => boolean }>
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
### Proxies configuration
|
### Proxies configuration
|
||||||
Support outbound protocol `VLESS`.
|
Support outbound protocol `VLESS`.
|
||||||
|
|
||||||
|
@ -23,7 +23,7 @@ func (d *Direct) DialContext(ctx context.Context, metadata *C.Metadata, opts ...
|
|||||||
|
|
||||||
tcpKeepAlive(c)
|
tcpKeepAlive(c)
|
||||||
|
|
||||||
if !metadata.Resolved() && c.RemoteAddr() != nil {
|
if !metadata.DstIP.IsValid() && c.RemoteAddr() != nil {
|
||||||
if h, _, err := net.SplitHostPort(c.RemoteAddr().String()); err == nil {
|
if h, _, err := net.SplitHostPort(c.RemoteAddr().String()); err == nil {
|
||||||
metadata.DstIP = netip.MustParseAddr(h)
|
metadata.DstIP = netip.MustParseAddr(h)
|
||||||
}
|
}
|
||||||
|
@ -24,7 +24,6 @@ func (m *Mitm) DialContext(_ context.Context, metadata *C.Metadata, _ ...dialer.
|
|||||||
|
|
||||||
_ = c.SetKeepAlive(true)
|
_ = c.SetKeepAlive(true)
|
||||||
_ = c.SetKeepAlivePeriod(60 * time.Second)
|
_ = c.SetKeepAlivePeriod(60 * time.Second)
|
||||||
_ = c.SetLinger(0)
|
|
||||||
|
|
||||||
metadata.Type = C.MITM
|
metadata.Type = C.MITM
|
||||||
|
|
||||||
|
@ -15,7 +15,6 @@ func tcpKeepAlive(c net.Conn) {
|
|||||||
if tcp, ok := c.(*net.TCPConn); ok {
|
if tcp, ok := c.(*net.TCPConn); ok {
|
||||||
_ = tcp.SetKeepAlive(true)
|
_ = tcp.SetKeepAlive(true)
|
||||||
_ = tcp.SetKeepAlivePeriod(30 * time.Second)
|
_ = tcp.SetKeepAlivePeriod(30 * time.Second)
|
||||||
_ = tcp.SetLinger(0)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -152,7 +152,7 @@ func (v *Vless) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
|||||||
func (v *Vless) StreamPacketConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
func (v *Vless) StreamPacketConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
||||||
// vmess use stream-oriented udp with a special address, so we needs 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.ResolveFirstIP(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")
|
||||||
}
|
}
|
||||||
@ -245,7 +245,7 @@ func (v *Vless) ListenPacketContext(ctx context.Context, metadata *C.Metadata, o
|
|||||||
if v.transport != nil && len(opts) == 0 {
|
if v.transport != nil && len(opts) == 0 {
|
||||||
// vless use stream-oriented udp with a special address, so we needs 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.ResolveFirstIP(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")
|
||||||
}
|
}
|
||||||
|
@ -91,16 +91,17 @@ func (v *Vmess) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
|||||||
wsOpts := &vmess.WebsocketConfig{
|
wsOpts := &vmess.WebsocketConfig{
|
||||||
Host: host,
|
Host: host,
|
||||||
Port: port,
|
Port: port,
|
||||||
Headers: http.Header{},
|
|
||||||
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,
|
||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
if v.option.TLS {
|
||||||
@ -116,9 +117,7 @@ func (v *Vmess) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
|||||||
wsOpts.TLSConfig.ServerName = host
|
wsOpts.TLSConfig.ServerName = host
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if wsOpts.Headers.Get("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)
|
||||||
@ -139,6 +138,9 @@ func (v *Vmess) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
|||||||
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)
|
||||||
@ -203,7 +205,7 @@ func (v *Vmess) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
|||||||
func (v *Vmess) StreamPacketConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
func (v *Vmess) StreamPacketConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
||||||
// vmess use stream-oriented udp with a special address, so we needs 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.ResolveFirstIP(metadata.Host)
|
ip, err := resolver.ResolveIP(metadata.Host)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return c, fmt.Errorf("can't resolve ip: %w", err)
|
return c, fmt.Errorf("can't resolve ip: %w", err)
|
||||||
}
|
}
|
||||||
@ -255,7 +257,7 @@ func (v *Vmess) ListenPacketContext(ctx context.Context, metadata *C.Metadata, o
|
|||||||
if v.transport != nil && len(opts) == 0 {
|
if v.transport != nil && len(opts) == 0 {
|
||||||
// vmess use stream-oriented udp with a special address, so we needs 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.ResolveFirstIP(metadata.Host)
|
ip, err := resolver.ResolveIP(metadata.Host)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("can't resolve ip: %w", err)
|
return nil, fmt.Errorf("can't resolve ip: %w", err)
|
||||||
}
|
}
|
||||||
|
@ -3,8 +3,6 @@ package outboundgroup
|
|||||||
import (
|
import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/adapter"
|
|
||||||
"github.com/Dreamacro/clash/adapter/outbound"
|
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
"github.com/Dreamacro/clash/constant/provider"
|
"github.com/Dreamacro/clash/constant/provider"
|
||||||
)
|
)
|
||||||
@ -13,19 +11,14 @@ const (
|
|||||||
defaultGetProxiesDuration = time.Second * 5
|
defaultGetProxiesDuration = time.Second * 5
|
||||||
)
|
)
|
||||||
|
|
||||||
var defaultRejectProxy = adapter.NewProxy(outbound.NewReject())
|
|
||||||
|
|
||||||
func getProvidersProxies(providers []provider.ProxyProvider, touch bool) []C.Proxy {
|
func getProvidersProxies(providers []provider.ProxyProvider, touch bool) []C.Proxy {
|
||||||
proxies := []C.Proxy{}
|
proxies := []C.Proxy{}
|
||||||
for _, pd := range providers {
|
for _, provider := range providers {
|
||||||
if touch {
|
if touch {
|
||||||
proxies = append(proxies, pd.ProxiesWithTouch()...)
|
proxies = append(proxies, provider.ProxiesWithTouch()...)
|
||||||
} else {
|
} else {
|
||||||
proxies = append(proxies, pd.Proxies()...)
|
proxies = append(proxies, provider.Proxies()...)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if len(proxies) == 0 {
|
|
||||||
proxies = append(proxies, defaultRejectProxy)
|
|
||||||
}
|
|
||||||
return proxies
|
return proxies
|
||||||
}
|
}
|
||||||
|
@ -78,11 +78,22 @@ func ParseProxyGroup(config map[string]any, proxyMap map[string]C.Proxy, provide
|
|||||||
return nil, errDuplicateProvider
|
return nil, errDuplicateProvider
|
||||||
}
|
}
|
||||||
|
|
||||||
hc, err := newHealthCheck(ps, groupOption)
|
// select don't need health check
|
||||||
|
if groupOption.Type == "select" || groupOption.Type == "relay" {
|
||||||
|
hc := provider.NewHealthCheck(ps, "", 0, true)
|
||||||
|
pd, err := provider.NewCompatibleProvider(groupName, ps, hc)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
providers = append(providers, pd)
|
||||||
|
providersMap[groupName] = pd
|
||||||
|
} else {
|
||||||
|
if groupOption.URL == "" || groupOption.Interval == 0 {
|
||||||
|
return nil, errMissHealthCheck
|
||||||
|
}
|
||||||
|
|
||||||
|
hc := provider.NewHealthCheck(ps, groupOption.URL, uint(groupOption.Interval), groupOption.Lazy)
|
||||||
pd, err := provider.NewCompatibleProvider(groupName, ps, hc)
|
pd, err := provider.NewCompatibleProvider(groupName, ps, hc)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -91,19 +102,15 @@ func ParseProxyGroup(config map[string]any, proxyMap map[string]C.Proxy, provide
|
|||||||
providers = append(providers, pd)
|
providers = append(providers, pd)
|
||||||
providersMap[groupName] = pd
|
providersMap[groupName] = pd
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if len(groupOption.Use) != 0 {
|
if len(groupOption.Use) != 0 {
|
||||||
list, err := getProviders(providersMap, groupOption, filterRegx)
|
list, err := getProviders(providersMap, groupOption, filterRegx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if groupOption.Type == "fallback" {
|
|
||||||
providers = append(list, providers...)
|
|
||||||
} else {
|
|
||||||
providers = append(providers, list...)
|
providers = append(providers, list...)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
var group C.ProxyAdapter
|
var group C.ProxyAdapter
|
||||||
switch groupOption.Type {
|
switch groupOption.Type {
|
||||||
@ -156,18 +163,22 @@ func getProviders(mapping map[string]types.ProxyProvider, groupOption *GroupComm
|
|||||||
}
|
}
|
||||||
|
|
||||||
if filterRegx != nil {
|
if filterRegx != nil {
|
||||||
hc, err := newHealthCheck([]C.Proxy{}, groupOption)
|
var hc *provider.HealthCheck
|
||||||
if err != nil {
|
if groupOption.Type == "select" || groupOption.Type == "relay" {
|
||||||
return nil, err
|
hc = provider.NewHealthCheck([]C.Proxy{}, "", 0, true)
|
||||||
|
} else {
|
||||||
|
if groupOption.URL == "" || groupOption.Interval == 0 {
|
||||||
|
return nil, errMissHealthCheck
|
||||||
|
}
|
||||||
|
hc = provider.NewHealthCheck([]C.Proxy{}, groupOption.URL, uint(groupOption.Interval), groupOption.Lazy)
|
||||||
}
|
}
|
||||||
|
|
||||||
gName := groupName
|
if _, ok = mapping[groupName]; ok {
|
||||||
if _, ok = mapping[gName]; ok {
|
groupName += "->" + p.Name()
|
||||||
gName = groupName + " -> " + p.Name()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pd := p.(*provider.ProxySetProvider)
|
pd := p.(*provider.ProxySetProvider)
|
||||||
p = provider.NewProxyFilterProvider(gName, pd, hc, filterRegx)
|
p = provider.NewProxyFilterProvider(groupName, pd, hc, filterRegx)
|
||||||
pd.RegisterProvidersInUse(p)
|
pd.RegisterProvidersInUse(p)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -175,18 +186,3 @@ func getProviders(mapping map[string]types.ProxyProvider, groupOption *GroupComm
|
|||||||
}
|
}
|
||||||
return ps, nil
|
return ps, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func newHealthCheck(ps []C.Proxy, groupOption *GroupCommonOption) (*provider.HealthCheck, error) {
|
|
||||||
var hc *provider.HealthCheck
|
|
||||||
|
|
||||||
// select don't need health check
|
|
||||||
if groupOption.Type == "select" || groupOption.Type == "relay" {
|
|
||||||
hc = provider.NewHealthCheck(ps, "", 0, true)
|
|
||||||
} else {
|
|
||||||
if groupOption.URL == "" || groupOption.Interval == 0 {
|
|
||||||
return nil, errMissHealthCheck
|
|
||||||
}
|
|
||||||
hc = provider.NewHealthCheck(ps, groupOption.URL, uint(groupOption.Interval), groupOption.Lazy)
|
|
||||||
}
|
|
||||||
return hc, nil
|
|
||||||
}
|
|
||||||
|
@ -98,11 +98,7 @@ func (s *Selector) selectedProxy(touch bool) C.Proxy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func NewSelector(option *GroupCommonOption, providers []provider.ProxyProvider) *Selector {
|
func NewSelector(option *GroupCommonOption, providers []provider.ProxyProvider) *Selector {
|
||||||
selected := "REJECT"
|
selected := providers[0].Proxies()[0].Name()
|
||||||
if len(providers) != 0 && len(providers[0].Proxies()) != 0 {
|
|
||||||
selected = providers[0].Proxies()[0].Name()
|
|
||||||
}
|
|
||||||
|
|
||||||
return &Selector{
|
return &Selector{
|
||||||
Base: outbound.NewBase(outbound.BaseOption{
|
Base: outbound.NewBase(outbound.BaseOption{
|
||||||
Name: option.Name,
|
Name: option.Name,
|
||||||
|
@ -48,6 +48,5 @@ func tcpKeepAlive(c net.Conn) {
|
|||||||
if tcp, ok := c.(*net.TCPConn); ok {
|
if tcp, ok := c.(*net.TCPConn); ok {
|
||||||
_ = tcp.SetKeepAlive(true)
|
_ = tcp.SetKeepAlive(true)
|
||||||
_ = tcp.SetKeepAlivePeriod(30 * time.Second)
|
_ = tcp.SetKeepAlivePeriod(30 * time.Second)
|
||||||
_ = tcp.SetLinger(0)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,7 +10,7 @@ import (
|
|||||||
|
|
||||||
func ParseProxy(mapping map[string]any, forceCertVerify bool) (C.Proxy, error) {
|
func ParseProxy(mapping map[string]any, forceCertVerify bool) (C.Proxy, error) {
|
||||||
decoder := structure.NewDecoder(structure.Option{TagName: "proxy", WeaklyTypedInput: true})
|
decoder := structure.NewDecoder(structure.Option{TagName: "proxy", WeaklyTypedInput: true})
|
||||||
proxyType, existType := mapping["type"]
|
proxyType, existType := mapping["type"].(string)
|
||||||
if !existType {
|
if !existType {
|
||||||
return nil, fmt.Errorf("missing type")
|
return nil, fmt.Errorf("missing type")
|
||||||
}
|
}
|
||||||
@ -19,7 +19,7 @@ func ParseProxy(mapping map[string]any, forceCertVerify bool) (C.Proxy, error) {
|
|||||||
proxy C.ProxyAdapter
|
proxy C.ProxyAdapter
|
||||||
err error
|
err error
|
||||||
)
|
)
|
||||||
switch proxyType.(string) {
|
switch proxyType {
|
||||||
case "ss":
|
case "ss":
|
||||||
ssOption := &outbound.ShadowSocksOption{}
|
ssOption := &outbound.ShadowSocksOption{}
|
||||||
err = decoder.Decode(mapping, ssOption)
|
err = decoder.Decode(mapping, ssOption)
|
||||||
@ -59,7 +59,6 @@ func ParseProxy(mapping map[string]any, forceCertVerify bool) (C.Proxy, error) {
|
|||||||
HTTPOpts: outbound.HTTPOptions{
|
HTTPOpts: outbound.HTTPOptions{
|
||||||
Method: "GET",
|
Method: "GET",
|
||||||
Path: []string{"/"},
|
Path: []string{"/"},
|
||||||
Headers: make(map[string][]string),
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
err = decoder.Decode(mapping, vmessOption)
|
err = decoder.Decode(mapping, vmessOption)
|
||||||
|
@ -25,16 +25,10 @@ type HealthCheck struct {
|
|||||||
interval uint
|
interval uint
|
||||||
lazy bool
|
lazy bool
|
||||||
lastTouch *atomic.Int64
|
lastTouch *atomic.Int64
|
||||||
running *atomic.Bool
|
|
||||||
done chan struct{}
|
done chan struct{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (hc *HealthCheck) process() {
|
func (hc *HealthCheck) process() {
|
||||||
if hc.running.Load() {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
hc.running.Store(true)
|
|
||||||
|
|
||||||
ticker := time.NewTicker(time.Duration(hc.interval) * time.Second)
|
ticker := time.NewTicker(time.Duration(hc.interval) * time.Second)
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
@ -90,10 +84,6 @@ func (hc *HealthCheck) check() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (hc *HealthCheck) close() {
|
func (hc *HealthCheck) close() {
|
||||||
if !hc.running.Load() {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
hc.running.Store(false)
|
|
||||||
hc.done <- struct{}{}
|
hc.done <- struct{}{}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -104,7 +94,6 @@ func NewHealthCheck(proxies []C.Proxy, url string, interval uint, lazy bool) *He
|
|||||||
interval: interval,
|
interval: interval,
|
||||||
lazy: lazy,
|
lazy: lazy,
|
||||||
lastTouch: atomic.NewInt64(0),
|
lastTouch: atomic.NewInt64(0),
|
||||||
running: atomic.NewBool(false),
|
|
||||||
done: make(chan struct{}, 1),
|
done: make(chan struct{}, 1),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -234,9 +234,7 @@ func (pf *proxyFilterProvider) HealthCheck() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (pf *proxyFilterProvider) Update() error {
|
func (pf *proxyFilterProvider) Update() error {
|
||||||
pf.healthCheck.close()
|
var proxies []C.Proxy
|
||||||
|
|
||||||
proxies := []C.Proxy{}
|
|
||||||
if pf.filter != nil {
|
if pf.filter != nil {
|
||||||
for _, proxy := range pf.psd.Proxies() {
|
for _, proxy := range pf.psd.Proxies() {
|
||||||
if !pf.filter.MatchString(proxy.Name()) {
|
if !pf.filter.MatchString(proxy.Name()) {
|
||||||
@ -250,10 +248,6 @@ func (pf *proxyFilterProvider) Update() error {
|
|||||||
|
|
||||||
pf.proxies = proxies
|
pf.proxies = proxies
|
||||||
pf.healthCheck.setProxy(proxies)
|
pf.healthCheck.setProxy(proxies)
|
||||||
|
|
||||||
if len(proxies) != 0 && pf.healthCheck.auto() {
|
|
||||||
go pf.healthCheck.process()
|
|
||||||
}
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -292,6 +286,10 @@ func NewProxyFilterProvider(name string, psd *ProxySetProvider, hc *HealthCheck,
|
|||||||
|
|
||||||
_ = pd.Update()
|
_ = pd.Update()
|
||||||
|
|
||||||
|
if hc.auto() {
|
||||||
|
go hc.process()
|
||||||
|
}
|
||||||
|
|
||||||
wrapper := &ProxyFilterProvider{pd}
|
wrapper := &ProxyFilterProvider{pd}
|
||||||
runtime.SetFinalizer(wrapper, stopProxyFilterProvider)
|
runtime.SetFinalizer(wrapper, stopProxyFilterProvider)
|
||||||
return wrapper
|
return wrapper
|
||||||
|
@ -11,6 +11,7 @@ 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"
|
||||||
|
C "github.com/Dreamacro/clash/constant"
|
||||||
types "github.com/Dreamacro/clash/constant/provider"
|
types "github.com/Dreamacro/clash/constant/provider"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -81,11 +82,13 @@ func (h *HTTPVehicle) Read() ([]byte, error) {
|
|||||||
IdleConnTimeout: 90 * time.Second,
|
IdleConnTimeout: 90 * time.Second,
|
||||||
TLSHandshakeTimeout: 10 * time.Second,
|
TLSHandshakeTimeout: 10 * time.Second,
|
||||||
ExpectContinueTimeout: 1 * time.Second,
|
ExpectContinueTimeout: 1 * time.Second,
|
||||||
DialContext: func(ctx context.Context, network, address string) (net.Conn, error) {
|
DialContext: func(ctx context.Context, network, address string) (conn net.Conn, err error) {
|
||||||
if req.URL.Scheme == "https" {
|
conn, err = dialer.DialContext(ctx, network, address) // with direct
|
||||||
return (&net.Dialer{}).DialContext(ctx, network, address) // forward to tun if tun enabled
|
if err != nil {
|
||||||
|
// fallback to tun if tun enabled
|
||||||
|
conn, err = (&net.Dialer{Timeout: C.DefaultTCPTimeout}).Dial(network, address)
|
||||||
}
|
}
|
||||||
return dialer.DialContext(ctx, network, address, dialer.WithDirect()) // with direct
|
return
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -21,25 +21,22 @@ func DecodeBase64(buf []byte) ([]byte, error) {
|
|||||||
return dBuf[:n], nil
|
return dBuf[:n], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func DecodeRawBase64(buf []byte) ([]byte, error) {
|
// DecodeBase64StringToString decode base64 string to string
|
||||||
dBuf := make([]byte, base64.RawStdEncoding.DecodedLen(len(buf)))
|
func DecodeBase64StringToString(s string) (string, error) {
|
||||||
n, err := base64.RawStdEncoding.Decode(dBuf, buf)
|
dBuf, err := enc.DecodeString(s)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
return dBuf[:n], nil
|
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, err := DecodeBase64(buf)
|
data, err := DecodeBase64(buf)
|
||||||
if err != nil {
|
|
||||||
data, err = DecodeRawBase64(buf)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
data = buf
|
data = buf
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
arr := strings.Split(string(data), "\n")
|
arr := strings.Split(string(data), "\n")
|
||||||
|
|
||||||
|
@ -4,6 +4,8 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/Dreamacro/clash/common/pool"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Relay copies between left and right bidirectionally.
|
// Relay copies between left and right bidirectionally.
|
||||||
@ -14,14 +16,18 @@ func Relay(leftConn, rightConn net.Conn) {
|
|||||||
tcpKeepAlive(rightConn)
|
tcpKeepAlive(rightConn)
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
|
buf := pool.Get(pool.RelayBufferSize)
|
||||||
// Wrapping to avoid using *net.TCPConn.(ReadFrom)
|
// Wrapping to avoid using *net.TCPConn.(ReadFrom)
|
||||||
// See also https://github.com/Dreamacro/clash/pull/1209
|
// See also https://github.com/Dreamacro/clash/pull/1209
|
||||||
_, err := io.Copy(WriteOnlyWriter{Writer: leftConn}, ReadOnlyReader{Reader: rightConn})
|
_, err := io.CopyBuffer(WriteOnlyWriter{Writer: leftConn}, ReadOnlyReader{Reader: rightConn}, buf)
|
||||||
|
_ = pool.Put(buf)
|
||||||
_ = leftConn.SetReadDeadline(time.Now())
|
_ = leftConn.SetReadDeadline(time.Now())
|
||||||
ch <- err
|
ch <- err
|
||||||
}()
|
}()
|
||||||
|
|
||||||
_, _ = io.Copy(WriteOnlyWriter{Writer: rightConn}, ReadOnlyReader{Reader: leftConn})
|
buf := pool.Get(pool.RelayBufferSize)
|
||||||
|
_, _ = io.CopyBuffer(WriteOnlyWriter{Writer: rightConn}, ReadOnlyReader{Reader: leftConn}, buf)
|
||||||
|
_ = pool.Put(buf)
|
||||||
_ = rightConn.SetReadDeadline(time.Now())
|
_ = rightConn.SetReadDeadline(time.Now())
|
||||||
<-ch
|
<-ch
|
||||||
}
|
}
|
||||||
|
@ -28,9 +28,6 @@ func ShouldFindProcess(metadata *C.Metadata) bool {
|
|||||||
if metadata.Process != "" {
|
if metadata.Process != "" {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if metadata.SrcIP.IsUnspecified() {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
for _, ip := range localIPs {
|
for _, ip := range localIPs {
|
||||||
if ip == metadata.SrcIP {
|
if ip == metadata.SrcIP {
|
||||||
return true
|
return true
|
||||||
@ -44,7 +41,7 @@ func AppendLocalIPs(ip ...netip.Addr) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func getLocalIPs() []netip.Addr {
|
func getLocalIPs() []netip.Addr {
|
||||||
var ips []netip.Addr
|
ips := []netip.Addr{netip.IPv4Unspecified(), netip.IPv6Unspecified()}
|
||||||
|
|
||||||
netInterfaces, err := net.Interfaces()
|
netInterfaces, err := net.Interfaces()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -37,17 +37,17 @@ var (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type Resolver interface {
|
type Resolver interface {
|
||||||
ResolveIP(host string, random bool) (ip netip.Addr, err error)
|
ResolveIP(host string) (ip netip.Addr, err error)
|
||||||
ResolveIPv4(host string, random bool) (ip netip.Addr, err error)
|
ResolveIPv4(host string) (ip netip.Addr, err error)
|
||||||
ResolveIPv6(host string, random bool) (ip netip.Addr, err error)
|
ResolveIPv6(host string) (ip netip.Addr, err error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ResolveIPv4 with a host, return ipv4
|
// ResolveIPv4 with a host, return ipv4
|
||||||
func ResolveIPv4(host string) (netip.Addr, error) {
|
func ResolveIPv4(host string) (netip.Addr, error) {
|
||||||
return resolveIPv4(host, true)
|
return ResolveIPv4WithResolver(host, DefaultResolver)
|
||||||
}
|
}
|
||||||
|
|
||||||
func ResolveIPv4WithResolver(host string, r Resolver, random bool) (netip.Addr, error) {
|
func ResolveIPv4WithResolver(host string, r Resolver) (netip.Addr, error) {
|
||||||
if node := DefaultHosts.Search(host); node != nil {
|
if node := DefaultHosts.Search(host); node != nil {
|
||||||
if ip := node.Data; ip.Is4() {
|
if ip := node.Data; ip.Is4() {
|
||||||
return ip, nil
|
return ip, nil
|
||||||
@ -56,7 +56,6 @@ func ResolveIPv4WithResolver(host string, r Resolver, random bool) (netip.Addr,
|
|||||||
|
|
||||||
ip, err := netip.ParseAddr(host)
|
ip, err := netip.ParseAddr(host)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
ip = ip.Unmap()
|
|
||||||
if ip.Is4() {
|
if ip.Is4() {
|
||||||
return ip, nil
|
return ip, nil
|
||||||
}
|
}
|
||||||
@ -64,7 +63,7 @@ func ResolveIPv4WithResolver(host string, r Resolver, random bool) (netip.Addr,
|
|||||||
}
|
}
|
||||||
|
|
||||||
if r != nil {
|
if r != nil {
|
||||||
return r.ResolveIPv4(host, random)
|
return r.ResolveIPv4(host)
|
||||||
}
|
}
|
||||||
|
|
||||||
if DefaultResolver == nil {
|
if DefaultResolver == nil {
|
||||||
@ -77,11 +76,7 @@ func ResolveIPv4WithResolver(host string, r Resolver, random bool) (netip.Addr,
|
|||||||
return netip.Addr{}, ErrIPNotFound
|
return netip.Addr{}, ErrIPNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
index := 0
|
ip := ipAddrs[rand.Intn(len(ipAddrs))].To4()
|
||||||
if random {
|
|
||||||
index = rand.Intn(len(ipAddrs))
|
|
||||||
}
|
|
||||||
ip := ipAddrs[index].To4()
|
|
||||||
if ip == nil {
|
if ip == nil {
|
||||||
return netip.Addr{}, ErrIPVersion
|
return netip.Addr{}, ErrIPVersion
|
||||||
}
|
}
|
||||||
@ -94,10 +89,10 @@ func ResolveIPv4WithResolver(host string, r Resolver, random bool) (netip.Addr,
|
|||||||
|
|
||||||
// ResolveIPv6 with a host, return ipv6
|
// ResolveIPv6 with a host, return ipv6
|
||||||
func ResolveIPv6(host string) (netip.Addr, error) {
|
func ResolveIPv6(host string) (netip.Addr, error) {
|
||||||
return ResolveIPv6WithResolver(host, DefaultResolver, true)
|
return ResolveIPv6WithResolver(host, DefaultResolver)
|
||||||
}
|
}
|
||||||
|
|
||||||
func ResolveIPv6WithResolver(host string, r Resolver, random bool) (netip.Addr, error) {
|
func ResolveIPv6WithResolver(host string, r Resolver) (netip.Addr, error) {
|
||||||
if DisableIPv6 {
|
if DisableIPv6 {
|
||||||
return netip.Addr{}, ErrIPv6Disabled
|
return netip.Addr{}, ErrIPv6Disabled
|
||||||
}
|
}
|
||||||
@ -117,7 +112,7 @@ func ResolveIPv6WithResolver(host string, r Resolver, random bool) (netip.Addr,
|
|||||||
}
|
}
|
||||||
|
|
||||||
if r != nil {
|
if r != nil {
|
||||||
return r.ResolveIPv6(host, random)
|
return r.ResolveIPv6(host)
|
||||||
}
|
}
|
||||||
|
|
||||||
if DefaultResolver == nil {
|
if DefaultResolver == nil {
|
||||||
@ -130,29 +125,25 @@ func ResolveIPv6WithResolver(host string, r Resolver, random bool) (netip.Addr,
|
|||||||
return netip.Addr{}, ErrIPNotFound
|
return netip.Addr{}, ErrIPNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
index := 0
|
return netip.AddrFrom16(*(*[16]byte)(ipAddrs[rand.Intn(len(ipAddrs))])), nil
|
||||||
if random {
|
|
||||||
index = rand.Intn(len(ipAddrs))
|
|
||||||
}
|
|
||||||
return netip.AddrFrom16(*(*[16]byte)(ipAddrs[index])), nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return netip.Addr{}, ErrIPNotFound
|
return netip.Addr{}, ErrIPNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
// ResolveIPWithResolver same as ResolveIP, but with a resolver
|
// ResolveIPWithResolver same as ResolveIP, but with a resolver
|
||||||
func ResolveIPWithResolver(host string, r Resolver, random bool) (netip.Addr, error) {
|
func ResolveIPWithResolver(host string, r Resolver) (netip.Addr, error) {
|
||||||
if node := DefaultHosts.Search(host); node != nil {
|
if node := DefaultHosts.Search(host); node != nil {
|
||||||
return node.Data, nil
|
return node.Data, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if r != nil {
|
if r != nil {
|
||||||
if DisableIPv6 {
|
if DisableIPv6 {
|
||||||
return r.ResolveIPv4(host, random)
|
return r.ResolveIPv4(host)
|
||||||
}
|
}
|
||||||
return r.ResolveIP(host, random)
|
return r.ResolveIP(host)
|
||||||
} else if DisableIPv6 {
|
} else if DisableIPv6 {
|
||||||
return resolveIPv4(host, random)
|
return ResolveIPv4(host)
|
||||||
}
|
}
|
||||||
|
|
||||||
ip, err := netip.ParseAddr(host)
|
ip, err := netip.ParseAddr(host)
|
||||||
@ -174,18 +165,13 @@ func ResolveIPWithResolver(host string, r Resolver, random bool) (netip.Addr, er
|
|||||||
|
|
||||||
// ResolveIP with a host, return ip
|
// ResolveIP with a host, return ip
|
||||||
func ResolveIP(host string) (netip.Addr, error) {
|
func ResolveIP(host string) (netip.Addr, error) {
|
||||||
return resolveIP(host, true)
|
return ResolveIPWithResolver(host, DefaultResolver)
|
||||||
}
|
|
||||||
|
|
||||||
// ResolveFirstIP with a host, return ip
|
|
||||||
func ResolveFirstIP(host string) (netip.Addr, error) {
|
|
||||||
return resolveIP(host, false)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ResolveIPv4ProxyServerHost proxies server host only
|
// ResolveIPv4ProxyServerHost proxies server host only
|
||||||
func ResolveIPv4ProxyServerHost(host string) (netip.Addr, error) {
|
func ResolveIPv4ProxyServerHost(host string) (netip.Addr, error) {
|
||||||
if ProxyServerHostResolver != nil {
|
if ProxyServerHostResolver != nil {
|
||||||
return ResolveIPv4WithResolver(host, ProxyServerHostResolver, true)
|
return ResolveIPv4WithResolver(host, ProxyServerHostResolver)
|
||||||
}
|
}
|
||||||
return ResolveIPv4(host)
|
return ResolveIPv4(host)
|
||||||
}
|
}
|
||||||
@ -193,7 +179,7 @@ func ResolveIPv4ProxyServerHost(host string) (netip.Addr, error) {
|
|||||||
// ResolveIPv6ProxyServerHost proxies server host only
|
// ResolveIPv6ProxyServerHost proxies server host only
|
||||||
func ResolveIPv6ProxyServerHost(host string) (netip.Addr, error) {
|
func ResolveIPv6ProxyServerHost(host string) (netip.Addr, error) {
|
||||||
if ProxyServerHostResolver != nil {
|
if ProxyServerHostResolver != nil {
|
||||||
return ResolveIPv6WithResolver(host, ProxyServerHostResolver, true)
|
return ResolveIPv6WithResolver(host, ProxyServerHostResolver)
|
||||||
}
|
}
|
||||||
return ResolveIPv6(host)
|
return ResolveIPv6(host)
|
||||||
}
|
}
|
||||||
@ -201,15 +187,7 @@ func ResolveIPv6ProxyServerHost(host string) (netip.Addr, error) {
|
|||||||
// ResolveProxyServerHost proxies server host only
|
// ResolveProxyServerHost proxies server host only
|
||||||
func ResolveProxyServerHost(host string) (netip.Addr, error) {
|
func ResolveProxyServerHost(host string) (netip.Addr, error) {
|
||||||
if ProxyServerHostResolver != nil {
|
if ProxyServerHostResolver != nil {
|
||||||
return ResolveIPWithResolver(host, ProxyServerHostResolver, true)
|
return ResolveIPWithResolver(host, ProxyServerHostResolver)
|
||||||
}
|
}
|
||||||
return ResolveIP(host)
|
return ResolveIP(host)
|
||||||
}
|
}
|
||||||
|
|
||||||
func resolveIP(host string, random bool) (netip.Addr, error) {
|
|
||||||
return ResolveIPWithResolver(host, DefaultResolver, random)
|
|
||||||
}
|
|
||||||
|
|
||||||
func resolveIPv4(host string, random bool) (netip.Addr, error) {
|
|
||||||
return ResolveIPv4WithResolver(host, DefaultResolver, random)
|
|
||||||
}
|
|
||||||
|
10
component/script/build_actions.go
Normal file
10
component/script/build_actions.go
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
//go:build build_actions
|
||||||
|
|
||||||
|
package script
|
||||||
|
|
||||||
|
/*
|
||||||
|
#cgo windows,amd64 CFLAGS: -ID:/python-amd64/include -DMS_WIN64
|
||||||
|
|
||||||
|
#cgo windows,amd64 LDFLAGS: -LD:/python-amd64/libs -lpython39 -lpthread -lm
|
||||||
|
*/
|
||||||
|
import "C"
|
8
component/script/build_local.go
Normal file
8
component/script/build_local.go
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
//go:build build_local
|
||||||
|
|
||||||
|
package script
|
||||||
|
|
||||||
|
/*
|
||||||
|
#cgo pkg-config: python3-embed
|
||||||
|
*/
|
||||||
|
import "C"
|
24
component/script/build_xgo.go
Normal file
24
component/script/build_xgo.go
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
//go:build !build_local
|
||||||
|
|
||||||
|
package script
|
||||||
|
|
||||||
|
/*
|
||||||
|
#cgo linux,amd64 pkg-config: python3-embed
|
||||||
|
|
||||||
|
#cgo darwin,amd64 CFLAGS: -I/build/python/python-3.9.7-darwin-amd64/include/python3.9
|
||||||
|
#cgo darwin,arm64 CFLAGS: -I/build/python/python-3.9.7-darwin-arm64/include/python3.9
|
||||||
|
#cgo windows,amd64 CFLAGS: -I/build/python/python-3.9.7-windows-amd64/include -DMS_WIN64
|
||||||
|
#cgo windows,386 CFLAGS: -I/build/python/python-3.9.7-windows-386/include
|
||||||
|
//#cgo linux,amd64 CFLAGS: -I/home/runner/work/clash/clash/bin/python/python-3.9.7-linux-amd64/include/python3.9
|
||||||
|
//#cgo linux,arm64 CFLAGS: -I/build/python/python-3.9.7-linux-arm64/include/python3.9
|
||||||
|
//#cgo linux,386 CFLAGS: -I/build/python/python-3.9.7-linux-386/include/python3.9
|
||||||
|
|
||||||
|
#cgo darwin,amd64 LDFLAGS: -L/build/python/python-3.9.7-darwin-amd64/lib -lpython3.9 -ldl -framework CoreFoundation
|
||||||
|
#cgo darwin,arm64 LDFLAGS: -L/build/python/python-3.9.7-darwin-arm64/lib -lpython3.9 -ldl -framework CoreFoundation
|
||||||
|
#cgo windows,amd64 LDFLAGS: -L/build/python/python-3.9.7-windows-amd64/lib -lpython39 -lpthread -lm
|
||||||
|
#cgo windows,386 LDFLAGS: -L/build/python/python-3.9.7-windows-386/lib -lpython39 -lpthread -lm
|
||||||
|
//#cgo linux,amd64 LDFLAGS: -L/home/runner/work/clash/clash/bin/python/python-3.9.7-linux-amd64/lib -lpython3.9 -lpthread -ldl -lutil -lm
|
||||||
|
//#cgo linux,arm64 LDFLAGS: -L/build/python/python-3.9.7-linux-arm64/lib -lpython3.9 -lpthread -ldl -lutil -lm
|
||||||
|
//#cgo linux,386 LDFLAGS: -L/build/python/python-3.9.7-linux-386/lib -lpython3.9 -lpthread -ldl -lutil -lm
|
||||||
|
*/
|
||||||
|
import "C"
|
743
component/script/clash_module.c
Normal file
743
component/script/clash_module.c
Normal file
@ -0,0 +1,743 @@
|
|||||||
|
#define PY_SSIZE_T_CLEAN
|
||||||
|
|
||||||
|
#include "clash_module.h"
|
||||||
|
#include <structmember.h>
|
||||||
|
|
||||||
|
PyObject *clash_module;
|
||||||
|
PyObject *main_fn;
|
||||||
|
PyObject *clash_context;
|
||||||
|
|
||||||
|
// init_python
|
||||||
|
void init_python(const char *program, const char *path) {
|
||||||
|
|
||||||
|
// Py_NoSiteFlag = 1;
|
||||||
|
// Py_FrozenFlag = 1;
|
||||||
|
// Py_IgnoreEnvironmentFlag = 1;
|
||||||
|
// Py_IsolatedFlag = 1;
|
||||||
|
|
||||||
|
append_inittab();
|
||||||
|
|
||||||
|
wchar_t *programName = Py_DecodeLocale(program, NULL);
|
||||||
|
if (programName != NULL) {
|
||||||
|
Py_SetProgramName(programName);
|
||||||
|
PyMem_RawFree(programName);
|
||||||
|
}
|
||||||
|
|
||||||
|
// wchar_t *newPath = Py_DecodeLocale(path, NULL);
|
||||||
|
// if (newPath != NULL) {
|
||||||
|
// Py_SetPath(newPath);
|
||||||
|
// PyMem_RawFree(newPath);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// Py_Initialize();
|
||||||
|
Py_InitializeEx(0);
|
||||||
|
|
||||||
|
char *pathPrefix = "import sys; sys.path.append('";
|
||||||
|
char *pathSuffix = "')";
|
||||||
|
char *newPath = (char *) malloc(strlen(pathPrefix) + strlen(path) + strlen(pathSuffix));
|
||||||
|
sprintf(newPath, "%s%s%s", pathPrefix, path, pathSuffix);
|
||||||
|
|
||||||
|
PyRun_SimpleString(newPath);
|
||||||
|
free(newPath);
|
||||||
|
|
||||||
|
/* Optionally import the module; alternatively,
|
||||||
|
import can be deferred until the embedded script
|
||||||
|
imports it. */
|
||||||
|
clash_module = PyImport_ImportModule("clash");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load function, same as "import module_name.func_name as obj" in Python
|
||||||
|
// Returns the function object or NULL if not found
|
||||||
|
PyObject *load_func(const char *module_name, char *func_name) {
|
||||||
|
// Import the module
|
||||||
|
PyObject *py_mod_name = PyUnicode_FromString(module_name);
|
||||||
|
if (py_mod_name == NULL) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
PyObject *module = PyImport_Import(py_mod_name);
|
||||||
|
Py_DECREF(py_mod_name);
|
||||||
|
if (module == NULL) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get function, same as "getattr(module, func_name)" in Python
|
||||||
|
PyObject *func = PyObject_GetAttrString(module, func_name);
|
||||||
|
Py_DECREF(module);
|
||||||
|
return func;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return last error as char *, NULL if there was no error
|
||||||
|
const char *py_last_error() {
|
||||||
|
PyObject *err = PyErr_Occurred();
|
||||||
|
if (err == NULL) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
PyObject *type, *value, *traceback;
|
||||||
|
PyErr_Fetch(&type, &value, &traceback);
|
||||||
|
|
||||||
|
if (value == NULL) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
PyObject *str = PyObject_Str(value);
|
||||||
|
const char *utf8 = PyUnicode_AsUTF8(str);
|
||||||
|
Py_DECREF(str);
|
||||||
|
PyErr_Clear();
|
||||||
|
return utf8;
|
||||||
|
}
|
||||||
|
|
||||||
|
void py_clear(PyObject *obj) {
|
||||||
|
Py_CLEAR(obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
void load_main_func() {
|
||||||
|
main_fn = load_func(CLASH_SCRIPT_MODULE_NAME, "main");
|
||||||
|
}
|
||||||
|
|
||||||
|
/** callback function, that call go function by python3 script. **/
|
||||||
|
|
||||||
|
resolve_ip_callback resolve_ip_callback_fn;
|
||||||
|
|
||||||
|
geoip_callback geoip_callback_fn;
|
||||||
|
|
||||||
|
rule_provider_callback rule_provider_callback_fn;
|
||||||
|
|
||||||
|
log_callback log_callback_fn;
|
||||||
|
|
||||||
|
void
|
||||||
|
set_resolve_ip_callback(resolve_ip_callback cb)
|
||||||
|
{
|
||||||
|
resolve_ip_callback_fn = cb;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
set_geoip_callback(geoip_callback cb)
|
||||||
|
{
|
||||||
|
geoip_callback_fn = cb;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
set_rule_provider_callback(rule_provider_callback cb)
|
||||||
|
{
|
||||||
|
rule_provider_callback_fn = cb;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
set_log_callback(log_callback cb)
|
||||||
|
{
|
||||||
|
log_callback_fn = cb;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** end callback function **/
|
||||||
|
|
||||||
|
/* --------------------------------------------------------------------- */
|
||||||
|
|
||||||
|
/* RuleProvider objects */
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
PyObject_HEAD
|
||||||
|
PyObject *name; /* rule provider name */
|
||||||
|
} RuleProviderObject;
|
||||||
|
|
||||||
|
static int
|
||||||
|
RuleProvider_traverse(RuleProviderObject *self, visitproc visit, void *arg)
|
||||||
|
{
|
||||||
|
Py_VISIT(self->name);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
RuleProvider_clear(RuleProviderObject *self)
|
||||||
|
{
|
||||||
|
Py_CLEAR(self->name);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
RuleProvider_dealloc(RuleProviderObject *self)
|
||||||
|
{
|
||||||
|
PyObject_GC_UnTrack(self);
|
||||||
|
RuleProvider_clear(self);
|
||||||
|
Py_TYPE(self)->tp_free((PyObject *) self);
|
||||||
|
}
|
||||||
|
|
||||||
|
static PyObject *
|
||||||
|
RuleProvider_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
|
||||||
|
{
|
||||||
|
RuleProviderObject *self;
|
||||||
|
self = (RuleProviderObject *) type->tp_alloc(type, 0);
|
||||||
|
if (self != NULL) {
|
||||||
|
self->name = PyUnicode_FromString("");
|
||||||
|
if (self->name == NULL) {
|
||||||
|
Py_DECREF(self);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (PyObject *) self;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
RuleProvider_init(RuleProviderObject *self, PyObject *args, PyObject *kwds)
|
||||||
|
{
|
||||||
|
static char *kwlist[] = {"name", NULL};
|
||||||
|
PyObject *name = NULL, *tmp;
|
||||||
|
|
||||||
|
if (!PyArg_ParseTupleAndKeywords(args, kwds, "|Us", kwlist, &name))
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
if (name) {
|
||||||
|
tmp = self->name;
|
||||||
|
Py_INCREF(name);
|
||||||
|
self->name = name;
|
||||||
|
Py_DECREF(tmp);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
//static PyMemberDef RuleProvider_members[] = {
|
||||||
|
// {"adapter_type", T_STRING, offsetof(RuleProviderObject, adapter_type), 0,
|
||||||
|
// "adapter type"},
|
||||||
|
// {NULL} /* Sentinel */
|
||||||
|
//};
|
||||||
|
|
||||||
|
static PyObject *
|
||||||
|
RuleProvider_getname(RuleProviderObject *self, void *closure)
|
||||||
|
{
|
||||||
|
Py_INCREF(self->name);
|
||||||
|
return self->name;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
RuleProvider_setname(RuleProviderObject *self, PyObject *value, void *closure)
|
||||||
|
{
|
||||||
|
if (value == NULL) {
|
||||||
|
PyErr_SetString(PyExc_TypeError, "Cannot delete the name attribute");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (!PyUnicode_Check(value)) {
|
||||||
|
PyErr_SetString(PyExc_TypeError,
|
||||||
|
"The name attribute value must be a string");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
Py_INCREF(value);
|
||||||
|
Py_CLEAR(self->name);
|
||||||
|
self->name = value;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static PyGetSetDef RuleProvider_getsetters[] = {
|
||||||
|
{"name", (getter) RuleProvider_getname, (setter) RuleProvider_setname,
|
||||||
|
"name", NULL},
|
||||||
|
{NULL} /* Sentinel */
|
||||||
|
};
|
||||||
|
|
||||||
|
static PyObject *
|
||||||
|
RuleProvider_name(RuleProviderObject *self, PyObject *Py_UNUSED(ignored))
|
||||||
|
{
|
||||||
|
Py_INCREF(self->name);
|
||||||
|
return self->name;
|
||||||
|
}
|
||||||
|
|
||||||
|
static PyObject *
|
||||||
|
RuleProvider_match(RuleProviderObject *self, PyObject *args)
|
||||||
|
{
|
||||||
|
PyObject *result;
|
||||||
|
PyObject *tmp;
|
||||||
|
const char *provider_name;
|
||||||
|
|
||||||
|
if (!PyArg_ParseTuple(args, "O!", &PyDict_Type, &tmp)) //Format "O","O!","O&": Borrowed reference.
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
if (tmp == NULL)
|
||||||
|
Py_RETURN_FALSE;
|
||||||
|
|
||||||
|
Py_INCREF(tmp);
|
||||||
|
// PyObject *py_src_port = PyDict_GetItemString(tmp, "src_port"); //Return value: Borrowed reference.
|
||||||
|
// PyObject *py_dst_port = PyDict_GetItemString(tmp, "dst_port"); //Return value: Borrowed reference.
|
||||||
|
// Py_INCREF(py_src_port);
|
||||||
|
// Py_INCREF(py_dst_port);
|
||||||
|
// char *c_src_port = (char *) malloc(PyLong_AsSize_t(py_src_port));
|
||||||
|
// char *c_dst_port = (char *) malloc(PyLong_AsSize_t(py_dst_port));
|
||||||
|
// sprintf(c_src_port, "%ld", PyLong_AsLong(py_src_port));
|
||||||
|
// sprintf(c_dst_port, "%ld", PyLong_AsLong(py_dst_port));
|
||||||
|
|
||||||
|
struct Metadata metadata = {
|
||||||
|
.type = PyUnicode_AsUTF8(PyDict_GetItemString(tmp, "type")), // PyDict_GetItemString() Return value: Borrowed reference.
|
||||||
|
.network = PyUnicode_AsUTF8(PyDict_GetItemString(tmp, "network")),
|
||||||
|
.process_name = PyUnicode_AsUTF8(PyDict_GetItemString(tmp, "process_name")),
|
||||||
|
.process_path = PyUnicode_AsUTF8(PyDict_GetItemString(tmp, "process_path")),
|
||||||
|
.host = PyUnicode_AsUTF8(PyDict_GetItemString(tmp, "host")),
|
||||||
|
.src_ip = PyUnicode_AsUTF8(PyDict_GetItemString(tmp, "src_ip")),
|
||||||
|
.src_port = (unsigned short)PyLong_AsUnsignedLong(PyDict_GetItemString(tmp, "src_port")),
|
||||||
|
.dst_ip = PyUnicode_AsUTF8(PyDict_GetItemString(tmp, "dst_ip")),
|
||||||
|
.dst_port = (unsigned short)PyLong_AsUnsignedLong(PyDict_GetItemString(tmp, "dst_port"))
|
||||||
|
};
|
||||||
|
|
||||||
|
// Py_DECREF(py_src_port);
|
||||||
|
// Py_DECREF(py_dst_port);
|
||||||
|
|
||||||
|
Py_INCREF(self->name);
|
||||||
|
provider_name = PyUnicode_AsUTF8(self->name);
|
||||||
|
Py_DECREF(self->name);
|
||||||
|
Py_DECREF(tmp);
|
||||||
|
|
||||||
|
int rs = rule_provider_callback_fn(provider_name, &metadata);
|
||||||
|
|
||||||
|
result = (rs == 1) ? Py_True : Py_False;
|
||||||
|
Py_INCREF(result);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static PyMethodDef RuleProvider_methods[] = {
|
||||||
|
{"name", (PyCFunction) RuleProvider_name, METH_NOARGS,
|
||||||
|
"Return the RuleProvider name"
|
||||||
|
},
|
||||||
|
{"match", (PyCFunction) RuleProvider_match, METH_VARARGS,
|
||||||
|
"Match the rule by the RuleProvider, match(metadata) -> boolean"
|
||||||
|
},
|
||||||
|
{NULL} /* Sentinel */
|
||||||
|
};
|
||||||
|
|
||||||
|
static PyTypeObject RuleProviderType = {
|
||||||
|
PyVarObject_HEAD_INIT(NULL, 0)
|
||||||
|
.tp_name = "clash.RuleProvider",
|
||||||
|
.tp_doc = "Clash RuleProvider objects",
|
||||||
|
.tp_basicsize = sizeof(RuleProviderObject),
|
||||||
|
.tp_itemsize = 0,
|
||||||
|
.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC,
|
||||||
|
.tp_new = RuleProvider_new,
|
||||||
|
.tp_init = (initproc) RuleProvider_init,
|
||||||
|
.tp_dealloc = (destructor) RuleProvider_dealloc,
|
||||||
|
.tp_traverse = (traverseproc) RuleProvider_traverse,
|
||||||
|
.tp_clear = (inquiry) RuleProvider_clear,
|
||||||
|
// .tp_members = RuleProvider_members,
|
||||||
|
.tp_methods = RuleProvider_methods,
|
||||||
|
.tp_getset = RuleProvider_getsetters,
|
||||||
|
};
|
||||||
|
|
||||||
|
/* end RuleProvider objects */
|
||||||
|
/* --------------------------------------------------------------------- */
|
||||||
|
|
||||||
|
/* Context objects */
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
PyObject_HEAD
|
||||||
|
PyObject *rule_providers; /* Dict<String, RuleProvider> */
|
||||||
|
} ContextObject;
|
||||||
|
|
||||||
|
static int
|
||||||
|
Context_traverse(ContextObject *self, visitproc visit, void *arg)
|
||||||
|
{
|
||||||
|
Py_VISIT(self->rule_providers);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
Context_clear(ContextObject *self)
|
||||||
|
{
|
||||||
|
Py_CLEAR(self->rule_providers);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
Context_dealloc(ContextObject *self)
|
||||||
|
{
|
||||||
|
PyObject_GC_UnTrack(self);
|
||||||
|
Context_clear(self);
|
||||||
|
Py_TYPE(self)->tp_free((PyObject *) self);
|
||||||
|
}
|
||||||
|
|
||||||
|
static PyObject *
|
||||||
|
Context_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
|
||||||
|
{
|
||||||
|
ContextObject *self;
|
||||||
|
self = (ContextObject *) type->tp_alloc(type, 0);
|
||||||
|
if (self != NULL) {
|
||||||
|
self->rule_providers = PyDict_New();
|
||||||
|
if (self->rule_providers == NULL) {
|
||||||
|
Py_DECREF(self);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (PyObject *) self;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
Context_init(ContextObject *self, PyObject *args, PyObject *kwds)
|
||||||
|
{
|
||||||
|
static char *kwlist[] = {"rule_providers", NULL};
|
||||||
|
PyObject *rule_providers = NULL, *tmp;
|
||||||
|
|
||||||
|
if (!PyArg_ParseTupleAndKeywords(args, kwds, "|O", kwlist,
|
||||||
|
&rule_providers))
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
if (rule_providers) {
|
||||||
|
tmp = self->rule_providers;
|
||||||
|
Py_INCREF(rule_providers);
|
||||||
|
self->rule_providers = rule_providers;
|
||||||
|
Py_DECREF(tmp);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static PyObject *
|
||||||
|
Context_getrule_providers(ContextObject *self, void *closure)
|
||||||
|
{
|
||||||
|
Py_INCREF(self->rule_providers);
|
||||||
|
return self->rule_providers;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
Context_setrule_providers(ContextObject *self, PyObject *value, void *closure)
|
||||||
|
{
|
||||||
|
if (value == NULL) {
|
||||||
|
PyErr_SetString(PyExc_TypeError, "Cannot delete the rule_providers attribute");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (!PyDict_Check(value)) {
|
||||||
|
PyErr_SetString(PyExc_TypeError,
|
||||||
|
"The rule_providers attribute value must be a dict");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
Py_INCREF(value);
|
||||||
|
Py_CLEAR(self->rule_providers);
|
||||||
|
self->rule_providers = value;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static PyGetSetDef Context_getsetters[] = {
|
||||||
|
{"rule_providers", (getter) Context_getrule_providers, (setter) Context_setrule_providers,
|
||||||
|
"rule_providers", NULL},
|
||||||
|
{NULL} /* Sentinel */
|
||||||
|
};
|
||||||
|
|
||||||
|
static PyObject *
|
||||||
|
Context_resolve_ip(PyObject *self, PyObject *args)
|
||||||
|
{
|
||||||
|
const char *host;
|
||||||
|
const char *ip;
|
||||||
|
|
||||||
|
if (!PyArg_ParseTuple(args, "s", &host))
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
if (host == NULL)
|
||||||
|
return PyUnicode_FromString("");
|
||||||
|
|
||||||
|
ip = resolve_ip_callback_fn(host);
|
||||||
|
|
||||||
|
return PyUnicode_FromString(ip);
|
||||||
|
}
|
||||||
|
|
||||||
|
static PyObject *
|
||||||
|
Context_geoip(PyObject *self, PyObject *args)
|
||||||
|
{
|
||||||
|
const char *ip;
|
||||||
|
const char *countryCode;
|
||||||
|
|
||||||
|
if (!PyArg_ParseTuple(args, "s", &ip))
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
if (ip == NULL)
|
||||||
|
return PyUnicode_FromString("");
|
||||||
|
|
||||||
|
countryCode = geoip_callback_fn(ip);
|
||||||
|
|
||||||
|
return PyUnicode_FromString(countryCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
static PyObject *
|
||||||
|
Context_log(PyObject *self, PyObject *args)
|
||||||
|
{
|
||||||
|
const char *msg;
|
||||||
|
|
||||||
|
if (!PyArg_ParseTuple(args, "s", &msg))
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
log_callback_fn(msg);
|
||||||
|
|
||||||
|
Py_RETURN_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static PyMethodDef Context_methods[] = {
|
||||||
|
{"resolve_ip", (PyCFunction) Context_resolve_ip, METH_VARARGS,
|
||||||
|
"resolve_ip(host) -> string"
|
||||||
|
},
|
||||||
|
{"geoip", (PyCFunction) Context_geoip, METH_VARARGS,
|
||||||
|
"geoip(ip) -> string"
|
||||||
|
},
|
||||||
|
{"log", (PyCFunction) Context_log, METH_VARARGS,
|
||||||
|
"log(msg) -> void"
|
||||||
|
},
|
||||||
|
{NULL} /* Sentinel */
|
||||||
|
};
|
||||||
|
|
||||||
|
static PyTypeObject ContextType = {
|
||||||
|
PyVarObject_HEAD_INIT(NULL, 0)
|
||||||
|
.tp_name = "clash.Context",
|
||||||
|
.tp_doc = "Clash Context objects",
|
||||||
|
.tp_basicsize = sizeof(ContextObject),
|
||||||
|
.tp_itemsize = 0,
|
||||||
|
.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC,
|
||||||
|
.tp_new = Context_new,
|
||||||
|
.tp_init = (initproc) Context_init,
|
||||||
|
.tp_dealloc = (destructor) Context_dealloc,
|
||||||
|
.tp_traverse = (traverseproc) Context_traverse,
|
||||||
|
.tp_clear = (inquiry) Context_clear,
|
||||||
|
.tp_methods = Context_methods,
|
||||||
|
.tp_getset = Context_getsetters,
|
||||||
|
};
|
||||||
|
|
||||||
|
static PyModuleDef clashmodule = {
|
||||||
|
PyModuleDef_HEAD_INIT,
|
||||||
|
.m_name = "clash",
|
||||||
|
.m_doc = "Clash module that creates an extension module for python3.",
|
||||||
|
.m_size = -1,
|
||||||
|
};
|
||||||
|
|
||||||
|
PyMODINIT_FUNC
|
||||||
|
PyInit_clash(void)
|
||||||
|
{
|
||||||
|
PyObject *m;
|
||||||
|
|
||||||
|
m = PyModule_Create(&clashmodule);
|
||||||
|
if (m == NULL)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
if (PyType_Ready(&RuleProviderType) < 0)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
Py_INCREF(&RuleProviderType);
|
||||||
|
if (PyModule_AddObject(m, "RuleProvider", (PyObject *) &RuleProviderType) < 0) {
|
||||||
|
Py_DECREF(&RuleProviderType);
|
||||||
|
Py_DECREF(m);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (PyType_Ready(&ContextType) < 0)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
Py_INCREF(&ContextType);
|
||||||
|
if (PyModule_AddObject(m, "Context", (PyObject *) &ContextType) < 0) {
|
||||||
|
Py_DECREF(&ContextType);
|
||||||
|
Py_DECREF(m);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return m;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* end Context objects */
|
||||||
|
|
||||||
|
/* --------------------------------------------------------------------- */
|
||||||
|
|
||||||
|
void
|
||||||
|
append_inittab()
|
||||||
|
{
|
||||||
|
/* Add a built-in module, before Py_Initialize */
|
||||||
|
PyImport_AppendInittab("clash", PyInit_clash);
|
||||||
|
}
|
||||||
|
|
||||||
|
int new_clash_py_context(const char *provider_name_arr[], int size) {
|
||||||
|
PyObject *dict = PyDict_New(); //Return value: New reference.
|
||||||
|
if (dict == NULL) {
|
||||||
|
PyErr_SetString(PyExc_TypeError,
|
||||||
|
"PyDict_New failure");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < size; i++) {
|
||||||
|
PyObject *rule_provider = RuleProvider_new(&RuleProviderType, NULL, NULL);
|
||||||
|
if (rule_provider == NULL) {
|
||||||
|
Py_DECREF(dict);
|
||||||
|
PyErr_SetString(PyExc_TypeError,
|
||||||
|
"RuleProvider_new failure");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
RuleProviderObject *providerObj = (RuleProviderObject *) rule_provider;
|
||||||
|
|
||||||
|
PyObject *py_name = PyUnicode_FromString(provider_name_arr[i]); //Return value: New reference.
|
||||||
|
RuleProvider_setname(providerObj, py_name, NULL);
|
||||||
|
Py_DECREF(py_name);
|
||||||
|
|
||||||
|
PyDict_SetItemString(dict, provider_name_arr[i], rule_provider); //Parameter value: New reference.
|
||||||
|
Py_DECREF(rule_provider);
|
||||||
|
}
|
||||||
|
|
||||||
|
clash_context = Context_new(&ContextType, NULL, NULL);
|
||||||
|
|
||||||
|
if (clash_context == NULL) {
|
||||||
|
Py_DECREF(dict);
|
||||||
|
PyErr_SetString(PyExc_TypeError,
|
||||||
|
"Context_new failure");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
Context_setrule_providers((ContextObject *) clash_context, dict, NULL);
|
||||||
|
Py_DECREF(dict);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *call_main(
|
||||||
|
const char *type,
|
||||||
|
const char *network,
|
||||||
|
const char *process_name,
|
||||||
|
const char *process_path,
|
||||||
|
const char *host,
|
||||||
|
const char *src_ip,
|
||||||
|
unsigned short src_port,
|
||||||
|
const char *dst_ip,
|
||||||
|
unsigned short dst_port) {
|
||||||
|
|
||||||
|
PyObject *metadataDict;
|
||||||
|
PyObject *tupleArgs;
|
||||||
|
PyObject *result;
|
||||||
|
|
||||||
|
metadataDict = PyDict_New(); //Return value: New reference.
|
||||||
|
|
||||||
|
if (metadataDict == NULL) {
|
||||||
|
PyErr_SetString(PyExc_TypeError,
|
||||||
|
"PyDict_New failure");
|
||||||
|
return "-1";
|
||||||
|
}
|
||||||
|
|
||||||
|
PyObject *p_type = PyUnicode_FromString(type); //Return value: New reference.
|
||||||
|
PyObject *p_network = PyUnicode_FromString(network); //Return value: New reference.
|
||||||
|
PyObject *p_process_name = PyUnicode_FromString(process_name); //Return value: New reference.
|
||||||
|
PyObject *p_process_path = PyUnicode_FromString(process_path); //Return value: New reference.
|
||||||
|
PyObject *p_host = PyUnicode_FromString(host); //Return value: New reference.
|
||||||
|
PyObject *p_src_ip = PyUnicode_FromString(src_ip); //Return value: New reference.
|
||||||
|
PyObject *p_src_port = PyLong_FromUnsignedLong((unsigned long)src_port); //Return value: New reference.
|
||||||
|
PyObject *p_dst_ip = PyUnicode_FromString(dst_ip); //Return value: New reference.
|
||||||
|
PyObject *p_dst_port = PyLong_FromUnsignedLong((unsigned long)dst_port); //Return value: New reference.
|
||||||
|
|
||||||
|
PyDict_SetItemString(metadataDict, "type", p_type); //Parameter value: New reference.
|
||||||
|
PyDict_SetItemString(metadataDict, "network", p_network); //Parameter value: New reference.
|
||||||
|
PyDict_SetItemString(metadataDict, "process_name", p_process_name); //Parameter value: New reference.
|
||||||
|
PyDict_SetItemString(metadataDict, "process_path", p_process_path); //Parameter value: New reference.
|
||||||
|
PyDict_SetItemString(metadataDict, "host", p_host); //Parameter value: New reference.
|
||||||
|
PyDict_SetItemString(metadataDict, "src_ip", p_src_ip); //Parameter value: New reference.
|
||||||
|
PyDict_SetItemString(metadataDict, "src_port", p_src_port); //Parameter value: New reference.
|
||||||
|
PyDict_SetItemString(metadataDict, "dst_ip", p_dst_ip); //Parameter value: New reference.
|
||||||
|
PyDict_SetItemString(metadataDict, "dst_port", p_dst_port); //Parameter value: New reference.
|
||||||
|
|
||||||
|
Py_DECREF(p_type);
|
||||||
|
Py_DECREF(p_network);
|
||||||
|
Py_DECREF(p_process_name);
|
||||||
|
Py_DECREF(p_process_path);
|
||||||
|
Py_DECREF(p_host);
|
||||||
|
Py_DECREF(p_src_ip);
|
||||||
|
Py_DECREF(p_src_port);
|
||||||
|
Py_DECREF(p_dst_ip);
|
||||||
|
Py_DECREF(p_dst_port);
|
||||||
|
|
||||||
|
tupleArgs = PyTuple_New(2); //Return value: New reference.
|
||||||
|
if (tupleArgs == NULL) {
|
||||||
|
Py_DECREF(metadataDict);
|
||||||
|
PyErr_SetString(PyExc_TypeError,
|
||||||
|
"PyTuple_New failure");
|
||||||
|
return "-1";
|
||||||
|
}
|
||||||
|
|
||||||
|
Py_INCREF(clash_context);
|
||||||
|
PyTuple_SetItem(tupleArgs, 0, clash_context); //clash_context Parameter value: Stolen reference.
|
||||||
|
PyTuple_SetItem(tupleArgs, 1, metadataDict); //metadataDict Parameter value: Stolen reference.
|
||||||
|
|
||||||
|
Py_INCREF(main_fn);
|
||||||
|
result = PyObject_CallObject(main_fn, tupleArgs); //Return value: New reference.
|
||||||
|
Py_DECREF(main_fn);
|
||||||
|
Py_DECREF(tupleArgs);
|
||||||
|
|
||||||
|
if (result == NULL) {
|
||||||
|
return "-1";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!PyUnicode_Check(result)) {
|
||||||
|
Py_DECREF(result);
|
||||||
|
PyErr_SetString(PyExc_TypeError,
|
||||||
|
"script main function return value must be a string");
|
||||||
|
return "-1";
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *adapter = PyUnicode_AsUTF8(result);
|
||||||
|
|
||||||
|
Py_DECREF(result);
|
||||||
|
|
||||||
|
return adapter;
|
||||||
|
}
|
||||||
|
|
||||||
|
int call_shortcut(PyObject *shortcut_fn,
|
||||||
|
const char *type,
|
||||||
|
const char *network,
|
||||||
|
const char *process_name,
|
||||||
|
const char *process_path,
|
||||||
|
const char *host,
|
||||||
|
const char *src_ip,
|
||||||
|
unsigned short src_port,
|
||||||
|
const char *dst_ip,
|
||||||
|
unsigned short dst_port) {
|
||||||
|
|
||||||
|
PyObject *args;
|
||||||
|
PyObject *result;
|
||||||
|
|
||||||
|
args = Py_BuildValue("{s:O, s:s, s:s, s:s, s:s, s:s, s:s, s:H, s:s, s:H}",
|
||||||
|
"ctx", clash_context,
|
||||||
|
"type", type,
|
||||||
|
"network", network,
|
||||||
|
"process_name", process_name,
|
||||||
|
"process_path", process_path,
|
||||||
|
"host", host,
|
||||||
|
"src_ip", src_ip,
|
||||||
|
"src_port", src_port,
|
||||||
|
"dst_ip", dst_ip,
|
||||||
|
"dst_port", dst_port); //Return value: New reference.
|
||||||
|
|
||||||
|
if (args == NULL) {
|
||||||
|
PyErr_SetString(PyExc_TypeError,
|
||||||
|
"Py_BuildValue failure");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
PyObject *tupleArgs = PyTuple_New(0); //Return value: New reference.
|
||||||
|
|
||||||
|
Py_INCREF(clash_context);
|
||||||
|
Py_INCREF(shortcut_fn);
|
||||||
|
result = PyObject_Call(shortcut_fn, tupleArgs, args); //Return value: New reference.
|
||||||
|
Py_DECREF(shortcut_fn);
|
||||||
|
Py_DECREF(clash_context);
|
||||||
|
Py_DECREF(tupleArgs);
|
||||||
|
Py_DECREF(args);
|
||||||
|
|
||||||
|
if (result == NULL) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!PyBool_Check(result)) {
|
||||||
|
Py_DECREF(result);
|
||||||
|
PyErr_SetString(PyExc_TypeError,
|
||||||
|
"script shortcut return value must be as boolean");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int rs = (result == Py_True) ? 1 : 0;
|
||||||
|
|
||||||
|
Py_DECREF(result);
|
||||||
|
|
||||||
|
return rs;
|
||||||
|
}
|
||||||
|
|
||||||
|
void finalize_Python() {
|
||||||
|
Py_CLEAR(main_fn);
|
||||||
|
Py_CLEAR(clash_context);
|
||||||
|
Py_CLEAR(clash_module);
|
||||||
|
Py_FinalizeEx();
|
||||||
|
|
||||||
|
clash_module = NULL;
|
||||||
|
main_fn = NULL;
|
||||||
|
clash_context = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --------------------------------------------------------------------- */
|
338
component/script/clash_module.go
Normal file
338
component/script/clash_module.go
Normal file
@ -0,0 +1,338 @@
|
|||||||
|
package script
|
||||||
|
|
||||||
|
/*
|
||||||
|
#include "clash_module.h"
|
||||||
|
|
||||||
|
extern const char *resolveIPCallbackFn(const char *host);
|
||||||
|
|
||||||
|
void
|
||||||
|
go_set_resolve_ip_callback() {
|
||||||
|
set_resolve_ip_callback(resolveIPCallbackFn);
|
||||||
|
}
|
||||||
|
|
||||||
|
extern const char *geoipCallbackFn(const char *ip);
|
||||||
|
|
||||||
|
void
|
||||||
|
go_set_geoip_callback() {
|
||||||
|
set_geoip_callback(geoipCallbackFn);
|
||||||
|
}
|
||||||
|
|
||||||
|
extern const int ruleProviderCallbackFn(const char *provider_name, struct Metadata *metadata);
|
||||||
|
|
||||||
|
void
|
||||||
|
go_set_rule_provider_callback() {
|
||||||
|
set_rule_provider_callback(ruleProviderCallbackFn);
|
||||||
|
}
|
||||||
|
|
||||||
|
extern void logCallbackFn(const char *msg);
|
||||||
|
|
||||||
|
void
|
||||||
|
go_set_log_callback() {
|
||||||
|
set_log_callback(logCallbackFn);
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
import "C"
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"runtime"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"github.com/Dreamacro/clash/constant"
|
||||||
|
"github.com/Dreamacro/clash/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
const ClashScriptModuleName = C.CLASH_SCRIPT_MODULE_NAME
|
||||||
|
|
||||||
|
var lock sync.Mutex
|
||||||
|
|
||||||
|
type PyObject C.PyObject
|
||||||
|
|
||||||
|
func togo(cobject *C.PyObject) *PyObject {
|
||||||
|
return (*PyObject)(cobject)
|
||||||
|
}
|
||||||
|
|
||||||
|
func toc(object *PyObject) *C.PyObject {
|
||||||
|
return (*C.PyObject)(object)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pyObject *PyObject) IncRef() {
|
||||||
|
C.Py_IncRef(toc(pyObject))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pyObject *PyObject) DecRef() {
|
||||||
|
C.Py_DecRef(toc(pyObject))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pyObject *PyObject) Clear() {
|
||||||
|
C.py_clear(toc(pyObject))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Py_Initialize initialize Python3
|
||||||
|
func Py_Initialize(program string, path string) error {
|
||||||
|
lock.Lock()
|
||||||
|
defer lock.Unlock()
|
||||||
|
|
||||||
|
if C.Py_IsInitialized() != 0 {
|
||||||
|
if pyThreadState != nil {
|
||||||
|
PyEval_RestoreThread(pyThreadState)
|
||||||
|
}
|
||||||
|
C.finalize_Python()
|
||||||
|
}
|
||||||
|
|
||||||
|
path = strings.ReplaceAll(path, "\\", "/")
|
||||||
|
cPath := C.CString(path)
|
||||||
|
|
||||||
|
C.init_python(C.CString(program), cPath)
|
||||||
|
err := PyLastError()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
if C.Py_IsInitialized() != 0 {
|
||||||
|
C.finalize_Python()
|
||||||
|
_ = os.RemoveAll(constant.Path.ScriptDir())
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
} else if C.Py_IsInitialized() == 0 {
|
||||||
|
err = errors.New("initialized script module failure")
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
initPython3Callback()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func Py_IsInitialized() bool {
|
||||||
|
lock.Lock()
|
||||||
|
defer lock.Unlock()
|
||||||
|
|
||||||
|
return C.Py_IsInitialized() != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func Py_Finalize() {
|
||||||
|
lock.Lock()
|
||||||
|
defer lock.Unlock()
|
||||||
|
|
||||||
|
if C.Py_IsInitialized() != 0 {
|
||||||
|
if pyThreadState != nil {
|
||||||
|
PyEval_RestoreThread(pyThreadState)
|
||||||
|
}
|
||||||
|
C.finalize_Python()
|
||||||
|
_ = os.RemoveAll(constant.Path.ScriptDir())
|
||||||
|
log.Warnln("Clash clean up script mode.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Py_GetVersion get
|
||||||
|
func Py_GetVersion() string {
|
||||||
|
cversion := C.Py_GetVersion()
|
||||||
|
return strings.Split(C.GoString(cversion), "\n")[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
// loadPyFunc loads a Python function by module and function name
|
||||||
|
func loadPyFunc(moduleName, funcName string) (*C.PyObject, error) {
|
||||||
|
// Convert names to C char*
|
||||||
|
cMod := C.CString(moduleName)
|
||||||
|
cFunc := C.CString(funcName)
|
||||||
|
|
||||||
|
// Free memory allocated by C.CString
|
||||||
|
defer func() {
|
||||||
|
C.free(unsafe.Pointer(cMod))
|
||||||
|
C.free(unsafe.Pointer(cFunc))
|
||||||
|
}()
|
||||||
|
|
||||||
|
fnc := C.load_func(cMod, cFunc)
|
||||||
|
if fnc == nil {
|
||||||
|
return nil, PyLastError()
|
||||||
|
}
|
||||||
|
|
||||||
|
return fnc, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
//PyLastError python last error
|
||||||
|
func PyLastError() error {
|
||||||
|
cp := C.py_last_error()
|
||||||
|
if cp == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return errors.New(C.GoString(cp))
|
||||||
|
}
|
||||||
|
|
||||||
|
func LoadShortcutFunction(shortcut string) (*PyObject, error) {
|
||||||
|
fnc, err := loadPyFunc(ClashScriptModuleName, shortcut)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return togo(fnc), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func LoadMainFunction() error {
|
||||||
|
C.load_main_func()
|
||||||
|
err := PyLastError()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
//CallPyMainFunction call python script main function
|
||||||
|
//return the proxy adapter name.
|
||||||
|
func CallPyMainFunction(mtd *constant.Metadata) (string, error) {
|
||||||
|
_type := C.CString(mtd.Type.String())
|
||||||
|
network := C.CString(mtd.NetWork.String())
|
||||||
|
processName := C.CString(mtd.Process)
|
||||||
|
processPath := C.CString(mtd.ProcessPath)
|
||||||
|
host := C.CString(mtd.Host)
|
||||||
|
|
||||||
|
srcPortGo, _ := strconv.ParseUint(mtd.SrcPort, 10, 16)
|
||||||
|
dstPortGo, _ := strconv.ParseUint(mtd.DstPort, 10, 16)
|
||||||
|
srcPort := C.ushort(srcPortGo)
|
||||||
|
dstPort := C.ushort(dstPortGo)
|
||||||
|
|
||||||
|
dstIpGo := ""
|
||||||
|
srcIpGo := ""
|
||||||
|
if mtd.SrcIP.IsValid() {
|
||||||
|
srcIpGo = mtd.SrcIP.String()
|
||||||
|
}
|
||||||
|
if mtd.DstIP.IsValid() {
|
||||||
|
dstIpGo = mtd.DstIP.String()
|
||||||
|
}
|
||||||
|
srcIp := C.CString(srcIpGo)
|
||||||
|
dstIp := C.CString(dstIpGo)
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
C.free(unsafe.Pointer(_type))
|
||||||
|
C.free(unsafe.Pointer(network))
|
||||||
|
C.free(unsafe.Pointer(processName))
|
||||||
|
C.free(unsafe.Pointer(processPath))
|
||||||
|
C.free(unsafe.Pointer(host))
|
||||||
|
C.free(unsafe.Pointer(srcIp))
|
||||||
|
C.free(unsafe.Pointer(dstIp))
|
||||||
|
}()
|
||||||
|
|
||||||
|
runtime.LockOSThread()
|
||||||
|
gilState := PyGILState_Ensure()
|
||||||
|
defer PyGILState_Release(gilState)
|
||||||
|
|
||||||
|
cRs := C.call_main(_type, network, processName, processPath, host, srcIp, srcPort, dstIp, dstPort)
|
||||||
|
|
||||||
|
rs := C.GoString(cRs)
|
||||||
|
if rs == "-1" {
|
||||||
|
err := PyLastError()
|
||||||
|
if err != nil {
|
||||||
|
log.Errorln("[Script] script code error: %v", err)
|
||||||
|
killSelf()
|
||||||
|
return "", fmt.Errorf("script code error: %w", err)
|
||||||
|
} else {
|
||||||
|
return "", fmt.Errorf("script code error, result: %v", rs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return rs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
//CallPyShortcut call python script shortcuts function
|
||||||
|
//param: shortcut name
|
||||||
|
//return the match result.
|
||||||
|
func CallPyShortcut(fn *PyObject, mtd *constant.Metadata) (bool, error) {
|
||||||
|
_type := C.CString(mtd.Type.String())
|
||||||
|
network := C.CString(mtd.NetWork.String())
|
||||||
|
processName := C.CString(mtd.Process)
|
||||||
|
processPath := C.CString(mtd.ProcessPath)
|
||||||
|
host := C.CString(mtd.Host)
|
||||||
|
|
||||||
|
srcPortGo, _ := strconv.ParseUint(mtd.SrcPort, 10, 16)
|
||||||
|
dstPortGo, _ := strconv.ParseUint(mtd.DstPort, 10, 16)
|
||||||
|
srcPort := C.ushort(srcPortGo)
|
||||||
|
dstPort := C.ushort(dstPortGo)
|
||||||
|
|
||||||
|
dstIpGo := ""
|
||||||
|
srcIpGo := ""
|
||||||
|
if mtd.SrcIP.IsValid() {
|
||||||
|
srcIpGo = mtd.SrcIP.String()
|
||||||
|
}
|
||||||
|
if mtd.DstIP.IsValid() {
|
||||||
|
dstIpGo = mtd.DstIP.String()
|
||||||
|
}
|
||||||
|
srcIp := C.CString(srcIpGo)
|
||||||
|
dstIp := C.CString(dstIpGo)
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
C.free(unsafe.Pointer(_type))
|
||||||
|
C.free(unsafe.Pointer(network))
|
||||||
|
C.free(unsafe.Pointer(processName))
|
||||||
|
C.free(unsafe.Pointer(processPath))
|
||||||
|
C.free(unsafe.Pointer(host))
|
||||||
|
C.free(unsafe.Pointer(srcIp))
|
||||||
|
C.free(unsafe.Pointer(dstIp))
|
||||||
|
}()
|
||||||
|
|
||||||
|
runtime.LockOSThread()
|
||||||
|
gilState := PyGILState_Ensure()
|
||||||
|
defer PyGILState_Release(gilState)
|
||||||
|
|
||||||
|
cRs := C.call_shortcut(toc(fn), _type, network, processName, processPath, host, srcIp, srcPort, dstIp, dstPort)
|
||||||
|
|
||||||
|
rs := int(cRs)
|
||||||
|
if rs == -1 {
|
||||||
|
err := PyLastError()
|
||||||
|
if err != nil {
|
||||||
|
log.Errorln("[Script] script shortcut code error: %v", err)
|
||||||
|
killSelf()
|
||||||
|
return false, fmt.Errorf("script shortcut code error: %w", err)
|
||||||
|
} else {
|
||||||
|
return false, fmt.Errorf("script shortcut code error: result: %d", rs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if rs == 1 {
|
||||||
|
return true, nil
|
||||||
|
} else {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func initPython3Callback() {
|
||||||
|
C.go_set_resolve_ip_callback()
|
||||||
|
C.go_set_geoip_callback()
|
||||||
|
C.go_set_rule_provider_callback()
|
||||||
|
C.go_set_log_callback()
|
||||||
|
}
|
||||||
|
|
||||||
|
//NewClashPyContext new clash context for python
|
||||||
|
func NewClashPyContext(ruleProvidersName []string) error {
|
||||||
|
length := len(ruleProvidersName)
|
||||||
|
cStringArr := make([]*C.char, length)
|
||||||
|
for i, v := range ruleProvidersName {
|
||||||
|
cStringArr[i] = C.CString(v)
|
||||||
|
defer C.free(unsafe.Pointer(cStringArr[i]))
|
||||||
|
}
|
||||||
|
|
||||||
|
cArrPointer := unsafe.Pointer(nil)
|
||||||
|
if length > 0 {
|
||||||
|
cArrPointer = unsafe.Pointer(&cStringArr[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
rs := C.new_clash_py_context((**C.char)(cArrPointer), C.int(length))
|
||||||
|
|
||||||
|
if int(rs) == 0 {
|
||||||
|
err := PyLastError()
|
||||||
|
return fmt.Errorf("new script module context failure: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func killSelf() {
|
||||||
|
p, err := os.FindProcess(os.Getpid())
|
||||||
|
if err != nil {
|
||||||
|
os.Exit(int(syscall.SIGINT))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_ = p.Signal(syscall.SIGINT)
|
||||||
|
}
|
65
component/script/clash_module.h
Normal file
65
component/script/clash_module.h
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
#ifndef CLASH_CALLBACK_MODULE_H__
|
||||||
|
#define CLASH_CALLBACK_MODULE_H__
|
||||||
|
|
||||||
|
#include <Python.h>
|
||||||
|
|
||||||
|
#define CLASH_SCRIPT_MODULE_NAME "clash_script"
|
||||||
|
|
||||||
|
struct Metadata {
|
||||||
|
const char *type; /* type socks5/http */
|
||||||
|
const char *network; /* network tcp/udp */
|
||||||
|
const char *process_name;
|
||||||
|
const char *process_path;
|
||||||
|
const char *host;
|
||||||
|
const char *src_ip;
|
||||||
|
unsigned short src_port;
|
||||||
|
const char *dst_ip;
|
||||||
|
unsigned short dst_port;
|
||||||
|
};
|
||||||
|
|
||||||
|
/** callback function, that call go function by python3 script. **/
|
||||||
|
typedef const char *(*resolve_ip_callback)(const char *host);
|
||||||
|
typedef const char *(*geoip_callback)(const char *ip);
|
||||||
|
typedef const int (*rule_provider_callback)(const char *provider_name, struct Metadata *metadata);
|
||||||
|
typedef void (*log_callback)(const char *msg);
|
||||||
|
|
||||||
|
void set_resolve_ip_callback(resolve_ip_callback cb);
|
||||||
|
void set_geoip_callback(geoip_callback cb);
|
||||||
|
void set_rule_provider_callback(rule_provider_callback cb);
|
||||||
|
void set_log_callback(log_callback cb);
|
||||||
|
/*---------------------------------------------------------------*/
|
||||||
|
|
||||||
|
void append_inittab();
|
||||||
|
void init_python(const char *program, const char *path);
|
||||||
|
void load_main_func();
|
||||||
|
void finalize_Python();
|
||||||
|
void py_clear(PyObject *obj);
|
||||||
|
const char *py_last_error();
|
||||||
|
|
||||||
|
PyObject *load_func(const char *module_name, char *func_name);
|
||||||
|
|
||||||
|
int new_clash_py_context(const char *provider_name_arr[], int size);
|
||||||
|
|
||||||
|
const char *call_main(
|
||||||
|
const char *type,
|
||||||
|
const char *network,
|
||||||
|
const char *process_name,
|
||||||
|
const char *process_path,
|
||||||
|
const char *host,
|
||||||
|
const char *src_ip,
|
||||||
|
unsigned short src_port,
|
||||||
|
const char *dst_ip,
|
||||||
|
unsigned short dst_port);
|
||||||
|
|
||||||
|
int call_shortcut(PyObject *shortcut_fn,
|
||||||
|
const char *type,
|
||||||
|
const char *network,
|
||||||
|
const char *process_name,
|
||||||
|
const char *process_path,
|
||||||
|
const char *host,
|
||||||
|
const char *src_ip,
|
||||||
|
unsigned short src_port,
|
||||||
|
const char *dst_ip,
|
||||||
|
unsigned short dst_port);
|
||||||
|
|
||||||
|
#endif // CLASH_CALLBACK_MODULE_H__
|
145
component/script/clash_module_export.go
Normal file
145
component/script/clash_module_export.go
Normal file
@ -0,0 +1,145 @@
|
|||||||
|
package script
|
||||||
|
|
||||||
|
/*
|
||||||
|
#include "clash_module.h"
|
||||||
|
*/
|
||||||
|
import "C"
|
||||||
|
import (
|
||||||
|
"net/netip"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"github.com/Dreamacro/clash/component/mmdb"
|
||||||
|
"github.com/Dreamacro/clash/component/resolver"
|
||||||
|
"github.com/Dreamacro/clash/constant"
|
||||||
|
"github.com/Dreamacro/clash/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ruleProviders = map[string]constant.Rule{}
|
||||||
|
pyThreadState *PyThreadState
|
||||||
|
)
|
||||||
|
|
||||||
|
func UpdateRuleProviders(rpd map[string]constant.Rule) {
|
||||||
|
ruleProviders = rpd
|
||||||
|
if Py_IsInitialized() {
|
||||||
|
pyThreadState = PyEval_SaveThread()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//export resolveIPCallbackFn
|
||||||
|
func resolveIPCallbackFn(cHost *C.char) *C.char {
|
||||||
|
host := C.GoString(cHost)
|
||||||
|
if len(host) == 0 {
|
||||||
|
cip := C.CString("")
|
||||||
|
defer C.free(unsafe.Pointer(cip))
|
||||||
|
return cip
|
||||||
|
}
|
||||||
|
if ip, err := resolver.ResolveIP(host); err == nil {
|
||||||
|
cip := C.CString(ip.String())
|
||||||
|
defer C.free(unsafe.Pointer(cip))
|
||||||
|
return cip
|
||||||
|
} else {
|
||||||
|
log.Errorln("[Script] resolve ip error: %s", err.Error())
|
||||||
|
cip := C.CString("")
|
||||||
|
defer C.free(unsafe.Pointer(cip))
|
||||||
|
return cip
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//export geoipCallbackFn
|
||||||
|
func geoipCallbackFn(cIP *C.char) *C.char {
|
||||||
|
dstIP, err := netip.ParseAddr(C.GoString(cIP))
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
emptyC := C.CString("")
|
||||||
|
defer C.free(unsafe.Pointer(emptyC))
|
||||||
|
|
||||||
|
return emptyC
|
||||||
|
}
|
||||||
|
|
||||||
|
if dstIP.IsPrivate() ||
|
||||||
|
dstIP.IsUnspecified() ||
|
||||||
|
dstIP.IsLoopback() ||
|
||||||
|
dstIP.IsMulticast() ||
|
||||||
|
dstIP.IsLinkLocalUnicast() ||
|
||||||
|
resolver.IsFakeBroadcastIP(dstIP) {
|
||||||
|
|
||||||
|
lanC := C.CString("LAN")
|
||||||
|
defer C.free(unsafe.Pointer(lanC))
|
||||||
|
|
||||||
|
return lanC
|
||||||
|
}
|
||||||
|
|
||||||
|
record, _ := mmdb.Instance().Country(dstIP.AsSlice())
|
||||||
|
|
||||||
|
rc := C.CString(strings.ToUpper(record.Country.IsoCode))
|
||||||
|
defer C.free(unsafe.Pointer(rc))
|
||||||
|
|
||||||
|
return rc
|
||||||
|
}
|
||||||
|
|
||||||
|
//export ruleProviderCallbackFn
|
||||||
|
func ruleProviderCallbackFn(cProviderName *C.char, cMetadata *C.struct_Metadata) C.int {
|
||||||
|
//_type := C.GoString(cMetadata._type)
|
||||||
|
//network := C.GoString(cMetadata.network)
|
||||||
|
processName := C.GoString(cMetadata.process_name)
|
||||||
|
processPath := C.GoString(cMetadata.process_path)
|
||||||
|
host := C.GoString(cMetadata.host)
|
||||||
|
srcIp := C.GoString(cMetadata.src_ip)
|
||||||
|
srcPort := strconv.Itoa(int(cMetadata.src_port))
|
||||||
|
dstIp := C.GoString(cMetadata.dst_ip)
|
||||||
|
dstPort := strconv.Itoa(int(cMetadata.dst_port))
|
||||||
|
|
||||||
|
addrType := constant.AtypDomainName
|
||||||
|
if h, err := netip.ParseAddr(host); err == nil {
|
||||||
|
if h.Is4() {
|
||||||
|
addrType = constant.AtypIPv4
|
||||||
|
} else {
|
||||||
|
addrType = constant.AtypIPv6
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
src, _ := netip.ParseAddr(srcIp)
|
||||||
|
dst, _ := netip.ParseAddr(dstIp)
|
||||||
|
|
||||||
|
metadata := &constant.Metadata{
|
||||||
|
AddrType: addrType,
|
||||||
|
SrcIP: src,
|
||||||
|
DstIP: dst,
|
||||||
|
SrcPort: srcPort,
|
||||||
|
DstPort: dstPort,
|
||||||
|
Host: host,
|
||||||
|
Process: processName,
|
||||||
|
ProcessPath: processPath,
|
||||||
|
}
|
||||||
|
|
||||||
|
providerName := C.GoString(cProviderName)
|
||||||
|
|
||||||
|
rule, ok := ruleProviders[providerName]
|
||||||
|
if !ok {
|
||||||
|
log.Warnln("[Script] rule provider [%s] not found", providerName)
|
||||||
|
return C.int(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.HasPrefix(providerName, "geosite:") {
|
||||||
|
if len(host) == 0 {
|
||||||
|
return C.int(0)
|
||||||
|
}
|
||||||
|
metadata.AddrType = constant.AtypDomainName
|
||||||
|
}
|
||||||
|
|
||||||
|
rs := rule.Match(metadata)
|
||||||
|
|
||||||
|
if rs {
|
||||||
|
return C.int(1)
|
||||||
|
}
|
||||||
|
return C.int(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
//export logCallbackFn
|
||||||
|
func logCallbackFn(msg *C.char) {
|
||||||
|
|
||||||
|
log.Infoln(C.GoString(msg))
|
||||||
|
}
|
52
component/script/thread.go
Normal file
52
component/script/thread.go
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
package script
|
||||||
|
|
||||||
|
/*
|
||||||
|
#include "Python.h"
|
||||||
|
*/
|
||||||
|
import "C"
|
||||||
|
|
||||||
|
//PyThreadState : https://docs.python.org/3/c-api/init.html#c.PyThreadState
|
||||||
|
type PyThreadState C.PyThreadState
|
||||||
|
|
||||||
|
//PyGILState is an opaque “handle” to the thread state when PyGILState_Ensure() was called, and must be passed to PyGILState_Release() to ensure Python is left in the same state
|
||||||
|
type PyGILState C.PyGILState_STATE
|
||||||
|
|
||||||
|
//PyEval_SaveThread : https://docs.python.org/3/c-api/init.html#c.PyEval_SaveThread
|
||||||
|
func PyEval_SaveThread() *PyThreadState {
|
||||||
|
return (*PyThreadState)(C.PyEval_SaveThread())
|
||||||
|
}
|
||||||
|
|
||||||
|
//PyEval_RestoreThread : https://docs.python.org/3/c-api/init.html#c.PyEval_RestoreThread
|
||||||
|
func PyEval_RestoreThread(tstate *PyThreadState) {
|
||||||
|
C.PyEval_RestoreThread((*C.PyThreadState)(tstate))
|
||||||
|
}
|
||||||
|
|
||||||
|
//PyThreadState_Get : https://docs.python.org/3/c-api/init.html#c.PyThreadState_Get
|
||||||
|
func PyThreadState_Get() *PyThreadState {
|
||||||
|
return (*PyThreadState)(C.PyThreadState_Get())
|
||||||
|
}
|
||||||
|
|
||||||
|
//PyThreadState_Swap : https://docs.python.org/3/c-api/init.html#c.PyThreadState_Swap
|
||||||
|
func PyThreadState_Swap(tstate *PyThreadState) *PyThreadState {
|
||||||
|
return (*PyThreadState)(C.PyThreadState_Swap((*C.PyThreadState)(tstate)))
|
||||||
|
}
|
||||||
|
|
||||||
|
//PyGILState_Ensure : https://docs.python.org/3/c-api/init.html#c.PyGILState_Ensure
|
||||||
|
func PyGILState_Ensure() PyGILState {
|
||||||
|
return PyGILState(C.PyGILState_Ensure())
|
||||||
|
}
|
||||||
|
|
||||||
|
//PyGILState_Release : https://docs.python.org/3/c-api/init.html#c.PyGILState_Release
|
||||||
|
func PyGILState_Release(state PyGILState) {
|
||||||
|
C.PyGILState_Release(C.PyGILState_STATE(state))
|
||||||
|
}
|
||||||
|
|
||||||
|
//PyGILState_GetThisThreadState : https://docs.python.org/3/c-api/init.html#c.PyGILState_GetThisThreadState
|
||||||
|
func PyGILState_GetThisThreadState() *PyThreadState {
|
||||||
|
return (*PyThreadState)(C.PyGILState_GetThisThreadState())
|
||||||
|
}
|
||||||
|
|
||||||
|
//PyGILState_Check : https://docs.python.org/3/c-api/init.html#c.PyGILState_Check
|
||||||
|
func PyGILState_Check() bool {
|
||||||
|
return C.PyGILState_Check() == 1
|
||||||
|
}
|
213
config/config.go
213
config/config.go
@ -7,6 +7,7 @@ import (
|
|||||||
"net/netip"
|
"net/netip"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
|
"regexp"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@ -20,6 +21,7 @@ import (
|
|||||||
"github.com/Dreamacro/clash/component/geodata"
|
"github.com/Dreamacro/clash/component/geodata"
|
||||||
"github.com/Dreamacro/clash/component/geodata/router"
|
"github.com/Dreamacro/clash/component/geodata/router"
|
||||||
_ "github.com/Dreamacro/clash/component/geodata/standard"
|
_ "github.com/Dreamacro/clash/component/geodata/standard"
|
||||||
|
S "github.com/Dreamacro/clash/component/script"
|
||||||
"github.com/Dreamacro/clash/component/trie"
|
"github.com/Dreamacro/clash/component/trie"
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
providerTypes "github.com/Dreamacro/clash/constant/provider"
|
providerTypes "github.com/Dreamacro/clash/constant/provider"
|
||||||
@ -107,6 +109,13 @@ type Tun struct {
|
|||||||
AutoDetectInterface bool `yaml:"auto-detect-interface" json:"auto-detect-interface"`
|
AutoDetectInterface bool `yaml:"auto-detect-interface" json:"auto-detect-interface"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Script config
|
||||||
|
type Script struct {
|
||||||
|
MainCode string `yaml:"code" json:"code"`
|
||||||
|
MainPath string `yaml:"path" json:"path"`
|
||||||
|
ShortcutsCode map[string]string `yaml:"shortcuts" json:"shortcuts"`
|
||||||
|
}
|
||||||
|
|
||||||
// IPTables config
|
// IPTables config
|
||||||
type IPTables struct {
|
type IPTables struct {
|
||||||
Enable bool `yaml:"enable" json:"enable"`
|
Enable bool `yaml:"enable" json:"enable"`
|
||||||
@ -132,6 +141,7 @@ type Config struct {
|
|||||||
Hosts *trie.DomainTrie[netip.Addr]
|
Hosts *trie.DomainTrie[netip.Addr]
|
||||||
Profile *Profile
|
Profile *Profile
|
||||||
Rules []C.Rule
|
Rules []C.Rule
|
||||||
|
RuleProviders map[string]C.Rule
|
||||||
Users []auth.AuthUser
|
Users []auth.AuthUser
|
||||||
Proxies map[string]C.Proxy
|
Proxies map[string]C.Proxy
|
||||||
Providers map[string]providerTypes.ProxyProvider
|
Providers map[string]providerTypes.ProxyProvider
|
||||||
@ -198,6 +208,7 @@ type RawConfig struct {
|
|||||||
Proxy []map[string]any `yaml:"proxies"`
|
Proxy []map[string]any `yaml:"proxies"`
|
||||||
ProxyGroup []map[string]any `yaml:"proxy-groups"`
|
ProxyGroup []map[string]any `yaml:"proxy-groups"`
|
||||||
Rule []string `yaml:"rules"`
|
Rule []string `yaml:"rules"`
|
||||||
|
Script Script `yaml:"script"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse config
|
// Parse config
|
||||||
@ -305,11 +316,18 @@ func ParseRawConfig(rawCfg *RawConfig) (*Config, error) {
|
|||||||
config.Proxies = proxies
|
config.Proxies = proxies
|
||||||
config.Providers = providers
|
config.Providers = providers
|
||||||
|
|
||||||
rules, err := parseRules(rawCfg, proxies)
|
rawRules, err := parseScript(rawCfg.Script, rawCfg.Rule)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
rawCfg.Rule = rawRules
|
||||||
|
|
||||||
|
rules, ruleProviders, err := parseRules(rawCfg, proxies)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
config.Rules = rules
|
config.Rules = rules
|
||||||
|
config.RuleProviders = ruleProviders
|
||||||
|
|
||||||
hosts, err := parseHosts(rawCfg)
|
hosts, err := parseHosts(rawCfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -415,11 +433,11 @@ func parseProxies(cfg *RawConfig) (proxies map[string]C.Proxy, providersMap map[
|
|||||||
|
|
||||||
// keep the original order of ProxyGroups in config file
|
// keep the original order of ProxyGroups in config file
|
||||||
for idx, mapping := range groupsConfig {
|
for idx, mapping := range groupsConfig {
|
||||||
groupName, existName := mapping["name"]
|
groupName, existName := mapping["name"].(string)
|
||||||
if !existName {
|
if !existName {
|
||||||
return nil, nil, fmt.Errorf("proxy group %d: missing name", idx)
|
return nil, nil, fmt.Errorf("proxy group %d: missing name", idx)
|
||||||
}
|
}
|
||||||
proxyList = append(proxyList, groupName.(string))
|
proxyList = append(proxyList, groupName)
|
||||||
}
|
}
|
||||||
|
|
||||||
// check if any loop exists and sort the ProxyGroups
|
// check if any loop exists and sort the ProxyGroups
|
||||||
@ -493,10 +511,15 @@ func parseProxies(cfg *RawConfig) (proxies map[string]C.Proxy, providersMap map[
|
|||||||
return proxies, providersMap, nil
|
return proxies, providersMap, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseRules(cfg *RawConfig, proxies map[string]C.Proxy) ([]C.Rule, error) {
|
func parseRules(cfg *RawConfig, proxies map[string]C.Proxy) ([]C.Rule, map[string]C.Rule, error) {
|
||||||
rulesConfig := cfg.Rule
|
var (
|
||||||
|
rules []C.Rule
|
||||||
|
providerNames []string
|
||||||
|
foundRP bool
|
||||||
|
|
||||||
var rules []C.Rule
|
ruleProviders = map[string]C.Rule{}
|
||||||
|
rulesConfig = cfg.Rule
|
||||||
|
)
|
||||||
|
|
||||||
// parse rules
|
// parse rules
|
||||||
for idx, line := range rulesConfig {
|
for idx, line := range rulesConfig {
|
||||||
@ -511,7 +534,7 @@ func parseRules(cfg *RawConfig, proxies map[string]C.Proxy) ([]C.Rule, error) {
|
|||||||
l := len(rule)
|
l := len(rule)
|
||||||
|
|
||||||
if l < 2 {
|
if l < 2 {
|
||||||
return nil, fmt.Errorf("rules[%d] [%s] error: format invalid", idx, line)
|
return nil, nil, fmt.Errorf("rules[%d] [%s] error: format invalid", idx, line)
|
||||||
}
|
}
|
||||||
|
|
||||||
if l < 4 {
|
if l < 4 {
|
||||||
@ -530,23 +553,40 @@ func parseRules(cfg *RawConfig, proxies map[string]C.Proxy) ([]C.Rule, error) {
|
|||||||
target = rule[l-1]
|
target = rule[l-1]
|
||||||
params = rule[l:]
|
params = rule[l:]
|
||||||
|
|
||||||
if _, ok := proxies[target]; !ok {
|
if _, ok := proxies[target]; !ok && ruleName != "GEOSITE" && target != C.ScriptRuleGeoSiteTarget {
|
||||||
return nil, fmt.Errorf("rules[%d] [%s] error: proxy [%s] not found", idx, line, target)
|
return nil, nil, fmt.Errorf("rules[%d] [%s] error: proxy [%s] not found", idx, line, target)
|
||||||
|
}
|
||||||
|
|
||||||
|
pvName := "geosite:" + strings.ToLower(payload)
|
||||||
|
_, foundRP = ruleProviders[pvName]
|
||||||
|
if ruleName == "GEOSITE" && target == C.ScriptRuleGeoSiteTarget && foundRP {
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
params = trimArr(params)
|
params = trimArr(params)
|
||||||
|
|
||||||
parsed, parseErr := R.ParseRule(ruleName, payload, target, params)
|
parsed, parseErr := R.ParseRule(ruleName, payload, target, params)
|
||||||
if parseErr != nil {
|
if parseErr != nil {
|
||||||
return nil, fmt.Errorf("rules[%d] [%s] error: %s", idx, line, parseErr.Error())
|
return nil, nil, fmt.Errorf("rules[%d] [%s] error: %s", idx, line, parseErr.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
if ruleName == "GEOSITE" && !foundRP {
|
||||||
|
providerNames = append(providerNames, pvName)
|
||||||
|
ruleProviders[pvName] = parsed
|
||||||
}
|
}
|
||||||
|
|
||||||
rules = append(rules, parsed)
|
rules = append(rules, parsed)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := S.NewClashPyContext(providerNames); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
} else {
|
||||||
|
log.Infoln("Start initial script context successful, provider records: %v", len(providerNames))
|
||||||
|
}
|
||||||
|
|
||||||
runtime.GC()
|
runtime.GC()
|
||||||
|
|
||||||
return rules, nil
|
return rules, ruleProviders, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseHosts(cfg *RawConfig) (*trie.DomainTrie[netip.Addr], error) {
|
func parseHosts(cfg *RawConfig) (*trie.DomainTrie[netip.Addr], error) {
|
||||||
@ -813,6 +853,157 @@ func parseAuthentication(rawRecords []string) []auth.AuthUser {
|
|||||||
return users
|
return users
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func parseScript(script Script, rawRules []string) ([]string, error) {
|
||||||
|
var (
|
||||||
|
path = script.MainPath
|
||||||
|
mainCode = script.MainCode
|
||||||
|
shortcutsCode = script.ShortcutsCode
|
||||||
|
)
|
||||||
|
|
||||||
|
if path != "" {
|
||||||
|
if !strings.HasSuffix(path, ".star") {
|
||||||
|
return nil, fmt.Errorf("initialized script file failure, script path [%s] invalid", path)
|
||||||
|
}
|
||||||
|
path = C.Path.Resolve(path)
|
||||||
|
if _, err := os.Stat(path); os.IsNotExist(err) {
|
||||||
|
return nil, fmt.Errorf("initialized script file failure, script path invalid: %w", err)
|
||||||
|
}
|
||||||
|
data, err := os.ReadFile(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("initialized script file failure, read file error: %w", err)
|
||||||
|
}
|
||||||
|
mainCode = string(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.TrimSpace(mainCode) == "" {
|
||||||
|
mainCode = `
|
||||||
|
def main(ctx, metadata):
|
||||||
|
return "DIRECT"
|
||||||
|
`
|
||||||
|
} else {
|
||||||
|
mainCode = cleanPyKeywords(mainCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !strings.Contains(mainCode, "def main(ctx, metadata):") {
|
||||||
|
return nil, fmt.Errorf(`initialized script code failure, the function 'def main(ctx, metadata):' is required`)
|
||||||
|
}
|
||||||
|
|
||||||
|
content := `# -*- coding: UTF-8 -*-
|
||||||
|
|
||||||
|
from datetime import datetime as whatever
|
||||||
|
|
||||||
|
class ClashTime:
|
||||||
|
def now(self):
|
||||||
|
return whatever.now()
|
||||||
|
|
||||||
|
def unix(self):
|
||||||
|
return int(whatever.now().timestamp())
|
||||||
|
|
||||||
|
def unix_nano(self):
|
||||||
|
return int(round(whatever.now().timestamp() * 1000))
|
||||||
|
|
||||||
|
time = ClashTime()
|
||||||
|
|
||||||
|
class ClashRuleProvider:
|
||||||
|
def __init__(self, c, m):
|
||||||
|
self.__ctx = c
|
||||||
|
self.__metadata = m
|
||||||
|
|
||||||
|
def match_provider(self, providerName):
|
||||||
|
try:
|
||||||
|
return self.__ctx.rule_providers[providerName].match(self.__metadata)
|
||||||
|
except Exception as err:
|
||||||
|
self.__ctx.log("[SCRIPT] shortcuts error: rule provider {0} not found".format(err))
|
||||||
|
return False
|
||||||
|
|
||||||
|
`
|
||||||
|
|
||||||
|
content += mainCode + "\n\n"
|
||||||
|
|
||||||
|
for k, v := range shortcutsCode {
|
||||||
|
v = cleanPyKeywords(v)
|
||||||
|
v = strings.TrimSpace(v)
|
||||||
|
if v == "" {
|
||||||
|
return nil, fmt.Errorf("initialized rule SCRIPT failure, shortcut [%s] code syntax invalid", k)
|
||||||
|
}
|
||||||
|
|
||||||
|
content += "def " + strings.ToLower(k) + "(ctx, type, network, process_name, process_path, host, src_ip, src_port, dst_ip, dst_port):"
|
||||||
|
if strings.Contains(v, "match_provider") {
|
||||||
|
content += `
|
||||||
|
metadata = {
|
||||||
|
"type": type,
|
||||||
|
"network": network,
|
||||||
|
"process_name": process_name,
|
||||||
|
"process_path": process_path,
|
||||||
|
"host": host,
|
||||||
|
"src_ip": src_ip,
|
||||||
|
"src_port": src_port,
|
||||||
|
"dst_ip": dst_ip,
|
||||||
|
"dst_port": dst_port
|
||||||
|
}
|
||||||
|
crp = ClashRuleProvider(ctx, metadata)
|
||||||
|
match_provider = crp.match_provider`
|
||||||
|
}
|
||||||
|
content += `
|
||||||
|
now = time.now()
|
||||||
|
return ` + v + "\n\n"
|
||||||
|
}
|
||||||
|
|
||||||
|
err := os.WriteFile(C.Path.Script(), []byte(content), 0o644)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("initialized script module failure, %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = S.Py_Initialize(C.Path.GetExecutableFullPath(), C.Path.ScriptDir()); err != nil {
|
||||||
|
return nil, fmt.Errorf("initialized script module failure, %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = S.LoadMainFunction(); err != nil {
|
||||||
|
return nil, fmt.Errorf("initialized script module failure, %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
rpdArr := findRuleProvidersName(content)
|
||||||
|
for _, v := range rpdArr {
|
||||||
|
if !strings.HasPrefix(v, "geosite:") {
|
||||||
|
return nil, fmt.Errorf("initialized script module failure, rule provider name must be start with \"geosite:\"")
|
||||||
|
}
|
||||||
|
v = strings.ToLower(v)
|
||||||
|
rule := fmt.Sprintf("GEOSITE,%s,%s", strings.TrimPrefix(v, "geosite:"), C.ScriptRuleGeoSiteTarget)
|
||||||
|
rawRules = append(rawRules, rule)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Infoln("Start initial script module successful, version: %s", S.Py_GetVersion())
|
||||||
|
|
||||||
|
return rawRules, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func cleanPyKeywords(code string) string {
|
||||||
|
keywords := []string{"import", "print"}
|
||||||
|
|
||||||
|
for _, kw := range keywords {
|
||||||
|
reg := regexp.MustCompile("(?m)[\r\n]+^.*" + kw + ".*$")
|
||||||
|
code = reg.ReplaceAllString(code, "")
|
||||||
|
}
|
||||||
|
return code
|
||||||
|
}
|
||||||
|
|
||||||
|
func findRuleProvidersName(s string) []string {
|
||||||
|
ruleProviderRegx := regexp.MustCompile("ctx.rule_providers\\[[\"'](\\S+)[\"']\\]\\.match|match_provider\\([\"'](\\S+)[\"']\\)")
|
||||||
|
arr := ruleProviderRegx.FindAllStringSubmatch(s, -1)
|
||||||
|
|
||||||
|
var rpd []string
|
||||||
|
for _, rpdArr := range arr {
|
||||||
|
for i, v := range rpdArr {
|
||||||
|
if i == 0 || v == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
rpd = append(rpd, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return rpd
|
||||||
|
}
|
||||||
|
|
||||||
func parseMitm(rawMitm RawMitm) (*Mitm, error) {
|
func parseMitm(rawMitm RawMitm) (*Mitm, error) {
|
||||||
var (
|
var (
|
||||||
req []C.Rewrite
|
req []C.Rewrite
|
||||||
|
@ -22,6 +22,7 @@ var Path = func() *path {
|
|||||||
type path struct {
|
type path struct {
|
||||||
homeDir string
|
homeDir string
|
||||||
configFile string
|
configFile string
|
||||||
|
scriptDir string
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetHomeDir is used to set the configuration path
|
// SetHomeDir is used to set the configuration path
|
||||||
@ -71,6 +72,32 @@ func (p *path) GeoSite() string {
|
|||||||
return P.Join(p.homeDir, "geosite.dat")
|
return P.Join(p.homeDir, "geosite.dat")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *path) ScriptDir() string {
|
||||||
|
if len(p.scriptDir) != 0 {
|
||||||
|
return p.scriptDir
|
||||||
|
}
|
||||||
|
if dir, err := os.MkdirTemp("", Name+"-"); err == nil {
|
||||||
|
p.scriptDir = dir
|
||||||
|
} else {
|
||||||
|
p.scriptDir = P.Join(os.TempDir(), Name)
|
||||||
|
_ = os.MkdirAll(p.scriptDir, 0o644)
|
||||||
|
}
|
||||||
|
return p.scriptDir
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *path) Script() string {
|
||||||
|
return P.Join(p.ScriptDir(), "clash_script.py")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *path) GetExecutableFullPath() string {
|
||||||
|
exePath, err := os.Executable()
|
||||||
|
if err != nil {
|
||||||
|
return "clash"
|
||||||
|
}
|
||||||
|
res, _ := filepath.EvalSymlinks(exePath)
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
func (p *path) RootCA() string {
|
func (p *path) RootCA() string {
|
||||||
return p.Resolve("mitm_ca.crt")
|
return p.Resolve("mitm_ca.crt")
|
||||||
}
|
}
|
||||||
|
@ -13,6 +13,7 @@ const (
|
|||||||
DstPort
|
DstPort
|
||||||
Process
|
Process
|
||||||
ProcessPath
|
ProcessPath
|
||||||
|
Script
|
||||||
UserAgent
|
UserAgent
|
||||||
MATCH
|
MATCH
|
||||||
)
|
)
|
||||||
@ -43,6 +44,8 @@ func (rt RuleType) String() string {
|
|||||||
return "Process"
|
return "Process"
|
||||||
case ProcessPath:
|
case ProcessPath:
|
||||||
return "ProcessPath"
|
return "ProcessPath"
|
||||||
|
case Script:
|
||||||
|
return "Script"
|
||||||
case UserAgent:
|
case UserAgent:
|
||||||
return "UserAgent"
|
return "UserAgent"
|
||||||
case MATCH:
|
case MATCH:
|
||||||
|
@ -7,6 +7,8 @@ import (
|
|||||||
"github.com/Dreamacro/clash/component/geodata/router"
|
"github.com/Dreamacro/clash/component/geodata/router"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const ScriptRuleGeoSiteTarget = "__WhateverTarget__"
|
||||||
|
|
||||||
type RuleExtra struct {
|
type RuleExtra struct {
|
||||||
Network NetWork
|
Network NetWork
|
||||||
SourceIPs []*netip.Prefix
|
SourceIPs []*netip.Prefix
|
||||||
|
@ -36,7 +36,7 @@ func (c *client) ExchangeContext(ctx context.Context, m *D.Msg) (*D.Msg, error)
|
|||||||
if c.r == nil {
|
if c.r == nil {
|
||||||
return nil, fmt.Errorf("dns %s not a valid ip", c.host)
|
return nil, fmt.Errorf("dns %s not a valid ip", c.host)
|
||||||
} else {
|
} else {
|
||||||
if ip, err = resolver.ResolveIPWithResolver(c.host, c.r, true); err != nil {
|
if ip, err = resolver.ResolveIPWithResolver(c.host, c.r); err != nil {
|
||||||
return nil, fmt.Errorf("use default dns resolve failed: %w", err)
|
return nil, fmt.Errorf("use default dns resolve failed: %w", err)
|
||||||
}
|
}
|
||||||
c.host = ip.String()
|
c.host = ip.String()
|
||||||
|
@ -94,7 +94,7 @@ func newDoHClient(url string, r *Resolver, proxyAdapter string) *dohClient {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
ip, err := resolver.ResolveIPWithResolver(host, r, true)
|
ip, err := resolver.ResolveIPWithResolver(host, r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -21,8 +21,6 @@ import (
|
|||||||
"golang.org/x/sync/singleflight"
|
"golang.org/x/sync/singleflight"
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ resolver.Resolver = (*Resolver)(nil)
|
|
||||||
|
|
||||||
type dnsClient interface {
|
type dnsClient interface {
|
||||||
Exchange(m *D.Msg) (msg *D.Msg, err error)
|
Exchange(m *D.Msg) (msg *D.Msg, err error)
|
||||||
ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg, err error)
|
ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg, err error)
|
||||||
@ -47,18 +45,18 @@ type Resolver struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ResolveIP request with TypeA and TypeAAAA, priority return TypeA
|
// ResolveIP request with TypeA and TypeAAAA, priority return TypeA
|
||||||
func (r *Resolver) ResolveIP(host string, random bool) (ip netip.Addr, err error) {
|
func (r *Resolver) ResolveIP(host string) (ip netip.Addr, err error) {
|
||||||
ch := make(chan netip.Addr, 1)
|
ch := make(chan netip.Addr, 1)
|
||||||
go func() {
|
go func() {
|
||||||
defer close(ch)
|
defer close(ch)
|
||||||
ip, err := r.resolveIP(host, D.TypeAAAA, random)
|
ip, err := r.resolveIP(host, D.TypeAAAA)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
ch <- ip
|
ch <- ip
|
||||||
}()
|
}()
|
||||||
|
|
||||||
ip, err = r.resolveIP(host, D.TypeA, random)
|
ip, err = r.resolveIP(host, D.TypeA)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -72,13 +70,13 @@ func (r *Resolver) ResolveIP(host string, random bool) (ip netip.Addr, err error
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ResolveIPv4 request with TypeA
|
// ResolveIPv4 request with TypeA
|
||||||
func (r *Resolver) ResolveIPv4(host string, random bool) (ip netip.Addr, err error) {
|
func (r *Resolver) ResolveIPv4(host string) (ip netip.Addr, err error) {
|
||||||
return r.resolveIP(host, D.TypeA, random)
|
return r.resolveIP(host, D.TypeA)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ResolveIPv6 request with TypeAAAA
|
// ResolveIPv6 request with TypeAAAA
|
||||||
func (r *Resolver) ResolveIPv6(host string, random bool) (ip netip.Addr, err error) {
|
func (r *Resolver) ResolveIPv6(host string) (ip netip.Addr, err error) {
|
||||||
return r.resolveIP(host, D.TypeAAAA, random)
|
return r.resolveIP(host, D.TypeAAAA)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Resolver) shouldIPFallback(ip netip.Addr) bool {
|
func (r *Resolver) shouldIPFallback(ip netip.Addr) bool {
|
||||||
@ -257,10 +255,9 @@ func (r *Resolver) ipExchange(ctx context.Context, m *D.Msg) (msg *D.Msg, err er
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Resolver) resolveIP(host string, dnsType uint16, random bool) (ip netip.Addr, err error) {
|
func (r *Resolver) resolveIP(host string, dnsType uint16) (ip netip.Addr, err error) {
|
||||||
ip, err = netip.ParseAddr(host)
|
ip, err = netip.ParseAddr(host)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
ip = ip.Unmap()
|
|
||||||
isIPv4 := ip.Is4()
|
isIPv4 := ip.Is4()
|
||||||
if dnsType == D.TypeAAAA && !isIPv4 {
|
if dnsType == D.TypeAAAA && !isIPv4 {
|
||||||
return ip, nil
|
return ip, nil
|
||||||
@ -285,12 +282,7 @@ func (r *Resolver) resolveIP(host string, dnsType uint16, random bool) (ip netip
|
|||||||
return netip.Addr{}, resolver.ErrIPNotFound
|
return netip.Addr{}, resolver.ErrIPNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
index := 0
|
ip = ips[rand.Intn(ipLength)]
|
||||||
if random {
|
|
||||||
index = rand.Intn(ipLength)
|
|
||||||
}
|
|
||||||
|
|
||||||
ip = ips[index]
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
16
go.mod
16
go.mod
@ -9,26 +9,26 @@ require (
|
|||||||
github.com/gofrs/uuid v4.2.0+incompatible
|
github.com/gofrs/uuid v4.2.0+incompatible
|
||||||
github.com/gorilla/websocket v1.5.0
|
github.com/gorilla/websocket v1.5.0
|
||||||
github.com/insomniacslk/dhcp v0.0.0-20220504074936-1ca156eafb9f
|
github.com/insomniacslk/dhcp v0.0.0-20220504074936-1ca156eafb9f
|
||||||
github.com/miekg/dns v1.1.50
|
github.com/miekg/dns v1.1.49
|
||||||
github.com/oschwald/geoip2-golang v1.7.0
|
github.com/oschwald/geoip2-golang v1.7.0
|
||||||
github.com/sirupsen/logrus v1.8.1
|
github.com/sirupsen/logrus v1.8.1
|
||||||
github.com/stretchr/testify v1.7.2
|
github.com/stretchr/testify v1.7.1
|
||||||
github.com/xtls/go v0.0.0-20210920065950-d4af136d3672
|
github.com/xtls/go v0.0.0-20210920065950-d4af136d3672
|
||||||
go.etcd.io/bbolt v1.3.6
|
go.etcd.io/bbolt v1.3.6
|
||||||
go.uber.org/atomic v1.9.0
|
go.uber.org/atomic v1.9.0
|
||||||
go.uber.org/automaxprocs v1.5.1
|
go.uber.org/automaxprocs v1.5.1
|
||||||
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d
|
golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e
|
||||||
golang.org/x/exp v0.0.0-20220613132600-b0d781184e0d
|
golang.org/x/exp v0.0.0-20220602145555-4a0574d9293f
|
||||||
golang.org/x/net v0.0.0-20220622184535-263ec571b305
|
golang.org/x/net v0.0.0-20220531201128-c960675eff93
|
||||||
golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f
|
golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f
|
||||||
golang.org/x/sys v0.0.0-20220622161953-175b2fd9d664
|
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a
|
||||||
golang.org/x/text v0.3.8-0.20220124021120-d1c84af989ab
|
golang.org/x/text v0.3.8-0.20220124021120-d1c84af989ab
|
||||||
golang.org/x/time v0.0.0-20220609170525-579cf78fd858
|
golang.org/x/time v0.0.0-20220411224347-583f2d630306
|
||||||
golang.zx2c4.com/wireguard v0.0.0-20220601130007-6a08d81f6bc4
|
golang.zx2c4.com/wireguard v0.0.0-20220601130007-6a08d81f6bc4
|
||||||
golang.zx2c4.com/wireguard/windows v0.5.4-0.20220328111914-004c22c5647e
|
golang.zx2c4.com/wireguard/windows v0.5.4-0.20220328111914-004c22c5647e
|
||||||
google.golang.org/protobuf v1.28.0
|
google.golang.org/protobuf v1.28.0
|
||||||
gopkg.in/yaml.v3 v3.0.1
|
gopkg.in/yaml.v3 v3.0.1
|
||||||
gvisor.dev/gvisor v0.0.0-20220616232550-d004a30ec069
|
gvisor.dev/gvisor v0.0.0-20220601233344-46e478629075
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
34
go.sum
34
go.sum
@ -20,7 +20,7 @@ github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw
|
|||||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
|
github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ=
|
||||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||||
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
|
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
|
||||||
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||||
@ -45,8 +45,8 @@ github.com/mdlayher/netlink v1.1.0/go.mod h1:H4WCitaheIsdF9yOYu8CFmCgQthAPIWZmcK
|
|||||||
github.com/mdlayher/netlink v1.1.1/go.mod h1:WTYpFb/WTvlRJAyKhZL5/uy69TDDpHHu2VZmb2XgV7o=
|
github.com/mdlayher/netlink v1.1.1/go.mod h1:WTYpFb/WTvlRJAyKhZL5/uy69TDDpHHu2VZmb2XgV7o=
|
||||||
github.com/mdlayher/raw v0.0.0-20190606142536-fef19f00fc18/go.mod h1:7EpbotpCmVZcu+KCX4g9WaRNuu11uyhiW7+Le1dKawg=
|
github.com/mdlayher/raw v0.0.0-20190606142536-fef19f00fc18/go.mod h1:7EpbotpCmVZcu+KCX4g9WaRNuu11uyhiW7+Le1dKawg=
|
||||||
github.com/mdlayher/raw v0.0.0-20191009151244-50f2db8cc065/go.mod h1:7EpbotpCmVZcu+KCX4g9WaRNuu11uyhiW7+Le1dKawg=
|
github.com/mdlayher/raw v0.0.0-20191009151244-50f2db8cc065/go.mod h1:7EpbotpCmVZcu+KCX4g9WaRNuu11uyhiW7+Le1dKawg=
|
||||||
github.com/miekg/dns v1.1.50 h1:DQUfb9uc6smULcREF09Uc+/Gd46YWqJd5DbpPE9xkcA=
|
github.com/miekg/dns v1.1.49 h1:qe0mQU3Z/XpFeE+AEBo2rqaS1IPBJ3anmqZ4XiZJVG8=
|
||||||
github.com/miekg/dns v1.1.50/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME=
|
github.com/miekg/dns v1.1.49/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME=
|
||||||
github.com/oschwald/geoip2-golang v1.7.0 h1:JW1r5AKi+vv2ujSxjKthySK3jo8w8oKWPyXsw+Qs/S8=
|
github.com/oschwald/geoip2-golang v1.7.0 h1:JW1r5AKi+vv2ujSxjKthySK3jo8w8oKWPyXsw+Qs/S8=
|
||||||
github.com/oschwald/geoip2-golang v1.7.0/go.mod h1:mdI/C7iK7NVMcIDDtf4bCKMJ7r0o7UwGeCo9eiitCMQ=
|
github.com/oschwald/geoip2-golang v1.7.0/go.mod h1:mdI/C7iK7NVMcIDDtf4bCKMJ7r0o7UwGeCo9eiitCMQ=
|
||||||
github.com/oschwald/maxminddb-golang v1.9.0 h1:tIk4nv6VT9OiPyrnDAfJS1s1xKDQMZOsGojab6EjC1Y=
|
github.com/oschwald/maxminddb-golang v1.9.0 h1:tIk4nv6VT9OiPyrnDAfJS1s1xKDQMZOsGojab6EjC1Y=
|
||||||
@ -62,8 +62,8 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
|
|||||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.7.2 h1:4jaiDzPyXQvSd7D0EjG45355tLlV3VOECpq10pLC+8s=
|
github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY=
|
||||||
github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals=
|
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/u-root/uio v0.0.0-20210528114334-82958018845c h1:BFvcl34IGnw8yvJi8hlqLFo9EshRInwWBs2M5fGWzQA=
|
github.com/u-root/uio v0.0.0-20210528114334-82958018845c h1:BFvcl34IGnw8yvJi8hlqLFo9EshRInwWBs2M5fGWzQA=
|
||||||
github.com/u-root/uio v0.0.0-20210528114334-82958018845c/go.mod h1:LpEX5FO/cB+WF4TYGY1V5qktpaZLkKkSegbr0V4eYXA=
|
github.com/u-root/uio v0.0.0-20210528114334-82958018845c/go.mod h1:LpEX5FO/cB+WF4TYGY1V5qktpaZLkKkSegbr0V4eYXA=
|
||||||
github.com/xtls/go v0.0.0-20210920065950-d4af136d3672 h1:4mkzGhKqt3JO1BWYjtD3iRFyAx4ow67hmSqOcGjuxqQ=
|
github.com/xtls/go v0.0.0-20210920065950-d4af136d3672 h1:4mkzGhKqt3JO1BWYjtD3iRFyAx4ow67hmSqOcGjuxqQ=
|
||||||
@ -78,10 +78,10 @@ go.uber.org/automaxprocs v1.5.1/go.mod h1:BF4eumQw0P9GtnuxxovUd06vwm1o18oMzFtK66
|
|||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d h1:sK3txAijHtOK88l68nt020reeT1ZdKLIYetKl95FzVY=
|
golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e h1:T8NU3HyQ8ClP4SEE+KbFlg6n0NhuTsN4MyznaarGsZM=
|
||||||
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||||
golang.org/x/exp v0.0.0-20220613132600-b0d781184e0d h1:vtUKgx8dahOomfFzLREU8nSv25YHnTgLBn4rDnWZdU0=
|
golang.org/x/exp v0.0.0-20220602145555-4a0574d9293f h1:KK6mxegmt5hGJRcAnEDjSNLxIRhZxDcgwMbcO/lMCRM=
|
||||||
golang.org/x/exp v0.0.0-20220613132600-b0d781184e0d/go.mod h1:Kr81I6Kryrl9sr8s2FK3vxD90NdsKWRuOIl2O4CvYbA=
|
golang.org/x/exp v0.0.0-20220602145555-4a0574d9293f/go.mod h1:yh0Ynu2b5ZUe3MQfp2nM0ecK7wsgouWTDN0FNeJuIys=
|
||||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 h1:kQgndtyPBW/JIYERgdxfwMYh3AVStj88WQTlNDi2a+o=
|
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 h1:kQgndtyPBW/JIYERgdxfwMYh3AVStj88WQTlNDi2a+o=
|
||||||
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY=
|
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY=
|
||||||
@ -97,8 +97,8 @@ golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwY
|
|||||||
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||||
golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
golang.org/x/net v0.0.0-20220622184535-263ec571b305 h1:dAgbJ2SP4jD6XYfMNLVj0BF21jo2PjChrtGaAvF5M3I=
|
golang.org/x/net v0.0.0-20220531201128-c960675eff93 h1:MYimHLfoXEpOhqd/zgoA/uoXzHB86AEky4LAx5ij9xA=
|
||||||
golang.org/x/net v0.0.0-20220622184535-263ec571b305/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
golang.org/x/net v0.0.0-20220531201128-c960675eff93/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f h1:Ax0t5p6N38Ga0dThY21weqDEyz2oklo4IvDkpigvkD8=
|
golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f h1:Ax0t5p6N38Ga0dThY21weqDEyz2oklo4IvDkpigvkD8=
|
||||||
@ -123,16 +123,16 @@ golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||||||
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20210525143221-35b2ab0089ea/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210525143221-35b2ab0089ea/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220622161953-175b2fd9d664 h1:wEZYwx+kK+KlZ0hpvP2Ls1Xr4+RWnlzGFwPP0aiDjIU=
|
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a h1:dGzPydgVsqGcTRVwiLJ1jVbufYwmzD3LfVPLKsKg+0k=
|
||||||
golang.org/x/sys v0.0.0-20220622161953-175b2fd9d664/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.8-0.20220124021120-d1c84af989ab h1:eHo2TTVBaAPw9lDGK2Gb9GyPMXT6g7O63W6sx3ylbzU=
|
golang.org/x/text v0.3.8-0.20220124021120-d1c84af989ab h1:eHo2TTVBaAPw9lDGK2Gb9GyPMXT6g7O63W6sx3ylbzU=
|
||||||
golang.org/x/text v0.3.8-0.20220124021120-d1c84af989ab/go.mod h1:EFNZuWvGYxIRUEX+K8UmCFwYmZjqcrnq15ZuVldZkZ0=
|
golang.org/x/text v0.3.8-0.20220124021120-d1c84af989ab/go.mod h1:EFNZuWvGYxIRUEX+K8UmCFwYmZjqcrnq15ZuVldZkZ0=
|
||||||
golang.org/x/time v0.0.0-20220609170525-579cf78fd858 h1:Dpdu/EMxGMFgq0CeYMh4fazTD2vtlZRYE7wyynxJb9U=
|
golang.org/x/time v0.0.0-20220411224347-583f2d630306 h1:+gHMid33q6pen7kv9xvT+JRinntgeXO2AeZVd0AWD3w=
|
||||||
golang.org/x/time v0.0.0-20220609170525-579cf78fd858/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20220411224347-583f2d630306/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
@ -158,5 +158,5 @@ gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33
|
|||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gvisor.dev/gvisor v0.0.0-20220616232550-d004a30ec069 h1:Ly12hwbYd06NuYM2/nssGPgQ9ZVw7xaNs2lc087VW1w=
|
gvisor.dev/gvisor v0.0.0-20220601233344-46e478629075 h1:ucwwit0X39HmMQ1iSeNCIXw4g/B8bi+O6TSxxDbPs9E=
|
||||||
gvisor.dev/gvisor v0.0.0-20220616232550-d004a30ec069/go.mod h1:TIvkJD0sxe8pIob3p6T8IzxXunlp6yfgktvTNp+DGNM=
|
gvisor.dev/gvisor v0.0.0-20220601233344-46e478629075/go.mod h1:TIvkJD0sxe8pIob3p6T8IzxXunlp6yfgktvTNp+DGNM=
|
||||||
|
@ -15,6 +15,7 @@ import (
|
|||||||
"github.com/Dreamacro/clash/component/profile"
|
"github.com/Dreamacro/clash/component/profile"
|
||||||
"github.com/Dreamacro/clash/component/profile/cachefile"
|
"github.com/Dreamacro/clash/component/profile/cachefile"
|
||||||
"github.com/Dreamacro/clash/component/resolver"
|
"github.com/Dreamacro/clash/component/resolver"
|
||||||
|
S "github.com/Dreamacro/clash/component/script"
|
||||||
"github.com/Dreamacro/clash/component/trie"
|
"github.com/Dreamacro/clash/component/trie"
|
||||||
"github.com/Dreamacro/clash/config"
|
"github.com/Dreamacro/clash/config"
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
@ -79,6 +80,7 @@ func ApplyConfig(cfg *config.Config, force bool) {
|
|||||||
updateUsers(cfg.Users)
|
updateUsers(cfg.Users)
|
||||||
updateProxies(cfg.Proxies, cfg.Providers)
|
updateProxies(cfg.Proxies, cfg.Providers)
|
||||||
updateRules(cfg.Rules)
|
updateRules(cfg.Rules)
|
||||||
|
updateRuleProviders(cfg.RuleProviders)
|
||||||
updateHosts(cfg.Hosts)
|
updateHosts(cfg.Hosts)
|
||||||
updateMitm(cfg.Mitm)
|
updateMitm(cfg.Mitm)
|
||||||
updateProfile(cfg)
|
updateProfile(cfg)
|
||||||
@ -141,11 +143,6 @@ func updateDNS(c *config.DNS, t config.Tun) {
|
|||||||
ProxyServer: c.ProxyServerNameserver,
|
ProxyServer: c.ProxyServerNameserver,
|
||||||
}
|
}
|
||||||
|
|
||||||
// deprecated warnning
|
|
||||||
if cfg.EnhancedMode == C.DNSMapping {
|
|
||||||
log.Warnln("[DNS] %s is deprecated, please use %s instead", cfg.EnhancedMode.String(), C.DNSFakeIP.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
r := dns.NewResolver(cfg)
|
r := dns.NewResolver(cfg)
|
||||||
pr := dns.NewProxyServerHostResolver(r)
|
pr := dns.NewProxyServerHostResolver(r)
|
||||||
m := dns.NewEnhancer(cfg)
|
m := dns.NewEnhancer(cfg)
|
||||||
@ -195,6 +192,10 @@ func updateRules(rules []C.Rule) {
|
|||||||
tunnel.UpdateRules(rules)
|
tunnel.UpdateRules(rules)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func updateRuleProviders(providers map[string]C.Rule) {
|
||||||
|
S.UpdateRuleProviders(providers)
|
||||||
|
}
|
||||||
|
|
||||||
func updateGeneral(general *config.General, force bool) {
|
func updateGeneral(general *config.General, force bool) {
|
||||||
tunnel.SetMode(general.Mode)
|
tunnel.SetMode(general.Mode)
|
||||||
resolver.DisableIPv6 = !general.IPv6
|
resolver.DisableIPv6 = !general.IPv6
|
||||||
@ -343,6 +344,7 @@ func updateMitm(mitm *config.Mitm) {
|
|||||||
|
|
||||||
func Shutdown() {
|
func Shutdown() {
|
||||||
P.Cleanup()
|
P.Cleanup()
|
||||||
|
S.Py_Finalize()
|
||||||
tproxy.CleanupTProxyIPTables()
|
tproxy.CleanupTProxyIPTables()
|
||||||
resolver.StoreFakePoolState()
|
resolver.StoreFakePoolState()
|
||||||
|
|
||||||
|
@ -132,7 +132,7 @@ func authentication(next http.Handler) http.Handler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func hello(w http.ResponseWriter, r *http.Request) {
|
func hello(w http.ResponseWriter, r *http.Request) {
|
||||||
render.JSON(w, r, render.M{"hello": "clash with tun"})
|
render.JSON(w, r, render.M{"hello": "clash plus pro"})
|
||||||
}
|
}
|
||||||
|
|
||||||
func traffic(w http.ResponseWriter, r *http.Request) {
|
func traffic(w http.ResponseWriter, r *http.Request) {
|
||||||
|
@ -2,13 +2,12 @@ package tproxy
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"net"
|
"net"
|
||||||
"net/netip"
|
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/common/pool"
|
"github.com/Dreamacro/clash/common/pool"
|
||||||
)
|
)
|
||||||
|
|
||||||
type packet struct {
|
type packet struct {
|
||||||
lAddr netip.AddrPort
|
lAddr *net.UDPAddr
|
||||||
buf []byte
|
buf []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -18,21 +17,21 @@ func (c *packet) Data() []byte {
|
|||||||
|
|
||||||
// WriteBack opens a new socket binding `addr` to write UDP packet back
|
// WriteBack opens a new socket binding `addr` to write UDP packet back
|
||||||
func (c *packet) WriteBack(b []byte, addr net.Addr) (n int, err error) {
|
func (c *packet) WriteBack(b []byte, addr net.Addr) (n int, err error) {
|
||||||
tc, err := dialUDP("udp", addr.(*net.UDPAddr).AddrPort(), c.lAddr)
|
tc, err := dialUDP("udp", addr.(*net.UDPAddr), c.lAddr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
n = 0
|
n = 0
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
n, err = tc.Write(b)
|
n, err = tc.Write(b)
|
||||||
_ = tc.Close()
|
tc.Close()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// LocalAddr returns the source IP/Port of UDP Packet
|
// LocalAddr returns the source IP/Port of UDP Packet
|
||||||
func (c *packet) LocalAddr() net.Addr {
|
func (c *packet) LocalAddr() net.Addr {
|
||||||
return net.UDPAddrFromAddrPort(c.lAddr)
|
return c.lAddr
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *packet) Drop() {
|
func (c *packet) Drop() {
|
||||||
_ = pool.Put(c.buf)
|
pool.Put(c.buf)
|
||||||
}
|
}
|
||||||
|
@ -32,7 +32,7 @@ func (l *Listener) Close() error {
|
|||||||
|
|
||||||
func (l *Listener) handleTProxy(conn net.Conn, in chan<- C.ConnContext) {
|
func (l *Listener) handleTProxy(conn net.Conn, in chan<- C.ConnContext) {
|
||||||
target := socks5.ParseAddrToSocksAddr(conn.LocalAddr())
|
target := socks5.ParseAddrToSocksAddr(conn.LocalAddr())
|
||||||
_ = conn.(*net.TCPConn).SetKeepAlive(true)
|
conn.(*net.TCPConn).SetKeepAlive(true)
|
||||||
in <- inbound.NewSocket(target, conn, C.TPROXY)
|
in <- inbound.NewSocket(target, conn, C.TPROXY)
|
||||||
}
|
}
|
||||||
|
|
@ -2,7 +2,6 @@ package tproxy
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"net"
|
"net"
|
||||||
"net/netip"
|
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/adapter/inbound"
|
"github.com/Dreamacro/clash/adapter/inbound"
|
||||||
"github.com/Dreamacro/clash/common/pool"
|
"github.com/Dreamacro/clash/common/pool"
|
||||||
@ -59,28 +58,28 @@ func NewUDP(addr string, in chan<- *inbound.PacketAdapter) (*UDPListener, error)
|
|||||||
oob := make([]byte, 1024)
|
oob := make([]byte, 1024)
|
||||||
for {
|
for {
|
||||||
buf := pool.Get(pool.UDPBufferSize)
|
buf := pool.Get(pool.UDPBufferSize)
|
||||||
n, oobn, _, lAddr, err := c.ReadMsgUDPAddrPort(buf, oob)
|
n, oobn, _, lAddr, err := c.ReadMsgUDP(buf, oob)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
_ = pool.Put(buf)
|
pool.Put(buf)
|
||||||
if rl.closed {
|
if rl.closed {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
rAddr, err := getOrigDst(oob[:oobn])
|
rAddr, err := getOrigDst(oob, oobn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
handlePacketConn(in, buf[:n], lAddr, rAddr)
|
handlePacketConn(l, in, buf[:n], lAddr, rAddr)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
return rl, nil
|
return rl, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func handlePacketConn(in chan<- *inbound.PacketAdapter, buf []byte, lAddr, rAddr netip.AddrPort) {
|
func handlePacketConn(pc net.PacketConn, in chan<- *inbound.PacketAdapter, buf []byte, lAddr *net.UDPAddr, rAddr *net.UDPAddr) {
|
||||||
target := socks5.AddrFromStdAddrPort(rAddr)
|
target := socks5.ParseAddrToSocksAddr(rAddr)
|
||||||
pkt := &packet{
|
pkt := &packet{
|
||||||
lAddr: lAddr,
|
lAddr: lAddr,
|
||||||
buf: buf,
|
buf: buf,
|
||||||
|
@ -3,14 +3,13 @@
|
|||||||
package tproxy
|
package tproxy
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"net/netip"
|
|
||||||
"os"
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
"golang.org/x/sys/unix"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -20,7 +19,7 @@ const (
|
|||||||
|
|
||||||
// dialUDP acts like net.DialUDP for transparent proxy.
|
// dialUDP acts like net.DialUDP for transparent proxy.
|
||||||
// It binds to a non-local address(`lAddr`).
|
// It binds to a non-local address(`lAddr`).
|
||||||
func dialUDP(network string, lAddr, rAddr netip.AddrPort) (uc *net.UDPConn, err error) {
|
func dialUDP(network string, lAddr *net.UDPAddr, rAddr *net.UDPAddr) (*net.UDPConn, error) {
|
||||||
rSockAddr, err := udpAddrToSockAddr(rAddr)
|
rSockAddr, err := udpAddrToSockAddr(rAddr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -36,25 +35,23 @@ func dialUDP(network string, lAddr, rAddr netip.AddrPort) (uc *net.UDPConn, err
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
defer func() {
|
|
||||||
if err != nil {
|
|
||||||
syscall.Close(fd)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
if err = syscall.SetsockoptInt(fd, syscall.SOL_SOCKET, syscall.SO_REUSEADDR, 1); err != nil {
|
if err = syscall.SetsockoptInt(fd, syscall.SOL_SOCKET, syscall.SO_REUSEADDR, 1); err != nil {
|
||||||
|
syscall.Close(fd)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = syscall.SetsockoptInt(fd, syscall.SOL_IP, syscall.IP_TRANSPARENT, 1); err != nil {
|
if err = syscall.SetsockoptInt(fd, syscall.SOL_IP, syscall.IP_TRANSPARENT, 1); err != nil {
|
||||||
|
syscall.Close(fd)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = syscall.Bind(fd, lSockAddr); err != nil {
|
if err = syscall.Bind(fd, lSockAddr); err != nil {
|
||||||
|
syscall.Close(fd)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = syscall.Connect(fd, rSockAddr); err != nil {
|
if err = syscall.Connect(fd, rSockAddr); err != nil {
|
||||||
|
syscall.Close(fd)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -63,26 +60,35 @@ func dialUDP(network string, lAddr, rAddr netip.AddrPort) (uc *net.UDPConn, err
|
|||||||
|
|
||||||
c, err := net.FileConn(fdFile)
|
c, err := net.FileConn(fdFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
syscall.Close(fd)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return c.(*net.UDPConn), nil
|
return c.(*net.UDPConn), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func udpAddrToSockAddr(addr netip.AddrPort) (syscall.Sockaddr, error) {
|
func udpAddrToSockAddr(addr *net.UDPAddr) (syscall.Sockaddr, error) {
|
||||||
if addr.Addr().Is4() {
|
switch {
|
||||||
return &syscall.SockaddrInet4{Addr: addr.Addr().As4(), Port: int(addr.Port())}, nil
|
case addr.IP.To4() != nil:
|
||||||
}
|
ip := [4]byte{}
|
||||||
|
copy(ip[:], addr.IP.To4())
|
||||||
|
|
||||||
zoneID, err := strconv.ParseUint(addr.Addr().Zone(), 10, 32)
|
return &syscall.SockaddrInet4{Addr: ip, Port: addr.Port}, nil
|
||||||
|
|
||||||
|
default:
|
||||||
|
ip := [16]byte{}
|
||||||
|
copy(ip[:], addr.IP.To16())
|
||||||
|
|
||||||
|
zoneID, err := strconv.ParseUint(addr.Zone, 10, 32)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
zoneID = 0
|
zoneID = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
return &syscall.SockaddrInet6{Addr: addr.Addr().As16(), Port: int(addr.Port()), ZoneId: uint32(zoneID)}, nil
|
return &syscall.SockaddrInet6{Addr: ip, Port: addr.Port, ZoneId: uint32(zoneID)}, nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func udpAddrFamily(net string, lAddr, rAddr netip.AddrPort) int {
|
func udpAddrFamily(net string, lAddr, rAddr *net.UDPAddr) int {
|
||||||
switch net[len(net)-1] {
|
switch net[len(net)-1] {
|
||||||
case '4':
|
case '4':
|
||||||
return syscall.AF_INET
|
return syscall.AF_INET
|
||||||
@ -90,35 +96,29 @@ func udpAddrFamily(net string, lAddr, rAddr netip.AddrPort) int {
|
|||||||
return syscall.AF_INET6
|
return syscall.AF_INET6
|
||||||
}
|
}
|
||||||
|
|
||||||
if lAddr.Addr().Is4() && rAddr.Addr().Is4() {
|
if (lAddr == nil || lAddr.IP.To4() != nil) && (rAddr == nil || lAddr.IP.To4() != nil) {
|
||||||
return syscall.AF_INET
|
return syscall.AF_INET
|
||||||
}
|
}
|
||||||
return syscall.AF_INET6
|
return syscall.AF_INET6
|
||||||
}
|
}
|
||||||
|
|
||||||
func getOrigDst(oob []byte) (netip.AddrPort, error) {
|
func getOrigDst(oob []byte, oobn int) (*net.UDPAddr, error) {
|
||||||
// oob contains socket control messages which we need to parse.
|
msgs, err := syscall.ParseSocketControlMessage(oob[:oobn])
|
||||||
scms, err := unix.ParseSocketControlMessage(oob)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return netip.AddrPort{}, fmt.Errorf("parse control message: %w", err)
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// retrieve the destination address from the SCM.
|
for _, msg := range msgs {
|
||||||
sa, err := unix.ParseOrigDstAddr(&scms[0])
|
if msg.Header.Level == syscall.SOL_IP && msg.Header.Type == syscall.IP_RECVORIGDSTADDR {
|
||||||
if err != nil {
|
ip := net.IP(msg.Data[4:8])
|
||||||
return netip.AddrPort{}, fmt.Errorf("retrieve destination: %w", err)
|
port := binary.BigEndian.Uint16(msg.Data[2:4])
|
||||||
|
return &net.UDPAddr{IP: ip, Port: int(port)}, nil
|
||||||
|
} else if msg.Header.Level == syscall.SOL_IPV6 && msg.Header.Type == IPV6_RECVORIGDSTADDR {
|
||||||
|
ip := net.IP(msg.Data[8:24])
|
||||||
|
port := binary.BigEndian.Uint16(msg.Data[2:4])
|
||||||
|
return &net.UDPAddr{IP: ip, Port: int(port)}, nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// encode the destination address into a cmsg.
|
return nil, errors.New("cannot find origDst")
|
||||||
var rAddr netip.AddrPort
|
|
||||||
switch v := sa.(type) {
|
|
||||||
case *unix.SockaddrInet4:
|
|
||||||
rAddr = netip.AddrPortFrom(netip.AddrFrom4(v.Addr), uint16(v.Port))
|
|
||||||
case *unix.SockaddrInet6:
|
|
||||||
rAddr = netip.AddrPortFrom(netip.AddrFrom16(v.Addr), uint16(v.Port))
|
|
||||||
default:
|
|
||||||
return netip.AddrPort{}, fmt.Errorf("unsupported address type: %T", v)
|
|
||||||
}
|
|
||||||
|
|
||||||
return rAddr, nil
|
|
||||||
}
|
}
|
||||||
|
@ -5,13 +5,12 @@ package tproxy
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"net"
|
"net"
|
||||||
"net/netip"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func getOrigDst(oob []byte) (netip.AddrPort, error) {
|
func getOrigDst(oob []byte, oobn int) (*net.UDPAddr, error) {
|
||||||
return netip.AddrPort{}, errors.New("UDP redir not supported on current platform")
|
return nil, errors.New("UDP redir not supported on current platform")
|
||||||
}
|
}
|
||||||
|
|
||||||
func dialUDP(network string, lAddr, rAddr netip.AddrPort) (*net.UDPConn, error) {
|
func dialUDP(network string, lAddr *net.UDPAddr, rAddr *net.UDPAddr) (*net.UDPConn, error) {
|
||||||
return nil, errors.New("UDP redir not supported on current platform")
|
return nil, errors.New("UDP redir not supported on current platform")
|
||||||
}
|
}
|
||||||
|
@ -8,10 +8,8 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/common/pool"
|
|
||||||
|
|
||||||
"gvisor.dev/gvisor/pkg/buffer"
|
|
||||||
"gvisor.dev/gvisor/pkg/tcpip"
|
"gvisor.dev/gvisor/pkg/tcpip"
|
||||||
|
"gvisor.dev/gvisor/pkg/tcpip/buffer"
|
||||||
"gvisor.dev/gvisor/pkg/tcpip/header"
|
"gvisor.dev/gvisor/pkg/tcpip/header"
|
||||||
"gvisor.dev/gvisor/pkg/tcpip/link/channel"
|
"gvisor.dev/gvisor/pkg/tcpip/link/channel"
|
||||||
"gvisor.dev/gvisor/pkg/tcpip/stack"
|
"gvisor.dev/gvisor/pkg/tcpip/stack"
|
||||||
@ -95,29 +93,23 @@ func (e *Endpoint) dispatchLoop(cancel context.CancelFunc) {
|
|||||||
|
|
||||||
mtu := int(e.mtu)
|
mtu := int(e.mtu)
|
||||||
for {
|
for {
|
||||||
data := pool.Get(mtu)
|
data := make([]byte, mtu)
|
||||||
|
|
||||||
n, err := e.rw.Read(data)
|
n, err := e.rw.Read(data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
_ = pool.Put(data)
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
if n == 0 || n > mtu {
|
if n == 0 || n > mtu {
|
||||||
_ = pool.Put(data)
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if !e.IsAttached() {
|
if !e.IsAttached() {
|
||||||
_ = pool.Put(data)
|
|
||||||
continue /* unattached, drop packet */
|
continue /* unattached, drop packet */
|
||||||
}
|
}
|
||||||
|
|
||||||
pkt := stack.NewPacketBuffer(stack.PacketBufferOptions{
|
pkt := stack.NewPacketBuffer(stack.PacketBufferOptions{
|
||||||
Payload: buffer.NewWithData(data[:n]),
|
Data: buffer.View(data[:n]).ToVectorisedView(),
|
||||||
OnRelease: func() {
|
|
||||||
_ = pool.Put(data)
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
|
|
||||||
switch header.IPVersion(data) {
|
switch header.IPVersion(data) {
|
||||||
@ -125,8 +117,6 @@ func (e *Endpoint) dispatchLoop(cancel context.CancelFunc) {
|
|||||||
e.InjectInbound(header.IPv4ProtocolNumber, pkt)
|
e.InjectInbound(header.IPv4ProtocolNumber, pkt)
|
||||||
case header.IPv6Version:
|
case header.IPv6Version:
|
||||||
e.InjectInbound(header.IPv6ProtocolNumber, pkt)
|
e.InjectInbound(header.IPv6ProtocolNumber, pkt)
|
||||||
default:
|
|
||||||
_ = pool.Put(data)
|
|
||||||
}
|
}
|
||||||
pkt.DecRef()
|
pkt.DecRef()
|
||||||
}
|
}
|
||||||
@ -148,8 +138,11 @@ func (e *Endpoint) outboundLoop(ctx context.Context) {
|
|||||||
func (e *Endpoint) writePacket(pkt *stack.PacketBuffer) tcpip.Error {
|
func (e *Endpoint) writePacket(pkt *stack.PacketBuffer) tcpip.Error {
|
||||||
defer pkt.DecRef()
|
defer pkt.DecRef()
|
||||||
|
|
||||||
buf := pkt.Buffer()
|
size := pkt.Size()
|
||||||
if _, err := e.rw.Write(buf.Flatten()); err != nil {
|
views := pkt.Views()
|
||||||
|
|
||||||
|
vView := buffer.NewVectorisedView(size, views)
|
||||||
|
if _, err := e.rw.Write(vView.ToView()); err != nil {
|
||||||
return &tcpip.ErrInvalidEndpointState{}
|
return &tcpip.ErrInvalidEndpointState{}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
@ -87,11 +87,7 @@ func (t *TUN) Write(packet []byte) (int, error) {
|
|||||||
|
|
||||||
packet = append(t.cache[:t.offset], packet...)
|
packet = append(t.cache[:t.offset], packet...)
|
||||||
|
|
||||||
n, err := t.nt.Write(packet, t.offset)
|
return t.nt.Write(packet, t.offset)
|
||||||
if n < t.offset {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
return n - t.offset, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *TUN) Close() error {
|
func (t *TUN) Close() error {
|
||||||
|
@ -2,15 +2,23 @@ package adapter
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"net"
|
"net"
|
||||||
|
|
||||||
|
"gvisor.dev/gvisor/pkg/tcpip/stack"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TCPConn implements the net.Conn interface.
|
// TCPConn implements the net.Conn interface.
|
||||||
type TCPConn interface {
|
type TCPConn interface {
|
||||||
net.Conn
|
net.Conn
|
||||||
|
|
||||||
|
// ID returns the transport endpoint id of TCPConn.
|
||||||
|
ID() *stack.TransportEndpointID
|
||||||
}
|
}
|
||||||
|
|
||||||
// UDPConn implements net.Conn and net.PacketConn.
|
// UDPConn implements net.Conn and net.PacketConn.
|
||||||
type UDPConn interface {
|
type UDPConn interface {
|
||||||
net.Conn
|
net.Conn
|
||||||
net.PacketConn
|
net.PacketConn
|
||||||
|
|
||||||
|
// ID returns the transport endpoint id of UDPConn.
|
||||||
|
ID() *stack.TransportEndpointID
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/adapter/inbound"
|
"github.com/Dreamacro/clash/adapter/inbound"
|
||||||
|
"github.com/Dreamacro/clash/common/nnip"
|
||||||
"github.com/Dreamacro/clash/common/pool"
|
"github.com/Dreamacro/clash/common/pool"
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
D "github.com/Dreamacro/clash/listener/tun/ipstack/commons"
|
D "github.com/Dreamacro/clash/listener/tun/ipstack/commons"
|
||||||
@ -26,7 +27,15 @@ type gvHandler struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (gh *gvHandler) HandleTCP(tunConn adapter.TCPConn) {
|
func (gh *gvHandler) HandleTCP(tunConn adapter.TCPConn) {
|
||||||
rAddrPort := tunConn.LocalAddr().(*net.TCPAddr).AddrPort()
|
id := tunConn.ID()
|
||||||
|
|
||||||
|
rAddr := &net.UDPAddr{
|
||||||
|
IP: net.IP(id.LocalAddress),
|
||||||
|
Port: int(id.LocalPort),
|
||||||
|
Zone: "",
|
||||||
|
}
|
||||||
|
|
||||||
|
rAddrPort := netip.AddrPortFrom(nnip.IpToAddr(rAddr.IP), id.LocalPort)
|
||||||
|
|
||||||
if D.ShouldHijackDns(gh.dnsHijack, rAddrPort, "tcp") {
|
if D.ShouldHijackDns(gh.dnsHijack, rAddrPort, "tcp") {
|
||||||
go func() {
|
go func() {
|
||||||
@ -34,8 +43,8 @@ func (gh *gvHandler) HandleTCP(tunConn adapter.TCPConn) {
|
|||||||
|
|
||||||
buf := pool.Get(pool.UDPBufferSize)
|
buf := pool.Get(pool.UDPBufferSize)
|
||||||
defer func() {
|
defer func() {
|
||||||
_ = tunConn.Close()
|
|
||||||
_ = pool.Put(buf)
|
_ = pool.Put(buf)
|
||||||
|
_ = tunConn.Close()
|
||||||
}()
|
}()
|
||||||
|
|
||||||
for {
|
for {
|
||||||
@ -69,18 +78,26 @@ func (gh *gvHandler) HandleTCP(tunConn adapter.TCPConn) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
gh.tcpIn <- inbound.NewSocket(socks5.AddrFromStdAddrPort(rAddrPort), tunConn, C.TUN)
|
gh.tcpIn <- inbound.NewSocket(socks5.ParseAddrToSocksAddr(rAddr), tunConn, C.TUN)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gh *gvHandler) HandleUDP(tunConn adapter.UDPConn) {
|
func (gh *gvHandler) HandleUDP(tunConn adapter.UDPConn) {
|
||||||
rAddrPort := tunConn.LocalAddr().(*net.UDPAddr).AddrPort()
|
id := tunConn.ID()
|
||||||
|
|
||||||
|
rAddr := &net.UDPAddr{
|
||||||
|
IP: net.IP(id.LocalAddress),
|
||||||
|
Port: int(id.LocalPort),
|
||||||
|
Zone: "",
|
||||||
|
}
|
||||||
|
|
||||||
|
rAddrPort := netip.AddrPortFrom(nnip.IpToAddr(rAddr.IP), id.LocalPort)
|
||||||
|
|
||||||
if rAddrPort.Addr() == gh.gateway {
|
if rAddrPort.Addr() == gh.gateway {
|
||||||
_ = tunConn.Close()
|
_ = tunConn.Close()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
target := socks5.AddrFromStdAddrPort(rAddrPort)
|
target := socks5.ParseAddrToSocksAddr(rAddr)
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
for {
|
for {
|
||||||
@ -92,20 +109,22 @@ func (gh *gvHandler) HandleUDP(tunConn adapter.UDPConn) {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
|
payload := buf[:n]
|
||||||
|
|
||||||
if D.ShouldHijackDns(gh.dnsHijack, rAddrPort, "udp") {
|
if D.ShouldHijackDns(gh.dnsHijack, rAddrPort, "udp") {
|
||||||
go func() {
|
go func() {
|
||||||
defer func() {
|
defer func() {
|
||||||
_ = pool.Put(buf)
|
_ = pool.Put(buf)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
msg, err1 := D.RelayDnsPacket(buf[:n])
|
msg, err1 := D.RelayDnsPacket(payload)
|
||||||
if err1 != nil {
|
if err1 != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
_, _ = tunConn.WriteTo(msg, addr)
|
_, _ = tunConn.WriteTo(msg, addr)
|
||||||
|
|
||||||
log.Debugln("[TUN] hijack dns udp: %s", rAddrPort.String())
|
log.Debugln("[TUN] hijack dns udp: %s", rAddr.String())
|
||||||
}()
|
}()
|
||||||
|
|
||||||
continue
|
continue
|
||||||
@ -114,14 +133,12 @@ func (gh *gvHandler) HandleUDP(tunConn adapter.UDPConn) {
|
|||||||
gvPacket := &packet{
|
gvPacket := &packet{
|
||||||
pc: tunConn,
|
pc: tunConn,
|
||||||
rAddr: addr,
|
rAddr: addr,
|
||||||
payload: buf,
|
payload: payload,
|
||||||
offset: n,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
select {
|
select {
|
||||||
case gh.udpIn <- inbound.NewPacket(target, gvPacket, C.TUN):
|
case gh.udpIn <- inbound.NewPacket(target, gvPacket, C.TUN):
|
||||||
default:
|
default:
|
||||||
gvPacket.Drop()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
@ -70,6 +70,7 @@ func withTCPHandler(handle adapter.TCPHandleFunc) option.Option {
|
|||||||
|
|
||||||
conn := &tcpConn{
|
conn := &tcpConn{
|
||||||
TCPConn: gonet.NewTCPConn(&wq, ep),
|
TCPConn: gonet.NewTCPConn(&wq, ep),
|
||||||
|
id: id,
|
||||||
}
|
}
|
||||||
handle(conn)
|
handle(conn)
|
||||||
})
|
})
|
||||||
@ -112,4 +113,9 @@ func setSocketOptions(s *stack.Stack, ep tcpip.Endpoint) tcpip.Error {
|
|||||||
|
|
||||||
type tcpConn struct {
|
type tcpConn struct {
|
||||||
*gonet.TCPConn
|
*gonet.TCPConn
|
||||||
|
id stack.TransportEndpointID
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *tcpConn) ID() *stack.TransportEndpointID {
|
||||||
|
return &c.id
|
||||||
}
|
}
|
||||||
|
@ -29,6 +29,7 @@ func withUDPHandler(handle adapter.UDPHandleFunc) option.Option {
|
|||||||
|
|
||||||
conn := &udpConn{
|
conn := &udpConn{
|
||||||
UDPConn: gonet.NewUDPConn(s, &wq, ep),
|
UDPConn: gonet.NewUDPConn(s, &wq, ep),
|
||||||
|
id: id,
|
||||||
}
|
}
|
||||||
handle(conn)
|
handle(conn)
|
||||||
})
|
})
|
||||||
@ -39,17 +40,21 @@ func withUDPHandler(handle adapter.UDPHandleFunc) option.Option {
|
|||||||
|
|
||||||
type udpConn struct {
|
type udpConn struct {
|
||||||
*gonet.UDPConn
|
*gonet.UDPConn
|
||||||
|
id stack.TransportEndpointID
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *udpConn) ID() *stack.TransportEndpointID {
|
||||||
|
return &c.id
|
||||||
}
|
}
|
||||||
|
|
||||||
type packet struct {
|
type packet struct {
|
||||||
pc adapter.UDPConn
|
pc adapter.UDPConn
|
||||||
rAddr net.Addr
|
rAddr net.Addr
|
||||||
payload []byte
|
payload []byte
|
||||||
offset int
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *packet) Data() []byte {
|
func (c *packet) Data() []byte {
|
||||||
return c.payload[:c.offset]
|
return c.payload
|
||||||
}
|
}
|
||||||
|
|
||||||
// WriteBack write UDP packet with source(ip, port) = `addr`
|
// WriteBack write UDP packet with source(ip, port) = `addr`
|
||||||
|
@ -5,8 +5,8 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
|
|
||||||
|
"github.com/Dreamacro/clash/common/pool"
|
||||||
"github.com/Dreamacro/clash/listener/tun/ipstack/system/mars/tcpip"
|
"github.com/Dreamacro/clash/listener/tun/ipstack/system/mars/tcpip"
|
||||||
"github.com/Dreamacro/clash/log"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func Start(device io.ReadWriter, gateway, portal, broadcast netip.Addr) (*TCP, *UDP, error) {
|
func Start(device io.ReadWriter, gateway, portal, broadcast netip.Addr) (*TCP, *UDP, error) {
|
||||||
@ -22,7 +22,7 @@ func Start(device io.ReadWriter, gateway, portal, broadcast netip.Addr) (*TCP, *
|
|||||||
tab := newTable()
|
tab := newTable()
|
||||||
udp := &UDP{
|
udp := &UDP{
|
||||||
device: device,
|
device: device,
|
||||||
buf: [0xffff]byte{},
|
buf: [pool.UDPBufferSize]byte{},
|
||||||
}
|
}
|
||||||
tcp := &TCP{
|
tcp := &TCP{
|
||||||
listener: listener,
|
listener: listener,
|
||||||
@ -38,7 +38,7 @@ func Start(device io.ReadWriter, gateway, portal, broadcast netip.Addr) (*TCP, *
|
|||||||
_ = udp.Close()
|
_ = udp.Close()
|
||||||
}()
|
}()
|
||||||
|
|
||||||
buf := make([]byte, 0xffff)
|
buf := make([]byte, pool.RelayBufferSize)
|
||||||
|
|
||||||
for {
|
for {
|
||||||
n, err := device.Read(buf)
|
n, err := device.Read(buf)
|
||||||
@ -133,11 +133,7 @@ func Start(device io.ReadWriter, gateway, portal, broadcast netip.Addr) (*TCP, *
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
port, err = tab.newConn(tup)
|
port = tab.newConn(tup)
|
||||||
if err != nil {
|
|
||||||
log.Warnln("[STACK] drop tcp packet by system stack: %v", err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ip.SetSourceIP(portal)
|
ip.SetSourceIP(portal)
|
||||||
@ -156,7 +152,7 @@ func Start(device io.ReadWriter, gateway, portal, broadcast netip.Addr) (*TCP, *
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
go udp.handleUDPPacket(ip, u)
|
udp.handleUDPPacket(ip, u)
|
||||||
case tcpip.ICMP:
|
case tcpip.ICMP:
|
||||||
i := tcpip.ICMPPacket(ip.Payload())
|
i := tcpip.ICMPPacket(ip.Payload())
|
||||||
|
|
||||||
|
@ -1,13 +1,9 @@
|
|||||||
package nat
|
package nat
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"sync"
|
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/common/generics/list"
|
"github.com/Dreamacro/clash/common/generics/list"
|
||||||
|
|
||||||
"golang.org/x/exp/maps"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -31,8 +27,6 @@ type table struct {
|
|||||||
tuples map[tuple]*list.Element[*binding]
|
tuples map[tuple]*list.Element[*binding]
|
||||||
ports [portLength]*list.Element[*binding]
|
ports [portLength]*list.Element[*binding]
|
||||||
available *list.List[*binding]
|
available *list.List[*binding]
|
||||||
mux sync.Mutex
|
|
||||||
count uint16
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *table) tupleOf(port uint16) tuple {
|
func (t *table) tupleOf(port uint16) tuple {
|
||||||
@ -49,64 +43,27 @@ func (t *table) tupleOf(port uint16) tuple {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (t *table) portOf(tuple tuple) uint16 {
|
func (t *table) portOf(tuple tuple) uint16 {
|
||||||
t.mux.Lock()
|
|
||||||
elm := t.tuples[tuple]
|
elm := t.tuples[tuple]
|
||||||
if elm == nil {
|
if elm == nil {
|
||||||
t.mux.Unlock()
|
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
t.mux.Unlock()
|
|
||||||
|
|
||||||
t.available.MoveToFront(elm)
|
t.available.MoveToFront(elm)
|
||||||
|
|
||||||
return portBegin + elm.Value.offset
|
return portBegin + elm.Value.offset
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *table) newConn(tuple tuple) (uint16, error) {
|
func (t *table) newConn(tuple tuple) uint16 {
|
||||||
t.mux.Lock()
|
elm := t.available.Back()
|
||||||
elm, err := t.availableConn()
|
b := elm.Value
|
||||||
if err != nil {
|
|
||||||
t.mux.Unlock()
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
elm.Value.tuple = tuple
|
delete(t.tuples, b.tuple)
|
||||||
t.tuples[tuple] = elm
|
t.tuples[tuple] = elm
|
||||||
t.mux.Unlock()
|
b.tuple = tuple
|
||||||
|
|
||||||
return portBegin + elm.Value.offset, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *table) availableConn() (*list.Element[*binding], error) {
|
|
||||||
var elm *list.Element[*binding]
|
|
||||||
|
|
||||||
for i := 0; i < portLength; i++ {
|
|
||||||
elm = t.available.Back()
|
|
||||||
t.available.MoveToFront(elm)
|
t.available.MoveToFront(elm)
|
||||||
|
|
||||||
offset := elm.Value.offset
|
return portBegin + b.offset
|
||||||
tup := t.ports[offset].Value.tuple
|
|
||||||
if t.tuples[tup] != nil && tup.SourceAddr.IsValid() {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if t.count == portLength { // resize
|
|
||||||
tuples := make(map[tuple]*list.Element[*binding], portLength)
|
|
||||||
maps.Copy(tuples, t.tuples)
|
|
||||||
t.tuples = tuples
|
|
||||||
t.count = 1
|
|
||||||
}
|
|
||||||
return elm, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, fmt.Errorf("too many open files, limits [%d, %d]", portLength, len(t.tuples))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *table) closeConn(tuple tuple) {
|
|
||||||
t.mux.Lock()
|
|
||||||
delete(t.tuples, tuple)
|
|
||||||
t.count++
|
|
||||||
t.mux.Unlock()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func newTable() *table {
|
func newTable() *table {
|
||||||
@ -114,7 +71,6 @@ func newTable() *table {
|
|||||||
tuples: make(map[tuple]*list.Element[*binding], portLength),
|
tuples: make(map[tuple]*list.Element[*binding], portLength),
|
||||||
ports: [portLength]*list.Element[*binding]{},
|
ports: [portLength]*list.Element[*binding]{},
|
||||||
available: list.New[*binding](),
|
available: list.New[*binding](),
|
||||||
count: 1,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for idx := range result.ports {
|
for idx := range result.ports {
|
||||||
|
@ -16,8 +16,6 @@ type conn struct {
|
|||||||
net.Conn
|
net.Conn
|
||||||
|
|
||||||
tuple tuple
|
tuple tuple
|
||||||
|
|
||||||
close func()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *TCP) Accept() (net.Conn, error) {
|
func (t *TCP) Accept() (net.Conn, error) {
|
||||||
@ -26,9 +24,9 @@ func (t *TCP) Accept() (net.Conn, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
addr := c.RemoteAddr().(*net.TCPAddr).AddrPort()
|
addr := c.RemoteAddr().(*net.TCPAddr)
|
||||||
tup := t.table.tupleOf(addr.Port())
|
tup := t.table.tupleOf(uint16(addr.Port))
|
||||||
if addr.Addr() != t.portal || tup == zeroTuple {
|
if !addr.IP.Equal(t.portal.AsSlice()) || tup == zeroTuple {
|
||||||
_ = c.Close()
|
_ = c.Close()
|
||||||
|
|
||||||
return nil, net.InvalidAddrError("unknown remote addr")
|
return nil, net.InvalidAddrError("unknown remote addr")
|
||||||
@ -36,14 +34,9 @@ func (t *TCP) Accept() (net.Conn, error) {
|
|||||||
|
|
||||||
addition(c)
|
addition(c)
|
||||||
|
|
||||||
_ = c.SetLinger(0)
|
|
||||||
|
|
||||||
return &conn{
|
return &conn{
|
||||||
Conn: c,
|
Conn: c,
|
||||||
tuple: tup,
|
tuple: tup,
|
||||||
close: func() {
|
|
||||||
t.table.closeConn(tup)
|
|
||||||
},
|
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -59,15 +52,16 @@ func (t *TCP) SetDeadline(time time.Time) error {
|
|||||||
return t.listener.SetDeadline(time)
|
return t.listener.SetDeadline(time)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *conn) Close() error {
|
|
||||||
c.close()
|
|
||||||
return c.Conn.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *conn) LocalAddr() net.Addr {
|
func (c *conn) LocalAddr() net.Addr {
|
||||||
return net.TCPAddrFromAddrPort(c.tuple.SourceAddr)
|
return &net.TCPAddr{
|
||||||
|
IP: c.tuple.SourceAddr.Addr().AsSlice(),
|
||||||
|
Port: int(c.tuple.SourceAddr.Port()),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *conn) RemoteAddr() net.Addr {
|
func (c *conn) RemoteAddr() net.Addr {
|
||||||
return net.TCPAddrFromAddrPort(c.tuple.DestinationAddr)
|
return &net.TCPAddr{
|
||||||
|
IP: c.tuple.DestinationAddr.Addr().AsSlice(),
|
||||||
|
Port: int(c.tuple.DestinationAddr.Port()),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,8 @@ import (
|
|||||||
"net/netip"
|
"net/netip"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
|
"github.com/Dreamacro/clash/common/nnip"
|
||||||
|
"github.com/Dreamacro/clash/common/pool"
|
||||||
"github.com/Dreamacro/clash/listener/tun/ipstack/system/mars/tcpip"
|
"github.com/Dreamacro/clash/listener/tun/ipstack/system/mars/tcpip"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -14,8 +16,8 @@ type call struct {
|
|||||||
cond *sync.Cond
|
cond *sync.Cond
|
||||||
buf []byte
|
buf []byte
|
||||||
n int
|
n int
|
||||||
source netip.AddrPort
|
source net.Addr
|
||||||
destination netip.AddrPort
|
destination net.Addr
|
||||||
}
|
}
|
||||||
|
|
||||||
type UDP struct {
|
type UDP struct {
|
||||||
@ -24,10 +26,10 @@ type UDP struct {
|
|||||||
queueLock sync.Mutex
|
queueLock sync.Mutex
|
||||||
queue []*call
|
queue []*call
|
||||||
bufLock sync.Mutex
|
bufLock sync.Mutex
|
||||||
buf [0xffff]byte
|
buf [pool.UDPBufferSize]byte
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *UDP) ReadFrom(buf []byte) (int, netip.AddrPort, netip.AddrPort, error) {
|
func (u *UDP) ReadFrom(buf []byte) (int, net.Addr, net.Addr, error) {
|
||||||
u.queueLock.Lock()
|
u.queueLock.Lock()
|
||||||
defer u.queueLock.Unlock()
|
defer u.queueLock.Unlock()
|
||||||
|
|
||||||
@ -36,8 +38,8 @@ func (u *UDP) ReadFrom(buf []byte) (int, netip.AddrPort, netip.AddrPort, error)
|
|||||||
cond: sync.NewCond(&u.queueLock),
|
cond: sync.NewCond(&u.queueLock),
|
||||||
buf: buf,
|
buf: buf,
|
||||||
n: -1,
|
n: -1,
|
||||||
source: netip.AddrPort{},
|
source: nil,
|
||||||
destination: netip.AddrPort{},
|
destination: nil,
|
||||||
}
|
}
|
||||||
|
|
||||||
u.queue = append(u.queue, c)
|
u.queue = append(u.queue, c)
|
||||||
@ -49,10 +51,10 @@ func (u *UDP) ReadFrom(buf []byte) (int, netip.AddrPort, netip.AddrPort, error)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return -1, netip.AddrPort{}, netip.AddrPort{}, net.ErrClosed
|
return -1, nil, nil, net.ErrClosed
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *UDP) WriteTo(buf []byte, local netip.AddrPort, remote netip.AddrPort) (int, error) {
|
func (u *UDP) WriteTo(buf []byte, local net.Addr, remote net.Addr) (int, error) {
|
||||||
if u.closed {
|
if u.closed {
|
||||||
return 0, net.ErrClosed
|
return 0, net.ErrClosed
|
||||||
}
|
}
|
||||||
@ -64,7 +66,16 @@ func (u *UDP) WriteTo(buf []byte, local netip.AddrPort, remote netip.AddrPort) (
|
|||||||
return 0, net.InvalidAddrError("invalid ip version")
|
return 0, net.InvalidAddrError("invalid ip version")
|
||||||
}
|
}
|
||||||
|
|
||||||
if !local.Addr().Is4() || !remote.Addr().Is4() {
|
srcAddr, srcOk := local.(*net.UDPAddr)
|
||||||
|
dstAddr, dstOk := remote.(*net.UDPAddr)
|
||||||
|
if !srcOk || !dstOk {
|
||||||
|
return 0, net.InvalidAddrError("invalid addr")
|
||||||
|
}
|
||||||
|
|
||||||
|
srcAddrPort := netip.AddrPortFrom(nnip.IpToAddr(srcAddr.IP), uint16(srcAddr.Port))
|
||||||
|
dstAddrPort := netip.AddrPortFrom(nnip.IpToAddr(dstAddr.IP), uint16(dstAddr.Port))
|
||||||
|
|
||||||
|
if !srcAddrPort.Addr().Is4() || !dstAddrPort.Addr().Is4() {
|
||||||
return 0, net.InvalidAddrError("invalid ip version")
|
return 0, net.InvalidAddrError("invalid ip version")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -78,13 +89,13 @@ func (u *UDP) WriteTo(buf []byte, local netip.AddrPort, remote netip.AddrPort) (
|
|||||||
ip.SetFragmentOffset(0)
|
ip.SetFragmentOffset(0)
|
||||||
ip.SetTimeToLive(64)
|
ip.SetTimeToLive(64)
|
||||||
ip.SetProtocol(tcpip.UDP)
|
ip.SetProtocol(tcpip.UDP)
|
||||||
ip.SetSourceIP(local.Addr())
|
ip.SetSourceIP(srcAddrPort.Addr())
|
||||||
ip.SetDestinationIP(remote.Addr())
|
ip.SetDestinationIP(dstAddrPort.Addr())
|
||||||
|
|
||||||
udp := tcpip.UDPPacket(ip.Payload())
|
udp := tcpip.UDPPacket(ip.Payload())
|
||||||
udp.SetLength(tcpip.UDPHeaderSize + uint16(len(buf)))
|
udp.SetLength(tcpip.UDPHeaderSize + uint16(len(buf)))
|
||||||
udp.SetSourcePort(local.Port())
|
udp.SetSourcePort(srcAddrPort.Port())
|
||||||
udp.SetDestinationPort(remote.Port())
|
udp.SetDestinationPort(dstAddrPort.Port())
|
||||||
copy(udp.Payload(), buf)
|
copy(udp.Payload(), buf)
|
||||||
|
|
||||||
ip.ResetChecksum()
|
ip.ResetChecksum()
|
||||||
@ -120,8 +131,14 @@ func (u *UDP) handleUDPPacket(ip tcpip.IP, pkt tcpip.UDPPacket) {
|
|||||||
u.queueLock.Unlock()
|
u.queueLock.Unlock()
|
||||||
|
|
||||||
if c != nil {
|
if c != nil {
|
||||||
c.source = netip.AddrPortFrom(ip.SourceIP(), pkt.SourcePort())
|
c.source = &net.UDPAddr{
|
||||||
c.destination = netip.AddrPortFrom(ip.DestinationIP(), pkt.DestinationPort())
|
IP: ip.SourceIP().AsSlice(),
|
||||||
|
Port: int(pkt.SourcePort()),
|
||||||
|
}
|
||||||
|
c.destination = &net.UDPAddr{
|
||||||
|
IP: ip.DestinationIP().AsSlice(),
|
||||||
|
Port: int(pkt.DestinationPort()),
|
||||||
|
}
|
||||||
c.n = copy(c.buf, pkt.Payload())
|
c.n = copy(c.buf, pkt.Payload())
|
||||||
c.cond.Signal()
|
c.cond.Signal()
|
||||||
}
|
}
|
||||||
|
@ -81,23 +81,26 @@ func New(device device.Device, dnsHijack []C.DNSUrl, tunAddress netip.Prefix, tc
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
lAddr := conn.LocalAddr().(*net.TCPAddr).AddrPort()
|
lAddr := conn.LocalAddr().(*net.TCPAddr)
|
||||||
rAddr := conn.RemoteAddr().(*net.TCPAddr).AddrPort()
|
rAddr := conn.RemoteAddr().(*net.TCPAddr)
|
||||||
|
|
||||||
if rAddr.Addr().IsLoopback() {
|
lAddrPort := netip.AddrPortFrom(nnip.IpToAddr(lAddr.IP), uint16(lAddr.Port))
|
||||||
|
rAddrPort := netip.AddrPortFrom(nnip.IpToAddr(rAddr.IP), uint16(rAddr.Port))
|
||||||
|
|
||||||
|
if rAddrPort.Addr().IsLoopback() {
|
||||||
_ = conn.Close()
|
_ = conn.Close()
|
||||||
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if D.ShouldHijackDns(dnsAddr, rAddr, "tcp") {
|
if D.ShouldHijackDns(dnsAddr, rAddrPort, "tcp") {
|
||||||
go func() {
|
go func() {
|
||||||
log.Debugln("[TUN] hijack dns tcp: %s", rAddr.String())
|
log.Debugln("[TUN] hijack dns tcp: %s", rAddrPort.String())
|
||||||
|
|
||||||
buf := pool.Get(pool.UDPBufferSize)
|
buf := pool.Get(pool.UDPBufferSize)
|
||||||
defer func() {
|
defer func() {
|
||||||
_ = conn.Close()
|
|
||||||
_ = pool.Put(buf)
|
_ = pool.Put(buf)
|
||||||
|
_ = conn.Close()
|
||||||
}()
|
}()
|
||||||
|
|
||||||
for {
|
for {
|
||||||
@ -134,10 +137,10 @@ func New(device device.Device, dnsHijack []C.DNSUrl, tunAddress netip.Prefix, tc
|
|||||||
metadata := &C.Metadata{
|
metadata := &C.Metadata{
|
||||||
NetWork: C.TCP,
|
NetWork: C.TCP,
|
||||||
Type: C.TUN,
|
Type: C.TUN,
|
||||||
SrcIP: lAddr.Addr(),
|
SrcIP: lAddrPort.Addr(),
|
||||||
DstIP: rAddr.Addr(),
|
DstIP: rAddrPort.Addr(),
|
||||||
SrcPort: strconv.FormatUint(uint64(lAddr.Port()), 10),
|
SrcPort: strconv.Itoa(lAddr.Port),
|
||||||
DstPort: strconv.FormatUint(uint64(rAddr.Port()), 10),
|
DstPort: strconv.Itoa(rAddr.Port),
|
||||||
AddrType: C.AtypIPv4,
|
AddrType: C.AtypIPv4,
|
||||||
Host: "",
|
Host: "",
|
||||||
}
|
}
|
||||||
@ -156,32 +159,37 @@ func New(device device.Device, dnsHijack []C.DNSUrl, tunAddress netip.Prefix, tc
|
|||||||
for !ipStack.closed {
|
for !ipStack.closed {
|
||||||
buf := pool.Get(pool.UDPBufferSize)
|
buf := pool.Get(pool.UDPBufferSize)
|
||||||
|
|
||||||
n, lAddr, rAddr, err := stack.UDP().ReadFrom(buf)
|
n, lRAddr, rRAddr, err := stack.UDP().ReadFrom(buf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
_ = pool.Put(buf)
|
_ = pool.Put(buf)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
if rAddr.Addr().IsLoopback() || rAddr.Addr() == gateway {
|
raw := buf[:n]
|
||||||
|
lAddr := lRAddr.(*net.UDPAddr)
|
||||||
|
rAddr := rRAddr.(*net.UDPAddr)
|
||||||
|
|
||||||
|
rAddrPort := netip.AddrPortFrom(nnip.IpToAddr(rAddr.IP), uint16(rAddr.Port))
|
||||||
|
|
||||||
|
if rAddrPort.Addr().IsLoopback() || rAddrPort.Addr() == gateway {
|
||||||
_ = pool.Put(buf)
|
_ = pool.Put(buf)
|
||||||
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if D.ShouldHijackDns(dnsAddr, rAddr, "udp") {
|
if D.ShouldHijackDns(dnsAddr, rAddrPort, "udp") {
|
||||||
go func() {
|
go func() {
|
||||||
defer func() {
|
msg, err := D.RelayDnsPacket(raw)
|
||||||
_ = pool.Put(buf)
|
|
||||||
}()
|
|
||||||
|
|
||||||
msg, err := D.RelayDnsPacket(buf[:n])
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
_ = pool.Put(buf)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
_, _ = stack.UDP().WriteTo(msg, rAddr, lAddr)
|
_, _ = stack.UDP().WriteTo(msg, rAddr, lAddr)
|
||||||
|
|
||||||
log.Debugln("[TUN] hijack dns udp: %s", rAddr.String())
|
_ = pool.Put(buf)
|
||||||
|
|
||||||
|
log.Debugln("[TUN] hijack dns udp: %s", rAddrPort.String())
|
||||||
}()
|
}()
|
||||||
|
|
||||||
continue
|
continue
|
||||||
@ -189,17 +197,18 @@ func New(device device.Device, dnsHijack []C.DNSUrl, tunAddress netip.Prefix, tc
|
|||||||
|
|
||||||
pkt := &packet{
|
pkt := &packet{
|
||||||
local: lAddr,
|
local: lAddr,
|
||||||
data: buf,
|
data: raw,
|
||||||
offset: n,
|
|
||||||
writeBack: func(b []byte, addr net.Addr) (int, error) {
|
writeBack: func(b []byte, addr net.Addr) (int, error) {
|
||||||
return stack.UDP().WriteTo(b, rAddr, lAddr)
|
return stack.UDP().WriteTo(b, rAddr, lAddr)
|
||||||
},
|
},
|
||||||
|
drop: func() {
|
||||||
|
_ = pool.Put(buf)
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
select {
|
select {
|
||||||
case udpIn <- inbound.NewPacket(socks5.AddrFromStdAddrPort(rAddr), pkt, C.TUN):
|
case udpIn <- inbound.NewPacket(socks5.ParseAddrToSocksAddr(rAddr), pkt, C.TUN):
|
||||||
default:
|
default:
|
||||||
pkt.Drop()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,21 +1,16 @@
|
|||||||
package system
|
package system
|
||||||
|
|
||||||
import (
|
import "net"
|
||||||
"net"
|
|
||||||
"net/netip"
|
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/common/pool"
|
|
||||||
)
|
|
||||||
|
|
||||||
type packet struct {
|
type packet struct {
|
||||||
local netip.AddrPort
|
local *net.UDPAddr
|
||||||
data []byte
|
data []byte
|
||||||
offset int
|
|
||||||
writeBack func(b []byte, addr net.Addr) (int, error)
|
writeBack func(b []byte, addr net.Addr) (int, error)
|
||||||
|
drop func()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pkt *packet) Data() []byte {
|
func (pkt *packet) Data() []byte {
|
||||||
return pkt.data[:pkt.offset]
|
return pkt.data
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pkt *packet) WriteBack(b []byte, addr net.Addr) (n int, err error) {
|
func (pkt *packet) WriteBack(b []byte, addr net.Addr) (n int, err error) {
|
||||||
@ -23,9 +18,9 @@ func (pkt *packet) WriteBack(b []byte, addr net.Addr) (n int, err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (pkt *packet) Drop() {
|
func (pkt *packet) Drop() {
|
||||||
_ = pool.Put(pkt.data)
|
pkt.drop()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pkt *packet) LocalAddr() net.Addr {
|
func (pkt *packet) LocalAddr() net.Addr {
|
||||||
return net.UDPAddrFromAddrPort(pkt.local)
|
return pkt.local
|
||||||
}
|
}
|
||||||
|
2
main.go
2
main.go
@ -48,7 +48,7 @@ func init() {
|
|||||||
func main() {
|
func main() {
|
||||||
_, _ = maxprocs.Set(maxprocs.Logger(func(string, ...any) {}))
|
_, _ = maxprocs.Set(maxprocs.Logger(func(string, ...any) {}))
|
||||||
if version {
|
if version {
|
||||||
fmt.Printf("Clash with tun deveice %s %s %s with %s %s\n", C.Version, runtime.GOOS, runtime.GOARCH, runtime.Version(), C.BuildTime)
|
fmt.Printf("Clash Plus Pro %s %s %s with %s %s\n", C.Version, runtime.GOOS, runtime.GOARCH, runtime.Version(), C.BuildTime)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -51,11 +51,14 @@ func NewGEOSITE(country string, adapter string) (*GEOSITE, error) {
|
|||||||
return nil, fmt.Errorf("load GeoSite data error, %s", err.Error())
|
return nil, fmt.Errorf("load GeoSite data error, %s", err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
count := fmt.Sprintf("%d", recordsCount)
|
cont := fmt.Sprintf("%d", recordsCount)
|
||||||
if recordsCount == 0 {
|
if recordsCount == 0 {
|
||||||
count = "from cache"
|
cont = "from cache"
|
||||||
}
|
}
|
||||||
log.Infoln("Start initial GeoSite rule %s => %s, records: %s", country, adapter, count)
|
if adapter == C.ScriptRuleGeoSiteTarget {
|
||||||
|
adapter = "Script"
|
||||||
|
}
|
||||||
|
log.Infoln("Start initial GeoSite rule %s => %s, records: %s", country, adapter, cont)
|
||||||
|
|
||||||
geoSite := &GEOSITE{
|
geoSite := &GEOSITE{
|
||||||
Base: &Base{},
|
Base: &Base{},
|
||||||
|
@ -37,6 +37,8 @@ func ParseRule(tp, payload, target string, params []string) (C.Rule, error) {
|
|||||||
parsed, parseErr = NewProcess(payload, target, true)
|
parsed, parseErr = NewProcess(payload, target, true)
|
||||||
case "PROCESS-PATH":
|
case "PROCESS-PATH":
|
||||||
parsed, parseErr = NewProcess(payload, target, false)
|
parsed, parseErr = NewProcess(payload, target, false)
|
||||||
|
case "SCRIPT":
|
||||||
|
parsed, parseErr = NewScript(payload, target)
|
||||||
case "USER-AGENT":
|
case "USER-AGENT":
|
||||||
parsed, parseErr = NewUserAgent(payload, target)
|
parsed, parseErr = NewUserAgent(payload, target)
|
||||||
case "MATCH":
|
case "MATCH":
|
||||||
|
77
rule/script.go
Normal file
77
rule/script.go
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
package rules
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
S "github.com/Dreamacro/clash/component/script"
|
||||||
|
C "github.com/Dreamacro/clash/constant"
|
||||||
|
"github.com/Dreamacro/clash/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Script struct {
|
||||||
|
*Base
|
||||||
|
shortcut string
|
||||||
|
adapter string
|
||||||
|
shortcutFunction *S.PyObject
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Script) RuleType() C.RuleType {
|
||||||
|
return C.Script
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Script) Match(metadata *C.Metadata) bool {
|
||||||
|
rs, err := S.CallPyShortcut(s.shortcutFunction, metadata)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorln("[Script] match rule error: %s", err.Error())
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return rs
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Script) Adapter() string {
|
||||||
|
return s.adapter
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Script) Payload() string {
|
||||||
|
return s.shortcut
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Script) ShouldResolveIP() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Script) RuleExtra() *C.RuleExtra {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewScript(shortcut string, adapter string) (*Script, error) {
|
||||||
|
shortcut = strings.ToLower(shortcut)
|
||||||
|
if !S.Py_IsInitialized() {
|
||||||
|
return nil, fmt.Errorf("load script shortcut [%s] failure, can't find any shortcuts in the config file", shortcut)
|
||||||
|
}
|
||||||
|
|
||||||
|
shortcutFunction, err := S.LoadShortcutFunction(shortcut)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("can't find script shortcut [%s] in the config file", shortcut)
|
||||||
|
}
|
||||||
|
|
||||||
|
obj := &Script{
|
||||||
|
Base: &Base{},
|
||||||
|
shortcut: shortcut,
|
||||||
|
adapter: adapter,
|
||||||
|
shortcutFunction: shortcutFunction,
|
||||||
|
}
|
||||||
|
|
||||||
|
runtime.SetFinalizer(obj, func(s *Script) {
|
||||||
|
s.shortcutFunction.Clear()
|
||||||
|
})
|
||||||
|
|
||||||
|
log.Infoln("Start initial script shortcut rule %s => %s", shortcut, adapter)
|
||||||
|
|
||||||
|
return obj, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ C.Rule = (*Script)(nil)
|
10
test/go.mod
10
test/go.mod
@ -8,7 +8,7 @@ require (
|
|||||||
github.com/docker/go-connections v0.4.0
|
github.com/docker/go-connections v0.4.0
|
||||||
github.com/miekg/dns v1.1.49
|
github.com/miekg/dns v1.1.49
|
||||||
github.com/stretchr/testify v1.7.1
|
github.com/stretchr/testify v1.7.1
|
||||||
golang.org/x/net v0.0.0-20220526153639-5463443f8c37
|
golang.org/x/net v0.0.0-20220531201128-c960675eff93
|
||||||
)
|
)
|
||||||
|
|
||||||
replace github.com/Dreamacro/clash => ../
|
replace github.com/Dreamacro/clash => ../
|
||||||
@ -37,20 +37,20 @@ require (
|
|||||||
go.etcd.io/bbolt v1.3.6 // indirect
|
go.etcd.io/bbolt v1.3.6 // indirect
|
||||||
go.uber.org/atomic v1.9.0 // indirect
|
go.uber.org/atomic v1.9.0 // indirect
|
||||||
golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e // indirect
|
golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e // indirect
|
||||||
golang.org/x/exp v0.0.0-20220518171630-0b5c67f07fdf // indirect
|
golang.org/x/exp v0.0.0-20220602145555-4a0574d9293f // indirect
|
||||||
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 // indirect
|
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 // indirect
|
||||||
golang.org/x/sync v0.0.0-20220513210516-0976fa681c29 // indirect
|
golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f // indirect
|
||||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a // indirect
|
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a // indirect
|
||||||
golang.org/x/text v0.3.8-0.20220124021120-d1c84af989ab // indirect
|
golang.org/x/text v0.3.8-0.20220124021120-d1c84af989ab // indirect
|
||||||
golang.org/x/time v0.0.0-20220411224347-583f2d630306 // indirect
|
golang.org/x/time v0.0.0-20220411224347-583f2d630306 // indirect
|
||||||
golang.org/x/tools v0.1.10 // indirect
|
golang.org/x/tools v0.1.10 // indirect
|
||||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
|
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
|
||||||
golang.zx2c4.com/wintun v0.0.0-20211104114900-415007cec224 // indirect
|
golang.zx2c4.com/wintun v0.0.0-20211104114900-415007cec224 // indirect
|
||||||
golang.zx2c4.com/wireguard v0.0.0-20220407013110-ef5c587f782d // indirect
|
golang.zx2c4.com/wireguard v0.0.0-20220601130007-6a08d81f6bc4 // indirect
|
||||||
golang.zx2c4.com/wireguard/windows v0.5.4-0.20220328111914-004c22c5647e // indirect
|
golang.zx2c4.com/wireguard/windows v0.5.4-0.20220328111914-004c22c5647e // indirect
|
||||||
google.golang.org/protobuf v1.28.0 // indirect
|
google.golang.org/protobuf v1.28.0 // indirect
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
gotest.tools/v3 v3.1.0 // indirect
|
gotest.tools/v3 v3.1.0 // indirect
|
||||||
gvisor.dev/gvisor v0.0.0-20220527053002-8ab279227ac8 // indirect
|
gvisor.dev/gvisor v0.0.0-20220601233344-46e478629075 // indirect
|
||||||
)
|
)
|
||||||
|
20
test/go.sum
20
test/go.sum
@ -101,8 +101,8 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U
|
|||||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e h1:T8NU3HyQ8ClP4SEE+KbFlg6n0NhuTsN4MyznaarGsZM=
|
golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e h1:T8NU3HyQ8ClP4SEE+KbFlg6n0NhuTsN4MyznaarGsZM=
|
||||||
golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||||
golang.org/x/exp v0.0.0-20220518171630-0b5c67f07fdf h1:oXVg4h2qJDd9htKxb5SCpFBHLipW6hXmL3qpUixS2jw=
|
golang.org/x/exp v0.0.0-20220602145555-4a0574d9293f h1:KK6mxegmt5hGJRcAnEDjSNLxIRhZxDcgwMbcO/lMCRM=
|
||||||
golang.org/x/exp v0.0.0-20220518171630-0b5c67f07fdf/go.mod h1:yh0Ynu2b5ZUe3MQfp2nM0ecK7wsgouWTDN0FNeJuIys=
|
golang.org/x/exp v0.0.0-20220602145555-4a0574d9293f/go.mod h1:yh0Ynu2b5ZUe3MQfp2nM0ecK7wsgouWTDN0FNeJuIys=
|
||||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
@ -122,14 +122,14 @@ golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwY
|
|||||||
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||||
golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
golang.org/x/net v0.0.0-20220526153639-5463443f8c37 h1:lUkvobShwKsOesNfWWlCS5q7fnbG1MEliIzwu886fn8=
|
golang.org/x/net v0.0.0-20220531201128-c960675eff93 h1:MYimHLfoXEpOhqd/zgoA/uoXzHB86AEky4LAx5ij9xA=
|
||||||
golang.org/x/net v0.0.0-20220526153639-5463443f8c37/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
golang.org/x/net v0.0.0-20220531201128-c960675eff93/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20220513210516-0976fa681c29 h1:w8s32wxx3sY+OjLlv9qltkLU5yvJzxjjgiHWLjdIcw4=
|
golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f h1:Ax0t5p6N38Ga0dThY21weqDEyz2oklo4IvDkpigvkD8=
|
||||||
golang.org/x/sync v0.0.0-20220513210516-0976fa681c29/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190411185658-b44545bcd369/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190411185658-b44545bcd369/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
@ -180,8 +180,8 @@ golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1N
|
|||||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.zx2c4.com/wintun v0.0.0-20211104114900-415007cec224 h1:Ug9qvr1myri/zFN6xL17LSCBGFDnphBBhzmILHsM5TY=
|
golang.zx2c4.com/wintun v0.0.0-20211104114900-415007cec224 h1:Ug9qvr1myri/zFN6xL17LSCBGFDnphBBhzmILHsM5TY=
|
||||||
golang.zx2c4.com/wintun v0.0.0-20211104114900-415007cec224/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI=
|
golang.zx2c4.com/wintun v0.0.0-20211104114900-415007cec224/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI=
|
||||||
golang.zx2c4.com/wireguard v0.0.0-20220407013110-ef5c587f782d h1:q4JksJ2n0fmbXC0Aj0eOs6E0AcPqnKglxWXWFqGD6x0=
|
golang.zx2c4.com/wireguard v0.0.0-20220601130007-6a08d81f6bc4 h1:QlbNZ9SwDAepRQwgeWHLi3rfEMo/kVEU4SmgsNM7HmQ=
|
||||||
golang.zx2c4.com/wireguard v0.0.0-20220407013110-ef5c587f782d/go.mod h1:bVQfyl2sCM/QIIGHpWbFGfHPuDvqnCNkT6MQLTCjO/U=
|
golang.zx2c4.com/wireguard v0.0.0-20220601130007-6a08d81f6bc4/go.mod h1:bVQfyl2sCM/QIIGHpWbFGfHPuDvqnCNkT6MQLTCjO/U=
|
||||||
golang.zx2c4.com/wireguard/windows v0.5.4-0.20220328111914-004c22c5647e h1:yV04h6Tx19uDR6LvuEbR19cDU+3QrB9LuGjtF7F5G0w=
|
golang.zx2c4.com/wireguard/windows v0.5.4-0.20220328111914-004c22c5647e h1:yV04h6Tx19uDR6LvuEbR19cDU+3QrB9LuGjtF7F5G0w=
|
||||||
golang.zx2c4.com/wireguard/windows v0.5.4-0.20220328111914-004c22c5647e/go.mod h1:1CeiatTZwcwSFA3cAtMm8CQoroviTldnxd7DOgM/vI4=
|
golang.zx2c4.com/wireguard/windows v0.5.4-0.20220328111914-004c22c5647e/go.mod h1:1CeiatTZwcwSFA3cAtMm8CQoroviTldnxd7DOgM/vI4=
|
||||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||||
@ -196,5 +196,5 @@ gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
|||||||
gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk=
|
gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk=
|
||||||
gotest.tools/v3 v3.1.0 h1:rVV8Tcg/8jHUkPUorwjaMTtemIMVXfIPKiOqnhEhakk=
|
gotest.tools/v3 v3.1.0 h1:rVV8Tcg/8jHUkPUorwjaMTtemIMVXfIPKiOqnhEhakk=
|
||||||
gotest.tools/v3 v3.1.0/go.mod h1:fHy7eyTmJFO5bQbUsEGQ1v4m2J3Jz9eWL54TP2/ZuYQ=
|
gotest.tools/v3 v3.1.0/go.mod h1:fHy7eyTmJFO5bQbUsEGQ1v4m2J3Jz9eWL54TP2/ZuYQ=
|
||||||
gvisor.dev/gvisor v0.0.0-20220527053002-8ab279227ac8 h1:K6RgHqNR+9t3sKVsfRFsvXryRL5kL6wtBPU5aPt1jLY=
|
gvisor.dev/gvisor v0.0.0-20220601233344-46e478629075 h1:ucwwit0X39HmMQ1iSeNCIXw4g/B8bi+O6TSxxDbPs9E=
|
||||||
gvisor.dev/gvisor v0.0.0-20220527053002-8ab279227ac8/go.mod h1:TIvkJD0sxe8pIob3p6T8IzxXunlp6yfgktvTNp+DGNM=
|
gvisor.dev/gvisor v0.0.0-20220601233344-46e478629075/go.mod h1:TIvkJD0sxe8pIob3p6T8IzxXunlp6yfgktvTNp+DGNM=
|
||||||
|
@ -6,7 +6,6 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
"net/netip"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/component/auth"
|
"github.com/Dreamacro/clash/component/auth"
|
||||||
@ -399,21 +398,6 @@ func ParseAddrToSocksAddr(addr net.Addr) Addr {
|
|||||||
return parsed
|
return parsed
|
||||||
}
|
}
|
||||||
|
|
||||||
func AddrFromStdAddrPort(addrPort netip.AddrPort) Addr {
|
|
||||||
addr := addrPort.Addr()
|
|
||||||
if addr.Is4() {
|
|
||||||
ip4 := addr.As4()
|
|
||||||
return []byte{AtypIPv4, ip4[0], ip4[1], ip4[2], ip4[3], byte(addrPort.Port() >> 8), byte(addrPort.Port())}
|
|
||||||
}
|
|
||||||
|
|
||||||
buf := make([]byte, 1+net.IPv6len+2)
|
|
||||||
buf[0] = AtypIPv6
|
|
||||||
copy(buf[1:], addr.AsSlice())
|
|
||||||
buf[1+net.IPv6len] = byte(addrPort.Port() >> 8)
|
|
||||||
buf[1+net.IPv6len+1] = byte(addrPort.Port())
|
|
||||||
return buf
|
|
||||||
}
|
|
||||||
|
|
||||||
// DecodeUDPPacket split `packet` to addr payload, and this function is mutable with `packet`
|
// DecodeUDPPacket split `packet` to addr payload, and this function is mutable with `packet`
|
||||||
func DecodeUDPPacket(packet []byte) (addr Addr, payload []byte, err error) {
|
func DecodeUDPPacket(packet []byte) (addr Addr, payload []byte, err error) {
|
||||||
if len(packet) < 5 {
|
if len(packet) < 5 {
|
||||||
|
@ -16,7 +16,7 @@ func handleUDPToRemote(packet C.UDPPacket, pc C.PacketConn, metadata *C.Metadata
|
|||||||
|
|
||||||
// local resolve UDP dns
|
// local resolve UDP dns
|
||||||
if !metadata.Resolved() {
|
if !metadata.Resolved() {
|
||||||
ip, err := resolver.ResolveFirstIP(metadata.Host)
|
ip, err := resolver.ResolveIP(metadata.Host)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -32,21 +32,19 @@ func handleUDPToRemote(packet C.UDPPacket, pc C.PacketConn, metadata *C.Metadata
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
// reset timeout
|
// reset timeout
|
||||||
_ = pc.SetReadDeadline(time.Now().Add(udpTimeout))
|
pc.SetReadDeadline(time.Now().Add(udpTimeout))
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleUDPToLocal(packet C.UDPPacket, pc net.PacketConn, key string, fAddr net.Addr) {
|
func handleUDPToLocal(packet C.UDPPacket, pc net.PacketConn, key string, fAddr net.Addr) {
|
||||||
buf := pool.Get(pool.UDPBufferSize)
|
buf := pool.Get(pool.UDPBufferSize)
|
||||||
defer func() {
|
defer pool.Put(buf)
|
||||||
_ = pc.Close()
|
defer natTable.Delete(key)
|
||||||
natTable.Delete(key)
|
defer pc.Close()
|
||||||
_ = pool.Put(buf)
|
|
||||||
}()
|
|
||||||
|
|
||||||
for {
|
for {
|
||||||
_ = pc.SetReadDeadline(time.Now().Add(udpTimeout))
|
pc.SetReadDeadline(time.Now().Add(udpTimeout))
|
||||||
n, from, err := pc.ReadFrom(buf)
|
n, from, err := pc.ReadFrom(buf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
|
@ -12,12 +12,14 @@ type TunnelMode int
|
|||||||
var ModeMapping = map[string]TunnelMode{
|
var ModeMapping = map[string]TunnelMode{
|
||||||
Global.String(): Global,
|
Global.String(): Global,
|
||||||
Rule.String(): Rule,
|
Rule.String(): Rule,
|
||||||
|
Script.String(): Script,
|
||||||
Direct.String(): Direct,
|
Direct.String(): Direct,
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
Global TunnelMode = iota
|
Global TunnelMode = iota
|
||||||
Rule
|
Rule
|
||||||
|
Script
|
||||||
Direct
|
Direct
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -61,6 +63,8 @@ func (m TunnelMode) String() string {
|
|||||||
return "global"
|
return "global"
|
||||||
case Rule:
|
case Rule:
|
||||||
return "rule"
|
return "rule"
|
||||||
|
case Script:
|
||||||
|
return "script"
|
||||||
case Direct:
|
case Direct:
|
||||||
return "direct"
|
return "direct"
|
||||||
default:
|
default:
|
||||||
|
@ -16,6 +16,7 @@ import (
|
|||||||
"github.com/Dreamacro/clash/component/nat"
|
"github.com/Dreamacro/clash/component/nat"
|
||||||
P "github.com/Dreamacro/clash/component/process"
|
P "github.com/Dreamacro/clash/component/process"
|
||||||
"github.com/Dreamacro/clash/component/resolver"
|
"github.com/Dreamacro/clash/component/resolver"
|
||||||
|
S "github.com/Dreamacro/clash/component/script"
|
||||||
"github.com/Dreamacro/clash/component/trie"
|
"github.com/Dreamacro/clash/component/trie"
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
"github.com/Dreamacro/clash/constant/provider"
|
"github.com/Dreamacro/clash/constant/provider"
|
||||||
@ -190,6 +191,19 @@ func preHandleMetadata(metadata *C.Metadata) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// pre resolve process name
|
||||||
|
srcPort, err := strconv.ParseUint(metadata.SrcPort, 10, 16)
|
||||||
|
if err == nil && P.ShouldFindProcess(metadata) {
|
||||||
|
path, err := P.FindProcessName(metadata.NetWork.String(), metadata.SrcIP, int(srcPort))
|
||||||
|
if err != nil {
|
||||||
|
log.Debugln("[Process] find process %s: %v", metadata.String(), err)
|
||||||
|
} else {
|
||||||
|
log.Debugln("[Process] %s from process %s", metadata.String(), path)
|
||||||
|
metadata.Process = filepath.Base(path)
|
||||||
|
metadata.ProcessPath = path
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -205,6 +219,8 @@ func resolveMetadata(_ C.PlainContext, metadata *C.Metadata) (proxy C.Proxy, rul
|
|||||||
proxy = proxies["DIRECT"]
|
proxy = proxies["DIRECT"]
|
||||||
case Global:
|
case Global:
|
||||||
proxy = proxies["GLOBAL"]
|
proxy = proxies["GLOBAL"]
|
||||||
|
case Script:
|
||||||
|
proxy, err = matchScript(metadata)
|
||||||
// Rule
|
// Rule
|
||||||
default:
|
default:
|
||||||
proxy, rule, err = match(metadata)
|
proxy, rule, err = match(metadata)
|
||||||
@ -286,6 +302,8 @@ func handleUDPConn(packet *inbound.PacketAdapter) {
|
|||||||
switch true {
|
switch true {
|
||||||
case rule != nil:
|
case rule != nil:
|
||||||
log.Infoln("[UDP] %s(%s) --> %s match %s(%s) using %s", metadata.SourceAddress(), metadata.Process, metadata.RemoteAddress(), rule.RuleType().String(), rule.Payload(), rawPc.Chains().String())
|
log.Infoln("[UDP] %s(%s) --> %s match %s(%s) using %s", metadata.SourceAddress(), metadata.Process, metadata.RemoteAddress(), rule.RuleType().String(), rule.Payload(), rawPc.Chains().String())
|
||||||
|
case mode == Script:
|
||||||
|
log.Infoln("[UDP] %s(%s) --> %s using SCRIPT %s", metadata.SourceAddress(), metadata.Process, metadata.RemoteAddress(), rawPc.Chains().String())
|
||||||
case mode == Global:
|
case mode == Global:
|
||||||
log.Infoln("[UDP] %s(%s) --> %s using GLOBAL", metadata.SourceAddress(), metadata.Process, metadata.RemoteAddress())
|
log.Infoln("[UDP] %s(%s) --> %s using GLOBAL", metadata.SourceAddress(), metadata.Process, metadata.RemoteAddress())
|
||||||
case mode == Direct:
|
case mode == Direct:
|
||||||
@ -353,6 +371,8 @@ func handleTCPConn(connCtx C.ConnContext) {
|
|||||||
break
|
break
|
||||||
case rule != nil:
|
case rule != nil:
|
||||||
log.Infoln("[TCP] %s(%s) --> %s match %s(%s) using %s", metadata.SourceAddress(), metadata.Process, metadata.RemoteAddress(), rule.RuleType().String(), rule.Payload(), remoteConn.Chains().String())
|
log.Infoln("[TCP] %s(%s) --> %s match %s(%s) using %s", metadata.SourceAddress(), metadata.Process, metadata.RemoteAddress(), rule.RuleType().String(), rule.Payload(), remoteConn.Chains().String())
|
||||||
|
case mode == Script:
|
||||||
|
log.Infoln("[TCP] %s(%s) --> %s using SCRIPT %s", metadata.SourceAddress(), metadata.Process, metadata.RemoteAddress(), remoteConn.Chains().String())
|
||||||
case mode == Global:
|
case mode == Global:
|
||||||
log.Infoln("[TCP] %s(%s) --> %s using GLOBAL", metadata.SourceAddress(), metadata.Process, metadata.RemoteAddress())
|
log.Infoln("[TCP] %s(%s) --> %s using GLOBAL", metadata.SourceAddress(), metadata.Process, metadata.RemoteAddress())
|
||||||
case mode == Direct:
|
case mode == Direct:
|
||||||
@ -372,10 +392,7 @@ func match(metadata *C.Metadata) (C.Proxy, C.Rule, error) {
|
|||||||
configMux.RLock()
|
configMux.RLock()
|
||||||
defer configMux.RUnlock()
|
defer configMux.RUnlock()
|
||||||
|
|
||||||
var (
|
var resolved bool
|
||||||
resolved bool
|
|
||||||
processFound bool
|
|
||||||
)
|
|
||||||
|
|
||||||
if node := resolver.DefaultHosts.Search(metadata.Host); node != nil {
|
if node := resolver.DefaultHosts.Search(metadata.Host); node != nil {
|
||||||
metadata.DstIP = node.Data
|
metadata.DstIP = node.Data
|
||||||
@ -394,22 +411,6 @@ func match(metadata *C.Metadata) (C.Proxy, C.Rule, error) {
|
|||||||
resolved = true
|
resolved = true
|
||||||
}
|
}
|
||||||
|
|
||||||
if !processFound && rule.ShouldFindProcess() && P.ShouldFindProcess(metadata) {
|
|
||||||
processFound = true
|
|
||||||
|
|
||||||
srcPort, err := strconv.ParseUint(metadata.SrcPort, 10, 16)
|
|
||||||
if err == nil {
|
|
||||||
path, err := P.FindProcessName(metadata.NetWork.String(), metadata.SrcIP, int(srcPort))
|
|
||||||
if err != nil {
|
|
||||||
log.Debugln("[Process] find process %s: %v", metadata.String(), err)
|
|
||||||
} else {
|
|
||||||
log.Debugln("[Process] %s from process %s", metadata.String(), path)
|
|
||||||
metadata.Process = filepath.Base(path)
|
|
||||||
metadata.ProcessPath = path
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if rule.Match(metadata) {
|
if rule.Match(metadata) {
|
||||||
adapter, ok := proxies[rule.Adapter()]
|
adapter, ok := proxies[rule.Adapter()]
|
||||||
if !ok {
|
if !ok {
|
||||||
@ -442,3 +443,23 @@ func match(metadata *C.Metadata) (C.Proxy, C.Rule, error) {
|
|||||||
|
|
||||||
return proxies["REJECT"], nil, nil
|
return proxies["REJECT"], nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func matchScript(metadata *C.Metadata) (C.Proxy, error) {
|
||||||
|
configMux.RLock()
|
||||||
|
defer configMux.RUnlock()
|
||||||
|
|
||||||
|
if node := resolver.DefaultHosts.Search(metadata.Host); node != nil {
|
||||||
|
metadata.DstIP = node.Data
|
||||||
|
}
|
||||||
|
|
||||||
|
adapter, err := S.CallPyMainFunction(metadata)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, ok := proxies[adapter]; !ok {
|
||||||
|
return nil, fmt.Errorf("proxy [%s] not found by script", adapter)
|
||||||
|
}
|
||||||
|
|
||||||
|
return proxies[adapter], nil
|
||||||
|
}
|
||||||
|
Reference in New Issue
Block a user