Code: refresh code & rebase branch 'with-tun'
This commit is contained in:
parent
2c0890854e
commit
a45354fa08
1
.github/workflows/linter.yml
vendored
1
.github/workflows/linter.yml
vendored
@ -20,3 +20,4 @@ jobs:
|
|||||||
uses: golangci/golangci-lint-action@v3
|
uses: golangci/golangci-lint-action@v3
|
||||||
with:
|
with:
|
||||||
version: latest
|
version: latest
|
||||||
|
args: --build-tags=build_local
|
||||||
|
43
.github/workflows/release.yml
vendored
43
.github/workflows/release.yml
vendored
@ -25,28 +25,65 @@ jobs:
|
|||||||
restore-keys: |
|
restore-keys: |
|
||||||
${{ runner.os }}-go-
|
${{ runner.os }}-go-
|
||||||
|
|
||||||
|
- name: Setup Python
|
||||||
|
uses: actions/setup-python@v2
|
||||||
|
with:
|
||||||
|
python-version: '3.9'
|
||||||
|
|
||||||
- name: Get dependencies, run test
|
- name: Get dependencies, run test
|
||||||
run: |
|
run: |
|
||||||
go test ./...
|
# fetch python cross compile source files
|
||||||
|
mkdir -p bin/python/
|
||||||
|
cd bin/python/
|
||||||
|
curl -LO https://raw.githubusercontent.com/yaling888/snack/main/python-3.9.7-darwin-amd64.tar.xz
|
||||||
|
curl -LO https://raw.githubusercontent.com/yaling888/snack/main/python-3.9.7-darwin-arm64.tar.xz
|
||||||
|
curl -LO https://raw.githubusercontent.com/yaling888/snack/main/python-3.9.7-windows-amd64.tar.xz
|
||||||
|
curl -LO https://raw.githubusercontent.com/yaling888/snack/main/python-3.9.7-windows-386.tar.xz
|
||||||
|
#curl -LO https://raw.githubusercontent.com/yaling888/snack/main/python-3.9.7-linux-amd64.tar.xz
|
||||||
|
#curl -LO https://raw.githubusercontent.com/yaling888/snack/main/python-3.9.7-linux-arm64.tar.xz
|
||||||
|
#curl -LO https://raw.githubusercontent.com/yaling888/snack/main/python-3.9.7-linux-386.tar.xz
|
||||||
|
tar -Jxf python-3.9.7-darwin-amd64.tar.xz
|
||||||
|
tar -Jxf python-3.9.7-darwin-arm64.tar.xz
|
||||||
|
tar -Jxf python-3.9.7-windows-amd64.tar.xz
|
||||||
|
tar -Jxf python-3.9.7-windows-386.tar.xz
|
||||||
|
#tar -Jxf python-3.9.7-linux-amd64.tar.xz
|
||||||
|
#tar -Jxf python-3.9.7-linux-arm64.tar.xz
|
||||||
|
#tar -Jxf python-3.9.7-linux-386.tar.xz
|
||||||
|
rm python-3.9.7-*.tar.xz
|
||||||
|
cd ../../
|
||||||
|
|
||||||
|
# go test
|
||||||
|
go test -tags build_local ./...
|
||||||
|
|
||||||
|
# init xgo
|
||||||
|
docker pull techknowlogick/xgo:latest
|
||||||
|
go install src.techknowlogick.com/xgo@latest
|
||||||
|
|
||||||
- name: SSH connection to Actions
|
- name: SSH connection to Actions
|
||||||
uses: P3TERX/ssh2actions@v1.0.0
|
uses: P3TERX/ssh2actions@v1.0.0
|
||||||
if: github.actor == github.repository_owner && contains(github.event.head_commit.message, '[ssh]')
|
if: github.actor == github.repository_owner && contains(github.event.head_commit.message, '[ssh]')
|
||||||
|
env:
|
||||||
|
SSH_PASSWORD: ${{ secrets.ADAWADLHIOH }}
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
#if: startsWith(github.ref, 'refs/tags/')
|
#if: startsWith(github.ref, 'refs/tags/')
|
||||||
env:
|
env:
|
||||||
NAME: clash
|
NAME: clash
|
||||||
BINDIR: bin
|
BINDIR: bin
|
||||||
run: make -j releases
|
run: |
|
||||||
|
make -j releases
|
||||||
|
#ls -lahF bin/python/
|
||||||
|
|
||||||
- name: Prepare upload
|
- name: Prepare upload
|
||||||
|
if: startsWith(github.ref, 'refs/tags/') == false
|
||||||
run: |
|
run: |
|
||||||
|
rm -rf bin/python/
|
||||||
echo "FILE_DATE=_$(date +"%Y%m%d%H%M")" >> $GITHUB_ENV
|
echo "FILE_DATE=_$(date +"%Y%m%d%H%M")" >> $GITHUB_ENV
|
||||||
echo "FILE_SHA=$(git describe --tags --always 2>/dev/null)" >> $GITHUB_ENV
|
echo "FILE_SHA=$(git describe --tags --always 2>/dev/null)" >> $GITHUB_ENV
|
||||||
|
|
||||||
- name: Upload files to Artifacts
|
- name: Upload files to Artifacts
|
||||||
uses: actions/upload-artifact@v2
|
uses: actions/upload-artifact@v2
|
||||||
|
if: startsWith(github.ref, 'refs/tags/') == false
|
||||||
with:
|
with:
|
||||||
name: clash_${{ env.FILE_SHA }}${{ env.FILE_DATE }}
|
name: clash_${{ env.FILE_SHA }}${{ env.FILE_DATE }}
|
||||||
path: |
|
path: |
|
||||||
@ -71,6 +108,6 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
keep_latest: 1
|
keep_latest: 1
|
||||||
delete_tags: true
|
delete_tags: true
|
||||||
delete_tag_pattern: tun
|
delete_tag_pattern: plus-pro
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
140
Makefile
140
Makefile
@ -1,119 +1,70 @@
|
|||||||
|
GOCMD=go
|
||||||
|
XGOCMD=xgo -go=go-1.18.x
|
||||||
|
GOBUILD=CGO_ENABLED=1 $(GOCMD) build -trimpath
|
||||||
|
GOCLEAN=$(GOCMD) clean
|
||||||
NAME=clash
|
NAME=clash
|
||||||
BINDIR=bin
|
BINDIR=$(shell pwd)/bin
|
||||||
VERSION=$(shell git describe --tags --always 2>/dev/null || date +%F)
|
VERSION=$(shell git describe --tags --always 2>/dev/null || date +%F)
|
||||||
BUILDTIME=$(shell date -u)
|
BUILDTIME=$(shell date -u)
|
||||||
GOBUILD=CGO_ENABLED=0 go build -trimpath -ldflags '-X "github.com/Dreamacro/clash/constant.Version=$(VERSION)" \
|
BUILD_PACKAGE=.
|
||||||
-X "github.com/Dreamacro/clash/constant.BuildTime=$(BUILDTIME)" \
|
RELEASE_LDFLAGS='-X "github.com/Dreamacro/clash/constant.Version=$(VERSION)" \
|
||||||
-w -s -buildid='
|
-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 = \
|
PLATFORM_LIST = \
|
||||||
darwin-amd64 \
|
darwin-amd64 \
|
||||||
darwin-amd64-v3 \
|
|
||||||
darwin-arm64 \
|
darwin-arm64 \
|
||||||
linux-386 \
|
linux-amd64
|
||||||
linux-amd64 \
|
# linux-arm64
|
||||||
linux-amd64-v3 \
|
# linux-386
|
||||||
linux-armv5 \
|
|
||||||
linux-armv6 \
|
|
||||||
linux-armv7 \
|
|
||||||
linux-arm64 \
|
|
||||||
linux-mips-softfloat \
|
|
||||||
linux-mips-hardfloat \
|
|
||||||
linux-mipsle-softfloat \
|
|
||||||
linux-mipsle-hardfloat \
|
|
||||||
linux-mips64 \
|
|
||||||
linux-mips64le \
|
|
||||||
freebsd-386 \
|
|
||||||
freebsd-amd64 \
|
|
||||||
freebsd-amd64-v3 \
|
|
||||||
freebsd-arm64
|
|
||||||
|
|
||||||
WINDOWS_ARCH_LIST = \
|
WINDOWS_ARCH_LIST = \
|
||||||
windows-386 \
|
|
||||||
windows-amd64 \
|
windows-amd64 \
|
||||||
windows-amd64-v3 \
|
windows-386
|
||||||
windows-arm64 \
|
# windows-arm64
|
||||||
windows-arm32v7
|
|
||||||
|
|
||||||
all: linux-amd64 darwin-amd64 windows-amd64 # Most used
|
all: linux-amd64 darwin-amd64 windows-amd64 # Most used
|
||||||
|
|
||||||
docker:
|
local:
|
||||||
$(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
$(GOBUILD) -ldflags $(RELEASE_LDFLAGS) -tags build_local -o $(BINDIR)/$(NAME)-$@
|
||||||
|
|
||||||
|
local-v3:
|
||||||
|
GOAMD64=v3 $(GOBUILD) -ldflags $(RELEASE_LDFLAGS) -tags build_local -o $(BINDIR)/$(NAME)-$@
|
||||||
|
|
||||||
darwin-amd64:
|
darwin-amd64:
|
||||||
GOARCH=amd64 GOOS=darwin $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
$(XGOCMD) -dest=$(BINDIR) -out=$(NAME) -trimpath=true -ldflags=$(RELEASE_LDFLAGS) -targets=darwin-10.12/amd64 $(BUILD_PACKAGE) && \
|
||||||
|
mv $(BINDIR)/$(NAME)-darwin-10.12-amd64 $(BINDIR)/$(NAME)-darwin-amd64
|
||||||
darwin-amd64-v3:
|
|
||||||
GOARCH=amd64 GOOS=darwin GOAMD64=v3 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
|
||||||
|
|
||||||
darwin-arm64:
|
darwin-arm64:
|
||||||
GOARCH=arm64 GOOS=darwin $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
$(XGOCMD) -dest=$(BINDIR) -out=$(NAME) -trimpath=true -ldflags=$(RELEASE_LDFLAGS) -targets=darwin-11.1/arm64 $(BUILD_PACKAGE) && \
|
||||||
|
mv $(BINDIR)/$(NAME)-darwin-11.1-arm64 $(BINDIR)/$(NAME)-darwin-arm64
|
||||||
|
|
||||||
linux-386:
|
linux-386:
|
||||||
GOARCH=386 GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
$(XGOCMD) -dest=$(BINDIR) -out=$(NAME) -trimpath=true -ldflags=$(STATIC_LDFLAGS) -targets=linux/386 $(BUILD_PACKAGE)
|
||||||
|
|
||||||
linux-amd64:
|
linux-amd64:
|
||||||
GOARCH=amd64 GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
$(GOBUILD) -ldflags $(RELEASE_LDFLAGS) -o $(BINDIR)/$(NAME)-$@
|
||||||
|
#GOARCH=amd64 GOOS=linux $(GOBUILD) -ldflags $(RELEASE_LDFLAGS) -o $(BINDIR)/$(NAME)-$@
|
||||||
linux-amd64-v3:
|
#$(XGOCMD) -dest=$(BINDIR) -out=$(NAME) -trimpath=true -ldflags=$(STATIC_LDFLAGS) -targets=linux/amd64 $(BUILD_PACKAGE)
|
||||||
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-arm64:
|
linux-arm64:
|
||||||
GOARCH=arm64 GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
$(XGOCMD) -dest=$(BINDIR) -out=$(NAME) -trimpath=true -ldflags=$(STATIC_LDFLAGS) -targets=linux/arm64 $(BUILD_PACKAGE)
|
||||||
|
|
||||||
linux-mips-softfloat:
|
|
||||||
GOARCH=mips GOMIPS=softfloat GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
|
||||||
|
|
||||||
linux-mips-hardfloat:
|
|
||||||
GOARCH=mips GOMIPS=hardfloat GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
|
||||||
|
|
||||||
linux-mipsle-softfloat:
|
|
||||||
GOARCH=mipsle GOMIPS=softfloat GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
|
||||||
|
|
||||||
linux-mipsle-hardfloat:
|
|
||||||
GOARCH=mipsle GOMIPS=hardfloat GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
|
||||||
|
|
||||||
linux-mips64:
|
|
||||||
GOARCH=mips64 GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
|
||||||
|
|
||||||
linux-mips64le:
|
|
||||||
GOARCH=mips64le GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
|
||||||
|
|
||||||
freebsd-386:
|
|
||||||
GOARCH=386 GOOS=freebsd $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
|
||||||
|
|
||||||
freebsd-amd64:
|
|
||||||
GOARCH=amd64 GOOS=freebsd $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
|
||||||
|
|
||||||
freebsd-amd64-v3:
|
|
||||||
GOARCH=amd64 GOOS=freebsd GOAMD64=v3 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
|
||||||
|
|
||||||
freebsd-arm64:
|
|
||||||
GOARCH=arm64 GOOS=freebsd $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
|
||||||
|
|
||||||
windows-386:
|
windows-386:
|
||||||
GOARCH=386 GOOS=windows $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.exe
|
$(XGOCMD) -dest=$(BINDIR) -out=$(NAME) -trimpath=true -ldflags=$(RELEASE_LDFLAGS) -targets=windows-4.0/386 $(BUILD_PACKAGE) && \
|
||||||
|
mv $(BINDIR)/$(NAME)-windows-4.0-386.exe $(BINDIR)/$(NAME)-windows-386.exe
|
||||||
|
|
||||||
windows-amd64:
|
windows-amd64:
|
||||||
GOARCH=amd64 GOOS=windows $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.exe
|
$(XGOCMD) -dest=$(BINDIR) -out=$(NAME) -trimpath=true -ldflags=$(RELEASE_LDFLAGS) -targets=windows-4.0/amd64 $(BUILD_PACKAGE) && \
|
||||||
|
mv $(BINDIR)/$(NAME)-windows-4.0-amd64.exe $(BINDIR)/$(NAME)-windows-amd64.exe
|
||||||
|
|
||||||
windows-amd64-v3:
|
#windows-arm64:
|
||||||
GOARCH=amd64 GOOS=windows GOAMD64=v3 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.exe
|
# $(XGOCMD) -dest=$(BINDIR) -out=$(NAME) -trimpath=true -ldflags=$(RELEASE_LDFLAGS) -targets=windows/arm64 $(BUILD_PACKAGE)
|
||||||
|
# mv $(NAME)-windows-4.0-arm64.exe $(NAME)-windows-arm64.exe
|
||||||
windows-arm64:
|
|
||||||
GOARCH=arm64 GOOS=windows $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.exe
|
|
||||||
|
|
||||||
windows-arm32v7:
|
|
||||||
GOARCH=arm GOOS=windows GOARM=7 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.exe
|
|
||||||
|
|
||||||
gz_releases=$(addsuffix .gz, $(PLATFORM_LIST))
|
gz_releases=$(addsuffix .gz, $(PLATFORM_LIST))
|
||||||
zip_releases=$(addsuffix .zip, $(WINDOWS_ARCH_LIST))
|
zip_releases=$(addsuffix .zip, $(WINDOWS_ARCH_LIST))
|
||||||
@ -130,10 +81,17 @@ all-arch: $(PLATFORM_LIST) $(WINDOWS_ARCH_LIST)
|
|||||||
releases: $(gz_releases) $(zip_releases)
|
releases: $(gz_releases) $(zip_releases)
|
||||||
|
|
||||||
vet:
|
vet:
|
||||||
go vet ./...
|
$(GOCMD) test -tags build_local ./...
|
||||||
|
|
||||||
lint:
|
lint:
|
||||||
golangci-lint run ./...
|
golangci-lint run --build-tags=build_local ./...
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm -rf $(BINDIR)/*
|
rm -rf $(BINDIR)/
|
||||||
|
mkdir -p $(BINDIR)
|
||||||
|
|
||||||
|
cleancache:
|
||||||
|
# go build cache may need to cleanup if changing C source code
|
||||||
|
$(GOCLEAN) -cache
|
||||||
|
rm -rf $(BINDIR)/
|
||||||
|
mkdir -p $(BINDIR)
|
85
README.md
85
README.md
@ -92,7 +92,18 @@ The `GEOIP` databases via [https://github.com/Loyalsoldier/geoip](https://raw.gi
|
|||||||
|
|
||||||
The `GEOSITE` databases via [https://github.com/Loyalsoldier/v2ray-rules-dat](https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat).
|
The `GEOSITE` databases via [https://github.com/Loyalsoldier/v2ray-rules-dat](https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat).
|
||||||
```yaml
|
```yaml
|
||||||
|
mode: rule
|
||||||
|
|
||||||
|
script:
|
||||||
|
shortcuts:
|
||||||
|
quic: 'network == "udp" and dst_port == 443'
|
||||||
|
privacy: '"analytics" in host or "adservice" in host or "firebase" in host or "safebrowsing" in host or "doubleclick" in host'
|
||||||
|
|
||||||
rules:
|
rules:
|
||||||
|
# rule SCRIPT
|
||||||
|
- SCRIPT,quic,REJECT # Disable QUIC, same as rule "DST-PORT,443,REJECT,udp"
|
||||||
|
- SCRIPT,privacy,REJECT
|
||||||
|
|
||||||
# network condition for all rules
|
# network condition for all rules
|
||||||
- DOMAIN-SUFFIX,example.com,DIRECT,tcp
|
- DOMAIN-SUFFIX,example.com,DIRECT,tcp
|
||||||
- DOMAIN-SUFFIX,example.com,REJECT,udp
|
- DOMAIN-SUFFIX,example.com,REJECT,udp
|
||||||
@ -124,6 +135,80 @@ rules:
|
|||||||
- MATCH,PROXY
|
- MATCH,PROXY
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Script configuration
|
||||||
|
Script enables users to programmatically select a policy for the packets with more flexibility.
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
mode: script
|
||||||
|
|
||||||
|
rules:
|
||||||
|
# the rule GEOSITE just as a rule provider in mode script
|
||||||
|
- GEOSITE,category-ads-all,Whatever
|
||||||
|
- GEOSITE,youtube,Whatever
|
||||||
|
- GEOSITE,geolocation-cn,Whatever
|
||||||
|
|
||||||
|
script:
|
||||||
|
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
|
||||||
|
src_ip: string
|
||||||
|
src_port: int
|
||||||
|
dst_ip: string
|
||||||
|
dst_port: int
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Context {
|
||||||
|
resolve_ip: (host: string) => string // ip string
|
||||||
|
geoip: (ip: string) => string // country code
|
||||||
|
log: (log: string) => void
|
||||||
|
rule_providers: Record<string, { match: (metadata: Metadata) => boolean }>
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
### Proxies configuration
|
### Proxies configuration
|
||||||
Support outbound transport protocol `VLESS`.
|
Support outbound transport protocol `VLESS`.
|
||||||
|
|
||||||
|
9
component/script/build_local.go
Normal file
9
component/script/build_local.go
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
//go:build build_local
|
||||||
|
// +build build_local
|
||||||
|
|
||||||
|
package script
|
||||||
|
|
||||||
|
/*
|
||||||
|
#cgo pkg-config: python3-embed
|
||||||
|
*/
|
||||||
|
import "C"
|
25
component/script/build_xgo.go
Normal file
25
component/script/build_xgo.go
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
//go:build !build_local && cgo
|
||||||
|
// +build !build_local,cgo
|
||||||
|
|
||||||
|
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"
|
735
component/script/clash_module.c
Normal file
735
component/script/clash_module.c
Normal file
@ -0,0 +1,735 @@
|
|||||||
|
#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")),
|
||||||
|
.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 *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_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, "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_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 *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:H, s:s, s:H}",
|
||||||
|
"ctx", clash_context,
|
||||||
|
"network", network,
|
||||||
|
"process_name", process_name,
|
||||||
|
"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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --------------------------------------------------------------------- */
|
334
component/script/clash_module.go
Normal file
334
component/script/clash_module.go
Normal file
@ -0,0 +1,334 @@
|
|||||||
|
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)
|
||||||
|
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 != nil {
|
||||||
|
srcIpGo = mtd.SrcIP.String()
|
||||||
|
}
|
||||||
|
if mtd.DstIP != nil {
|
||||||
|
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(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, 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)
|
||||||
|
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 != nil {
|
||||||
|
srcIpGo = mtd.SrcIP.String()
|
||||||
|
}
|
||||||
|
if mtd.DstIP != nil {
|
||||||
|
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(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, 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)
|
||||||
|
}
|
62
component/script/clash_module.h
Normal file
62
component/script/clash_module.h
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
#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 *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 *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 *host,
|
||||||
|
const char *src_ip,
|
||||||
|
unsigned short src_port,
|
||||||
|
const char *dst_ip,
|
||||||
|
unsigned short dst_port);
|
||||||
|
|
||||||
|
#endif // CLASH_CALLBACK_MODULE_H__
|
142
component/script/clash_module_export.go
Normal file
142
component/script/clash_module_export.go
Normal file
@ -0,0 +1,142 @@
|
|||||||
|
package script
|
||||||
|
|
||||||
|
/*
|
||||||
|
#include "clash_module.h"
|
||||||
|
*/
|
||||||
|
import "C"
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"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 := net.ParseIP(C.GoString(cIP))
|
||||||
|
|
||||||
|
if dstIP == 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)
|
||||||
|
|
||||||
|
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)
|
||||||
|
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))
|
||||||
|
|
||||||
|
dst := net.ParseIP(dstIp)
|
||||||
|
addrType := constant.AtypDomainName
|
||||||
|
|
||||||
|
if dst != nil {
|
||||||
|
if dst.To4() != nil {
|
||||||
|
addrType = constant.AtypIPv4
|
||||||
|
} else {
|
||||||
|
addrType = constant.AtypIPv6
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
metadata := &constant.Metadata{
|
||||||
|
Process: processName,
|
||||||
|
SrcIP: net.ParseIP(srcIp),
|
||||||
|
DstIP: dst,
|
||||||
|
SrcPort: srcPort,
|
||||||
|
DstPort: dstPort,
|
||||||
|
AddrType: addrType,
|
||||||
|
Host: host,
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
167
config/config.go
167
config/config.go
@ -7,6 +7,7 @@ import (
|
|||||||
"net/netip"
|
"net/netip"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
|
"regexp"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@ -19,6 +20,7 @@ import (
|
|||||||
"github.com/Dreamacro/clash/component/fakeip"
|
"github.com/Dreamacro/clash/component/fakeip"
|
||||||
"github.com/Dreamacro/clash/component/geodata"
|
"github.com/Dreamacro/clash/component/geodata"
|
||||||
"github.com/Dreamacro/clash/component/geodata/router"
|
"github.com/Dreamacro/clash/component/geodata/router"
|
||||||
|
S "github.com/Dreamacro/clash/component/script"
|
||||||
"github.com/Dreamacro/clash/component/trie"
|
"github.com/Dreamacro/clash/component/trie"
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
providerTypes "github.com/Dreamacro/clash/constant/provider"
|
providerTypes "github.com/Dreamacro/clash/constant/provider"
|
||||||
@ -100,21 +102,28 @@ type Tun struct {
|
|||||||
AutoRoute bool `yaml:"auto-route" json:"auto-route"`
|
AutoRoute bool `yaml:"auto-route" json:"auto-route"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Script config
|
||||||
|
type Script struct {
|
||||||
|
MainCode string `yaml:"code" json:"code"`
|
||||||
|
ShortcutsCode map[string]string `yaml:"shortcuts" json:"shortcuts"`
|
||||||
|
}
|
||||||
|
|
||||||
// Experimental config
|
// Experimental config
|
||||||
type Experimental struct{}
|
type Experimental struct{}
|
||||||
|
|
||||||
// Config is clash config manager
|
// Config is clash config manager
|
||||||
type Config struct {
|
type Config struct {
|
||||||
General *General
|
General *General
|
||||||
Tun *Tun
|
Tun *Tun
|
||||||
DNS *DNS
|
DNS *DNS
|
||||||
Experimental *Experimental
|
Experimental *Experimental
|
||||||
Hosts *trie.DomainTrie
|
Hosts *trie.DomainTrie
|
||||||
Profile *Profile
|
Profile *Profile
|
||||||
Rules []C.Rule
|
Rules []C.Rule
|
||||||
Users []auth.AuthUser
|
RuleProviders map[string]C.Rule
|
||||||
Proxies map[string]C.Proxy
|
Users []auth.AuthUser
|
||||||
Providers map[string]providerTypes.ProxyProvider
|
Proxies map[string]C.Proxy
|
||||||
|
Providers map[string]providerTypes.ProxyProvider
|
||||||
}
|
}
|
||||||
|
|
||||||
type RawDNS struct {
|
type RawDNS struct {
|
||||||
@ -175,6 +184,7 @@ type RawConfig struct {
|
|||||||
Proxy []map[string]any `yaml:"proxies"`
|
Proxy []map[string]any `yaml:"proxies"`
|
||||||
ProxyGroup []map[string]any `yaml:"proxy-groups"`
|
ProxyGroup []map[string]any `yaml:"proxy-groups"`
|
||||||
Rule []string `yaml:"rules"`
|
Rule []string `yaml:"rules"`
|
||||||
|
Script Script `yaml:"script"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse config
|
// Parse config
|
||||||
@ -265,11 +275,17 @@ func ParseRawConfig(rawCfg *RawConfig) (*Config, error) {
|
|||||||
config.Proxies = proxies
|
config.Proxies = proxies
|
||||||
config.Providers = providers
|
config.Providers = providers
|
||||||
|
|
||||||
rules, err := parseRules(rawCfg, proxies)
|
err = parseScript(rawCfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
rules, ruleProviders, err := parseRules(rawCfg, proxies)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
config.Rules = rules
|
config.Rules = rules
|
||||||
|
config.RuleProviders = ruleProviders
|
||||||
|
|
||||||
hosts, err := parseHosts(rawCfg)
|
hosts, err := parseHosts(rawCfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -430,10 +446,16 @@ func parseProxies(cfg *RawConfig) (proxies map[string]C.Proxy, providersMap map[
|
|||||||
return proxies, providersMap, nil
|
return proxies, providersMap, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseRules(cfg *RawConfig, proxies map[string]C.Proxy) ([]C.Rule, error) {
|
func parseRules(cfg *RawConfig, proxies map[string]C.Proxy) ([]C.Rule, map[string]C.Rule, error) {
|
||||||
rulesConfig := cfg.Rule
|
var (
|
||||||
|
rules []C.Rule
|
||||||
|
providerNames []string
|
||||||
|
|
||||||
var rules []C.Rule
|
ruleProviders = map[string]C.Rule{}
|
||||||
|
rulesConfig = cfg.Rule
|
||||||
|
mode = cfg.Mode
|
||||||
|
isPyInit = S.Py_IsInitialized()
|
||||||
|
)
|
||||||
|
|
||||||
// parse rules
|
// parse rules
|
||||||
for idx, line := range rulesConfig {
|
for idx, line := range rulesConfig {
|
||||||
@ -445,10 +467,14 @@ func parseRules(cfg *RawConfig, proxies map[string]C.Proxy) ([]C.Rule, error) {
|
|||||||
ruleName = strings.ToUpper(rule[0])
|
ruleName = strings.ToUpper(rule[0])
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if mode == T.Script && ruleName != "GEOSITE" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
l := len(rule)
|
l := len(rule)
|
||||||
|
|
||||||
if l < 2 {
|
if l < 2 {
|
||||||
return nil, fmt.Errorf("rules[%d] [%s] error: format invalid", idx, line)
|
return nil, nil, fmt.Errorf("rules[%d] [%s] error: format invalid", idx, line)
|
||||||
}
|
}
|
||||||
|
|
||||||
if l < 4 {
|
if l < 4 {
|
||||||
@ -467,23 +493,42 @@ func parseRules(cfg *RawConfig, proxies map[string]C.Proxy) ([]C.Rule, error) {
|
|||||||
target = rule[l-1]
|
target = rule[l-1]
|
||||||
params = rule[l:]
|
params = rule[l:]
|
||||||
|
|
||||||
if _, ok := proxies[target]; !ok {
|
if _, ok := proxies[target]; mode != T.Script && !ok {
|
||||||
return nil, fmt.Errorf("rules[%d] [%s] error: proxy [%s] not found", idx, line, target)
|
return nil, nil, fmt.Errorf("rules[%d] [%s] error: proxy [%s] not found", idx, line, target)
|
||||||
}
|
}
|
||||||
|
|
||||||
params = trimArr(params)
|
params = trimArr(params)
|
||||||
|
|
||||||
parsed, parseErr := R.ParseRule(ruleName, payload, target, params)
|
parsed, parseErr := R.ParseRule(ruleName, payload, target, params)
|
||||||
if parseErr != nil {
|
if parseErr != nil {
|
||||||
return nil, fmt.Errorf("rules[%d] [%s] error: %s", idx, line, parseErr.Error())
|
return nil, nil, fmt.Errorf("rules[%d] [%s] error: %s", idx, line, parseErr.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
rules = append(rules, parsed)
|
if isPyInit {
|
||||||
|
if ruleName == "GEOSITE" {
|
||||||
|
pvName := "geosite:" + strings.ToLower(payload)
|
||||||
|
providerNames = append(providerNames, pvName)
|
||||||
|
ruleProviders[pvName] = parsed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if mode != T.Script {
|
||||||
|
rules = append(rules, parsed)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
runtime.GC()
|
runtime.GC()
|
||||||
|
|
||||||
return rules, nil
|
if isPyInit {
|
||||||
|
err := S.NewClashPyContext(providerNames)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
} else {
|
||||||
|
log.Infoln("Start initial script context successful, provider records: %v", len(providerNames))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return rules, ruleProviders, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseHosts(cfg *RawConfig) (*trie.DomainTrie, error) {
|
func parseHosts(cfg *RawConfig) (*trie.DomainTrie, error) {
|
||||||
@ -784,3 +829,85 @@ func parseTun(rawTun RawTun, general *General) (*Tun, error) {
|
|||||||
AutoRoute: rawTun.AutoRoute,
|
AutoRoute: rawTun.AutoRoute,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func parseScript(cfg *RawConfig) error {
|
||||||
|
mode := cfg.Mode
|
||||||
|
script := cfg.Script
|
||||||
|
mainCode := cleanPyKeywords(script.MainCode)
|
||||||
|
shortcutsCode := script.ShortcutsCode
|
||||||
|
|
||||||
|
if mode != T.Script && len(shortcutsCode) == 0 {
|
||||||
|
return nil
|
||||||
|
} else if mode == T.Script && len(mainCode) == 0 {
|
||||||
|
return fmt.Errorf("initialized script module failure, can't find script code in the config file")
|
||||||
|
}
|
||||||
|
|
||||||
|
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()
|
||||||
|
|
||||||
|
`
|
||||||
|
|
||||||
|
var shouldInitPy bool
|
||||||
|
if mode == T.Script {
|
||||||
|
content += mainCode + "\n\n"
|
||||||
|
shouldInitPy = true
|
||||||
|
}
|
||||||
|
|
||||||
|
for k, v := range shortcutsCode {
|
||||||
|
v = cleanPyKeywords(v)
|
||||||
|
v = strings.TrimSpace(v)
|
||||||
|
if len(v) == 0 {
|
||||||
|
return fmt.Errorf("initialized rule SCRIPT failure, shortcut [%s] code invalid syntax", k)
|
||||||
|
}
|
||||||
|
|
||||||
|
content += "def " + strings.ToLower(k) + "(ctx, network, process_name, host, src_ip, src_port, dst_ip, dst_port):\n return " + v + "\n\n"
|
||||||
|
shouldInitPy = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if !shouldInitPy {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
err := os.WriteFile(C.Path.Script(), []byte(content), 0o644)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("initialized script module failure, %s", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = S.Py_Initialize(C.Path.GetExecutableFullPath(), C.Path.ScriptDir()); err != nil {
|
||||||
|
return fmt.Errorf("initialized script module failure, %s", err.Error())
|
||||||
|
} else if mode == T.Script {
|
||||||
|
if err = S.LoadMainFunction(); err != nil {
|
||||||
|
return fmt.Errorf("initialized script module failure, %s", err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Infoln("Start initial script module successful, version: %s", S.Py_GetVersion())
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func cleanPyKeywords(code string) string {
|
||||||
|
if len(code) == 0 {
|
||||||
|
return code
|
||||||
|
}
|
||||||
|
keywords := []string{"import", "print"}
|
||||||
|
|
||||||
|
for _, kw := range keywords {
|
||||||
|
reg := regexp.MustCompile("(?m)[\r\n]+^.*" + kw + ".*$")
|
||||||
|
code = reg.ReplaceAllString(code, "")
|
||||||
|
}
|
||||||
|
return code
|
||||||
|
}
|
||||||
|
@ -22,6 +22,7 @@ var Path = func() *path {
|
|||||||
type path struct {
|
type path struct {
|
||||||
homeDir string
|
homeDir string
|
||||||
configFile string
|
configFile string
|
||||||
|
scriptDir string
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetHomeDir is used to set the configuration path
|
// SetHomeDir is used to set the configuration path
|
||||||
@ -71,6 +72,32 @@ func (p *path) GeoSite() string {
|
|||||||
return P.Join(p.homeDir, "geosite.dat")
|
return P.Join(p.homeDir, "geosite.dat")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *path) ScriptDir() string {
|
||||||
|
if len(p.scriptDir) != 0 {
|
||||||
|
return p.scriptDir
|
||||||
|
}
|
||||||
|
if dir, err := os.MkdirTemp("", Name+"-"); err == nil {
|
||||||
|
p.scriptDir = dir
|
||||||
|
} else {
|
||||||
|
p.scriptDir = P.Join(os.TempDir(), Name)
|
||||||
|
_ = os.MkdirAll(p.scriptDir, 0o644)
|
||||||
|
}
|
||||||
|
return p.scriptDir
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *path) Script() string {
|
||||||
|
return P.Join(p.ScriptDir(), "clash_script.py")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *path) GetExecutableFullPath() string {
|
||||||
|
exePath, err := os.Executable()
|
||||||
|
if err != nil {
|
||||||
|
return "clash"
|
||||||
|
}
|
||||||
|
res, _ := filepath.EvalSymlinks(exePath)
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
func (p *path) GetAssetLocation(file string) string {
|
func (p *path) GetAssetLocation(file string) string {
|
||||||
return P.Join(p.homeDir, file)
|
return P.Join(p.homeDir, file)
|
||||||
}
|
}
|
||||||
|
@ -13,6 +13,7 @@ const (
|
|||||||
DstPort
|
DstPort
|
||||||
Process
|
Process
|
||||||
ProcessPath
|
ProcessPath
|
||||||
|
Script
|
||||||
MATCH
|
MATCH
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -42,6 +43,8 @@ func (rt RuleType) String() string {
|
|||||||
return "Process"
|
return "Process"
|
||||||
case ProcessPath:
|
case ProcessPath:
|
||||||
return "ProcessPath"
|
return "ProcessPath"
|
||||||
|
case Script:
|
||||||
|
return "Script"
|
||||||
case MATCH:
|
case MATCH:
|
||||||
return "Match"
|
return "Match"
|
||||||
default:
|
default:
|
||||||
|
@ -16,6 +16,7 @@ import (
|
|||||||
"github.com/Dreamacro/clash/component/profile"
|
"github.com/Dreamacro/clash/component/profile"
|
||||||
"github.com/Dreamacro/clash/component/profile/cachefile"
|
"github.com/Dreamacro/clash/component/profile/cachefile"
|
||||||
"github.com/Dreamacro/clash/component/resolver"
|
"github.com/Dreamacro/clash/component/resolver"
|
||||||
|
S "github.com/Dreamacro/clash/component/script"
|
||||||
"github.com/Dreamacro/clash/component/trie"
|
"github.com/Dreamacro/clash/component/trie"
|
||||||
"github.com/Dreamacro/clash/config"
|
"github.com/Dreamacro/clash/config"
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
@ -76,6 +77,7 @@ func ApplyConfig(cfg *config.Config, force bool) {
|
|||||||
updateUsers(cfg.Users)
|
updateUsers(cfg.Users)
|
||||||
updateProxies(cfg.Proxies, cfg.Providers)
|
updateProxies(cfg.Proxies, cfg.Providers)
|
||||||
updateRules(cfg.Rules)
|
updateRules(cfg.Rules)
|
||||||
|
updateRuleProviders(cfg.RuleProviders)
|
||||||
updateHosts(cfg.Hosts)
|
updateHosts(cfg.Hosts)
|
||||||
updateProfile(cfg)
|
updateProfile(cfg)
|
||||||
updateDNS(cfg.DNS, cfg.Tun)
|
updateDNS(cfg.DNS, cfg.Tun)
|
||||||
@ -176,6 +178,10 @@ func updateRules(rules []C.Rule) {
|
|||||||
tunnel.UpdateRules(rules)
|
tunnel.UpdateRules(rules)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func updateRuleProviders(providers map[string]C.Rule) {
|
||||||
|
S.UpdateRuleProviders(providers)
|
||||||
|
}
|
||||||
|
|
||||||
func updateTun(tun *config.Tun, tunAddressPrefix string) {
|
func updateTun(tun *config.Tun, tunAddressPrefix string) {
|
||||||
P.ReCreateTun(tun, tunAddressPrefix, tunnel.TCPIn(), tunnel.UDPIn())
|
P.ReCreateTun(tun, tunAddressPrefix, tunnel.TCPIn(), tunnel.UDPIn())
|
||||||
}
|
}
|
||||||
@ -308,4 +314,5 @@ func Cleanup() {
|
|||||||
if runtime.GOOS == "linux" {
|
if runtime.GOOS == "linux" {
|
||||||
tproxy.CleanUpTProxyLinuxIPTables()
|
tproxy.CleanUpTProxyLinuxIPTables()
|
||||||
}
|
}
|
||||||
|
S.Py_Finalize()
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,7 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/adapter/inbound"
|
"github.com/Dreamacro/clash/adapter/inbound"
|
||||||
|
S "github.com/Dreamacro/clash/component/script"
|
||||||
"github.com/Dreamacro/clash/config"
|
"github.com/Dreamacro/clash/config"
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
"github.com/Dreamacro/clash/listener/http"
|
"github.com/Dreamacro/clash/listener/http"
|
||||||
@ -315,6 +316,7 @@ func ReCreateTun(tunConf *config.Tun, tunAddressPrefix string, tcpIn chan<- C.Co
|
|||||||
defer func() {
|
defer func() {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorln("Start TUN listening error: %s", err.Error())
|
log.Errorln("Start TUN listening error: %s", err.Error())
|
||||||
|
S.Py_Finalize()
|
||||||
os.Exit(2)
|
os.Exit(2)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
2
main.go
2
main.go
@ -48,7 +48,7 @@ func init() {
|
|||||||
func main() {
|
func main() {
|
||||||
_, _ = maxprocs.Set(maxprocs.Logger(func(string, ...any) {}))
|
_, _ = maxprocs.Set(maxprocs.Logger(func(string, ...any) {}))
|
||||||
if version {
|
if version {
|
||||||
fmt.Printf("Clash with tun deveice %s %s %s with %s %s\n", C.Version, runtime.GOOS, runtime.GOARCH, runtime.Version(), C.BuildTime)
|
fmt.Printf("Clash Plus Pro %s %s %s with %s %s\n", C.Version, runtime.GOOS, runtime.GOARCH, runtime.Version(), C.BuildTime)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -37,6 +37,8 @@ func ParseRule(tp, payload, target string, params []string) (C.Rule, error) {
|
|||||||
parsed, parseErr = NewProcess(payload, target, true)
|
parsed, parseErr = NewProcess(payload, target, true)
|
||||||
case "PROCESS-PATH":
|
case "PROCESS-PATH":
|
||||||
parsed, parseErr = NewProcess(payload, target, false)
|
parsed, parseErr = NewProcess(payload, target, false)
|
||||||
|
case "SCRIPT":
|
||||||
|
parsed, parseErr = NewScript(payload, target)
|
||||||
case "MATCH":
|
case "MATCH":
|
||||||
parsed = NewMatch(target)
|
parsed = NewMatch(target)
|
||||||
default:
|
default:
|
||||||
|
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)
|
@ -12,12 +12,14 @@ type TunnelMode int
|
|||||||
var ModeMapping = map[string]TunnelMode{
|
var ModeMapping = map[string]TunnelMode{
|
||||||
Global.String(): Global,
|
Global.String(): Global,
|
||||||
Rule.String(): Rule,
|
Rule.String(): Rule,
|
||||||
|
Script.String(): Script,
|
||||||
Direct.String(): Direct,
|
Direct.String(): Direct,
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
Global TunnelMode = iota
|
Global TunnelMode = iota
|
||||||
Rule
|
Rule
|
||||||
|
Script
|
||||||
Direct
|
Direct
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -61,6 +63,8 @@ func (m TunnelMode) String() string {
|
|||||||
return "global"
|
return "global"
|
||||||
case Rule:
|
case Rule:
|
||||||
return "rule"
|
return "rule"
|
||||||
|
case Script:
|
||||||
|
return "script"
|
||||||
case Direct:
|
case Direct:
|
||||||
return "direct"
|
return "direct"
|
||||||
default:
|
default:
|
||||||
|
@ -14,6 +14,7 @@ import (
|
|||||||
"github.com/Dreamacro/clash/component/nat"
|
"github.com/Dreamacro/clash/component/nat"
|
||||||
P "github.com/Dreamacro/clash/component/process"
|
P "github.com/Dreamacro/clash/component/process"
|
||||||
"github.com/Dreamacro/clash/component/resolver"
|
"github.com/Dreamacro/clash/component/resolver"
|
||||||
|
S "github.com/Dreamacro/clash/component/script"
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
"github.com/Dreamacro/clash/constant/provider"
|
"github.com/Dreamacro/clash/constant/provider"
|
||||||
icontext "github.com/Dreamacro/clash/context"
|
icontext "github.com/Dreamacro/clash/context"
|
||||||
@ -171,6 +172,8 @@ func resolveMetadata(ctx C.PlainContext, metadata *C.Metadata) (proxy C.Proxy, r
|
|||||||
proxy = proxies["DIRECT"]
|
proxy = proxies["DIRECT"]
|
||||||
case Global:
|
case Global:
|
||||||
proxy = proxies["GLOBAL"]
|
proxy = proxies["GLOBAL"]
|
||||||
|
case Script:
|
||||||
|
proxy, err = matchScript(metadata)
|
||||||
// Rule
|
// Rule
|
||||||
default:
|
default:
|
||||||
proxy, rule, err = match(metadata)
|
proxy, rule, err = match(metadata)
|
||||||
@ -252,6 +255,8 @@ func handleUDPConn(packet *inbound.PacketAdapter) {
|
|||||||
switch true {
|
switch true {
|
||||||
case rule != nil:
|
case rule != nil:
|
||||||
log.Infoln("[UDP] %s(%s) --> %s match %s(%s) using %s", metadata.SourceAddress(), metadata.Process, metadata.RemoteAddress(), rule.RuleType().String(), rule.Payload(), rawPc.Chains().String())
|
log.Infoln("[UDP] %s(%s) --> %s match %s(%s) using %s", metadata.SourceAddress(), metadata.Process, metadata.RemoteAddress(), rule.RuleType().String(), rule.Payload(), rawPc.Chains().String())
|
||||||
|
case mode == Script:
|
||||||
|
log.Infoln("[UDP] %s(%s) --> %s using SCRIPT %s", metadata.SourceAddress(), metadata.Process, metadata.RemoteAddress(), rawPc.Chains().String())
|
||||||
case mode == Global:
|
case mode == Global:
|
||||||
log.Infoln("[UDP] %s(%s) --> %s using GLOBAL", metadata.SourceAddress(), metadata.Process, metadata.RemoteAddress())
|
log.Infoln("[UDP] %s(%s) --> %s using GLOBAL", metadata.SourceAddress(), metadata.Process, metadata.RemoteAddress())
|
||||||
case mode == Direct:
|
case mode == Direct:
|
||||||
@ -304,6 +309,8 @@ func handleTCPConn(connCtx C.ConnContext) {
|
|||||||
switch true {
|
switch true {
|
||||||
case rule != nil:
|
case rule != nil:
|
||||||
log.Infoln("[TCP] %s(%s) --> %s match %s(%s) using %s", metadata.SourceAddress(), metadata.Process, metadata.RemoteAddress(), rule.RuleType().String(), rule.Payload(), remoteConn.Chains().String())
|
log.Infoln("[TCP] %s(%s) --> %s match %s(%s) using %s", metadata.SourceAddress(), metadata.Process, metadata.RemoteAddress(), rule.RuleType().String(), rule.Payload(), remoteConn.Chains().String())
|
||||||
|
case mode == Script:
|
||||||
|
log.Infoln("[TCP] %s(%s) --> %s using SCRIPT %s", metadata.SourceAddress(), metadata.Process, metadata.RemoteAddress(), remoteConn.Chains().String())
|
||||||
case mode == Global:
|
case mode == Global:
|
||||||
log.Infoln("[TCP] %s(%s) --> %s using GLOBAL", metadata.SourceAddress(), metadata.Process, metadata.RemoteAddress())
|
log.Infoln("[TCP] %s(%s) --> %s using GLOBAL", metadata.SourceAddress(), metadata.Process, metadata.RemoteAddress())
|
||||||
case mode == Direct:
|
case mode == Direct:
|
||||||
@ -375,3 +382,24 @@ func match(metadata *C.Metadata) (C.Proxy, C.Rule, error) {
|
|||||||
|
|
||||||
return proxies["REJECT"], nil, nil
|
return proxies["REJECT"], nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func matchScript(metadata *C.Metadata) (C.Proxy, error) {
|
||||||
|
configMux.RLock()
|
||||||
|
defer configMux.RUnlock()
|
||||||
|
|
||||||
|
if node := resolver.DefaultHosts.Search(metadata.Host); node != nil {
|
||||||
|
ip := node.Data.(net.IP)
|
||||||
|
metadata.DstIP = ip
|
||||||
|
}
|
||||||
|
|
||||||
|
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