Compare commits
38 Commits
v1.2.0_tun
...
plus_pro
Author | SHA1 | Date | |
---|---|---|---|
3610d3dd84 | |||
663017a775 | |||
05f0c1060b | |||
e03f1d0565 | |||
c1821e28d3 | |||
763929997b | |||
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
|
||||
with:
|
||||
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
|
||||
on: [push]
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- rm
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
@ -33,29 +36,63 @@ jobs:
|
||||
restore-keys: |
|
||||
${{ runner.os }}-go-
|
||||
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: '3.9'
|
||||
|
||||
- name: Get dependencies, run test
|
||||
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
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
#if: startsWith(github.ref, 'refs/tags/')
|
||||
env:
|
||||
NAME: clash
|
||||
BINDIR: bin
|
||||
run: make -j releases
|
||||
run: |
|
||||
make -j releases
|
||||
#ls -lahF bin/python/
|
||||
|
||||
#- name: Prepare upload
|
||||
# run: |
|
||||
# echo "FILE_DATE=_$(date +"%Y%m%d%H%M")" >> $GITHUB_ENV
|
||||
# echo "FILE_SHA=$(git describe --tags --always 2>/dev/null)" >> $GITHUB_ENV
|
||||
#
|
||||
#- name: Upload files to Artifacts
|
||||
# uses: actions/upload-artifact@v2
|
||||
# if: startsWith(github.ref, 'refs/tags/') == false
|
||||
# with:
|
||||
# name: clash_${{ env.FILE_SHA }}${{ env.FILE_DATE }}
|
||||
# path: |
|
||||
# bin/*
|
||||
- name: Prepare upload
|
||||
if: startsWith(github.ref, 'refs/tags/') == false
|
||||
run: |
|
||||
rm -rf bin/python/
|
||||
echo "FILE_DATE=_$(date +"%Y%m%d%H%M")" >> $GITHUB_ENV
|
||||
echo "FILE_SHA=$(git describe --tags --always 2>/dev/null)" >> $GITHUB_ENV
|
||||
|
||||
- name: Upload files to Artifacts
|
||||
uses: actions/upload-artifact@v2
|
||||
if: startsWith(github.ref, 'refs/tags/') == false
|
||||
with:
|
||||
name: clash_${{ env.FILE_SHA }}${{ env.FILE_DATE }}
|
||||
path: |
|
||||
bin/*
|
||||
|
||||
- name: Upload Release
|
||||
uses: softprops/action-gh-release@v1
|
||||
@ -71,3 +108,13 @@ jobs:
|
||||
# with:
|
||||
# retain_days: 1
|
||||
# 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
|
||||
BINDIR=bin
|
||||
VERSION=$(shell git describe --tags --always 2>/dev/null || echo "unknown version")
|
||||
BINDIR=$(shell pwd)/bin
|
||||
VERSION=$(shell git describe --tags --always 2>/dev/null || date +%F)
|
||||
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)" \
|
||||
-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 = \
|
||||
darwin-amd64 \
|
||||
darwin-amd64-v3 \
|
||||
darwin-arm64 \
|
||||
linux-386 \
|
||||
linux-amd64 \
|
||||
linux-amd64-v3 \
|
||||
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
|
||||
linux-amd64
|
||||
# linux-arm64
|
||||
# linux-386
|
||||
|
||||
WINDOWS_ARCH_LIST = \
|
||||
windows-386 \
|
||||
windows-amd64 \
|
||||
windows-amd64-v3 \
|
||||
windows-arm64 \
|
||||
windows-arm32v7
|
||||
windows-386
|
||||
# windows-arm64
|
||||
|
||||
all: linux-amd64 darwin-amd64 windows-amd64 # Most used
|
||||
|
||||
docker:
|
||||
$(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
||||
local:
|
||||
$(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:
|
||||
GOARCH=amd64 GOOS=darwin $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
||||
|
||||
darwin-amd64-v3:
|
||||
GOARCH=amd64 GOOS=darwin GOAMD64=v3 $(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-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:
|
||||
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:
|
||||
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:
|
||||
GOARCH=amd64 GOOS=linux GOAMD64=v3 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
||||
|
||||
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)-$@
|
||||
linux-arm64:
|
||||
$(XGOCMD) -dest=$(BINDIR) -out=$(NAME) -trimpath=true -ldflags=$(STATIC_LDFLAGS) -targets=linux/arm64 $(BUILD_PACKAGE)
|
||||
|
||||
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:
|
||||
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:
|
||||
GOARCH=amd64 GOOS=windows GOAMD64=v3 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.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
|
||||
#windows-arm64:
|
||||
# $(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
|
||||
|
||||
gz_releases=$(addsuffix .gz, $(PLATFORM_LIST))
|
||||
zip_releases=$(addsuffix .zip, $(WINDOWS_ARCH_LIST))
|
||||
@ -130,14 +81,17 @@ all-arch: $(PLATFORM_LIST) $(WINDOWS_ARCH_LIST)
|
||||
releases: $(gz_releases) $(zip_releases)
|
||||
|
||||
vet:
|
||||
go test ./...
|
||||
$(GOCMD) test -tags build_local ./...
|
||||
|
||||
lint:
|
||||
GOOS=darwin golangci-lint run ./...
|
||||
GOOS=windows golangci-lint run ./...
|
||||
GOOS=linux golangci-lint run ./...
|
||||
GOOS=freebsd golangci-lint run ./...
|
||||
GOOS=openbsd golangci-lint run ./...
|
||||
golangci-lint run --build-tags=build_local ./...
|
||||
|
||||
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)
|
108
README.md
108
README.md
@ -36,6 +36,29 @@
|
||||
Documentations are now moved to [GitHub Wiki](https://github.com/Dreamacro/clash/wiki).
|
||||
|
||||
## 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
|
||||
```yaml
|
||||
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).
|
||||
```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:
|
||||
# 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
|
||||
- DOMAIN-SUFFIX,example.com,DIRECT,tcp
|
||||
- DOMAIN-SUFFIX,example.com,REJECT,udp
|
||||
@ -209,6 +245,76 @@ rules:
|
||||
- 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
|
||||
Support outbound protocol `VLESS`.
|
||||
|
||||
@ -363,7 +469,7 @@ external-ui: dashboard
|
||||
Open [http://127.0.0.1:9090/ui/](http://127.0.0.1:9090/ui/) by web browser.
|
||||
|
||||
## Plus Pro Release
|
||||
[Release](https://github.com/yaling888/clash/releases/tag/plus)
|
||||
[Release](https://github.com/yaling888/clash/releases/tag/plus_pro)
|
||||
|
||||
## Development
|
||||
If you want to build an application that uses clash as a library, check out the the [GitHub Wiki](https://github.com/Dreamacro/clash/wiki/use-clash-as-a-library)
|
||||
|
@ -9,6 +9,7 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/Dreamacro/clash/common/convert"
|
||||
"github.com/Dreamacro/clash/component/dialer"
|
||||
"github.com/Dreamacro/clash/component/resolver"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
@ -115,6 +116,9 @@ func (v *Vmess) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
||||
} else if host := wsOpts.Headers.Get("Host"); host != "" {
|
||||
wsOpts.TLSConfig.ServerName = host
|
||||
}
|
||||
} else {
|
||||
wsOpts.Headers.Set("Host", convert.RandHost())
|
||||
convert.SetUserAgent(wsOpts.Headers)
|
||||
}
|
||||
c, err = vmess.StreamWebsocketConn(c, wsOpts)
|
||||
case "http":
|
||||
@ -134,6 +138,9 @@ func (v *Vmess) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
||||
if err != nil {
|
||||
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)
|
||||
|
@ -308,7 +308,7 @@ func proxiesParseAndFilter(filter string, filterReg *regexp.Regexp, forceCertVer
|
||||
if err := yaml.Unmarshal(buf, schema); err != nil {
|
||||
proxies, err1 := convert.ConvertsV2Ray(buf)
|
||||
if err1 != nil {
|
||||
return nil, fmt.Errorf("%s, %w", err.Error(), err1)
|
||||
return nil, fmt.Errorf("%w, %s", err, err1.Error())
|
||||
}
|
||||
schema.Proxies = proxies
|
||||
}
|
||||
|
@ -72,9 +72,7 @@ func (h *HTTPVehicle) Read() ([]byte, error) {
|
||||
req.SetBasicAuth(user.Username(), password)
|
||||
}
|
||||
|
||||
if req.UserAgent() == "" {
|
||||
convert.SetUserAgent(req)
|
||||
}
|
||||
convert.SetUserAgent(req.Header)
|
||||
|
||||
req = req.WithContext(ctx)
|
||||
|
||||
|
@ -62,7 +62,7 @@ func ConvertsV2Ray(buf []byte) ([]map[string]any, error) {
|
||||
continue
|
||||
}
|
||||
|
||||
q := urlTrojan.Query()
|
||||
query := urlTrojan.Query()
|
||||
|
||||
name := uniqueName(names, urlTrojan.Fragment)
|
||||
trojan := make(map[string]any, 20)
|
||||
@ -75,12 +75,12 @@ func ConvertsV2Ray(buf []byte) ([]map[string]any, error) {
|
||||
trojan["udp"] = true
|
||||
trojan["skip-cert-verify"] = false
|
||||
|
||||
sni := q.Get("sni")
|
||||
sni := query.Get("sni")
|
||||
if sni != "" {
|
||||
trojan["sni"] = sni
|
||||
}
|
||||
|
||||
network := strings.ToLower(q.Get("type"))
|
||||
network := strings.ToLower(query.Get("type"))
|
||||
if network != "" {
|
||||
trojan["network"] = network
|
||||
}
|
||||
@ -92,7 +92,7 @@ func ConvertsV2Ray(buf []byte) ([]map[string]any, error) {
|
||||
headers["Host"] = RandHost()
|
||||
headers["User-Agent"] = RandUserAgent()
|
||||
|
||||
wsOpts["path"] = q.Get("path")
|
||||
wsOpts["path"] = query.Get("path")
|
||||
wsOpts["headers"] = headers
|
||||
|
||||
trojan["ws-opts"] = wsOpts
|
||||
@ -267,7 +267,7 @@ func ConvertsV2Ray(buf []byte) ([]map[string]any, error) {
|
||||
continue
|
||||
}
|
||||
|
||||
q := urlVless.Query()
|
||||
query := urlVless.Query()
|
||||
|
||||
name := uniqueName(names, urlVless.Fragment)
|
||||
vless := make(map[string]any, 20)
|
||||
@ -280,17 +280,17 @@ func ConvertsV2Ray(buf []byte) ([]map[string]any, error) {
|
||||
vless["udp"] = true
|
||||
vless["skip-cert-verify"] = false
|
||||
|
||||
sni := q.Get("sni")
|
||||
sni := query.Get("sni")
|
||||
if sni != "" {
|
||||
vless["servername"] = sni
|
||||
}
|
||||
|
||||
flow := strings.ToLower(q.Get("flow"))
|
||||
flow := strings.ToLower(query.Get("flow"))
|
||||
if flow != "" {
|
||||
vless["flow"] = flow
|
||||
}
|
||||
|
||||
network := strings.ToLower(q.Get("type"))
|
||||
network := strings.ToLower(query.Get("type"))
|
||||
if network != "" {
|
||||
vless["network"] = network
|
||||
}
|
||||
@ -302,7 +302,7 @@ func ConvertsV2Ray(buf []byte) ([]map[string]any, error) {
|
||||
headers["Host"] = RandHost()
|
||||
headers["User-Agent"] = RandUserAgent()
|
||||
|
||||
wsOpts["path"] = q.Get("path")
|
||||
wsOpts["path"] = query.Get("path")
|
||||
wsOpts["headers"] = headers
|
||||
|
||||
vless["ws-opts"] = wsOpts
|
||||
@ -335,7 +335,7 @@ func uniqueName(names map[string]int, name string) string {
|
||||
if index, ok := names[name]; ok {
|
||||
index++
|
||||
names[name] = index
|
||||
name = name + "-" + fmt.Sprintf("%02d", index)
|
||||
name = fmt.Sprintf("%s-%02d", name, index)
|
||||
} else {
|
||||
index = 0
|
||||
names[name] = index
|
||||
|
@ -29,7 +29,6 @@ var hostsSuffix = []string{
|
||||
".alidns.com",
|
||||
".cdngslb.com",
|
||||
".mxhichina.com",
|
||||
".alibabadns.com",
|
||||
}
|
||||
|
||||
var userAgents = []string{
|
||||
@ -307,7 +306,10 @@ func RandUserAgent() string {
|
||||
return userAgents[rand.Intn(uaLen)]
|
||||
}
|
||||
|
||||
func SetUserAgent(req *http.Request) {
|
||||
userAgent := RandUserAgent()
|
||||
req.Header.Set("User-Agent", userAgent)
|
||||
func SetUserAgent(header http.Header) {
|
||||
if header.Get("User-Agent") != "" {
|
||||
return
|
||||
}
|
||||
userAgent := RandUserAgent()
|
||||
header.Set("User-Agent", userAgent)
|
||||
}
|
||||
|
@ -2,9 +2,11 @@ package geodata
|
||||
|
||||
import (
|
||||
"github.com/Dreamacro/clash/component/geodata/router"
|
||||
|
||||
"golang.org/x/exp/maps"
|
||||
)
|
||||
|
||||
func LoadGeoSiteMatcher(countryCode string) (*router.DomainMatcher, int, error) {
|
||||
func loadGeoSiteMatcher(countryCode string) (*router.DomainMatcher, int, error) {
|
||||
geoLoaderName := "standard"
|
||||
geoLoader, err := GetGeoDataLoader(geoLoaderName)
|
||||
if err != nil {
|
||||
@ -28,3 +30,33 @@ func LoadGeoSiteMatcher(countryCode string) (*router.DomainMatcher, int, error)
|
||||
|
||||
return matcher, len(domains), nil
|
||||
}
|
||||
|
||||
var ruleProviders = make(map[string]*router.DomainMatcher)
|
||||
|
||||
// HasProvider has geo site provider by county code
|
||||
func HasProvider(countyCode string) (ok bool) {
|
||||
_, ok = ruleProviders[countyCode]
|
||||
return ok
|
||||
}
|
||||
|
||||
// GetProvidersList get geo site providers
|
||||
func GetProvidersList(countyCode string) []*router.DomainMatcher {
|
||||
return maps.Values(ruleProviders)
|
||||
}
|
||||
|
||||
// GetProviderByCode get geo site provider by county code
|
||||
func GetProviderByCode(countyCode string) (matcher *router.DomainMatcher, ok bool) {
|
||||
matcher, ok = ruleProviders[countyCode]
|
||||
return
|
||||
}
|
||||
|
||||
func LoadProviderByCode(countyCode string) (matcher *router.DomainMatcher, count int, err error) {
|
||||
var ok bool
|
||||
matcher, ok = ruleProviders[countyCode]
|
||||
if !ok {
|
||||
if matcher, count, err = loadGeoSiteMatcher(countyCode); err == nil {
|
||||
ruleProviders[countyCode] = matcher
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
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
|
||||
}
|
238
config/config.go
238
config/config.go
@ -7,6 +7,7 @@ import (
|
||||
"net/netip"
|
||||
"net/url"
|
||||
"os"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
@ -19,6 +20,8 @@ import (
|
||||
"github.com/Dreamacro/clash/component/fakeip"
|
||||
"github.com/Dreamacro/clash/component/geodata"
|
||||
"github.com/Dreamacro/clash/component/geodata/router"
|
||||
_ "github.com/Dreamacro/clash/component/geodata/standard"
|
||||
S "github.com/Dreamacro/clash/component/script"
|
||||
"github.com/Dreamacro/clash/component/trie"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
providerTypes "github.com/Dreamacro/clash/constant/provider"
|
||||
@ -106,6 +109,13 @@ type Tun struct {
|
||||
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
|
||||
type IPTables struct {
|
||||
Enable bool `yaml:"enable" json:"enable"`
|
||||
@ -131,6 +141,7 @@ type Config struct {
|
||||
Hosts *trie.DomainTrie[netip.Addr]
|
||||
Profile *Profile
|
||||
Rules []C.Rule
|
||||
RuleProviders map[string]C.Rule
|
||||
Users []auth.AuthUser
|
||||
Proxies map[string]C.Proxy
|
||||
Providers map[string]providerTypes.ProxyProvider
|
||||
@ -197,6 +208,7 @@ type RawConfig struct {
|
||||
Proxy []map[string]any `yaml:"proxies"`
|
||||
ProxyGroup []map[string]any `yaml:"proxy-groups"`
|
||||
Rule []string `yaml:"rules"`
|
||||
Script Script `yaml:"script"`
|
||||
}
|
||||
|
||||
// Parse config
|
||||
@ -304,11 +316,18 @@ func ParseRawConfig(rawCfg *RawConfig) (*Config, error) {
|
||||
config.Proxies = proxies
|
||||
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 {
|
||||
return nil, err
|
||||
}
|
||||
config.Rules = rules
|
||||
config.RuleProviders = ruleProviders
|
||||
|
||||
hosts, err := parseHosts(rawCfg)
|
||||
if err != nil {
|
||||
@ -316,7 +335,7 @@ func ParseRawConfig(rawCfg *RawConfig) (*Config, error) {
|
||||
}
|
||||
config.Hosts = hosts
|
||||
|
||||
dnsCfg, err := parseDNS(rawCfg, hosts, rules)
|
||||
dnsCfg, err := parseDNS(rawCfg, hosts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -492,10 +511,15 @@ func parseProxies(cfg *RawConfig) (proxies map[string]C.Proxy, providersMap map[
|
||||
return proxies, providersMap, nil
|
||||
}
|
||||
|
||||
func parseRules(cfg *RawConfig, proxies map[string]C.Proxy) ([]C.Rule, error) {
|
||||
rulesConfig := cfg.Rule
|
||||
func parseRules(cfg *RawConfig, proxies map[string]C.Proxy) ([]C.Rule, map[string]C.Rule, error) {
|
||||
var (
|
||||
rules []C.Rule
|
||||
providerNames []string
|
||||
foundRP bool
|
||||
|
||||
var rules []C.Rule
|
||||
ruleProviders = map[string]C.Rule{}
|
||||
rulesConfig = cfg.Rule
|
||||
)
|
||||
|
||||
// parse rules
|
||||
for idx, line := range rulesConfig {
|
||||
@ -510,7 +534,7 @@ func parseRules(cfg *RawConfig, proxies map[string]C.Proxy) ([]C.Rule, error) {
|
||||
l := len(rule)
|
||||
|
||||
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 {
|
||||
@ -529,23 +553,40 @@ func parseRules(cfg *RawConfig, proxies map[string]C.Proxy) ([]C.Rule, error) {
|
||||
target = rule[l-1]
|
||||
params = rule[l:]
|
||||
|
||||
if _, ok := proxies[target]; !ok {
|
||||
return nil, fmt.Errorf("rules[%d] [%s] error: proxy [%s] not found", idx, line, target)
|
||||
if _, ok := proxies[target]; !ok && ruleName != "GEOSITE" && target != C.ScriptRuleGeoSiteTarget {
|
||||
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)
|
||||
|
||||
parsed, parseErr := R.ParseRule(ruleName, payload, target, params)
|
||||
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)
|
||||
}
|
||||
|
||||
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()
|
||||
|
||||
return rules, nil
|
||||
return rules, ruleProviders, nil
|
||||
}
|
||||
|
||||
func parseHosts(cfg *RawConfig) (*trie.DomainTrie[netip.Addr], error) {
|
||||
@ -674,37 +715,27 @@ func parseFallbackIPCIDR(ips []string) ([]*netip.Prefix, error) {
|
||||
return ipNets, nil
|
||||
}
|
||||
|
||||
func parseFallbackGeoSite(countries []string, rules []C.Rule) ([]*router.DomainMatcher, error) {
|
||||
func parseFallbackGeoSite(countries []string) ([]*router.DomainMatcher, error) {
|
||||
var sites []*router.DomainMatcher
|
||||
|
||||
for _, country := range countries {
|
||||
found := false
|
||||
for _, rule := range rules {
|
||||
if rule.RuleType() == C.GEOSITE {
|
||||
if strings.EqualFold(country, rule.Payload()) {
|
||||
found = true
|
||||
sites = append(sites, rule.(C.RuleGeoSite).GetDomainMatcher())
|
||||
log.Infoln("Start initial GeoSite dns fallback filter from rule `%s`", country)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
matcher, recordsCount, err := geodata.LoadGeoSiteMatcher(country)
|
||||
matcher, recordsCount, err := geodata.LoadProviderByCode(country)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sites = append(sites, matcher)
|
||||
|
||||
log.Infoln("Start initial GeoSite dns fallback filter `%s`, records: %d", country, recordsCount)
|
||||
cont := fmt.Sprintf("%d", recordsCount)
|
||||
if recordsCount == 0 {
|
||||
cont = "from cache"
|
||||
}
|
||||
log.Infoln("Start initial GeoSite dns fallback filter `%s`, records: %s", country, cont)
|
||||
}
|
||||
runtime.GC()
|
||||
return sites, nil
|
||||
}
|
||||
|
||||
func parseDNS(rawCfg *RawConfig, hosts *trie.DomainTrie[netip.Addr], rules []C.Rule) (*DNS, error) {
|
||||
func parseDNS(rawCfg *RawConfig, hosts *trie.DomainTrie[netip.Addr]) (*DNS, error) {
|
||||
cfg := rawCfg.DNS
|
||||
if cfg.Enable && len(cfg.NameServer) == 0 {
|
||||
return nil, fmt.Errorf("if DNS configuration is turned on, NameServer cannot be empty")
|
||||
@ -798,7 +829,7 @@ func parseDNS(rawCfg *RawConfig, hosts *trie.DomainTrie[netip.Addr], rules []C.R
|
||||
dnsCfg.FallbackFilter.IPCIDR = fallbackip
|
||||
}
|
||||
dnsCfg.FallbackFilter.Domain = cfg.FallbackFilter.Domain
|
||||
fallbackGeoSite, err := parseFallbackGeoSite(cfg.FallbackFilter.GeoSite, rules)
|
||||
fallbackGeoSite, err := parseFallbackGeoSite(cfg.FallbackFilter.GeoSite)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("load GeoSite dns fallback filter error, %w", err)
|
||||
}
|
||||
@ -822,6 +853,157 @@ func parseAuthentication(rawRecords []string) []auth.AuthUser {
|
||||
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) {
|
||||
var (
|
||||
req []C.Rewrite
|
||||
|
@ -119,7 +119,7 @@ func doGet(url string) (resp *http.Response, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
convert.SetUserAgent(req)
|
||||
convert.SetUserAgent(req.Header)
|
||||
|
||||
resp, err = http.DefaultClient.Do(req)
|
||||
return
|
||||
|
@ -6,7 +6,6 @@ import (
|
||||
"runtime"
|
||||
|
||||
"github.com/Dreamacro/clash/component/geodata"
|
||||
_ "github.com/Dreamacro/clash/component/geodata/standard"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
|
||||
"github.com/oschwald/geoip2-golang"
|
||||
|
@ -22,6 +22,7 @@ var Path = func() *path {
|
||||
type path struct {
|
||||
homeDir string
|
||||
configFile string
|
||||
scriptDir string
|
||||
}
|
||||
|
||||
// SetHomeDir is used to set the configuration path
|
||||
@ -71,6 +72,32 @@ func (p *path) GeoSite() string {
|
||||
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 {
|
||||
return p.Resolve("mitm_ca.crt")
|
||||
}
|
||||
|
@ -13,6 +13,7 @@ const (
|
||||
DstPort
|
||||
Process
|
||||
ProcessPath
|
||||
Script
|
||||
UserAgent
|
||||
MATCH
|
||||
)
|
||||
@ -43,6 +44,8 @@ func (rt RuleType) String() string {
|
||||
return "Process"
|
||||
case ProcessPath:
|
||||
return "ProcessPath"
|
||||
case Script:
|
||||
return "Script"
|
||||
case UserAgent:
|
||||
return "UserAgent"
|
||||
case MATCH:
|
||||
|
@ -7,6 +7,8 @@ import (
|
||||
"github.com/Dreamacro/clash/component/geodata/router"
|
||||
)
|
||||
|
||||
const ScriptRuleGeoSiteTarget = "__WhateverTarget__"
|
||||
|
||||
type RuleExtra struct {
|
||||
Network NetWork
|
||||
SourceIPs []*netip.Prefix
|
||||
|
@ -15,6 +15,7 @@ import (
|
||||
"github.com/Dreamacro/clash/component/profile"
|
||||
"github.com/Dreamacro/clash/component/profile/cachefile"
|
||||
"github.com/Dreamacro/clash/component/resolver"
|
||||
S "github.com/Dreamacro/clash/component/script"
|
||||
"github.com/Dreamacro/clash/component/trie"
|
||||
"github.com/Dreamacro/clash/config"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
@ -79,6 +80,7 @@ func ApplyConfig(cfg *config.Config, force bool) {
|
||||
updateUsers(cfg.Users)
|
||||
updateProxies(cfg.Proxies, cfg.Providers)
|
||||
updateRules(cfg.Rules)
|
||||
updateRuleProviders(cfg.RuleProviders)
|
||||
updateHosts(cfg.Hosts)
|
||||
updateMitm(cfg.Mitm)
|
||||
updateProfile(cfg)
|
||||
@ -190,6 +192,10 @@ func updateRules(rules []C.Rule) {
|
||||
tunnel.UpdateRules(rules)
|
||||
}
|
||||
|
||||
func updateRuleProviders(providers map[string]C.Rule) {
|
||||
S.UpdateRuleProviders(providers)
|
||||
}
|
||||
|
||||
func updateGeneral(general *config.General, force bool) {
|
||||
tunnel.SetMode(general.Mode)
|
||||
resolver.DisableIPv6 = !general.IPv6
|
||||
@ -338,6 +344,7 @@ func updateMitm(mitm *config.Mitm) {
|
||||
|
||||
func Shutdown() {
|
||||
P.Cleanup()
|
||||
S.Py_Finalize()
|
||||
tproxy.CleanupTProxyIPTables()
|
||||
resolver.StoreFakePoolState()
|
||||
|
||||
|
@ -169,13 +169,13 @@ func updateConfigs(w http.ResponseWriter, r *http.Request) {
|
||||
var err error
|
||||
|
||||
if req.Payload != "" {
|
||||
log.Warnln("[REST-API] update config by payload")
|
||||
cfg, err = executor.ParseWithBytes([]byte(req.Payload))
|
||||
if err != nil {
|
||||
render.Status(r, http.StatusBadRequest)
|
||||
render.JSON(w, r, newError(err.Error()))
|
||||
return
|
||||
}
|
||||
log.Warnln("[REST-API] update config by payload")
|
||||
} else {
|
||||
if req.Path == "" {
|
||||
req.Path = constant.Path.Config()
|
||||
@ -186,13 +186,13 @@ func updateConfigs(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
log.Warnln("[REST-API] reload config from path: %s", req.Path)
|
||||
cfg, err = executor.ParseWithPath(req.Path)
|
||||
if err != nil {
|
||||
render.Status(r, http.StatusBadRequest)
|
||||
render.JSON(w, r, newError(err.Error()))
|
||||
return
|
||||
}
|
||||
log.Warnln("[REST-API] reload config from path: %s", req.Path)
|
||||
}
|
||||
|
||||
executor.ApplyConfig(cfg, force)
|
||||
|
@ -132,7 +132,7 @@ func authentication(next http.Handler) http.Handler {
|
||||
}
|
||||
|
||||
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) {
|
||||
|
@ -48,31 +48,6 @@ import (
|
||||
//go:linkname procWintunStartSession golang.zx2c4.com/wintun.procWintunStartSession
|
||||
|
||||
var (
|
||||
modwintun *lazyDLL
|
||||
procWintunCreateAdapter *lazyProc
|
||||
procWintunOpenAdapter *lazyProc
|
||||
procWintunCloseAdapter *lazyProc
|
||||
procWintunDeleteDriver *lazyProc
|
||||
procWintunGetAdapterLUID *lazyProc
|
||||
procWintunGetRunningDriverVersion *lazyProc
|
||||
procWintunAllocateSendPacket *lazyProc
|
||||
procWintunEndSession *lazyProc
|
||||
procWintunGetReadWaitEvent *lazyProc
|
||||
procWintunReceivePacket *lazyProc
|
||||
procWintunReleaseReceivePacket *lazyProc
|
||||
procWintunSendPacket *lazyProc
|
||||
procWintunStartSession *lazyProc
|
||||
)
|
||||
|
||||
type loggerLevel int
|
||||
|
||||
const (
|
||||
logInfo loggerLevel = iota
|
||||
logWarn
|
||||
logErr
|
||||
)
|
||||
|
||||
func init() {
|
||||
modwintun = newLazyDLL("wintun.dll", setupLogger)
|
||||
procWintunCreateAdapter = modwintun.NewProc("WintunCreateAdapter")
|
||||
procWintunOpenAdapter = modwintun.NewProc("WintunOpenAdapter")
|
||||
@ -87,7 +62,15 @@ func init() {
|
||||
procWintunReleaseReceivePacket = modwintun.NewProc("WintunReleaseReceivePacket")
|
||||
procWintunSendPacket = modwintun.NewProc("WintunSendPacket")
|
||||
procWintunStartSession = modwintun.NewProc("WintunStartSession")
|
||||
}
|
||||
)
|
||||
|
||||
type loggerLevel int
|
||||
|
||||
const (
|
||||
logInfo loggerLevel = iota
|
||||
logWarn
|
||||
logErr
|
||||
)
|
||||
|
||||
func InitWintun() (err error) {
|
||||
defer func() {
|
||||
|
2
main.go
2
main.go
@ -48,7 +48,7 @@ func init() {
|
||||
func main() {
|
||||
_, _ = maxprocs.Set(maxprocs.Logger(func(string, ...any) {}))
|
||||
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
|
||||
}
|
||||
|
||||
|
@ -5,7 +5,6 @@ import (
|
||||
|
||||
"github.com/Dreamacro/clash/component/geodata"
|
||||
"github.com/Dreamacro/clash/component/geodata/router"
|
||||
_ "github.com/Dreamacro/clash/component/geodata/standard"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/log"
|
||||
)
|
||||
@ -47,12 +46,19 @@ func (gs *GEOSITE) GetDomainMatcher() *router.DomainMatcher {
|
||||
}
|
||||
|
||||
func NewGEOSITE(country string, adapter string) (*GEOSITE, error) {
|
||||
matcher, recordsCount, err := geodata.LoadGeoSiteMatcher(country)
|
||||
matcher, recordsCount, err := geodata.LoadProviderByCode(country)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("load GeoSite data error, %s", err.Error())
|
||||
}
|
||||
|
||||
log.Infoln("Start initial GeoSite rule %s => %s, records: %d", country, adapter, recordsCount)
|
||||
cont := fmt.Sprintf("%d", recordsCount)
|
||||
if recordsCount == 0 {
|
||||
cont = "from cache"
|
||||
}
|
||||
if adapter == C.ScriptRuleGeoSiteTarget {
|
||||
adapter = "Script"
|
||||
}
|
||||
log.Infoln("Start initial GeoSite rule %s => %s, records: %s", country, adapter, cont)
|
||||
|
||||
geoSite := &GEOSITE{
|
||||
Base: &Base{},
|
||||
|
@ -37,6 +37,8 @@ func ParseRule(tp, payload, target string, params []string) (C.Rule, error) {
|
||||
parsed, parseErr = NewProcess(payload, target, true)
|
||||
case "PROCESS-PATH":
|
||||
parsed, parseErr = NewProcess(payload, target, false)
|
||||
case "SCRIPT":
|
||||
parsed, parseErr = NewScript(payload, target)
|
||||
case "USER-AGENT":
|
||||
parsed, parseErr = NewUserAgent(payload, target)
|
||||
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/miekg/dns v1.1.49
|
||||
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 => ../
|
||||
@ -37,20 +37,20 @@ require (
|
||||
go.etcd.io/bbolt v1.3.6 // indirect
|
||||
go.uber.org/atomic v1.9.0 // 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/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/text v0.3.8-0.20220124021120-d1c84af989ab // indirect
|
||||
golang.org/x/time v0.0.0-20220411224347-583f2d630306 // indirect
|
||||
golang.org/x/tools v0.1.10 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // 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
|
||||
google.golang.org/protobuf v1.28.0 // indirect
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // 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-20220525230936-793ad666bf5e h1:T8NU3HyQ8ClP4SEE+KbFlg6n0NhuTsN4MyznaarGsZM=
|
||||
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-20220518171630-0b5c67f07fdf/go.mod h1:yh0Ynu2b5ZUe3MQfp2nM0ecK7wsgouWTDN0FNeJuIys=
|
||||
golang.org/x/exp v0.0.0-20220602145555-4a0574d9293f h1:KK6mxegmt5hGJRcAnEDjSNLxIRhZxDcgwMbcO/lMCRM=
|
||||
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.3.0/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-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-20220526153639-5463443f8c37 h1:lUkvobShwKsOesNfWWlCS5q7fnbG1MEliIzwu886fn8=
|
||||
golang.org/x/net v0.0.0-20220526153639-5463443f8c37/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.0.0-20220531201128-c960675eff93 h1:MYimHLfoXEpOhqd/zgoA/uoXzHB86AEky4LAx5ij9xA=
|
||||
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-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-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-20220513210516-0976fa681c29/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/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-20190312061237-fead79001313/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.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/wireguard v0.0.0-20220407013110-ef5c587f782d h1:q4JksJ2n0fmbXC0Aj0eOs6E0AcPqnKglxWXWFqGD6x0=
|
||||
golang.zx2c4.com/wireguard v0.0.0-20220407013110-ef5c587f782d/go.mod h1:bVQfyl2sCM/QIIGHpWbFGfHPuDvqnCNkT6MQLTCjO/U=
|
||||
golang.zx2c4.com/wireguard v0.0.0-20220601130007-6a08d81f6bc4 h1:QlbNZ9SwDAepRQwgeWHLi3rfEMo/kVEU4SmgsNM7HmQ=
|
||||
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/go.mod h1:1CeiatTZwcwSFA3cAtMm8CQoroviTldnxd7DOgM/vI4=
|
||||
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.1.0 h1:rVV8Tcg/8jHUkPUorwjaMTtemIMVXfIPKiOqnhEhakk=
|
||||
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-20220527053002-8ab279227ac8/go.mod h1:TIvkJD0sxe8pIob3p6T8IzxXunlp6yfgktvTNp+DGNM=
|
||||
gvisor.dev/gvisor v0.0.0-20220601233344-46e478629075 h1:ucwwit0X39HmMQ1iSeNCIXw4g/B8bi+O6TSxxDbPs9E=
|
||||
gvisor.dev/gvisor v0.0.0-20220601233344-46e478629075/go.mod h1:TIvkJD0sxe8pIob3p6T8IzxXunlp6yfgktvTNp+DGNM=
|
||||
|
@ -9,6 +9,7 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/Dreamacro/clash/common/convert"
|
||||
"github.com/Dreamacro/clash/common/pool"
|
||||
)
|
||||
|
||||
@ -118,7 +119,7 @@ func (c *httpConn) Write(b []byte) (int, error) {
|
||||
buf.WriteString(body + "\r\n\r\n")
|
||||
} else {
|
||||
buf.WriteString("User-Agent: ")
|
||||
buf.WriteString(userAgent[rand.Intn(len(userAgent))])
|
||||
buf.WriteString(convert.RandUserAgent())
|
||||
buf.WriteString("\r\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\nAccept-Language: en-US,en;q=0.8\r\nAccept-Encoding: gzip, deflate\r\n")
|
||||
if c.post {
|
||||
packBoundary(buf)
|
||||
@ -150,256 +151,3 @@ func packBoundary(buf *bytes.Buffer) {
|
||||
}
|
||||
buf.WriteString("\r\n")
|
||||
}
|
||||
|
||||
var userAgent = []string{
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.162 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.85 Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 7.0; Moto C Build/NRD90M.059) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 6.0.1; SM-G532M Build/MMB29T; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/55.0.2883.91 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.80 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.101 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.2; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/40.0.2214.111 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.94 Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 5.1.1; SM-J120M Build/LMY47X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.80 Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 7.0; Moto G (5) Build/NPPS25.137-93-14) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.106 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 7.0; SM-G570M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.80 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 5.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.80 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.112 Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 6.0; CAM-L03 Build/HUAWEICAM-L03) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.76 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.117 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/534.3 (KHTML, like Gecko) Chrome/6.0.472.63 Safari/534.3",
|
||||
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.106 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/534.7 (KHTML, like Gecko) Chrome/7.0.517.44 Safari/534.7",
|
||||
"Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.75 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.71 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/534.3 (KHTML, like Gecko) Chrome/6.0.472.63 Safari/534.3",
|
||||
"Mozilla/5.0 (Linux; Android 8.0.0; FIG-LX3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.80 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/40.0.2214.115 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/534.10 (KHTML, like Gecko) Chrome/8.0.552.237 Safari/534.10",
|
||||
"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36",
|
||||
"Mozilla/5.0 (X11; U; Linux x86_64; en-US) AppleWebKit/533.2 (KHTML, like Gecko) Chrome/5.0.342.1 Safari/533.2",
|
||||
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.110 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.89 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.81 Safari/537.36",
|
||||
"Mozilla/5.0 (X11; Datanyze; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.71 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.1916.153 Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 5.1.1; SM-J111M Build/LMY47V) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.106 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/37.0.2062.120 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.84 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.99 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/32.0.1700.107 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 6.0.1; SM-J700M Build/MMB29K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.63 Safari/537.36",
|
||||
"Mozilla/5.0 (X11; Linux i686) AppleWebKit/534.30 (KHTML, like Gecko) Slackware/Chrome/12.0.742.100 Safari/534.30",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.86 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.167 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/48.0.2564.116 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.102 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.143 Safari/537.36",
|
||||
"Mozilla/5.0 (X11; Linux i686) AppleWebKit/534.30 (KHTML, like Gecko) Chrome/12.0.742.100 Safari/534.30",
|
||||
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 8.0.0; WAS-LX3 Build/HUAWEIWAS-LX3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.99 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.87 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.102 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.57 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.101 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.1805 Safari/537.36 MVisionPlayer/1.0.0.0",
|
||||
"Mozilla/5.0 (Linux; Android 7.0; TRT-LX3 Build/HUAWEITRT-LX3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.89 Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 6.0; vivo 1610 Build/MMB29M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.124 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.2; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.87 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.111 Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 4.4.2; de-de; SAMSUNG GT-I9195 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Version/1.5 Chrome/28.0.1500.94 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.95 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.90 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.110 Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 8.0.0; ANE-LX3 Build/HUAWEIANE-LX3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.112 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.87 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.143 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36",
|
||||
"Mozilla/5.0 (X11; U; Linux i586; en-US) AppleWebKit/533.2 (KHTML, like Gecko) Chrome/5.0.342.1 Safari/533.2",
|
||||
"Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/43.0.2357.65 Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 7.0; SM-G610M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.80 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 6.0.1; SM-J500M Build/MMB29M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/534.7 (KHTML, like Gecko) Chrome/7.0.517.44 Safari/534.7",
|
||||
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.104 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 6.0; vivo 1606 Build/MMB29M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.124 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36",
|
||||
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.135 Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 7.0; SM-G610M Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 7.1; vivo 1716 Build/N2G47H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.98 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.93 Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 7.0; SM-G570M Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 6.0; MYA-L22 Build/HUAWEIMYA-L22) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.84 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 5.1; A1601 Build/LMY47I) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.98 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 7.0; TRT-LX2 Build/HUAWEITRT-LX2; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/59.0.3071.125 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.2; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 5.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/534.17 (KHTML, like Gecko) Chrome/10.0.649.0 Safari/534.17",
|
||||
"Mozilla/5.0 (Linux; Android 6.0; CAM-L21 Build/HUAWEICAM-L21; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/62.0.3202.84 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.94 Safari/537.36",
|
||||
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/534.24 (KHTML, like Gecko) Chrome/11.0.696.3 Safari/534.24",
|
||||
"Mozilla/5.0 (Linux; Android 7.1.2; Redmi 4X Build/N2G47H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.111 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.106 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.181 Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 4.4.2; SM-G7102 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.84 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/48.0.2564.109 Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 5.1; HUAWEI CUN-L22 Build/HUAWEICUN-L22; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/62.0.3202.84 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 5.1.1; A37fw Build/LMY47V) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.84 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 7.0; SM-J730GM Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.111 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 7.0; SM-G610F Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.111 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.101 Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 7.1.2; Redmi Note 5A Build/N2G47H; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/63.0.3239.111 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 7.0; Redmi Note 4 Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.111 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.106 Safari/537.36",
|
||||
"Mozilla/5.0 (Unknown; Linux) AppleWebKit/538.1 (KHTML, like Gecko) Chrome/v1.0.0 Safari/538.1",
|
||||
"Mozilla/5.0 (Linux; Android 7.0; BLL-L22 Build/HUAWEIBLL-L22) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.91 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 7.0; SM-J710F Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.84 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 6.0.1; SM-G532M Build/MMB29T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.91 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 7.1.1; CPH1723 Build/N6F26Q) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.98 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.118 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.79 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.101 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.94 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.139 Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 8.0.0; FIG-LX3 Build/HUAWEIFIG-LX3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Windows; U; Windows NT 6.1; de-DE) AppleWebKit/534.17 (KHTML, like Gecko) Chrome/10.0.649.0 Safari/534.17",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.63 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2228.0 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.67 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.94 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.99 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.67 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.67 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.67 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/43.0.2357.65 Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 7.1; Mi A1 Build/N2G47H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.83 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.117 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/533.4 (KHTML, like Gecko) Chrome/5.0.375.99 Safari/533.4",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/36.0.1985.125 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.89 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.111 Safari/537.36 MVisionPlayer/1.0.0.0",
|
||||
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.103 Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 5.1; A37f Build/LMY47V) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/43.0.2357.93 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.2; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.100 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.86 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.76 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 5.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.80 Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 6.0.1; CPH1607 Build/MMB29M; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/63.0.3239.111 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.99 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.95 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.100 Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 6.0.1; vivo 1603 Build/MMB29M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.83 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.186 Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 6.0.1; SM-G532M Build/MMB29T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.87 Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 6.0.1; Redmi 4A Build/MMB29M; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/60.0.3112.116 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.103 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.112 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36",
|
||||
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/44.0.2403.157 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.71 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 5.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.2; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.90 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.100 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.31 (KHTML, like Gecko) Chrome/26.0.1410.64 Safari/537.31",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.84 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.186 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.80 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/36.0.1985.143 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.112 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.67 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.181 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.106 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 6.0.1; SM-G532G Build/MMB29T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.83 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/48.0.2564.109 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.117 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.139 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.67 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/34.0.1847.131 Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 6.0; vivo 1713 Build/MRA58K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.124 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.89 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.80 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.101 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.67 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36",
|
||||
}
|
||||
|
@ -12,12 +12,14 @@ type TunnelMode int
|
||||
var ModeMapping = map[string]TunnelMode{
|
||||
Global.String(): Global,
|
||||
Rule.String(): Rule,
|
||||
Script.String(): Script,
|
||||
Direct.String(): Direct,
|
||||
}
|
||||
|
||||
const (
|
||||
Global TunnelMode = iota
|
||||
Rule
|
||||
Script
|
||||
Direct
|
||||
)
|
||||
|
||||
@ -61,6 +63,8 @@ func (m TunnelMode) String() string {
|
||||
return "global"
|
||||
case Rule:
|
||||
return "rule"
|
||||
case Script:
|
||||
return "script"
|
||||
case Direct:
|
||||
return "direct"
|
||||
default:
|
||||
|
@ -16,6 +16,7 @@ import (
|
||||
"github.com/Dreamacro/clash/component/nat"
|
||||
P "github.com/Dreamacro/clash/component/process"
|
||||
"github.com/Dreamacro/clash/component/resolver"
|
||||
S "github.com/Dreamacro/clash/component/script"
|
||||
"github.com/Dreamacro/clash/component/trie"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/constant/provider"
|
||||
@ -218,6 +219,8 @@ func resolveMetadata(_ C.PlainContext, metadata *C.Metadata) (proxy C.Proxy, rul
|
||||
proxy = proxies["DIRECT"]
|
||||
case Global:
|
||||
proxy = proxies["GLOBAL"]
|
||||
case Script:
|
||||
proxy, err = matchScript(metadata)
|
||||
// Rule
|
||||
default:
|
||||
proxy, rule, err = match(metadata)
|
||||
@ -299,6 +302,8 @@ func handleUDPConn(packet *inbound.PacketAdapter) {
|
||||
switch true {
|
||||
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())
|
||||
case mode == Script:
|
||||
log.Infoln("[UDP] %s(%s) --> %s using SCRIPT %s", metadata.SourceAddress(), metadata.Process, metadata.RemoteAddress(), rawPc.Chains().String())
|
||||
case mode == Global:
|
||||
log.Infoln("[UDP] %s(%s) --> %s using GLOBAL", metadata.SourceAddress(), metadata.Process, metadata.RemoteAddress())
|
||||
case mode == Direct:
|
||||
@ -366,6 +371,8 @@ func handleTCPConn(connCtx C.ConnContext) {
|
||||
break
|
||||
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())
|
||||
case mode == Script:
|
||||
log.Infoln("[TCP] %s(%s) --> %s using SCRIPT %s", metadata.SourceAddress(), metadata.Process, metadata.RemoteAddress(), remoteConn.Chains().String())
|
||||
case mode == Global:
|
||||
log.Infoln("[TCP] %s(%s) --> %s using GLOBAL", metadata.SourceAddress(), metadata.Process, metadata.RemoteAddress())
|
||||
case mode == Direct:
|
||||
@ -436,3 +443,23 @@ func match(metadata *C.Metadata) (C.Proxy, C.Rule, error) {
|
||||
|
||||
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