Compare commits

..

16 Commits

80 changed files with 691 additions and 2602 deletions

View File

@ -1,142 +0,0 @@
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

View File

@ -20,4 +20,3 @@ jobs:
uses: golangci/golangci-lint-action@v3 uses: golangci/golangci-lint-action@v3
with: with:
version: latest version: latest
args: --build-tags=build_local

View File

@ -1,8 +1,5 @@
name: Release name: Release
on: on: [push]
push:
branches:
- rm
jobs: jobs:
build: build:
runs-on: ubuntu-latest runs-on: ubuntu-latest
@ -36,63 +33,29 @@ 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: |
# fetch python cross compile source files go test ./...
mkdir -p bin/python/
cd bin/python/
curl -LO https://raw.githubusercontent.com/yaling888/snack/main/python-3.9.7-darwin-amd64.tar.xz
curl -LO https://raw.githubusercontent.com/yaling888/snack/main/python-3.9.7-darwin-arm64.tar.xz
curl -LO https://raw.githubusercontent.com/yaling888/snack/main/python-3.9.7-windows-amd64.tar.xz
curl -LO https://raw.githubusercontent.com/yaling888/snack/main/python-3.9.7-windows-386.tar.xz
#curl -LO https://raw.githubusercontent.com/yaling888/snack/main/python-3.9.7-linux-amd64.tar.xz
#curl -LO https://raw.githubusercontent.com/yaling888/snack/main/python-3.9.7-linux-arm64.tar.xz
#curl -LO https://raw.githubusercontent.com/yaling888/snack/main/python-3.9.7-linux-386.tar.xz
tar -Jxf python-3.9.7-darwin-amd64.tar.xz
tar -Jxf python-3.9.7-darwin-arm64.tar.xz
tar -Jxf python-3.9.7-windows-amd64.tar.xz
tar -Jxf python-3.9.7-windows-386.tar.xz
#tar -Jxf python-3.9.7-linux-amd64.tar.xz
#tar -Jxf python-3.9.7-linux-arm64.tar.xz
#tar -Jxf python-3.9.7-linux-386.tar.xz
rm python-3.9.7-*.tar.xz
cd ../../
# go test
go test -tags build_local ./...
# init xgo
docker pull techknowlogick/xgo:latest
go install src.techknowlogick.com/xgo@latest
- name: Build - name: Build
#if: startsWith(github.ref, 'refs/tags/') if: startsWith(github.ref, 'refs/tags/')
env: env:
NAME: clash NAME: clash
BINDIR: bin BINDIR: bin
run: | run: make -j releases
make -j releases
#ls -lahF bin/python/
- name: Prepare upload #- name: Prepare upload
if: startsWith(github.ref, 'refs/tags/') == false # run: |
run: | # echo "FILE_DATE=_$(date +"%Y%m%d%H%M")" >> $GITHUB_ENV
rm -rf bin/python/ # echo "FILE_SHA=$(git describe --tags --always 2>/dev/null)" >> $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 #- name: Upload files to Artifacts
# uses: actions/upload-artifact@v2
- name: Upload files to Artifacts # if: startsWith(github.ref, 'refs/tags/') == false
uses: actions/upload-artifact@v2 # with:
if: startsWith(github.ref, 'refs/tags/') == false # name: clash_${{ env.FILE_SHA }}${{ env.FILE_DATE }}
with: # path: |
name: clash_${{ env.FILE_SHA }}${{ env.FILE_DATE }} # bin/*
path: |
bin/*
- name: Upload Release - name: Upload Release
uses: softprops/action-gh-release@v1 uses: softprops/action-gh-release@v1
@ -108,13 +71,3 @@ jobs:
# with: # with:
# retain_days: 1 # retain_days: 1
# keep_minimum_runs: 2 # keep_minimum_runs: 2
- name: Remove old Releases
uses: dev-drprasad/delete-older-releases@v0.2.0
if: startsWith(github.ref, 'refs/tags/') && !cancelled()
with:
keep_latest: 1
delete_tags: true
delete_tag_pattern: plus-pro
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

144
Makefile
View File

@ -1,70 +1,119 @@
GOCMD=go
XGOCMD=xgo -go=go-1.18.x
GOBUILD=CGO_ENABLED=1 $(GOCMD) build -trimpath
GOCLEAN=$(GOCMD) clean
NAME=clash NAME=clash
BINDIR=$(shell pwd)/bin BINDIR=bin
VERSION=$(shell git describe --tags --always 2>/dev/null || date +%F) VERSION=$(shell git describe --tags --always 2>/dev/null || echo "unknown version")
BUILDTIME=$(shell date -u) BUILDTIME=$(shell date -u)
BUILD_PACKAGE=. GOBUILD=CGO_ENABLED=0 go build -trimpath -ldflags '-X "github.com/Dreamacro/clash/constant.Version=$(VERSION)" \
RELEASE_LDFLAGS='-X "github.com/Dreamacro/clash/constant.Version=$(VERSION)" \
-X "github.com/Dreamacro/clash/constant.BuildTime=$(BUILDTIME)" \ -X "github.com/Dreamacro/clash/constant.BuildTime=$(BUILDTIME)" \
-w -s -buildid=' -w -s -buildid='
STATIC_LDFLAGS='-X "github.com/Dreamacro/clash/constant.Version=$(VERSION)" \
-X "github.com/Dreamacro/clash/constant.BuildTime=$(BUILDTIME)" \
-extldflags "-static" \
-w -s -buildid='
PLATFORM_LIST = \ PLATFORM_LIST = \
darwin-amd64 \ darwin-amd64 \
darwin-amd64-v3 \
darwin-arm64 \ darwin-arm64 \
linux-amd64 linux-386 \
# linux-arm64 linux-amd64 \
# linux-386 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
WINDOWS_ARCH_LIST = \ WINDOWS_ARCH_LIST = \
windows-386 \
windows-amd64 \ windows-amd64 \
windows-386 windows-amd64-v3 \
# windows-arm64 windows-arm64 \
windows-arm32v7
all: linux-amd64 darwin-amd64 windows-amd64 # Most used all: linux-amd64 darwin-amd64 windows-amd64 # Most used
local: docker:
$(GOBUILD) -ldflags $(RELEASE_LDFLAGS) -tags build_local -o $(BINDIR)/$(NAME)-$@ $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
local-v3:
GOAMD64=v3 $(GOBUILD) -ldflags $(RELEASE_LDFLAGS) -tags build_local -o $(BINDIR)/$(NAME)-$@
darwin-amd64: darwin-amd64:
$(XGOCMD) -dest=$(BINDIR) -out=$(NAME) -trimpath=true -ldflags=$(RELEASE_LDFLAGS) -targets=darwin-10.12/amd64 $(BUILD_PACKAGE) && \ GOARCH=amd64 GOOS=darwin $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
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:
$(XGOCMD) -dest=$(BINDIR) -out=$(NAME) -trimpath=true -ldflags=$(RELEASE_LDFLAGS) -targets=darwin-11.1/arm64 $(BUILD_PACKAGE) && \ GOARCH=arm64 GOOS=darwin $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
mv $(BINDIR)/$(NAME)-darwin-11.1-arm64 $(BINDIR)/$(NAME)-darwin-arm64
linux-386: linux-386:
$(XGOCMD) -dest=$(BINDIR) -out=$(NAME) -trimpath=true -ldflags=$(STATIC_LDFLAGS) -targets=linux/386 $(BUILD_PACKAGE) GOARCH=386 GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
linux-amd64: linux-amd64:
$(GOBUILD) -ldflags $(RELEASE_LDFLAGS) -o $(BINDIR)/$(NAME)-$@ GOARCH=amd64 GOOS=linux $(GOBUILD) -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-arm64: linux-amd64-v3:
$(XGOCMD) -dest=$(BINDIR) -out=$(NAME) -trimpath=true -ldflags=$(STATIC_LDFLAGS) -targets=linux/arm64 $(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-armv8:
GOARCH=arm64 GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
linux-mips-softfloat:
GOARCH=mips GOMIPS=softfloat GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
linux-mips-hardfloat:
GOARCH=mips GOMIPS=hardfloat GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
linux-mipsle-softfloat:
GOARCH=mipsle GOMIPS=softfloat GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
linux-mipsle-hardfloat:
GOARCH=mipsle GOMIPS=hardfloat GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
linux-mips64:
GOARCH=mips64 GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
linux-mips64le:
GOARCH=mips64le GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
freebsd-386:
GOARCH=386 GOOS=freebsd $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
freebsd-amd64:
GOARCH=amd64 GOOS=freebsd $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
freebsd-amd64-v3:
GOARCH=amd64 GOOS=freebsd GOAMD64=v3 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
freebsd-arm64:
GOARCH=arm64 GOOS=freebsd $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
windows-386: windows-386:
$(XGOCMD) -dest=$(BINDIR) -out=$(NAME) -trimpath=true -ldflags=$(RELEASE_LDFLAGS) -targets=windows-6.0/386 $(BUILD_PACKAGE) && \ GOARCH=386 GOOS=windows $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.exe
mv $(BINDIR)/$(NAME)-windows-6.0-386.exe $(BINDIR)/$(NAME)-windows-386.exe
windows-amd64: windows-amd64:
$(XGOCMD) -dest=$(BINDIR) -out=$(NAME) -trimpath=true -ldflags=$(RELEASE_LDFLAGS) -targets=windows-6.0/amd64 $(BUILD_PACKAGE) && \ GOARCH=amd64 GOOS=windows $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.exe
mv $(BINDIR)/$(NAME)-windows-6.0-amd64.exe $(BINDIR)/$(NAME)-windows-amd64.exe
#windows-arm64: windows-amd64-v3:
# $(XGOCMD) -dest=$(BINDIR) -out=$(NAME) -trimpath=true -ldflags=$(RELEASE_LDFLAGS) -targets=windows/arm64 $(BUILD_PACKAGE) GOARCH=amd64 GOOS=windows GOAMD64=v3 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.exe
# 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))
@ -81,17 +130,14 @@ all-arch: $(PLATFORM_LIST) $(WINDOWS_ARCH_LIST)
releases: $(gz_releases) $(zip_releases) releases: $(gz_releases) $(zip_releases)
vet: vet:
$(GOCMD) test -tags build_local ./... go test ./...
lint: lint:
golangci-lint run --build-tags=build_local ./... 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 ./...
clean: clean:
rm -rf $(BINDIR)/ rm -rf $(BINDIR)/*
mkdir -p $(BINDIR)
cleancache:
# go build cache may need to cleanup if changing C source code
$(GOCLEAN) -cache
rm -rf $(BINDIR)/
mkdir -p $(BINDIR)

114
README.md
View File

@ -13,11 +13,11 @@
<img src="https://goreportcard.com/badge/github.com/Dreamacro/clash?style=flat-square"> <img src="https://goreportcard.com/badge/github.com/Dreamacro/clash?style=flat-square">
</a> </a>
<img src="https://img.shields.io/github/go-mod/go-version/Dreamacro/clash?style=flat-square"> <img src="https://img.shields.io/github/go-mod/go-version/Dreamacro/clash?style=flat-square">
<a href="https://github.com/Dreamacro/clash/releases"> <a href="https://github.com/yaling888/clash/releases">
<img src="https://img.shields.io/github/release/Dreamacro/clash/all.svg?style=flat-square"> <img src="https://img.shields.io/github/release/yaling888/clash/all.svg?style=flat-square">
</a> </a>
<a href="https://github.com/Dreamacro/clash/releases/tag/premium"> <a href="https://github.com/yaling888/clash/releases/tag/plus_pro">
<img src="https://img.shields.io/badge/release-Premium-00b4f0?style=flat-square"> <img src="https://img.shields.io/badge/release-Plus Pro-00b4f0?style=flat-square">
</a> </a>
</p> </p>
@ -36,29 +36,6 @@
Documentations are now moved to [GitHub Wiki](https://github.com/Dreamacro/clash/wiki). Documentations are now moved to [GitHub Wiki](https://github.com/Dreamacro/clash/wiki).
## Advanced usage for this branch ## Advanced usage for this branch
### Build
This branch requires cgo and Python3.9, so make sure you set up Python3.9 before building.
For example, build on macOS:
```sh
brew update
brew install python@3.9
export PKG_CONFIG_PATH=$(find /usr/local/Cellar -name 'pkgconfig' -type d | grep lib/pkgconfig | tr '\n' ':' | sed s/.$//)
git clone -b plus-pro https://github.com/yaling888/clash.git
cd clash
# build
make local
# or make local-v3
ls bin/
# run
sudo bin/clash-local
```
### General configuration ### General configuration
```yaml ```yaml
sniffing: true # Sniff TLS SNI sniffing: true # Sniff TLS SNI
@ -197,20 +174,7 @@ The `GEOIP` databases via [https://github.com/Loyalsoldier/geoip](https://raw.gi
The `GEOSITE` databases via [https://github.com/Loyalsoldier/v2ray-rules-dat](https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat). The `GEOSITE` databases via [https://github.com/Loyalsoldier/v2ray-rules-dat](https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat).
```yaml ```yaml
mode: rule
script:
shortcuts:
quic: 'network == "udp" and dst_port == 443'
privacy: '"analytics" in host or "adservice" in host or "firebase" in host or "safebrowsing" in host or "doubleclick" in host'
BilibiliUdp: |
network == "udp" and match_provider("geosite:bilibili")
rules: rules:
# rule SCRIPT shortcuts
- SCRIPT,quic,REJECT # Disable QUIC, same as rule "DST-PORT,443,REJECT,udp"
- SCRIPT,privacy,REJECT
- SCRIPT,BilibiliUdp,REJECT # same as rule "GEOSITE,bilibili,REJECT,udp"
# network condition for all rules # network condition for all rules
- DOMAIN-SUFFIX,example.com,DIRECT,tcp - DOMAIN-SUFFIX,example.com,DIRECT,tcp
- DOMAIN-SUFFIX,example.com,REJECT,udp - DOMAIN-SUFFIX,example.com,REJECT,udp
@ -245,76 +209,6 @@ rules:
- MATCH,PROXY - MATCH,PROXY
``` ```
### Script configuration
Script enables users to programmatically select a policy for the packets with more flexibility.
```yaml
mode: script
script:
# path: ./script.star
code: |
def main(ctx, metadata):
if metadata["process_name"] == 'apsd':
return "DIRECT"
if metadata["network"] == 'udp' and metadata["dst_port"] == 443:
return "REJECT"
host = metadata["host"]
for kw in ['analytics', 'adservice', 'firebase', 'bugly', 'safebrowsing', 'doubleclick']:
if kw in host:
return "REJECT"
now = time.now()
if (now.hour < 8 or now.hour > 17) and metadata["src_ip"] == '192.168.1.99':
return "REJECT"
if ctx.rule_providers["geosite:category-ads-all"].match(metadata):
return "REJECT"
if ctx.rule_providers["geosite:youtube"].match(metadata):
ctx.log('[Script] domain %s matched youtube' % host)
return "Proxy"
if ctx.rule_providers["geosite:geolocation-cn"].match(metadata):
ctx.log('[Script] domain %s matched geolocation-cn' % host)
return "DIRECT"
ip = metadata["dst_ip"]
if host != "":
ip = ctx.resolve_ip(host)
if ip == "":
return "Proxy"
code = ctx.geoip(ip)
if code == "LAN" or code == "CN":
return "DIRECT"
return "Proxy" # default policy for requests which are not matched by any other script
```
the context and metadata
```ts
interface Metadata {
type: string // socks5、http
network: string // tcp
host: string
process_name: string
process_path: string
src_ip: string
src_port: int
dst_ip: string
dst_port: int
}
interface Context {
resolve_ip: (host: string) => string // ip string
geoip: (ip: string) => string // country code
log: (log: string) => void
rule_providers: Record<string, { match: (metadata: Metadata) => boolean }>
}
```
### Proxies configuration ### Proxies configuration
Support outbound protocol `VLESS`. Support outbound protocol `VLESS`.

View File

@ -23,7 +23,7 @@ func (d *Direct) DialContext(ctx context.Context, metadata *C.Metadata, opts ...
tcpKeepAlive(c) tcpKeepAlive(c)
if !metadata.DstIP.IsValid() && c.RemoteAddr() != nil { if !metadata.Resolved() && c.RemoteAddr() != nil {
if h, _, err := net.SplitHostPort(c.RemoteAddr().String()); err == nil { if h, _, err := net.SplitHostPort(c.RemoteAddr().String()); err == nil {
metadata.DstIP = netip.MustParseAddr(h) metadata.DstIP = netip.MustParseAddr(h)
} }

View File

@ -24,6 +24,7 @@ func (m *Mitm) DialContext(_ context.Context, metadata *C.Metadata, _ ...dialer.
_ = c.SetKeepAlive(true) _ = c.SetKeepAlive(true)
_ = c.SetKeepAlivePeriod(60 * time.Second) _ = c.SetKeepAlivePeriod(60 * time.Second)
_ = c.SetLinger(0)
metadata.Type = C.MITM metadata.Type = C.MITM

View File

@ -15,6 +15,7 @@ func tcpKeepAlive(c net.Conn) {
if tcp, ok := c.(*net.TCPConn); ok { if tcp, ok := c.(*net.TCPConn); ok {
_ = tcp.SetKeepAlive(true) _ = tcp.SetKeepAlive(true)
_ = tcp.SetKeepAlivePeriod(30 * time.Second) _ = tcp.SetKeepAlivePeriod(30 * time.Second)
_ = tcp.SetLinger(0)
} }
} }

View File

@ -152,7 +152,7 @@ func (v *Vless) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
func (v *Vless) StreamPacketConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) { func (v *Vless) StreamPacketConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
// vmess use stream-oriented udp with a special address, so we needs a net.UDPAddr // vmess use stream-oriented udp with a special address, so we needs a net.UDPAddr
if !metadata.Resolved() { if !metadata.Resolved() {
ip, err := resolver.ResolveIP(metadata.Host) ip, err := resolver.ResolveFirstIP(metadata.Host)
if err != nil { if err != nil {
return nil, errors.New("can't resolve ip") return nil, errors.New("can't resolve ip")
} }
@ -245,7 +245,7 @@ func (v *Vless) ListenPacketContext(ctx context.Context, metadata *C.Metadata, o
if v.transport != nil && len(opts) == 0 { if v.transport != nil && len(opts) == 0 {
// vless use stream-oriented udp with a special address, so we needs a net.UDPAddr // vless use stream-oriented udp with a special address, so we needs a net.UDPAddr
if !metadata.Resolved() { if !metadata.Resolved() {
ip, err := resolver.ResolveIP(metadata.Host) ip, err := resolver.ResolveFirstIP(metadata.Host)
if err != nil { if err != nil {
return nil, errors.New("can't resolve ip") return nil, errors.New("can't resolve ip")
} }

View File

@ -91,17 +91,16 @@ func (v *Vmess) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
wsOpts := &vmess.WebsocketConfig{ wsOpts := &vmess.WebsocketConfig{
Host: host, Host: host,
Port: port, Port: port,
Headers: http.Header{},
Path: v.option.WSOpts.Path, Path: v.option.WSOpts.Path,
MaxEarlyData: v.option.WSOpts.MaxEarlyData, MaxEarlyData: v.option.WSOpts.MaxEarlyData,
EarlyDataHeaderName: v.option.WSOpts.EarlyDataHeaderName, EarlyDataHeaderName: v.option.WSOpts.EarlyDataHeaderName,
} }
if len(v.option.WSOpts.Headers) != 0 { if len(v.option.WSOpts.Headers) != 0 {
header := http.Header{}
for key, value := range v.option.WSOpts.Headers { for key, value := range v.option.WSOpts.Headers {
header.Add(key, value) wsOpts.Headers.Add(key, value)
} }
wsOpts.Headers = header
} }
if v.option.TLS { if v.option.TLS {
@ -117,7 +116,9 @@ func (v *Vmess) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
wsOpts.TLSConfig.ServerName = host wsOpts.TLSConfig.ServerName = host
} }
} else { } else {
if wsOpts.Headers.Get("Host") == "" {
wsOpts.Headers.Set("Host", convert.RandHost()) wsOpts.Headers.Set("Host", convert.RandHost())
}
convert.SetUserAgent(wsOpts.Headers) convert.SetUserAgent(wsOpts.Headers)
} }
c, err = vmess.StreamWebsocketConn(c, wsOpts) c, err = vmess.StreamWebsocketConn(c, wsOpts)
@ -138,9 +139,6 @@ func (v *Vmess) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
} else {
http.Header(v.option.HTTPOpts.Headers).Set("Host", convert.RandHost())
convert.SetUserAgent(v.option.HTTPOpts.Headers)
} }
host, _, _ := net.SplitHostPort(v.addr) host, _, _ := net.SplitHostPort(v.addr)
@ -205,7 +203,7 @@ func (v *Vmess) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
func (v *Vmess) StreamPacketConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) { func (v *Vmess) StreamPacketConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
// vmess use stream-oriented udp with a special address, so we needs a net.UDPAddr // vmess use stream-oriented udp with a special address, so we needs a net.UDPAddr
if !metadata.Resolved() { if !metadata.Resolved() {
ip, err := resolver.ResolveIP(metadata.Host) ip, err := resolver.ResolveFirstIP(metadata.Host)
if err != nil { if err != nil {
return c, fmt.Errorf("can't resolve ip: %w", err) return c, fmt.Errorf("can't resolve ip: %w", err)
} }
@ -257,7 +255,7 @@ func (v *Vmess) ListenPacketContext(ctx context.Context, metadata *C.Metadata, o
if v.transport != nil && len(opts) == 0 { if v.transport != nil && len(opts) == 0 {
// vmess use stream-oriented udp with a special address, so we needs a net.UDPAddr // vmess use stream-oriented udp with a special address, so we needs a net.UDPAddr
if !metadata.Resolved() { if !metadata.Resolved() {
ip, err := resolver.ResolveIP(metadata.Host) ip, err := resolver.ResolveFirstIP(metadata.Host)
if err != nil { if err != nil {
return nil, fmt.Errorf("can't resolve ip: %w", err) return nil, fmt.Errorf("can't resolve ip: %w", err)
} }

View File

@ -3,6 +3,8 @@ package outboundgroup
import ( import (
"time" "time"
"github.com/Dreamacro/clash/adapter"
"github.com/Dreamacro/clash/adapter/outbound"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/constant/provider" "github.com/Dreamacro/clash/constant/provider"
) )
@ -11,14 +13,19 @@ const (
defaultGetProxiesDuration = time.Second * 5 defaultGetProxiesDuration = time.Second * 5
) )
var defaultRejectProxy = adapter.NewProxy(outbound.NewReject())
func getProvidersProxies(providers []provider.ProxyProvider, touch bool) []C.Proxy { func getProvidersProxies(providers []provider.ProxyProvider, touch bool) []C.Proxy {
proxies := []C.Proxy{} proxies := []C.Proxy{}
for _, provider := range providers { for _, pd := range providers {
if touch { if touch {
proxies = append(proxies, provider.ProxiesWithTouch()...) proxies = append(proxies, pd.ProxiesWithTouch()...)
} else { } else {
proxies = append(proxies, provider.Proxies()...) proxies = append(proxies, pd.Proxies()...)
} }
} }
if len(proxies) == 0 {
proxies = append(proxies, defaultRejectProxy)
}
return proxies return proxies
} }

View File

@ -30,11 +30,9 @@ type LoadBalance struct {
var errStrategy = errors.New("unsupported strategy") var errStrategy = errors.New("unsupported strategy")
func parseStrategy(config map[string]any) string { func parseStrategy(config map[string]any) string {
if elm, ok := config["strategy"]; ok { if strategy, ok := config["strategy"].(string); ok {
if strategy, ok := elm.(string); ok {
return strategy return strategy
} }
}
return "consistent-hashing" return "consistent-hashing"
} }

View File

@ -78,9 +78,11 @@ func ParseProxyGroup(config map[string]any, proxyMap map[string]C.Proxy, provide
return nil, errDuplicateProvider return nil, errDuplicateProvider
} }
// select don't need health check hc, err := newHealthCheck(ps, groupOption)
if groupOption.Type == "select" || groupOption.Type == "relay" { if err != nil {
hc := provider.NewHealthCheck(ps, "", 0, true) return nil, err
}
pd, err := provider.NewCompatibleProvider(groupName, ps, hc) pd, err := provider.NewCompatibleProvider(groupName, ps, hc)
if err != nil { if err != nil {
return nil, err return nil, err
@ -88,20 +90,6 @@ func ParseProxyGroup(config map[string]any, proxyMap map[string]C.Proxy, provide
providers = append(providers, pd) providers = append(providers, pd)
providersMap[groupName] = pd providersMap[groupName] = pd
} else {
if groupOption.URL == "" || groupOption.Interval == 0 {
return nil, errMissHealthCheck
}
hc := provider.NewHealthCheck(ps, groupOption.URL, uint(groupOption.Interval), groupOption.Lazy)
pd, err := provider.NewCompatibleProvider(groupName, ps, hc)
if err != nil {
return nil, err
}
providers = append(providers, pd)
providersMap[groupName] = pd
}
} }
if len(groupOption.Use) != 0 { if len(groupOption.Use) != 0 {
@ -109,8 +97,13 @@ func ParseProxyGroup(config map[string]any, proxyMap map[string]C.Proxy, provide
if err != nil { if err != nil {
return nil, err return nil, err
} }
if groupOption.Type == "fallback" {
providers = append(list, providers...)
} else {
providers = append(providers, list...) providers = append(providers, list...)
} }
}
var group C.ProxyAdapter var group C.ProxyAdapter
switch groupOption.Type { switch groupOption.Type {
@ -163,22 +156,18 @@ func getProviders(mapping map[string]types.ProxyProvider, groupOption *GroupComm
} }
if filterRegx != nil { if filterRegx != nil {
var hc *provider.HealthCheck hc, err := newHealthCheck([]C.Proxy{}, groupOption)
if groupOption.Type == "select" || groupOption.Type == "relay" { if err != nil {
hc = provider.NewHealthCheck([]C.Proxy{}, "", 0, true) return nil, err
} else {
if groupOption.URL == "" || groupOption.Interval == 0 {
return nil, errMissHealthCheck
}
hc = provider.NewHealthCheck([]C.Proxy{}, groupOption.URL, uint(groupOption.Interval), groupOption.Lazy)
} }
if _, ok = mapping[groupName]; ok { gName := groupName
groupName += "->" + p.Name() if _, ok = mapping[gName]; ok {
gName = groupName + " -> " + p.Name()
} }
pd := p.(*provider.ProxySetProvider) pd := p.(*provider.ProxySetProvider)
p = provider.NewProxyFilterProvider(groupName, pd, hc, filterRegx) p = provider.NewProxyFilterProvider(gName, pd, hc, filterRegx)
pd.RegisterProvidersInUse(p) pd.RegisterProvidersInUse(p)
} }
@ -186,3 +175,18 @@ func getProviders(mapping map[string]types.ProxyProvider, groupOption *GroupComm
} }
return ps, nil return ps, nil
} }
func newHealthCheck(ps []C.Proxy, groupOption *GroupCommonOption) (*provider.HealthCheck, error) {
var hc *provider.HealthCheck
// select don't need health check
if groupOption.Type == "select" || groupOption.Type == "relay" {
hc = provider.NewHealthCheck(ps, "", 0, true)
} else {
if groupOption.URL == "" || groupOption.Interval == 0 {
return nil, errMissHealthCheck
}
hc = provider.NewHealthCheck(ps, groupOption.URL, uint(groupOption.Interval), groupOption.Lazy)
}
return hc, nil
}

View File

@ -98,7 +98,11 @@ func (s *Selector) selectedProxy(touch bool) C.Proxy {
} }
func NewSelector(option *GroupCommonOption, providers []provider.ProxyProvider) *Selector { func NewSelector(option *GroupCommonOption, providers []provider.ProxyProvider) *Selector {
selected := providers[0].Proxies()[0].Name() selected := "REJECT"
if len(providers) != 0 && len(providers[0].Proxies()) != 0 {
selected = providers[0].Proxies()[0].Name()
}
return &Selector{ return &Selector{
Base: outbound.NewBase(outbound.BaseOption{ Base: outbound.NewBase(outbound.BaseOption{
Name: option.Name, Name: option.Name,

View File

@ -125,11 +125,9 @@ func parseURLTestOption(config map[string]any) []urlTestOption {
opts := []urlTestOption{} opts := []urlTestOption{}
// tolerance // tolerance
if elm, ok := config["tolerance"]; ok { if tolerance, ok := config["tolerance"].(int); ok {
if tolerance, ok := elm.(int); ok {
opts = append(opts, urlTestWithTolerance(uint16(tolerance))) opts = append(opts, urlTestWithTolerance(uint16(tolerance)))
} }
}
return opts return opts
} }

View File

@ -48,5 +48,6 @@ func tcpKeepAlive(c net.Conn) {
if tcp, ok := c.(*net.TCPConn); ok { if tcp, ok := c.(*net.TCPConn); ok {
_ = tcp.SetKeepAlive(true) _ = tcp.SetKeepAlive(true)
_ = tcp.SetKeepAlivePeriod(30 * time.Second) _ = tcp.SetKeepAlivePeriod(30 * time.Second)
_ = tcp.SetLinger(0)
} }
} }

View File

@ -59,6 +59,7 @@ func ParseProxy(mapping map[string]any, forceCertVerify bool) (C.Proxy, error) {
HTTPOpts: outbound.HTTPOptions{ HTTPOpts: outbound.HTTPOptions{
Method: "GET", Method: "GET",
Path: []string{"/"}, Path: []string{"/"},
Headers: make(map[string][]string),
}, },
} }
err = decoder.Decode(mapping, vmessOption) err = decoder.Decode(mapping, vmessOption)

View File

@ -5,6 +5,7 @@ import (
"crypto/md5" "crypto/md5"
"os" "os"
"path/filepath" "path/filepath"
"regexp"
"time" "time"
types "github.com/Dreamacro/clash/constant/provider" types "github.com/Dreamacro/clash/constant/provider"
@ -14,6 +15,8 @@ import (
var ( var (
fileMode os.FileMode = 0o666 fileMode os.FileMode = 0o666
dirMode os.FileMode = 0o755 dirMode os.FileMode = 0o755
commentRegx = regexp.MustCompile(`(.*#.*\n)`)
) )
type parser[V any] func([]byte) (V, error) type parser[V any] func([]byte) (V, error)
@ -168,6 +171,18 @@ func safeWrite(path string, buf []byte) error {
return os.WriteFile(path, buf, fileMode) return os.WriteFile(path, buf, fileMode)
} }
func removeComment(buf []byte) []byte {
arr := commentRegx.FindAllSubmatch(buf, -1)
for _, subs := range arr {
sub := subs[0]
if !bytes.HasPrefix(bytes.TrimLeft(sub, " "), []byte("#")) {
continue
}
buf = bytes.Replace(buf, sub, []byte(""), 1)
}
return buf
}
func newFetcher[V any](name string, interval time.Duration, vehicle types.Vehicle, parser parser[V], onUpdate func(V)) *fetcher[V] { func newFetcher[V any](name string, interval time.Duration, vehicle types.Vehicle, parser parser[V], onUpdate func(V)) *fetcher[V] {
var ticker *time.Ticker var ticker *time.Ticker
if interval != 0 { if interval != 0 {

View File

@ -25,10 +25,16 @@ type HealthCheck struct {
interval uint interval uint
lazy bool lazy bool
lastTouch *atomic.Int64 lastTouch *atomic.Int64
running *atomic.Bool
done chan struct{} done chan struct{}
} }
func (hc *HealthCheck) process() { func (hc *HealthCheck) process() {
if hc.running.Load() {
return
}
hc.running.Store(true)
ticker := time.NewTicker(time.Duration(hc.interval) * time.Second) ticker := time.NewTicker(time.Duration(hc.interval) * time.Second)
go func() { go func() {
@ -84,6 +90,10 @@ func (hc *HealthCheck) check() {
} }
func (hc *HealthCheck) close() { func (hc *HealthCheck) close() {
if !hc.running.Load() {
return
}
hc.running.Store(false)
hc.done <- struct{}{} hc.done <- struct{}{}
} }
@ -94,6 +104,7 @@ func NewHealthCheck(proxies []C.Proxy, url string, interval uint, lazy bool) *He
interval: interval, interval: interval,
lazy: lazy, lazy: lazy,
lastTouch: atomic.NewInt64(0), lastTouch: atomic.NewInt64(0),
running: atomic.NewBool(false),
done: make(chan struct{}, 1), done: make(chan struct{}, 1),
} }
} }

View File

@ -234,7 +234,9 @@ func (pf *proxyFilterProvider) HealthCheck() {
} }
func (pf *proxyFilterProvider) Update() error { func (pf *proxyFilterProvider) Update() error {
var proxies []C.Proxy pf.healthCheck.close()
proxies := []C.Proxy{}
if pf.filter != nil { if pf.filter != nil {
for _, proxy := range pf.psd.Proxies() { for _, proxy := range pf.psd.Proxies() {
if !pf.filter.MatchString(proxy.Name()) { if !pf.filter.MatchString(proxy.Name()) {
@ -248,6 +250,10 @@ func (pf *proxyFilterProvider) Update() error {
pf.proxies = proxies pf.proxies = proxies
pf.healthCheck.setProxy(proxies) pf.healthCheck.setProxy(proxies)
if len(proxies) != 0 && pf.healthCheck.auto() {
go pf.healthCheck.process()
}
return nil return nil
} }
@ -286,10 +292,6 @@ func NewProxyFilterProvider(name string, psd *ProxySetProvider, hc *HealthCheck,
_ = pd.Update() _ = pd.Update()
if hc.auto() {
go hc.process()
}
wrapper := &ProxyFilterProvider{pd} wrapper := &ProxyFilterProvider{pd}
runtime.SetFinalizer(wrapper, stopProxyFilterProvider) runtime.SetFinalizer(wrapper, stopProxyFilterProvider)
return wrapper return wrapper
@ -319,12 +321,13 @@ func proxiesParseAndFilter(filter string, filterReg *regexp.Regexp, forceCertVer
proxies := []C.Proxy{} proxies := []C.Proxy{}
for idx, mapping := range schema.Proxies { for idx, mapping := range schema.Proxies {
if name, ok := mapping["name"]; ok && len(filter) > 0 && !filterReg.MatchString(name.(string)) { name, ok := mapping["name"].(string)
if ok && len(filter) > 0 && !filterReg.MatchString(name) {
continue continue
} }
if prefixName != "" { if prefixName != "" {
mapping["name"] = prefixName + mapping["name"].(string) mapping["name"] = prefixName + name
} }
proxy, err := adapter.ParseProxy(mapping, forceCertVerify) proxy, err := adapter.ParseProxy(mapping, forceCertVerify)

View File

@ -11,7 +11,6 @@ import (
"github.com/Dreamacro/clash/common/convert" "github.com/Dreamacro/clash/common/convert"
"github.com/Dreamacro/clash/component/dialer" "github.com/Dreamacro/clash/component/dialer"
C "github.com/Dreamacro/clash/constant"
types "github.com/Dreamacro/clash/constant/provider" types "github.com/Dreamacro/clash/constant/provider"
) )
@ -82,13 +81,11 @@ func (h *HTTPVehicle) Read() ([]byte, error) {
IdleConnTimeout: 90 * time.Second, IdleConnTimeout: 90 * time.Second,
TLSHandshakeTimeout: 10 * time.Second, TLSHandshakeTimeout: 10 * time.Second,
ExpectContinueTimeout: 1 * time.Second, ExpectContinueTimeout: 1 * time.Second,
DialContext: func(ctx context.Context, network, address string) (conn net.Conn, err error) { DialContext: func(ctx context.Context, network, address string) (net.Conn, error) {
conn, err = dialer.DialContext(ctx, network, address) // with direct if req.URL.Scheme == "https" {
if err != nil { return (&net.Dialer{}).DialContext(ctx, network, address) // forward to tun if tun enabled
// fallback to tun if tun enabled
conn, err = (&net.Dialer{Timeout: C.DefaultTCPTimeout}).Dial(network, address)
} }
return return dialer.DialContext(ctx, network, address, dialer.WithDirect()) // with direct
}, },
} }
@ -106,7 +103,7 @@ func (h *HTTPVehicle) Read() ([]byte, error) {
return nil, err return nil, err
} }
return buf, nil return removeComment(buf), nil
} }
func NewHTTPVehicle(url string, path string, header http.Header) *HTTPVehicle { func NewHTTPVehicle(url string, path string, header http.Header) *HTTPVehicle {

View File

@ -2,6 +2,7 @@ package cert
import ( import (
"crypto/tls" "crypto/tls"
"sync"
"github.com/Dreamacro/clash/component/trie" "github.com/Dreamacro/clash/component/trie"
) )
@ -9,10 +10,14 @@ import (
// DomainTrieCertsStorage cache wildcard certificates // DomainTrieCertsStorage cache wildcard certificates
type DomainTrieCertsStorage struct { type DomainTrieCertsStorage struct {
certsCache *trie.DomainTrie[*tls.Certificate] certsCache *trie.DomainTrie[*tls.Certificate]
lock sync.RWMutex
} }
// Get gets the certificate from the storage // Get gets the certificate from the storage
func (c *DomainTrieCertsStorage) Get(key string) (*tls.Certificate, bool) { func (c *DomainTrieCertsStorage) Get(key string) (*tls.Certificate, bool) {
c.lock.RLock()
defer c.lock.RUnlock()
ca := c.certsCache.Search(key) ca := c.certsCache.Search(key)
if ca == nil { if ca == nil {
return nil, false return nil, false
@ -22,7 +27,9 @@ func (c *DomainTrieCertsStorage) Get(key string) (*tls.Certificate, bool) {
// Set saves the certificate to the storage // Set saves the certificate to the storage
func (c *DomainTrieCertsStorage) Set(key string, cert *tls.Certificate) { func (c *DomainTrieCertsStorage) Set(key string, cert *tls.Certificate) {
c.lock.Lock()
_ = c.certsCache.Insert(key, cert) _ = c.certsCache.Insert(key, cert)
c.lock.Unlock()
} }
func NewDomainTrieCertsStorage() *DomainTrieCertsStorage { func NewDomainTrieCertsStorage() *DomainTrieCertsStorage {

View File

@ -21,22 +21,25 @@ func DecodeBase64(buf []byte) ([]byte, error) {
return dBuf[:n], nil return dBuf[:n], nil
} }
// DecodeBase64StringToString decode base64 string to string func DecodeRawBase64(buf []byte) ([]byte, error) {
func DecodeBase64StringToString(s string) (string, error) { dBuf := make([]byte, base64.RawStdEncoding.DecodedLen(len(buf)))
dBuf, err := enc.DecodeString(s) n, err := base64.RawStdEncoding.Decode(dBuf, buf)
if err != nil { if err != nil {
return "", err return nil, err
} }
return string(dBuf), nil return dBuf[:n], nil
} }
// ConvertsV2Ray convert V2Ray subscribe proxies data to clash proxies config // ConvertsV2Ray convert V2Ray subscribe proxies data to clash proxies config
func ConvertsV2Ray(buf []byte) ([]map[string]any, error) { func ConvertsV2Ray(buf []byte) ([]map[string]any, error) {
data, err := DecodeBase64(buf) data, err := DecodeBase64(buf)
if err != nil {
data, err = DecodeRawBase64(buf)
if err != nil { if err != nil {
data = buf data = buf
} }
}
arr := strings.Split(string(data), "\n") arr := strings.Split(string(data), "\n")

View File

@ -4,8 +4,6 @@ import (
"io" "io"
"net" "net"
"time" "time"
"github.com/Dreamacro/clash/common/pool"
) )
// Relay copies between left and right bidirectionally. // Relay copies between left and right bidirectionally.
@ -16,18 +14,14 @@ func Relay(leftConn, rightConn net.Conn) {
tcpKeepAlive(rightConn) tcpKeepAlive(rightConn)
go func() { go func() {
buf := pool.Get(pool.RelayBufferSize)
// Wrapping to avoid using *net.TCPConn.(ReadFrom) // Wrapping to avoid using *net.TCPConn.(ReadFrom)
// See also https://github.com/Dreamacro/clash/pull/1209 // See also https://github.com/Dreamacro/clash/pull/1209
_, err := io.CopyBuffer(WriteOnlyWriter{Writer: leftConn}, ReadOnlyReader{Reader: rightConn}, buf) _, err := io.Copy(WriteOnlyWriter{Writer: leftConn}, ReadOnlyReader{Reader: rightConn})
_ = pool.Put(buf)
_ = leftConn.SetReadDeadline(time.Now()) _ = leftConn.SetReadDeadline(time.Now())
ch <- err ch <- err
}() }()
buf := pool.Get(pool.RelayBufferSize) _, _ = io.Copy(WriteOnlyWriter{Writer: rightConn}, ReadOnlyReader{Reader: leftConn})
_, _ = io.CopyBuffer(WriteOnlyWriter{Writer: rightConn}, ReadOnlyReader{Reader: leftConn}, buf)
_ = pool.Put(buf)
_ = rightConn.SetReadDeadline(time.Now()) _ = rightConn.SetReadDeadline(time.Now())
<-ch <-ch
} }

View File

@ -29,13 +29,13 @@ func bindMarkToControl(mark int, chain controlFn) controlFn {
return return
} }
return c.Control(func(fd uintptr) { var innerErr error
switch network { err = c.Control(func(fd uintptr) {
case "tcp4", "udp4": innerErr = syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, syscall.SO_MARK, mark)
_ = syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, syscall.SO_MARK, mark)
case "tcp6", "udp6":
_ = syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, syscall.SO_MARK, mark)
}
}) })
if err == nil && innerErr != nil {
err = innerErr
}
return
} }
} }

View File

@ -2,11 +2,7 @@ package process
import ( import (
"errors" "errors"
"net"
"net/netip" "net/netip"
"github.com/Dreamacro/clash/common/nnip"
C "github.com/Dreamacro/clash/constant"
) )
var ( var (
@ -23,49 +19,3 @@ const (
func FindProcessName(network string, srcIP netip.Addr, srcPort int) (string, error) { func FindProcessName(network string, srcIP netip.Addr, srcPort int) (string, error) {
return findProcessName(network, srcIP, srcPort) return findProcessName(network, srcIP, srcPort)
} }
func ShouldFindProcess(metadata *C.Metadata) bool {
if metadata.Process != "" {
return false
}
for _, ip := range localIPs {
if ip == metadata.SrcIP {
return true
}
}
return false
}
func AppendLocalIPs(ip ...netip.Addr) {
localIPs = append(ip, localIPs...)
}
func getLocalIPs() []netip.Addr {
ips := []netip.Addr{netip.IPv4Unspecified(), netip.IPv6Unspecified()}
netInterfaces, err := net.Interfaces()
if err != nil {
ips = append(ips, netip.AddrFrom4([4]byte{127, 0, 0, 1}), nnip.IpToAddr(net.IPv6loopback))
return ips
}
for i := 0; i < len(netInterfaces); i++ {
if (netInterfaces[i].Flags & net.FlagUp) != 0 {
adds, _ := netInterfaces[i].Addrs()
for _, address := range adds {
if ipNet, ok := address.(*net.IPNet); ok {
ips = append(ips, nnip.IpToAddr(ipNet.IP))
}
}
}
}
return ips
}
var localIPs []netip.Addr
func init() {
localIPs = getLocalIPs()
}

View File

@ -6,8 +6,6 @@ import (
"syscall" "syscall"
"unsafe" "unsafe"
"github.com/Dreamacro/clash/common/nnip"
"golang.org/x/sys/unix" "golang.org/x/sys/unix"
) )
@ -63,10 +61,10 @@ func findProcessName(network string, ip netip.Addr, port int) (string, error) {
switch { switch {
case flag&0x1 > 0 && isIPv4: case flag&0x1 > 0 && isIPv4:
// ipv4 // ipv4
srcIP = nnip.IpToAddr(buf[inp+76 : inp+80]) srcIP, _ = netip.AddrFromSlice(buf[inp+76 : inp+80])
case flag&0x2 > 0 && !isIPv4: case flag&0x2 > 0 && !isIPv4:
// ipv6 // ipv6
srcIP = nnip.IpToAddr(buf[inp+64 : inp+80]) srcIP, _ = netip.AddrFromSlice(buf[inp+64 : inp+80])
default: default:
continue continue
} }

View File

@ -10,7 +10,6 @@ import (
"syscall" "syscall"
"unsafe" "unsafe"
"github.com/Dreamacro/clash/common/nnip"
"github.com/Dreamacro/clash/log" "github.com/Dreamacro/clash/log"
) )
@ -135,10 +134,10 @@ func (s *searcher) Search(buf []byte, ip netip.Addr, port uint16, isTCP bool) (u
switch { switch {
case flag&0x1 > 0 && isIPv4: case flag&0x1 > 0 && isIPv4:
// ipv4 // ipv4
srcIP = nnip.IpToAddr(buf[inp+s.ip : inp+s.ip+4]) srcIP, _ = netip.AddrFromSlice(buf[inp+s.ip : inp+s.ip+4])
case flag&0x2 > 0 && !isIPv4: case flag&0x2 > 0 && !isIPv4:
// ipv6 // ipv6
srcIP = nnip.IpToAddr(buf[inp+s.ip-12 : inp+s.ip+4]) srcIP, _ = netip.AddrFromSlice(buf[inp+s.ip-12 : inp+s.ip+4])
default: default:
continue continue
} }

View File

@ -7,7 +7,6 @@ import (
"syscall" "syscall"
"unsafe" "unsafe"
"github.com/Dreamacro/clash/common/nnip"
"github.com/Dreamacro/clash/log" "github.com/Dreamacro/clash/log"
"golang.org/x/sys/windows" "golang.org/x/sys/windows"
@ -132,7 +131,7 @@ func (s *searcher) Search(b []byte, ip netip.Addr, port uint16) (uint32, error)
continue continue
} }
srcIP := nnip.IpToAddr(row[s.ip : s.ip+s.ipSize]) srcIP, _ := netip.AddrFromSlice(row[s.ip : s.ip+s.ipSize])
// windows binds an unbound udp socket to 0.0.0.0/[::] while first sendto // windows binds an unbound udp socket to 0.0.0.0/[::] while first sendto
if ip != srcIP && (!srcIP.IsUnspecified() || s.tcpState != -1) { if ip != srcIP && (!srcIP.IsUnspecified() || s.tcpState != -1) {
continue continue

View File

@ -37,17 +37,17 @@ var (
) )
type Resolver interface { type Resolver interface {
ResolveIP(host string) (ip netip.Addr, err error) ResolveIP(host string, random bool) (ip netip.Addr, err error)
ResolveIPv4(host string) (ip netip.Addr, err error) ResolveIPv4(host string, random bool) (ip netip.Addr, err error)
ResolveIPv6(host string) (ip netip.Addr, err error) ResolveIPv6(host string, random bool) (ip netip.Addr, err error)
} }
// ResolveIPv4 with a host, return ipv4 // ResolveIPv4 with a host, return ipv4
func ResolveIPv4(host string) (netip.Addr, error) { func ResolveIPv4(host string) (netip.Addr, error) {
return ResolveIPv4WithResolver(host, DefaultResolver) return resolveIPv4(host, true)
} }
func ResolveIPv4WithResolver(host string, r Resolver) (netip.Addr, error) { func ResolveIPv4WithResolver(host string, r Resolver, random bool) (netip.Addr, error) {
if node := DefaultHosts.Search(host); node != nil { if node := DefaultHosts.Search(host); node != nil {
if ip := node.Data; ip.Is4() { if ip := node.Data; ip.Is4() {
return ip, nil return ip, nil
@ -56,6 +56,7 @@ func ResolveIPv4WithResolver(host string, r Resolver) (netip.Addr, error) {
ip, err := netip.ParseAddr(host) ip, err := netip.ParseAddr(host)
if err == nil { if err == nil {
ip = ip.Unmap()
if ip.Is4() { if ip.Is4() {
return ip, nil return ip, nil
} }
@ -63,7 +64,7 @@ func ResolveIPv4WithResolver(host string, r Resolver) (netip.Addr, error) {
} }
if r != nil { if r != nil {
return r.ResolveIPv4(host) return r.ResolveIPv4(host, random)
} }
if DefaultResolver == nil { if DefaultResolver == nil {
@ -76,7 +77,11 @@ func ResolveIPv4WithResolver(host string, r Resolver) (netip.Addr, error) {
return netip.Addr{}, ErrIPNotFound return netip.Addr{}, ErrIPNotFound
} }
ip := ipAddrs[rand.Intn(len(ipAddrs))].To4() index := 0
if random {
index = rand.Intn(len(ipAddrs))
}
ip := ipAddrs[index].To4()
if ip == nil { if ip == nil {
return netip.Addr{}, ErrIPVersion return netip.Addr{}, ErrIPVersion
} }
@ -89,10 +94,10 @@ func ResolveIPv4WithResolver(host string, r Resolver) (netip.Addr, error) {
// ResolveIPv6 with a host, return ipv6 // ResolveIPv6 with a host, return ipv6
func ResolveIPv6(host string) (netip.Addr, error) { func ResolveIPv6(host string) (netip.Addr, error) {
return ResolveIPv6WithResolver(host, DefaultResolver) return ResolveIPv6WithResolver(host, DefaultResolver, true)
} }
func ResolveIPv6WithResolver(host string, r Resolver) (netip.Addr, error) { func ResolveIPv6WithResolver(host string, r Resolver, random bool) (netip.Addr, error) {
if DisableIPv6 { if DisableIPv6 {
return netip.Addr{}, ErrIPv6Disabled return netip.Addr{}, ErrIPv6Disabled
} }
@ -112,7 +117,7 @@ func ResolveIPv6WithResolver(host string, r Resolver) (netip.Addr, error) {
} }
if r != nil { if r != nil {
return r.ResolveIPv6(host) return r.ResolveIPv6(host, random)
} }
if DefaultResolver == nil { if DefaultResolver == nil {
@ -125,25 +130,29 @@ func ResolveIPv6WithResolver(host string, r Resolver) (netip.Addr, error) {
return netip.Addr{}, ErrIPNotFound return netip.Addr{}, ErrIPNotFound
} }
return netip.AddrFrom16(*(*[16]byte)(ipAddrs[rand.Intn(len(ipAddrs))])), nil index := 0
if random {
index = rand.Intn(len(ipAddrs))
}
return netip.AddrFrom16(*(*[16]byte)(ipAddrs[index])), nil
} }
return netip.Addr{}, ErrIPNotFound return netip.Addr{}, ErrIPNotFound
} }
// ResolveIPWithResolver same as ResolveIP, but with a resolver // ResolveIPWithResolver same as ResolveIP, but with a resolver
func ResolveIPWithResolver(host string, r Resolver) (netip.Addr, error) { func ResolveIPWithResolver(host string, r Resolver, random bool) (netip.Addr, error) {
if node := DefaultHosts.Search(host); node != nil { if node := DefaultHosts.Search(host); node != nil {
return node.Data, nil return node.Data, nil
} }
if r != nil { if r != nil {
if DisableIPv6 { if DisableIPv6 {
return r.ResolveIPv4(host) return r.ResolveIPv4(host, random)
} }
return r.ResolveIP(host) return r.ResolveIP(host, random)
} else if DisableIPv6 { } else if DisableIPv6 {
return ResolveIPv4(host) return resolveIPv4(host, random)
} }
ip, err := netip.ParseAddr(host) ip, err := netip.ParseAddr(host)
@ -165,13 +174,18 @@ func ResolveIPWithResolver(host string, r Resolver) (netip.Addr, error) {
// ResolveIP with a host, return ip // ResolveIP with a host, return ip
func ResolveIP(host string) (netip.Addr, error) { func ResolveIP(host string) (netip.Addr, error) {
return ResolveIPWithResolver(host, DefaultResolver) return resolveIP(host, true)
}
// ResolveFirstIP with a host, return ip
func ResolveFirstIP(host string) (netip.Addr, error) {
return resolveIP(host, false)
} }
// ResolveIPv4ProxyServerHost proxies server host only // ResolveIPv4ProxyServerHost proxies server host only
func ResolveIPv4ProxyServerHost(host string) (netip.Addr, error) { func ResolveIPv4ProxyServerHost(host string) (netip.Addr, error) {
if ProxyServerHostResolver != nil { if ProxyServerHostResolver != nil {
return ResolveIPv4WithResolver(host, ProxyServerHostResolver) return ResolveIPv4WithResolver(host, ProxyServerHostResolver, true)
} }
return ResolveIPv4(host) return ResolveIPv4(host)
} }
@ -179,7 +193,7 @@ func ResolveIPv4ProxyServerHost(host string) (netip.Addr, error) {
// ResolveIPv6ProxyServerHost proxies server host only // ResolveIPv6ProxyServerHost proxies server host only
func ResolveIPv6ProxyServerHost(host string) (netip.Addr, error) { func ResolveIPv6ProxyServerHost(host string) (netip.Addr, error) {
if ProxyServerHostResolver != nil { if ProxyServerHostResolver != nil {
return ResolveIPv6WithResolver(host, ProxyServerHostResolver) return ResolveIPv6WithResolver(host, ProxyServerHostResolver, true)
} }
return ResolveIPv6(host) return ResolveIPv6(host)
} }
@ -187,7 +201,15 @@ func ResolveIPv6ProxyServerHost(host string) (netip.Addr, error) {
// ResolveProxyServerHost proxies server host only // ResolveProxyServerHost proxies server host only
func ResolveProxyServerHost(host string) (netip.Addr, error) { func ResolveProxyServerHost(host string) (netip.Addr, error) {
if ProxyServerHostResolver != nil { if ProxyServerHostResolver != nil {
return ResolveIPWithResolver(host, ProxyServerHostResolver) return ResolveIPWithResolver(host, ProxyServerHostResolver, true)
} }
return ResolveIP(host) return ResolveIP(host)
} }
func resolveIP(host string, random bool) (netip.Addr, error) {
return ResolveIPWithResolver(host, DefaultResolver, random)
}
func resolveIPv4(host string, random bool) (netip.Addr, error) {
return ResolveIPv4WithResolver(host, DefaultResolver, random)
}

View File

@ -1,10 +0,0 @@
//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"

View File

@ -1,8 +0,0 @@
//go:build build_local
package script
/*
#cgo pkg-config: python3-embed
*/
import "C"

View File

@ -1,24 +0,0 @@
//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"

View File

@ -1,743 +0,0 @@
#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;
}
/* --------------------------------------------------------------------- */

View File

@ -1,338 +0,0 @@
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)
}

View File

@ -1,65 +0,0 @@
#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__

View File

@ -1,145 +0,0 @@
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))
}

View File

@ -1,52 +0,0 @@
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
}

View File

@ -7,7 +7,6 @@ import (
"net/netip" "net/netip"
"net/url" "net/url"
"os" "os"
"regexp"
"runtime" "runtime"
"strings" "strings"
@ -21,7 +20,6 @@ import (
"github.com/Dreamacro/clash/component/geodata" "github.com/Dreamacro/clash/component/geodata"
"github.com/Dreamacro/clash/component/geodata/router" "github.com/Dreamacro/clash/component/geodata/router"
_ "github.com/Dreamacro/clash/component/geodata/standard" _ "github.com/Dreamacro/clash/component/geodata/standard"
S "github.com/Dreamacro/clash/component/script"
"github.com/Dreamacro/clash/component/trie" "github.com/Dreamacro/clash/component/trie"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
providerTypes "github.com/Dreamacro/clash/constant/provider" providerTypes "github.com/Dreamacro/clash/constant/provider"
@ -107,13 +105,7 @@ type Tun struct {
DNSHijack []C.DNSUrl `yaml:"dns-hijack" json:"dns-hijack"` DNSHijack []C.DNSUrl `yaml:"dns-hijack" json:"dns-hijack"`
AutoRoute bool `yaml:"auto-route" json:"auto-route"` AutoRoute bool `yaml:"auto-route" json:"auto-route"`
AutoDetectInterface bool `yaml:"auto-detect-interface" json:"auto-detect-interface"` AutoDetectInterface bool `yaml:"auto-detect-interface" json:"auto-detect-interface"`
} TunAddressPrefix *netip.Prefix `yaml:"_" json:"_"`
// Script config
type Script struct {
MainCode string `yaml:"code" json:"code"`
MainPath string `yaml:"path" json:"path"`
ShortcutsCode map[string]string `yaml:"shortcuts" json:"shortcuts"`
} }
// IPTables config // IPTables config
@ -141,7 +133,6 @@ type Config struct {
Hosts *trie.DomainTrie[netip.Addr] Hosts *trie.DomainTrie[netip.Addr]
Profile *Profile Profile *Profile
Rules []C.Rule Rules []C.Rule
RuleProviders map[string]C.Rule
Users []auth.AuthUser Users []auth.AuthUser
Proxies map[string]C.Proxy Proxies map[string]C.Proxy
Providers map[string]providerTypes.ProxyProvider Providers map[string]providerTypes.ProxyProvider
@ -208,7 +199,6 @@ 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
@ -316,18 +306,11 @@ func ParseRawConfig(rawCfg *RawConfig) (*Config, error) {
config.Proxies = proxies config.Proxies = proxies
config.Providers = providers config.Providers = providers
rawRules, err := parseScript(rawCfg.Script, rawCfg.Rule) rules, err := parseRules(rawCfg, proxies)
if err != nil {
return nil, err
}
rawCfg.Rule = rawRules
rules, ruleProviders, err := parseRules(rawCfg, proxies)
if err != nil { if err != nil {
return nil, err return nil, err
} }
config.Rules = rules config.Rules = rules
config.RuleProviders = ruleProviders
hosts, err := parseHosts(rawCfg) hosts, err := parseHosts(rawCfg)
if err != nil { if err != nil {
@ -511,15 +494,10 @@ 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, map[string]C.Rule, error) { func parseRules(cfg *RawConfig, proxies map[string]C.Proxy) ([]C.Rule, error) {
var ( rulesConfig := cfg.Rule
rules []C.Rule
providerNames []string
foundRP bool
ruleProviders = map[string]C.Rule{} var rules []C.Rule
rulesConfig = cfg.Rule
)
// parse rules // parse rules
for idx, line := range rulesConfig { for idx, line := range rulesConfig {
@ -534,7 +512,7 @@ func parseRules(cfg *RawConfig, proxies map[string]C.Proxy) ([]C.Rule, map[strin
l := len(rule) l := len(rule)
if l < 2 { if l < 2 {
return nil, nil, fmt.Errorf("rules[%d] [%s] error: format invalid", idx, line) return nil, fmt.Errorf("rules[%d] [%s] error: format invalid", idx, line)
} }
if l < 4 { if l < 4 {
@ -553,40 +531,23 @@ func parseRules(cfg *RawConfig, proxies map[string]C.Proxy) ([]C.Rule, map[strin
target = rule[l-1] target = rule[l-1]
params = rule[l:] params = rule[l:]
if _, ok := proxies[target]; !ok && ruleName != "GEOSITE" && target != C.ScriptRuleGeoSiteTarget { if _, ok := proxies[target]; !ok {
return nil, nil, fmt.Errorf("rules[%d] [%s] error: proxy [%s] not found", idx, line, target) return nil, fmt.Errorf("rules[%d] [%s] error: proxy [%s] not found", idx, line, target)
}
pvName := "geosite:" + strings.ToLower(payload)
_, foundRP = ruleProviders[pvName]
if ruleName == "GEOSITE" && target == C.ScriptRuleGeoSiteTarget && foundRP {
continue
} }
params = trimArr(params) params = trimArr(params)
parsed, parseErr := R.ParseRule(ruleName, payload, target, params) parsed, parseErr := R.ParseRule(ruleName, payload, target, params)
if parseErr != nil { if parseErr != nil {
return nil, nil, fmt.Errorf("rules[%d] [%s] error: %s", idx, line, parseErr.Error()) return nil, fmt.Errorf("rules[%d] [%s] error: %s", idx, line, parseErr.Error())
}
if ruleName == "GEOSITE" && !foundRP {
providerNames = append(providerNames, pvName)
ruleProviders[pvName] = parsed
} }
rules = append(rules, parsed) rules = append(rules, parsed)
} }
if err := S.NewClashPyContext(providerNames); err != nil {
return nil, nil, err
} else {
log.Infoln("Start initial script context successful, provider records: %v", len(providerNames))
}
runtime.GC() runtime.GC()
return rules, ruleProviders, nil return rules, nil
} }
func parseHosts(cfg *RawConfig) (*trie.DomainTrie[netip.Addr], error) { func parseHosts(cfg *RawConfig) (*trie.DomainTrie[netip.Addr], error) {
@ -853,157 +814,6 @@ func parseAuthentication(rawRecords []string) []auth.AuthUser {
return users return users
} }
func parseScript(script Script, rawRules []string) ([]string, error) {
var (
path = script.MainPath
mainCode = script.MainCode
shortcutsCode = script.ShortcutsCode
)
if path != "" {
if !strings.HasSuffix(path, ".star") {
return nil, fmt.Errorf("initialized script file failure, script path [%s] invalid", path)
}
path = C.Path.Resolve(path)
if _, err := os.Stat(path); os.IsNotExist(err) {
return nil, fmt.Errorf("initialized script file failure, script path invalid: %w", err)
}
data, err := os.ReadFile(path)
if err != nil {
return nil, fmt.Errorf("initialized script file failure, read file error: %w", err)
}
mainCode = string(data)
}
if strings.TrimSpace(mainCode) == "" {
mainCode = `
def main(ctx, metadata):
return "DIRECT"
`
} else {
mainCode = cleanPyKeywords(mainCode)
}
if !strings.Contains(mainCode, "def main(ctx, metadata):") {
return nil, fmt.Errorf(`initialized script code failure, the function 'def main(ctx, metadata):' is required`)
}
content := `# -*- coding: UTF-8 -*-
from datetime import datetime as whatever
class ClashTime:
def now(self):
return whatever.now()
def unix(self):
return int(whatever.now().timestamp())
def unix_nano(self):
return int(round(whatever.now().timestamp() * 1000))
time = ClashTime()
class ClashRuleProvider:
def __init__(self, c, m):
self.__ctx = c
self.__metadata = m
def match_provider(self, providerName):
try:
return self.__ctx.rule_providers[providerName].match(self.__metadata)
except Exception as err:
self.__ctx.log("[SCRIPT] shortcuts error: rule provider {0} not found".format(err))
return False
`
content += mainCode + "\n\n"
for k, v := range shortcutsCode {
v = cleanPyKeywords(v)
v = strings.TrimSpace(v)
if v == "" {
return nil, fmt.Errorf("initialized rule SCRIPT failure, shortcut [%s] code syntax invalid", k)
}
content += "def " + strings.ToLower(k) + "(ctx, type, network, process_name, process_path, host, src_ip, src_port, dst_ip, dst_port):"
if strings.Contains(v, "match_provider") {
content += `
metadata = {
"type": type,
"network": network,
"process_name": process_name,
"process_path": process_path,
"host": host,
"src_ip": src_ip,
"src_port": src_port,
"dst_ip": dst_ip,
"dst_port": dst_port
}
crp = ClashRuleProvider(ctx, metadata)
match_provider = crp.match_provider`
}
content += `
now = time.now()
return ` + v + "\n\n"
}
err := os.WriteFile(C.Path.Script(), []byte(content), 0o644)
if err != nil {
return nil, fmt.Errorf("initialized script module failure, %w", err)
}
if err = S.Py_Initialize(C.Path.GetExecutableFullPath(), C.Path.ScriptDir()); err != nil {
return nil, fmt.Errorf("initialized script module failure, %w", err)
}
if err = S.LoadMainFunction(); err != nil {
return nil, fmt.Errorf("initialized script module failure, %w", err)
}
rpdArr := findRuleProvidersName(content)
for _, v := range rpdArr {
if !strings.HasPrefix(v, "geosite:") {
return nil, fmt.Errorf("initialized script module failure, rule provider name must be start with \"geosite:\"")
}
v = strings.ToLower(v)
rule := fmt.Sprintf("GEOSITE,%s,%s", strings.TrimPrefix(v, "geosite:"), C.ScriptRuleGeoSiteTarget)
rawRules = append(rawRules, rule)
}
log.Infoln("Start initial script module successful, version: %s", S.Py_GetVersion())
return rawRules, nil
}
func cleanPyKeywords(code string) string {
keywords := []string{"import", "print"}
for _, kw := range keywords {
reg := regexp.MustCompile("(?m)[\r\n]+^.*" + kw + ".*$")
code = reg.ReplaceAllString(code, "")
}
return code
}
func findRuleProvidersName(s string) []string {
ruleProviderRegx := regexp.MustCompile("ctx.rule_providers\\[[\"'](\\S+)[\"']\\]\\.match|match_provider\\([\"'](\\S+)[\"']\\)")
arr := ruleProviderRegx.FindAllStringSubmatch(s, -1)
var rpd []string
for _, rpdArr := range arr {
for i, v := range rpdArr {
if i == 0 || v == "" {
continue
}
rpd = append(rpd, v)
}
}
return rpd
}
func parseMitm(rawMitm RawMitm) (*Mitm, error) { func parseMitm(rawMitm RawMitm) (*Mitm, error) {
var ( var (
req []C.Rewrite req []C.Rewrite

View File

@ -22,7 +22,6 @@ 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
@ -72,32 +71,6 @@ func (p *path) GeoSite() string {
return P.Join(p.homeDir, "geosite.dat") return P.Join(p.homeDir, "geosite.dat")
} }
func (p *path) ScriptDir() string {
if len(p.scriptDir) != 0 {
return p.scriptDir
}
if dir, err := os.MkdirTemp("", Name+"-"); err == nil {
p.scriptDir = dir
} else {
p.scriptDir = P.Join(os.TempDir(), Name)
_ = os.MkdirAll(p.scriptDir, 0o644)
}
return p.scriptDir
}
func (p *path) Script() string {
return P.Join(p.ScriptDir(), "clash_script.py")
}
func (p *path) GetExecutableFullPath() string {
exePath, err := os.Executable()
if err != nil {
return "clash"
}
res, _ := filepath.EvalSymlinks(exePath)
return res
}
func (p *path) RootCA() string { func (p *path) RootCA() string {
return p.Resolve("mitm_ca.crt") return p.Resolve("mitm_ca.crt")
} }

View File

@ -13,7 +13,6 @@ const (
DstPort DstPort
Process Process
ProcessPath ProcessPath
Script
UserAgent UserAgent
MATCH MATCH
) )
@ -44,8 +43,6 @@ func (rt RuleType) String() string {
return "Process" return "Process"
case ProcessPath: case ProcessPath:
return "ProcessPath" return "ProcessPath"
case Script:
return "Script"
case UserAgent: case UserAgent:
return "UserAgent" return "UserAgent"
case MATCH: case MATCH:

View File

@ -7,8 +7,6 @@ import (
"github.com/Dreamacro/clash/component/geodata/router" "github.com/Dreamacro/clash/component/geodata/router"
) )
const ScriptRuleGeoSiteTarget = "__WhateverTarget__"
type RuleExtra struct { type RuleExtra struct {
Network NetWork Network NetWork
SourceIPs []*netip.Prefix SourceIPs []*netip.Prefix

View File

@ -36,7 +36,7 @@ func (c *client) ExchangeContext(ctx context.Context, m *D.Msg) (*D.Msg, error)
if c.r == nil { if c.r == nil {
return nil, fmt.Errorf("dns %s not a valid ip", c.host) return nil, fmt.Errorf("dns %s not a valid ip", c.host)
} else { } else {
if ip, err = resolver.ResolveIPWithResolver(c.host, c.r); err != nil { if ip, err = resolver.ResolveIPWithResolver(c.host, c.r, true); err != nil {
return nil, fmt.Errorf("use default dns resolve failed: %w", err) return nil, fmt.Errorf("use default dns resolve failed: %w", err)
} }
c.host = ip.String() c.host = ip.String()

View File

@ -94,7 +94,7 @@ func newDoHClient(url string, r *Resolver, proxyAdapter string) *dohClient {
return nil, err return nil, err
} }
ip, err := resolver.ResolveIPWithResolver(host, r) ip, err := resolver.ResolveIPWithResolver(host, r, true)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -21,6 +21,8 @@ import (
"golang.org/x/sync/singleflight" "golang.org/x/sync/singleflight"
) )
var _ resolver.Resolver = (*Resolver)(nil)
type dnsClient interface { type dnsClient interface {
Exchange(m *D.Msg) (msg *D.Msg, err error) Exchange(m *D.Msg) (msg *D.Msg, err error)
ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg, err error) ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg, err error)
@ -45,18 +47,18 @@ type Resolver struct {
} }
// ResolveIP request with TypeA and TypeAAAA, priority return TypeA // ResolveIP request with TypeA and TypeAAAA, priority return TypeA
func (r *Resolver) ResolveIP(host string) (ip netip.Addr, err error) { func (r *Resolver) ResolveIP(host string, random bool) (ip netip.Addr, err error) {
ch := make(chan netip.Addr, 1) ch := make(chan netip.Addr, 1)
go func() { go func() {
defer close(ch) defer close(ch)
ip, err := r.resolveIP(host, D.TypeAAAA) ip, err := r.resolveIP(host, D.TypeAAAA, random)
if err != nil { if err != nil {
return return
} }
ch <- ip ch <- ip
}() }()
ip, err = r.resolveIP(host, D.TypeA) ip, err = r.resolveIP(host, D.TypeA, random)
if err == nil { if err == nil {
return return
} }
@ -70,13 +72,13 @@ func (r *Resolver) ResolveIP(host string) (ip netip.Addr, err error) {
} }
// ResolveIPv4 request with TypeA // ResolveIPv4 request with TypeA
func (r *Resolver) ResolveIPv4(host string) (ip netip.Addr, err error) { func (r *Resolver) ResolveIPv4(host string, random bool) (ip netip.Addr, err error) {
return r.resolveIP(host, D.TypeA) return r.resolveIP(host, D.TypeA, random)
} }
// ResolveIPv6 request with TypeAAAA // ResolveIPv6 request with TypeAAAA
func (r *Resolver) ResolveIPv6(host string) (ip netip.Addr, err error) { func (r *Resolver) ResolveIPv6(host string, random bool) (ip netip.Addr, err error) {
return r.resolveIP(host, D.TypeAAAA) return r.resolveIP(host, D.TypeAAAA, random)
} }
func (r *Resolver) shouldIPFallback(ip netip.Addr) bool { func (r *Resolver) shouldIPFallback(ip netip.Addr) bool {
@ -255,9 +257,10 @@ func (r *Resolver) ipExchange(ctx context.Context, m *D.Msg) (msg *D.Msg, err er
return return
} }
func (r *Resolver) resolveIP(host string, dnsType uint16) (ip netip.Addr, err error) { func (r *Resolver) resolveIP(host string, dnsType uint16, random bool) (ip netip.Addr, err error) {
ip, err = netip.ParseAddr(host) ip, err = netip.ParseAddr(host)
if err == nil { if err == nil {
ip = ip.Unmap()
isIPv4 := ip.Is4() isIPv4 := ip.Is4()
if dnsType == D.TypeAAAA && !isIPv4 { if dnsType == D.TypeAAAA && !isIPv4 {
return ip, nil return ip, nil
@ -282,7 +285,12 @@ func (r *Resolver) resolveIP(host string, dnsType uint16) (ip netip.Addr, err er
return netip.Addr{}, resolver.ErrIPNotFound return netip.Addr{}, resolver.ErrIPNotFound
} }
ip = ips[rand.Intn(ipLength)] index := 0
if random {
index = rand.Intn(ipLength)
}
ip = ips[index]
return return
} }

16
go.mod
View File

@ -9,26 +9,26 @@ require (
github.com/gofrs/uuid v4.2.0+incompatible github.com/gofrs/uuid v4.2.0+incompatible
github.com/gorilla/websocket v1.5.0 github.com/gorilla/websocket v1.5.0
github.com/insomniacslk/dhcp v0.0.0-20220504074936-1ca156eafb9f github.com/insomniacslk/dhcp v0.0.0-20220504074936-1ca156eafb9f
github.com/miekg/dns v1.1.49 github.com/miekg/dns v1.1.50
github.com/oschwald/geoip2-golang v1.7.0 github.com/oschwald/geoip2-golang v1.7.0
github.com/sirupsen/logrus v1.8.1 github.com/sirupsen/logrus v1.8.1
github.com/stretchr/testify v1.7.1 github.com/stretchr/testify v1.7.2
github.com/xtls/go v0.0.0-20210920065950-d4af136d3672 github.com/xtls/go v0.0.0-20210920065950-d4af136d3672
go.etcd.io/bbolt v1.3.6 go.etcd.io/bbolt v1.3.6
go.uber.org/atomic v1.9.0 go.uber.org/atomic v1.9.0
go.uber.org/automaxprocs v1.5.1 go.uber.org/automaxprocs v1.5.1
golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d
golang.org/x/exp v0.0.0-20220602145555-4a0574d9293f golang.org/x/exp v0.0.0-20220613132600-b0d781184e0d
golang.org/x/net v0.0.0-20220531201128-c960675eff93 golang.org/x/net v0.0.0-20220630215102-69896b714898
golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a golang.org/x/sys v0.0.0-20220627191245-f75cf1eec38b
golang.org/x/text v0.3.8-0.20220124021120-d1c84af989ab golang.org/x/text v0.3.8-0.20220124021120-d1c84af989ab
golang.org/x/time v0.0.0-20220411224347-583f2d630306 golang.org/x/time v0.0.0-20220609170525-579cf78fd858
golang.zx2c4.com/wireguard v0.0.0-20220601130007-6a08d81f6bc4 golang.zx2c4.com/wireguard v0.0.0-20220601130007-6a08d81f6bc4
golang.zx2c4.com/wireguard/windows v0.5.4-0.20220328111914-004c22c5647e golang.zx2c4.com/wireguard/windows v0.5.4-0.20220328111914-004c22c5647e
google.golang.org/protobuf v1.28.0 google.golang.org/protobuf v1.28.0
gopkg.in/yaml.v3 v3.0.1 gopkg.in/yaml.v3 v3.0.1
gvisor.dev/gvisor v0.0.0-20220601233344-46e478629075 gvisor.dev/gvisor v0.0.0-20220630235058-ce952880a3d3
) )
require ( require (

34
go.sum
View File

@ -20,7 +20,7 @@ github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
@ -45,8 +45,8 @@ github.com/mdlayher/netlink v1.1.0/go.mod h1:H4WCitaheIsdF9yOYu8CFmCgQthAPIWZmcK
github.com/mdlayher/netlink v1.1.1/go.mod h1:WTYpFb/WTvlRJAyKhZL5/uy69TDDpHHu2VZmb2XgV7o= github.com/mdlayher/netlink v1.1.1/go.mod h1:WTYpFb/WTvlRJAyKhZL5/uy69TDDpHHu2VZmb2XgV7o=
github.com/mdlayher/raw v0.0.0-20190606142536-fef19f00fc18/go.mod h1:7EpbotpCmVZcu+KCX4g9WaRNuu11uyhiW7+Le1dKawg= github.com/mdlayher/raw v0.0.0-20190606142536-fef19f00fc18/go.mod h1:7EpbotpCmVZcu+KCX4g9WaRNuu11uyhiW7+Le1dKawg=
github.com/mdlayher/raw v0.0.0-20191009151244-50f2db8cc065/go.mod h1:7EpbotpCmVZcu+KCX4g9WaRNuu11uyhiW7+Le1dKawg= github.com/mdlayher/raw v0.0.0-20191009151244-50f2db8cc065/go.mod h1:7EpbotpCmVZcu+KCX4g9WaRNuu11uyhiW7+Le1dKawg=
github.com/miekg/dns v1.1.49 h1:qe0mQU3Z/XpFeE+AEBo2rqaS1IPBJ3anmqZ4XiZJVG8= github.com/miekg/dns v1.1.50 h1:DQUfb9uc6smULcREF09Uc+/Gd46YWqJd5DbpPE9xkcA=
github.com/miekg/dns v1.1.49/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME= github.com/miekg/dns v1.1.50/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME=
github.com/oschwald/geoip2-golang v1.7.0 h1:JW1r5AKi+vv2ujSxjKthySK3jo8w8oKWPyXsw+Qs/S8= github.com/oschwald/geoip2-golang v1.7.0 h1:JW1r5AKi+vv2ujSxjKthySK3jo8w8oKWPyXsw+Qs/S8=
github.com/oschwald/geoip2-golang v1.7.0/go.mod h1:mdI/C7iK7NVMcIDDtf4bCKMJ7r0o7UwGeCo9eiitCMQ= github.com/oschwald/geoip2-golang v1.7.0/go.mod h1:mdI/C7iK7NVMcIDDtf4bCKMJ7r0o7UwGeCo9eiitCMQ=
github.com/oschwald/maxminddb-golang v1.9.0 h1:tIk4nv6VT9OiPyrnDAfJS1s1xKDQMZOsGojab6EjC1Y= github.com/oschwald/maxminddb-golang v1.9.0 h1:tIk4nv6VT9OiPyrnDAfJS1s1xKDQMZOsGojab6EjC1Y=
@ -62,8 +62,8 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY= github.com/stretchr/testify v1.7.2 h1:4jaiDzPyXQvSd7D0EjG45355tLlV3VOECpq10pLC+8s=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals=
github.com/u-root/uio v0.0.0-20210528114334-82958018845c h1:BFvcl34IGnw8yvJi8hlqLFo9EshRInwWBs2M5fGWzQA= github.com/u-root/uio v0.0.0-20210528114334-82958018845c h1:BFvcl34IGnw8yvJi8hlqLFo9EshRInwWBs2M5fGWzQA=
github.com/u-root/uio v0.0.0-20210528114334-82958018845c/go.mod h1:LpEX5FO/cB+WF4TYGY1V5qktpaZLkKkSegbr0V4eYXA= github.com/u-root/uio v0.0.0-20210528114334-82958018845c/go.mod h1:LpEX5FO/cB+WF4TYGY1V5qktpaZLkKkSegbr0V4eYXA=
github.com/xtls/go v0.0.0-20210920065950-d4af136d3672 h1:4mkzGhKqt3JO1BWYjtD3iRFyAx4ow67hmSqOcGjuxqQ= github.com/xtls/go v0.0.0-20210920065950-d4af136d3672 h1:4mkzGhKqt3JO1BWYjtD3iRFyAx4ow67hmSqOcGjuxqQ=
@ -78,10 +78,10 @@ go.uber.org/automaxprocs v1.5.1/go.mod h1:BF4eumQw0P9GtnuxxovUd06vwm1o18oMzFtK66
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e h1:T8NU3HyQ8ClP4SEE+KbFlg6n0NhuTsN4MyznaarGsZM= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d h1:sK3txAijHtOK88l68nt020reeT1ZdKLIYetKl95FzVY=
golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/exp v0.0.0-20220602145555-4a0574d9293f h1:KK6mxegmt5hGJRcAnEDjSNLxIRhZxDcgwMbcO/lMCRM= golang.org/x/exp v0.0.0-20220613132600-b0d781184e0d h1:vtUKgx8dahOomfFzLREU8nSv25YHnTgLBn4rDnWZdU0=
golang.org/x/exp v0.0.0-20220602145555-4a0574d9293f/go.mod h1:yh0Ynu2b5ZUe3MQfp2nM0ecK7wsgouWTDN0FNeJuIys= golang.org/x/exp v0.0.0-20220613132600-b0d781184e0d/go.mod h1:Kr81I6Kryrl9sr8s2FK3vxD90NdsKWRuOIl2O4CvYbA=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 h1:kQgndtyPBW/JIYERgdxfwMYh3AVStj88WQTlNDi2a+o= golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 h1:kQgndtyPBW/JIYERgdxfwMYh3AVStj88WQTlNDi2a+o=
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY= golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY=
@ -97,8 +97,8 @@ golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwY
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220531201128-c960675eff93 h1:MYimHLfoXEpOhqd/zgoA/uoXzHB86AEky4LAx5ij9xA= golang.org/x/net v0.0.0-20220630215102-69896b714898 h1:K7wO6V1IrczY9QOQ2WkVpw4JQSwCd52UsxVEirZUfiw=
golang.org/x/net v0.0.0-20220531201128-c960675eff93/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.0.0-20220630215102-69896b714898/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f h1:Ax0t5p6N38Ga0dThY21weqDEyz2oklo4IvDkpigvkD8= golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f h1:Ax0t5p6N38Ga0dThY21weqDEyz2oklo4IvDkpigvkD8=
@ -123,16 +123,16 @@ golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210525143221-35b2ab0089ea/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210525143221-35b2ab0089ea/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a h1:dGzPydgVsqGcTRVwiLJ1jVbufYwmzD3LfVPLKsKg+0k= golang.org/x/sys v0.0.0-20220627191245-f75cf1eec38b h1:2n253B2r0pYSmEV+UNCQoPfU/FiaizQEK5Gu4Bq4JE8=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220627191245-f75cf1eec38b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.8-0.20220124021120-d1c84af989ab h1:eHo2TTVBaAPw9lDGK2Gb9GyPMXT6g7O63W6sx3ylbzU= golang.org/x/text v0.3.8-0.20220124021120-d1c84af989ab h1:eHo2TTVBaAPw9lDGK2Gb9GyPMXT6g7O63W6sx3ylbzU=
golang.org/x/text v0.3.8-0.20220124021120-d1c84af989ab/go.mod h1:EFNZuWvGYxIRUEX+K8UmCFwYmZjqcrnq15ZuVldZkZ0= golang.org/x/text v0.3.8-0.20220124021120-d1c84af989ab/go.mod h1:EFNZuWvGYxIRUEX+K8UmCFwYmZjqcrnq15ZuVldZkZ0=
golang.org/x/time v0.0.0-20220411224347-583f2d630306 h1:+gHMid33q6pen7kv9xvT+JRinntgeXO2AeZVd0AWD3w= golang.org/x/time v0.0.0-20220609170525-579cf78fd858 h1:Dpdu/EMxGMFgq0CeYMh4fazTD2vtlZRYE7wyynxJb9U=
golang.org/x/time v0.0.0-20220411224347-583f2d630306/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20220609170525-579cf78fd858/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
@ -158,5 +158,5 @@ gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gvisor.dev/gvisor v0.0.0-20220601233344-46e478629075 h1:ucwwit0X39HmMQ1iSeNCIXw4g/B8bi+O6TSxxDbPs9E= gvisor.dev/gvisor v0.0.0-20220630235058-ce952880a3d3 h1:rnajnEGeshXCEr3k/7pWAM6zXh1Ws4jkZgWuWXjCJSw=
gvisor.dev/gvisor v0.0.0-20220601233344-46e478629075/go.mod h1:TIvkJD0sxe8pIob3p6T8IzxXunlp6yfgktvTNp+DGNM= gvisor.dev/gvisor v0.0.0-20220630235058-ce952880a3d3/go.mod h1:TIvkJD0sxe8pIob3p6T8IzxXunlp6yfgktvTNp+DGNM=

View File

@ -15,7 +15,6 @@ 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"
@ -80,11 +79,10 @@ func ApplyConfig(cfg *config.Config, force bool) {
updateUsers(cfg.Users) updateUsers(cfg.Users)
updateProxies(cfg.Proxies, cfg.Providers) updateProxies(cfg.Proxies, cfg.Providers)
updateRules(cfg.Rules) updateRules(cfg.Rules)
updateRuleProviders(cfg.RuleProviders)
updateHosts(cfg.Hosts) updateHosts(cfg.Hosts)
updateMitm(cfg.Mitm) updateMitm(cfg.Mitm)
updateProfile(cfg) updateProfile(cfg)
updateDNS(cfg.DNS, cfg.General.Tun) updateDNS(cfg.DNS, &cfg.General.Tun)
updateGeneral(cfg.General, force) updateGeneral(cfg.General, force)
updateIPTables(cfg) updateIPTables(cfg)
updateExperimental(cfg) updateExperimental(cfg)
@ -123,7 +121,7 @@ func GetGeneral() *config.General {
func updateExperimental(_ *config.Config) {} func updateExperimental(_ *config.Config) {}
func updateDNS(c *config.DNS, t config.Tun) { func updateDNS(c *config.DNS, t *config.Tun) {
cfg := dns.Config{ cfg := dns.Config{
Main: c.NameServer, Main: c.NameServer,
Fallback: c.Fallback, Fallback: c.Fallback,
@ -143,6 +141,11 @@ func updateDNS(c *config.DNS, t config.Tun) {
ProxyServer: c.ProxyServerNameserver, ProxyServer: c.ProxyServerNameserver,
} }
// deprecated warnning
if cfg.EnhancedMode == C.DNSMapping {
log.Warnln("[DNS] %s is deprecated, please use %s instead", cfg.EnhancedMode.String(), C.DNSFakeIP.String())
}
r := dns.NewResolver(cfg) r := dns.NewResolver(cfg)
pr := dns.NewProxyServerHostResolver(r) pr := dns.NewProxyServerHostResolver(r)
m := dns.NewEnhancer(cfg) m := dns.NewEnhancer(cfg)
@ -176,7 +179,7 @@ func updateDNS(c *config.DNS, t config.Tun) {
} }
if cfg.Pool != nil { if cfg.Pool != nil {
P.SetTunAddressPrefix(cfg.Pool.IPNet()) t.TunAddressPrefix = cfg.Pool.IPNet()
} }
} }
@ -192,10 +195,6 @@ func updateRules(rules []C.Rule) {
tunnel.UpdateRules(rules) tunnel.UpdateRules(rules)
} }
func updateRuleProviders(providers map[string]C.Rule) {
S.UpdateRuleProviders(providers)
}
func updateGeneral(general *config.General, force bool) { func updateGeneral(general *config.General, force bool) {
tunnel.SetMode(general.Mode) tunnel.SetMode(general.Mode)
resolver.DisableIPv6 = !general.IPv6 resolver.DisableIPv6 = !general.IPv6
@ -344,7 +343,6 @@ func updateMitm(mitm *config.Mitm) {
func Shutdown() { func Shutdown() {
P.Cleanup() P.Cleanup()
S.Py_Finalize()
tproxy.CleanupTProxyIPTables() tproxy.CleanupTProxyIPTables()
resolver.StoreFakePoolState() resolver.StoreFakePoolState()

View File

@ -132,7 +132,7 @@ func authentication(next http.Handler) http.Handler {
} }
func hello(w http.ResponseWriter, r *http.Request) { func hello(w http.ResponseWriter, r *http.Request) {
render.JSON(w, r, render.M{"hello": "clash plus pro"}) render.JSON(w, r, render.M{"hello": "clash with tun"})
} }
func traffic(w http.ResponseWriter, r *http.Request) { func traffic(w http.ResponseWriter, r *http.Request) {

View File

@ -34,8 +34,6 @@ var (
allowLan = false allowLan = false
bindAddress = "*" bindAddress = "*"
lastTunConf *config.Tun lastTunConf *config.Tun
lastTunAddressPrefix *netip.Prefix
tunAddressPrefix *netip.Prefix
socksListener *socks.Listener socksListener *socks.Listener
socksUDPListener *socks.UDPListener socksUDPListener *socks.UDPListener
@ -109,10 +107,6 @@ func SetBindAddress(host string) {
bindAddress = host bindAddress = host
} }
func SetTunAddressPrefix(tunAddress *netip.Prefix) {
tunAddressPrefix = tunAddress
}
func ReCreateHTTP(port int, tcpIn chan<- C.ConnContext) { func ReCreateHTTP(port int, tcpIn chan<- C.ConnContext) {
httpMux.Lock() httpMux.Lock()
defer httpMux.Unlock() defer httpMux.Unlock()
@ -363,14 +357,10 @@ func ReCreateTun(tunConf *config.Tun, tcpIn chan<- C.ConnContext, udpIn chan<- *
} }
}() }()
if tunAddressPrefix == nil {
tunAddressPrefix = lastTunAddressPrefix
}
tunConf.DNSHijack = C.RemoveDuplicateDNSUrl(tunConf.DNSHijack) tunConf.DNSHijack = C.RemoveDuplicateDNSUrl(tunConf.DNSHijack)
if tunStackListener != nil { if tunStackListener != nil {
if !hasTunConfigChange(tunConf, tunAddressPrefix) { if !hasTunConfigChange(tunConf) {
return return
} }
@ -379,7 +369,6 @@ func ReCreateTun(tunConf *config.Tun, tcpIn chan<- C.ConnContext, udpIn chan<- *
} }
lastTunConf = tunConf lastTunConf = tunConf
lastTunAddressPrefix = tunAddressPrefix
if !tunConf.Enable { if !tunConf.Enable {
return return
@ -391,7 +380,7 @@ func ReCreateTun(tunConf *config.Tun, tcpIn chan<- C.ConnContext, udpIn chan<- *
udpIn: udpIn, udpIn: udpIn,
} }
tunStackListener, err = tun.New(tunConf, tunAddressPrefix, tcpIn, udpIn, callback) tunStackListener, err = tun.New(tunConf, tcpIn, udpIn, callback)
if err != nil { if err != nil {
return return
} }
@ -535,7 +524,7 @@ func genAddr(host string, port int, allowLan bool) string {
return fmt.Sprintf("127.0.0.1:%d", port) return fmt.Sprintf("127.0.0.1:%d", port)
} }
func hasTunConfigChange(tunConf *config.Tun, tunAddressPrefix *netip.Prefix) bool { func hasTunConfigChange(tunConf *config.Tun) bool {
if lastTunConf == nil { if lastTunConf == nil {
return true return true
} }
@ -558,11 +547,11 @@ func hasTunConfigChange(tunConf *config.Tun, tunAddressPrefix *netip.Prefix) boo
return true return true
} }
if (tunAddressPrefix != nil && lastTunAddressPrefix == nil) || (tunAddressPrefix == nil && lastTunAddressPrefix != nil) { if (lastTunConf.TunAddressPrefix != nil && tunConf.TunAddressPrefix == nil) || (lastTunConf.TunAddressPrefix == nil && tunConf.TunAddressPrefix != nil) {
return true return true
} }
if tunAddressPrefix != nil && lastTunAddressPrefix != nil && *tunAddressPrefix != *lastTunAddressPrefix { if lastTunConf.TunAddressPrefix != nil && tunConf.TunAddressPrefix != nil && *lastTunConf.TunAddressPrefix != *tunConf.TunAddressPrefix {
return true return true
} }

View File

@ -2,12 +2,13 @@ package tproxy
import ( import (
"net" "net"
"net/netip"
"github.com/Dreamacro/clash/common/pool" "github.com/Dreamacro/clash/common/pool"
) )
type packet struct { type packet struct {
lAddr *net.UDPAddr lAddr netip.AddrPort
buf []byte buf []byte
} }
@ -17,21 +18,21 @@ func (c *packet) Data() []byte {
// WriteBack opens a new socket binding `addr` to write UDP packet back // WriteBack opens a new socket binding `addr` to write UDP packet back
func (c *packet) WriteBack(b []byte, addr net.Addr) (n int, err error) { func (c *packet) WriteBack(b []byte, addr net.Addr) (n int, err error) {
tc, err := dialUDP("udp", addr.(*net.UDPAddr), c.lAddr) tc, err := dialUDP("udp", addr.(*net.UDPAddr).AddrPort(), c.lAddr)
if err != nil { if err != nil {
n = 0 n = 0
return return
} }
n, err = tc.Write(b) n, err = tc.Write(b)
tc.Close() _ = tc.Close()
return return
} }
// LocalAddr returns the source IP/Port of UDP Packet // LocalAddr returns the source IP/Port of UDP Packet
func (c *packet) LocalAddr() net.Addr { func (c *packet) LocalAddr() net.Addr {
return c.lAddr return net.UDPAddrFromAddrPort(c.lAddr)
} }
func (c *packet) Drop() { func (c *packet) Drop() {
pool.Put(c.buf) _ = pool.Put(c.buf)
} }

View File

@ -32,7 +32,7 @@ func (l *Listener) Close() error {
func (l *Listener) handleTProxy(conn net.Conn, in chan<- C.ConnContext) { func (l *Listener) handleTProxy(conn net.Conn, in chan<- C.ConnContext) {
target := socks5.ParseAddrToSocksAddr(conn.LocalAddr()) target := socks5.ParseAddrToSocksAddr(conn.LocalAddr())
conn.(*net.TCPConn).SetKeepAlive(true) _ = conn.(*net.TCPConn).SetKeepAlive(true)
in <- inbound.NewSocket(target, conn, C.TPROXY) in <- inbound.NewSocket(target, conn, C.TPROXY)
} }

View File

@ -2,6 +2,7 @@ package tproxy
import ( import (
"net" "net"
"net/netip"
"github.com/Dreamacro/clash/adapter/inbound" "github.com/Dreamacro/clash/adapter/inbound"
"github.com/Dreamacro/clash/common/pool" "github.com/Dreamacro/clash/common/pool"
@ -58,28 +59,33 @@ func NewUDP(addr string, in chan<- *inbound.PacketAdapter) (*UDPListener, error)
oob := make([]byte, 1024) oob := make([]byte, 1024)
for { for {
buf := pool.Get(pool.UDPBufferSize) buf := pool.Get(pool.UDPBufferSize)
n, oobn, _, lAddr, err := c.ReadMsgUDP(buf, oob) n, oobn, _, lAddr, err := c.ReadMsgUDPAddrPort(buf, oob)
if err != nil { if err != nil {
pool.Put(buf) _ = pool.Put(buf)
if rl.closed { if rl.closed {
break break
} }
continue continue
} }
rAddr, err := getOrigDst(oob, oobn) rAddr, err := getOrigDst(oob[:oobn])
if err != nil { if err != nil {
continue continue
} }
handlePacketConn(l, in, buf[:n], lAddr, rAddr)
if rAddr.Addr().Is4() {
// try to unmap 4in6 address
lAddr = netip.AddrPortFrom(lAddr.Addr().Unmap(), lAddr.Port())
}
handlePacketConn(in, buf[:n], lAddr, rAddr)
} }
}() }()
return rl, nil return rl, nil
} }
func handlePacketConn(pc net.PacketConn, in chan<- *inbound.PacketAdapter, buf []byte, lAddr *net.UDPAddr, rAddr *net.UDPAddr) { func handlePacketConn(in chan<- *inbound.PacketAdapter, buf []byte, lAddr, rAddr netip.AddrPort) {
target := socks5.ParseAddrToSocksAddr(rAddr) target := socks5.AddrFromStdAddrPort(rAddr)
pkt := &packet{ pkt := &packet{
lAddr: lAddr, lAddr: lAddr,
buf: buf, buf: buf,

View File

@ -3,13 +3,14 @@
package tproxy package tproxy
import ( import (
"encoding/binary"
"errors"
"fmt" "fmt"
"net" "net"
"net/netip"
"os" "os"
"strconv" "strconv"
"syscall" "syscall"
"golang.org/x/sys/unix"
) )
const ( const (
@ -19,7 +20,7 @@ const (
// dialUDP acts like net.DialUDP for transparent proxy. // dialUDP acts like net.DialUDP for transparent proxy.
// It binds to a non-local address(`lAddr`). // It binds to a non-local address(`lAddr`).
func dialUDP(network string, lAddr *net.UDPAddr, rAddr *net.UDPAddr) (*net.UDPConn, error) { func dialUDP(network string, lAddr, rAddr netip.AddrPort) (uc *net.UDPConn, err error) {
rSockAddr, err := udpAddrToSockAddr(rAddr) rSockAddr, err := udpAddrToSockAddr(rAddr)
if err != nil { if err != nil {
return nil, err return nil, err
@ -35,23 +36,25 @@ func dialUDP(network string, lAddr *net.UDPAddr, rAddr *net.UDPAddr) (*net.UDPCo
return nil, err return nil, err
} }
if err = syscall.SetsockoptInt(fd, syscall.SOL_SOCKET, syscall.SO_REUSEADDR, 1); err != nil { defer func() {
if err != nil {
syscall.Close(fd) syscall.Close(fd)
}
}()
if err = syscall.SetsockoptInt(fd, syscall.SOL_SOCKET, syscall.SO_REUSEADDR, 1); err != nil {
return nil, err return nil, err
} }
if err = syscall.SetsockoptInt(fd, syscall.SOL_IP, syscall.IP_TRANSPARENT, 1); err != nil { if err = syscall.SetsockoptInt(fd, syscall.SOL_IP, syscall.IP_TRANSPARENT, 1); err != nil {
syscall.Close(fd)
return nil, err return nil, err
} }
if err = syscall.Bind(fd, lSockAddr); err != nil { if err = syscall.Bind(fd, lSockAddr); err != nil {
syscall.Close(fd)
return nil, err return nil, err
} }
if err = syscall.Connect(fd, rSockAddr); err != nil { if err = syscall.Connect(fd, rSockAddr); err != nil {
syscall.Close(fd)
return nil, err return nil, err
} }
@ -60,35 +63,26 @@ func dialUDP(network string, lAddr *net.UDPAddr, rAddr *net.UDPAddr) (*net.UDPCo
c, err := net.FileConn(fdFile) c, err := net.FileConn(fdFile)
if err != nil { if err != nil {
syscall.Close(fd)
return nil, err return nil, err
} }
return c.(*net.UDPConn), nil return c.(*net.UDPConn), nil
} }
func udpAddrToSockAddr(addr *net.UDPAddr) (syscall.Sockaddr, error) { func udpAddrToSockAddr(addr netip.AddrPort) (syscall.Sockaddr, error) {
switch { if addr.Addr().Is4() {
case addr.IP.To4() != nil: return &syscall.SockaddrInet4{Addr: addr.Addr().As4(), Port: int(addr.Port())}, nil
ip := [4]byte{} }
copy(ip[:], addr.IP.To4())
return &syscall.SockaddrInet4{Addr: ip, Port: addr.Port}, nil zoneID, err := strconv.ParseUint(addr.Addr().Zone(), 10, 32)
default:
ip := [16]byte{}
copy(ip[:], addr.IP.To16())
zoneID, err := strconv.ParseUint(addr.Zone, 10, 32)
if err != nil { if err != nil {
zoneID = 0 zoneID = 0
} }
return &syscall.SockaddrInet6{Addr: ip, Port: addr.Port, ZoneId: uint32(zoneID)}, nil return &syscall.SockaddrInet6{Addr: addr.Addr().As16(), Port: int(addr.Port()), ZoneId: uint32(zoneID)}, nil
}
} }
func udpAddrFamily(net string, lAddr, rAddr *net.UDPAddr) int { func udpAddrFamily(net string, lAddr, rAddr netip.AddrPort) int {
switch net[len(net)-1] { switch net[len(net)-1] {
case '4': case '4':
return syscall.AF_INET return syscall.AF_INET
@ -96,29 +90,35 @@ func udpAddrFamily(net string, lAddr, rAddr *net.UDPAddr) int {
return syscall.AF_INET6 return syscall.AF_INET6
} }
if (lAddr == nil || lAddr.IP.To4() != nil) && (rAddr == nil || lAddr.IP.To4() != nil) { if lAddr.Addr().Is4() && rAddr.Addr().Is4() {
return syscall.AF_INET return syscall.AF_INET
} }
return syscall.AF_INET6 return syscall.AF_INET6
} }
func getOrigDst(oob []byte, oobn int) (*net.UDPAddr, error) { func getOrigDst(oob []byte) (netip.AddrPort, error) {
msgs, err := syscall.ParseSocketControlMessage(oob[:oobn]) // oob contains socket control messages which we need to parse.
scms, err := unix.ParseSocketControlMessage(oob)
if err != nil { if err != nil {
return nil, err return netip.AddrPort{}, fmt.Errorf("parse control message: %w", err)
} }
for _, msg := range msgs { // retrieve the destination address from the SCM.
if msg.Header.Level == syscall.SOL_IP && msg.Header.Type == syscall.IP_RECVORIGDSTADDR { sa, err := unix.ParseOrigDstAddr(&scms[0])
ip := net.IP(msg.Data[4:8]) if err != nil {
port := binary.BigEndian.Uint16(msg.Data[2:4]) return netip.AddrPort{}, fmt.Errorf("retrieve destination: %w", err)
return &net.UDPAddr{IP: ip, Port: int(port)}, nil
} else if msg.Header.Level == syscall.SOL_IPV6 && msg.Header.Type == IPV6_RECVORIGDSTADDR {
ip := net.IP(msg.Data[8:24])
port := binary.BigEndian.Uint16(msg.Data[2:4])
return &net.UDPAddr{IP: ip, Port: int(port)}, nil
}
} }
return nil, errors.New("cannot find origDst") // encode the destination address into a cmsg.
var rAddr netip.AddrPort
switch v := sa.(type) {
case *unix.SockaddrInet4:
rAddr = netip.AddrPortFrom(netip.AddrFrom4(v.Addr), uint16(v.Port))
case *unix.SockaddrInet6:
rAddr = netip.AddrPortFrom(netip.AddrFrom16(v.Addr), uint16(v.Port))
default:
return netip.AddrPort{}, fmt.Errorf("unsupported address type: %T", v)
}
return rAddr, nil
} }

View File

@ -5,12 +5,13 @@ package tproxy
import ( import (
"errors" "errors"
"net" "net"
"net/netip"
) )
func getOrigDst(oob []byte, oobn int) (*net.UDPAddr, error) { func getOrigDst(oob []byte) (netip.AddrPort, error) {
return nil, errors.New("UDP redir not supported on current platform") return netip.AddrPort{}, errors.New("UDP redir not supported on current platform")
} }
func dialUDP(network string, lAddr *net.UDPAddr, rAddr *net.UDPAddr) (*net.UDPConn, error) { func dialUDP(network string, lAddr, rAddr netip.AddrPort) (*net.UDPConn, error) {
return nil, errors.New("UDP redir not supported on current platform") return nil, errors.New("UDP redir not supported on current platform")
} }

View File

@ -41,6 +41,10 @@ func (f *FD) Name() string {
return strconv.Itoa(f.fd) return strconv.Itoa(f.fd)
} }
func (f *FD) MTU() uint32 {
return f.mtu
}
func (f *FD) Close() error { func (f *FD) Close() error {
err := unix.Close(f.fd) err := unix.Close(f.fd)
if f.file != nil { if f.file != nil {

View File

@ -4,8 +4,48 @@ import (
"errors" "errors"
"github.com/Dreamacro/clash/listener/tun/device" "github.com/Dreamacro/clash/listener/tun/device"
"gvisor.dev/gvisor/pkg/tcpip/stack"
) )
func Open(name string, mtu uint32) (device.Device, error) { type FD struct {
stack.LinkEndpoint
}
func Open(_ string, _ uint32) (device.Device, error) {
return nil, errors.New("not supported") return nil, errors.New("not supported")
} }
func (f *FD) Name() string {
return ""
}
func (f *FD) Type() string {
return Driver
}
func (f *FD) Read(_ []byte) (int, error) {
return 0, nil
}
func (f *FD) Write(_ []byte) (int, error) {
return 0, nil
}
func (f *FD) Close() error {
return nil
}
func (f *FD) UseEndpoint() error {
return nil
}
func (f *FD) UseIOBased() error {
return nil
}
func (f *FD) MTU() uint32 {
return 0
}
var _ device.Device = (*FD)(nil)

View File

@ -8,8 +8,10 @@ import (
"io" "io"
"sync" "sync"
"github.com/Dreamacro/clash/common/pool"
"gvisor.dev/gvisor/pkg/buffer"
"gvisor.dev/gvisor/pkg/tcpip" "gvisor.dev/gvisor/pkg/tcpip"
"gvisor.dev/gvisor/pkg/tcpip/buffer"
"gvisor.dev/gvisor/pkg/tcpip/header" "gvisor.dev/gvisor/pkg/tcpip/header"
"gvisor.dev/gvisor/pkg/tcpip/link/channel" "gvisor.dev/gvisor/pkg/tcpip/link/channel"
"gvisor.dev/gvisor/pkg/tcpip/stack" "gvisor.dev/gvisor/pkg/tcpip/stack"
@ -93,23 +95,29 @@ func (e *Endpoint) dispatchLoop(cancel context.CancelFunc) {
mtu := int(e.mtu) mtu := int(e.mtu)
for { for {
data := make([]byte, mtu) data := pool.Get(mtu)
n, err := e.rw.Read(data) n, err := e.rw.Read(data)
if err != nil { if err != nil {
_ = pool.Put(data)
break break
} }
if n == 0 || n > mtu { if n == 0 || n > mtu {
_ = pool.Put(data)
continue continue
} }
if !e.IsAttached() { if !e.IsAttached() {
_ = pool.Put(data)
continue /* unattached, drop packet */ continue /* unattached, drop packet */
} }
pkt := stack.NewPacketBuffer(stack.PacketBufferOptions{ pkt := stack.NewPacketBuffer(stack.PacketBufferOptions{
Data: buffer.View(data[:n]).ToVectorisedView(), Payload: buffer.NewWithData(data[:n]),
OnRelease: func() {
_ = pool.Put(data)
},
}) })
switch header.IPVersion(data) { switch header.IPVersion(data) {
@ -117,6 +125,8 @@ func (e *Endpoint) dispatchLoop(cancel context.CancelFunc) {
e.InjectInbound(header.IPv4ProtocolNumber, pkt) e.InjectInbound(header.IPv4ProtocolNumber, pkt)
case header.IPv6Version: case header.IPv6Version:
e.InjectInbound(header.IPv6ProtocolNumber, pkt) e.InjectInbound(header.IPv6ProtocolNumber, pkt)
default:
_ = pool.Put(data)
} }
pkt.DecRef() pkt.DecRef()
} }
@ -138,11 +148,8 @@ func (e *Endpoint) outboundLoop(ctx context.Context) {
func (e *Endpoint) writePacket(pkt *stack.PacketBuffer) tcpip.Error { func (e *Endpoint) writePacket(pkt *stack.PacketBuffer) tcpip.Error {
defer pkt.DecRef() defer pkt.DecRef()
size := pkt.Size() buf := pkt.Buffer()
views := pkt.Views() if _, err := e.rw.Write(buf.Flatten()); err != nil {
vView := buffer.NewVectorisedView(size, views)
if _, err := e.rw.Write(vView.ToView()); err != nil {
return &tcpip.ErrInvalidEndpointState{} return &tcpip.ErrInvalidEndpointState{}
} }
return nil return nil

View File

@ -59,10 +59,13 @@ func Open(name string, mtu uint32) (_ device.Device, err error) {
if err != nil { if err != nil {
return nil, fmt.Errorf("get mtu: %w", err) return nil, fmt.Errorf("get mtu: %w", err)
} }
if t.mtu == 0 {
t.mtu = uint32(tunMTU) t.mtu = uint32(tunMTU)
}
if t.offset > 0 { if t.offset > 0 {
t.cache = make([]byte, 65535) t.cache = make([]byte, int(t.mtu)+t.offset)
} }
return t, nil return t, nil
@ -87,7 +90,11 @@ func (t *TUN) Write(packet []byte) (int, error) {
packet = append(t.cache[:t.offset], packet...) packet = append(t.cache[:t.offset], packet...)
return t.nt.Write(packet, t.offset) n, err := t.nt.Write(packet, t.offset)
if n < t.offset {
return 0, err
}
return n - t.offset, err
} }
func (t *TUN) Close() error { func (t *TUN) Close() error {
@ -104,6 +111,10 @@ func (t *TUN) Name() string {
return name return name
} }
func (t *TUN) MTU() uint32 {
return t.mtu
}
func (t *TUN) UseEndpoint() error { func (t *TUN) UseEndpoint() error {
ep, err := iobased.New(t, t.mtu, t.offset) ep, err := iobased.New(t, t.mtu, t.offset)
if err != nil { if err != nil {

View File

@ -2,23 +2,15 @@ package adapter
import ( import (
"net" "net"
"gvisor.dev/gvisor/pkg/tcpip/stack"
) )
// TCPConn implements the net.Conn interface. // TCPConn implements the net.Conn interface.
type TCPConn interface { type TCPConn interface {
net.Conn net.Conn
// ID returns the transport endpoint id of TCPConn.
ID() *stack.TransportEndpointID
} }
// UDPConn implements net.Conn and net.PacketConn. // UDPConn implements net.Conn and net.PacketConn.
type UDPConn interface { type UDPConn interface {
net.Conn net.Conn
net.PacketConn net.PacketConn
// ID returns the transport endpoint id of UDPConn.
ID() *stack.TransportEndpointID
} }

View File

@ -7,7 +7,6 @@ import (
"time" "time"
"github.com/Dreamacro/clash/adapter/inbound" "github.com/Dreamacro/clash/adapter/inbound"
"github.com/Dreamacro/clash/common/nnip"
"github.com/Dreamacro/clash/common/pool" "github.com/Dreamacro/clash/common/pool"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
D "github.com/Dreamacro/clash/listener/tun/ipstack/commons" D "github.com/Dreamacro/clash/listener/tun/ipstack/commons"
@ -27,15 +26,7 @@ type gvHandler struct {
} }
func (gh *gvHandler) HandleTCP(tunConn adapter.TCPConn) { func (gh *gvHandler) HandleTCP(tunConn adapter.TCPConn) {
id := tunConn.ID() rAddrPort := tunConn.LocalAddr().(*net.TCPAddr).AddrPort()
rAddr := &net.UDPAddr{
IP: net.IP(id.LocalAddress),
Port: int(id.LocalPort),
Zone: "",
}
rAddrPort := netip.AddrPortFrom(nnip.IpToAddr(rAddr.IP), id.LocalPort)
if D.ShouldHijackDns(gh.dnsHijack, rAddrPort, "tcp") { if D.ShouldHijackDns(gh.dnsHijack, rAddrPort, "tcp") {
go func() { go func() {
@ -43,8 +34,8 @@ func (gh *gvHandler) HandleTCP(tunConn adapter.TCPConn) {
buf := pool.Get(pool.UDPBufferSize) buf := pool.Get(pool.UDPBufferSize)
defer func() { defer func() {
_ = pool.Put(buf)
_ = tunConn.Close() _ = tunConn.Close()
_ = pool.Put(buf)
}() }()
for { for {
@ -78,26 +69,18 @@ func (gh *gvHandler) HandleTCP(tunConn adapter.TCPConn) {
return return
} }
gh.tcpIn <- inbound.NewSocket(socks5.ParseAddrToSocksAddr(rAddr), tunConn, C.TUN) gh.tcpIn <- inbound.NewSocket(socks5.AddrFromStdAddrPort(rAddrPort), tunConn, C.TUN)
} }
func (gh *gvHandler) HandleUDP(tunConn adapter.UDPConn) { func (gh *gvHandler) HandleUDP(tunConn adapter.UDPConn) {
id := tunConn.ID() rAddrPort := tunConn.LocalAddr().(*net.UDPAddr).AddrPort()
rAddr := &net.UDPAddr{
IP: net.IP(id.LocalAddress),
Port: int(id.LocalPort),
Zone: "",
}
rAddrPort := netip.AddrPortFrom(nnip.IpToAddr(rAddr.IP), id.LocalPort)
if rAddrPort.Addr() == gh.gateway { if rAddrPort.Addr() == gh.gateway {
_ = tunConn.Close() _ = tunConn.Close()
return return
} }
target := socks5.ParseAddrToSocksAddr(rAddr) target := socks5.AddrFromStdAddrPort(rAddrPort)
go func() { go func() {
for { for {
@ -109,22 +92,20 @@ func (gh *gvHandler) HandleUDP(tunConn adapter.UDPConn) {
break break
} }
payload := buf[:n]
if D.ShouldHijackDns(gh.dnsHijack, rAddrPort, "udp") { if D.ShouldHijackDns(gh.dnsHijack, rAddrPort, "udp") {
go func() { go func() {
defer func() { defer func() {
_ = pool.Put(buf) _ = pool.Put(buf)
}() }()
msg, err1 := D.RelayDnsPacket(payload) msg, err1 := D.RelayDnsPacket(buf[:n])
if err1 != nil { if err1 != nil {
return return
} }
_, _ = tunConn.WriteTo(msg, addr) _, _ = tunConn.WriteTo(msg, addr)
log.Debugln("[TUN] hijack dns udp: %s", rAddr.String()) log.Debugln("[TUN] hijack dns udp: %s", rAddrPort.String())
}() }()
continue continue
@ -133,12 +114,14 @@ func (gh *gvHandler) HandleUDP(tunConn adapter.UDPConn) {
gvPacket := &packet{ gvPacket := &packet{
pc: tunConn, pc: tunConn,
rAddr: addr, rAddr: addr,
payload: payload, payload: buf,
offset: n,
} }
select { select {
case gh.udpIn <- inbound.NewPacket(target, gvPacket, C.TUN): case gh.udpIn <- inbound.NewPacket(target, gvPacket, C.TUN):
default: default:
gvPacket.Drop()
} }
} }
}() }()

View File

@ -70,7 +70,6 @@ func withTCPHandler(handle adapter.TCPHandleFunc) option.Option {
conn := &tcpConn{ conn := &tcpConn{
TCPConn: gonet.NewTCPConn(&wq, ep), TCPConn: gonet.NewTCPConn(&wq, ep),
id: id,
} }
handle(conn) handle(conn)
}) })
@ -113,9 +112,4 @@ func setSocketOptions(s *stack.Stack, ep tcpip.Endpoint) tcpip.Error {
type tcpConn struct { type tcpConn struct {
*gonet.TCPConn *gonet.TCPConn
id stack.TransportEndpointID
}
func (c *tcpConn) ID() *stack.TransportEndpointID {
return &c.id
} }

View File

@ -29,7 +29,6 @@ func withUDPHandler(handle adapter.UDPHandleFunc) option.Option {
conn := &udpConn{ conn := &udpConn{
UDPConn: gonet.NewUDPConn(s, &wq, ep), UDPConn: gonet.NewUDPConn(s, &wq, ep),
id: id,
} }
handle(conn) handle(conn)
}) })
@ -40,21 +39,17 @@ func withUDPHandler(handle adapter.UDPHandleFunc) option.Option {
type udpConn struct { type udpConn struct {
*gonet.UDPConn *gonet.UDPConn
id stack.TransportEndpointID
}
func (c *udpConn) ID() *stack.TransportEndpointID {
return &c.id
} }
type packet struct { type packet struct {
pc adapter.UDPConn pc adapter.UDPConn
rAddr net.Addr rAddr net.Addr
payload []byte payload []byte
offset int
} }
func (c *packet) Data() []byte { func (c *packet) Data() []byte {
return c.payload return c.payload[:c.offset]
} }
// WriteBack write UDP packet with source(ip, port) = `addr` // WriteBack write UDP packet with source(ip, port) = `addr`

View File

@ -5,7 +5,9 @@ import (
"net" "net"
"net/netip" "net/netip"
"github.com/Dreamacro/clash/common/pool" dev "github.com/Dreamacro/clash/listener/tun/device"
"github.com/Dreamacro/clash/listener/tun/device/fdbased"
"github.com/Dreamacro/clash/listener/tun/device/tun"
"github.com/Dreamacro/clash/listener/tun/ipstack/system/mars/tcpip" "github.com/Dreamacro/clash/listener/tun/ipstack/system/mars/tcpip"
) )
@ -19,10 +21,27 @@ func Start(device io.ReadWriter, gateway, portal, broadcast netip.Addr) (*TCP, *
return nil, nil, err return nil, nil, err
} }
var (
t dev.Device
mtu uint32
ok bool
)
if t, ok = device.(*tun.TUN); ok {
mtu = t.MTU()
} else if t, ok = device.(*fdbased.FD); ok {
mtu = t.MTU()
}
bufferSize := int(mtu)
if bufferSize == 0 {
bufferSize = 64 * 1024
}
tab := newTable() tab := newTable()
udp := &UDP{ udp := &UDP{
device: device, device: device,
buf: [pool.UDPBufferSize]byte{}, buf: make([]byte, bufferSize),
} }
tcp := &TCP{ tcp := &TCP{
listener: listener, listener: listener,
@ -38,7 +57,7 @@ func Start(device io.ReadWriter, gateway, portal, broadcast netip.Addr) (*TCP, *
_ = udp.Close() _ = udp.Close()
}() }()
buf := make([]byte, pool.RelayBufferSize) buf := make([]byte, bufferSize)
for { for {
n, err := device.Read(buf) n, err := device.Read(buf)

View File

@ -24,9 +24,9 @@ func (t *TCP) Accept() (net.Conn, error) {
return nil, err return nil, err
} }
addr := c.RemoteAddr().(*net.TCPAddr) addr := c.RemoteAddr().(*net.TCPAddr).AddrPort()
tup := t.table.tupleOf(uint16(addr.Port)) tup := t.table.tupleOf(addr.Port())
if !addr.IP.Equal(t.portal.AsSlice()) || tup == zeroTuple { if addr.Addr() != t.portal || tup == zeroTuple {
_ = c.Close() _ = c.Close()
return nil, net.InvalidAddrError("unknown remote addr") return nil, net.InvalidAddrError("unknown remote addr")
@ -34,6 +34,8 @@ func (t *TCP) Accept() (net.Conn, error) {
addition(c) addition(c)
_ = c.SetLinger(0)
return &conn{ return &conn{
Conn: c, Conn: c,
tuple: tup, tuple: tup,
@ -52,16 +54,14 @@ func (t *TCP) SetDeadline(time time.Time) error {
return t.listener.SetDeadline(time) return t.listener.SetDeadline(time)
} }
func (c *conn) Close() error {
return c.Conn.Close()
}
func (c *conn) LocalAddr() net.Addr { func (c *conn) LocalAddr() net.Addr {
return &net.TCPAddr{ return net.TCPAddrFromAddrPort(c.tuple.SourceAddr)
IP: c.tuple.SourceAddr.Addr().AsSlice(),
Port: int(c.tuple.SourceAddr.Port()),
}
} }
func (c *conn) RemoteAddr() net.Addr { func (c *conn) RemoteAddr() net.Addr {
return &net.TCPAddr{ return net.TCPAddrFromAddrPort(c.tuple.DestinationAddr)
IP: c.tuple.DestinationAddr.Addr().AsSlice(),
Port: int(c.tuple.DestinationAddr.Port()),
}
} }

View File

@ -7,8 +7,6 @@ import (
"net/netip" "net/netip"
"sync" "sync"
"github.com/Dreamacro/clash/common/nnip"
"github.com/Dreamacro/clash/common/pool"
"github.com/Dreamacro/clash/listener/tun/ipstack/system/mars/tcpip" "github.com/Dreamacro/clash/listener/tun/ipstack/system/mars/tcpip"
) )
@ -16,8 +14,8 @@ type call struct {
cond *sync.Cond cond *sync.Cond
buf []byte buf []byte
n int n int
source net.Addr source netip.AddrPort
destination net.Addr destination netip.AddrPort
} }
type UDP struct { type UDP struct {
@ -26,10 +24,10 @@ type UDP struct {
queueLock sync.Mutex queueLock sync.Mutex
queue []*call queue []*call
bufLock sync.Mutex bufLock sync.Mutex
buf [pool.UDPBufferSize]byte buf []byte
} }
func (u *UDP) ReadFrom(buf []byte) (int, net.Addr, net.Addr, error) { func (u *UDP) ReadFrom(buf []byte) (int, netip.AddrPort, netip.AddrPort, error) {
u.queueLock.Lock() u.queueLock.Lock()
defer u.queueLock.Unlock() defer u.queueLock.Unlock()
@ -38,8 +36,8 @@ func (u *UDP) ReadFrom(buf []byte) (int, net.Addr, net.Addr, error) {
cond: sync.NewCond(&u.queueLock), cond: sync.NewCond(&u.queueLock),
buf: buf, buf: buf,
n: -1, n: -1,
source: nil, source: netip.AddrPort{},
destination: nil, destination: netip.AddrPort{},
} }
u.queue = append(u.queue, c) u.queue = append(u.queue, c)
@ -51,10 +49,10 @@ func (u *UDP) ReadFrom(buf []byte) (int, net.Addr, net.Addr, error) {
} }
} }
return -1, nil, nil, net.ErrClosed return -1, netip.AddrPort{}, netip.AddrPort{}, net.ErrClosed
} }
func (u *UDP) WriteTo(buf []byte, local net.Addr, remote net.Addr) (int, error) { func (u *UDP) WriteTo(buf []byte, local netip.AddrPort, remote netip.AddrPort) (int, error) {
if u.closed { if u.closed {
return 0, net.ErrClosed return 0, net.ErrClosed
} }
@ -66,16 +64,7 @@ func (u *UDP) WriteTo(buf []byte, local net.Addr, remote net.Addr) (int, error)
return 0, net.InvalidAddrError("invalid ip version") return 0, net.InvalidAddrError("invalid ip version")
} }
srcAddr, srcOk := local.(*net.UDPAddr) if !local.Addr().Is4() || !remote.Addr().Is4() {
dstAddr, dstOk := remote.(*net.UDPAddr)
if !srcOk || !dstOk {
return 0, net.InvalidAddrError("invalid addr")
}
srcAddrPort := netip.AddrPortFrom(nnip.IpToAddr(srcAddr.IP), uint16(srcAddr.Port))
dstAddrPort := netip.AddrPortFrom(nnip.IpToAddr(dstAddr.IP), uint16(dstAddr.Port))
if !srcAddrPort.Addr().Is4() || !dstAddrPort.Addr().Is4() {
return 0, net.InvalidAddrError("invalid ip version") return 0, net.InvalidAddrError("invalid ip version")
} }
@ -89,13 +78,13 @@ func (u *UDP) WriteTo(buf []byte, local net.Addr, remote net.Addr) (int, error)
ip.SetFragmentOffset(0) ip.SetFragmentOffset(0)
ip.SetTimeToLive(64) ip.SetTimeToLive(64)
ip.SetProtocol(tcpip.UDP) ip.SetProtocol(tcpip.UDP)
ip.SetSourceIP(srcAddrPort.Addr()) ip.SetSourceIP(local.Addr())
ip.SetDestinationIP(dstAddrPort.Addr()) ip.SetDestinationIP(remote.Addr())
udp := tcpip.UDPPacket(ip.Payload()) udp := tcpip.UDPPacket(ip.Payload())
udp.SetLength(tcpip.UDPHeaderSize + uint16(len(buf))) udp.SetLength(tcpip.UDPHeaderSize + uint16(len(buf)))
udp.SetSourcePort(srcAddrPort.Port()) udp.SetSourcePort(local.Port())
udp.SetDestinationPort(dstAddrPort.Port()) udp.SetDestinationPort(remote.Port())
copy(udp.Payload(), buf) copy(udp.Payload(), buf)
ip.ResetChecksum() ip.ResetChecksum()
@ -131,14 +120,8 @@ func (u *UDP) handleUDPPacket(ip tcpip.IP, pkt tcpip.UDPPacket) {
u.queueLock.Unlock() u.queueLock.Unlock()
if c != nil { if c != nil {
c.source = &net.UDPAddr{ c.source = netip.AddrPortFrom(ip.SourceIP(), pkt.SourcePort())
IP: ip.SourceIP().AsSlice(), c.destination = netip.AddrPortFrom(ip.DestinationIP(), pkt.DestinationPort())
Port: int(pkt.SourcePort()),
}
c.destination = &net.UDPAddr{
IP: ip.DestinationIP().AsSlice(),
Port: int(pkt.DestinationPort()),
}
c.n = copy(c.buf, pkt.Payload()) c.n = copy(c.buf, pkt.Payload())
c.cond.Signal() c.cond.Signal()
} }

View File

@ -81,26 +81,23 @@ func New(device device.Device, dnsHijack []C.DNSUrl, tunAddress netip.Prefix, tc
continue continue
} }
lAddr := conn.LocalAddr().(*net.TCPAddr) lAddr := conn.LocalAddr().(*net.TCPAddr).AddrPort()
rAddr := conn.RemoteAddr().(*net.TCPAddr) rAddr := conn.RemoteAddr().(*net.TCPAddr).AddrPort()
lAddrPort := netip.AddrPortFrom(nnip.IpToAddr(lAddr.IP), uint16(lAddr.Port)) if rAddr.Addr().IsLoopback() {
rAddrPort := netip.AddrPortFrom(nnip.IpToAddr(rAddr.IP), uint16(rAddr.Port))
if rAddrPort.Addr().IsLoopback() {
_ = conn.Close() _ = conn.Close()
continue continue
} }
if D.ShouldHijackDns(dnsAddr, rAddrPort, "tcp") { if D.ShouldHijackDns(dnsAddr, rAddr, "tcp") {
go func() { go func() {
log.Debugln("[TUN] hijack dns tcp: %s", rAddrPort.String()) log.Debugln("[TUN] hijack dns tcp: %s", rAddr.String())
buf := pool.Get(pool.UDPBufferSize) buf := pool.Get(pool.UDPBufferSize)
defer func() { defer func() {
_ = pool.Put(buf)
_ = conn.Close() _ = conn.Close()
_ = pool.Put(buf)
}() }()
for { for {
@ -137,10 +134,10 @@ func New(device device.Device, dnsHijack []C.DNSUrl, tunAddress netip.Prefix, tc
metadata := &C.Metadata{ metadata := &C.Metadata{
NetWork: C.TCP, NetWork: C.TCP,
Type: C.TUN, Type: C.TUN,
SrcIP: lAddrPort.Addr(), SrcIP: lAddr.Addr(),
DstIP: rAddrPort.Addr(), DstIP: rAddr.Addr(),
SrcPort: strconv.Itoa(lAddr.Port), SrcPort: strconv.FormatUint(uint64(lAddr.Port()), 10),
DstPort: strconv.Itoa(rAddr.Port), DstPort: strconv.FormatUint(uint64(rAddr.Port()), 10),
AddrType: C.AtypIPv4, AddrType: C.AtypIPv4,
Host: "", Host: "",
} }
@ -159,37 +156,32 @@ func New(device device.Device, dnsHijack []C.DNSUrl, tunAddress netip.Prefix, tc
for !ipStack.closed { for !ipStack.closed {
buf := pool.Get(pool.UDPBufferSize) buf := pool.Get(pool.UDPBufferSize)
n, lRAddr, rRAddr, err := stack.UDP().ReadFrom(buf) n, lAddr, rAddr, err := stack.UDP().ReadFrom(buf)
if err != nil { if err != nil {
_ = pool.Put(buf) _ = pool.Put(buf)
break break
} }
raw := buf[:n] if rAddr.Addr().IsLoopback() || rAddr.Addr() == gateway {
lAddr := lRAddr.(*net.UDPAddr)
rAddr := rRAddr.(*net.UDPAddr)
rAddrPort := netip.AddrPortFrom(nnip.IpToAddr(rAddr.IP), uint16(rAddr.Port))
if rAddrPort.Addr().IsLoopback() || rAddrPort.Addr() == gateway {
_ = pool.Put(buf) _ = pool.Put(buf)
continue continue
} }
if D.ShouldHijackDns(dnsAddr, rAddrPort, "udp") { if D.ShouldHijackDns(dnsAddr, rAddr, "udp") {
go func() { go func() {
msg, err := D.RelayDnsPacket(raw) defer func() {
if err != nil {
_ = pool.Put(buf) _ = pool.Put(buf)
}()
msg, err := D.RelayDnsPacket(buf[:n])
if err != nil {
return return
} }
_, _ = stack.UDP().WriteTo(msg, rAddr, lAddr) _, _ = stack.UDP().WriteTo(msg, rAddr, lAddr)
_ = pool.Put(buf) log.Debugln("[TUN] hijack dns udp: %s", rAddr.String())
log.Debugln("[TUN] hijack dns udp: %s", rAddrPort.String())
}() }()
continue continue
@ -197,18 +189,17 @@ func New(device device.Device, dnsHijack []C.DNSUrl, tunAddress netip.Prefix, tc
pkt := &packet{ pkt := &packet{
local: lAddr, local: lAddr,
data: raw, data: buf,
offset: n,
writeBack: func(b []byte, addr net.Addr) (int, error) { writeBack: func(b []byte, addr net.Addr) (int, error) {
return stack.UDP().WriteTo(b, rAddr, lAddr) return stack.UDP().WriteTo(b, rAddr, lAddr)
}, },
drop: func() {
_ = pool.Put(buf)
},
} }
select { select {
case udpIn <- inbound.NewPacket(socks5.ParseAddrToSocksAddr(rAddr), pkt, C.TUN): case udpIn <- inbound.NewPacket(socks5.AddrFromStdAddrPort(rAddr), pkt, C.TUN):
default: default:
pkt.Drop()
} }
} }

View File

@ -1,16 +1,21 @@
package system package system
import "net" import (
"net"
"net/netip"
"github.com/Dreamacro/clash/common/pool"
)
type packet struct { type packet struct {
local *net.UDPAddr local netip.AddrPort
data []byte data []byte
offset int
writeBack func(b []byte, addr net.Addr) (int, error) writeBack func(b []byte, addr net.Addr) (int, error)
drop func()
} }
func (pkt *packet) Data() []byte { func (pkt *packet) Data() []byte {
return pkt.data return pkt.data[:pkt.offset]
} }
func (pkt *packet) WriteBack(b []byte, addr net.Addr) (n int, err error) { func (pkt *packet) WriteBack(b []byte, addr net.Addr) (n int, err error) {
@ -18,9 +23,9 @@ func (pkt *packet) WriteBack(b []byte, addr net.Addr) (n int, err error) {
} }
func (pkt *packet) Drop() { func (pkt *packet) Drop() {
pkt.drop() _ = pool.Put(pkt.data)
} }
func (pkt *packet) LocalAddr() net.Addr { func (pkt *packet) LocalAddr() net.Addr {
return pkt.local return net.UDPAddrFromAddrPort(pkt.local)
} }

View File

@ -9,7 +9,6 @@ import (
"github.com/Dreamacro/clash/adapter/inbound" "github.com/Dreamacro/clash/adapter/inbound"
"github.com/Dreamacro/clash/common/cmd" "github.com/Dreamacro/clash/common/cmd"
"github.com/Dreamacro/clash/component/process"
"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/tun/device" "github.com/Dreamacro/clash/listener/tun/device"
@ -24,7 +23,7 @@ import (
) )
// New TunAdapter // New TunAdapter
func New(tunConf *config.Tun, tunAddressPrefix *netip.Prefix, tcpIn chan<- C.ConnContext, udpIn chan<- *inbound.PacketAdapter, tunChangeCallback C.TUNChangeCallback) (ipstack.Stack, error) { func New(tunConf *config.Tun, tcpIn chan<- C.ConnContext, udpIn chan<- *inbound.PacketAdapter, tunChangeCallback C.TUNChangeCallback) (ipstack.Stack, error) {
var ( var (
tunAddress = netip.Prefix{} tunAddress = netip.Prefix{}
devName = tunConf.Device devName = tunConf.Device
@ -48,16 +47,14 @@ func New(tunConf *config.Tun, tunAddressPrefix *netip.Prefix, tcpIn chan<- C.Con
devName = generateDeviceName() devName = generateDeviceName()
} }
if tunAddressPrefix != nil { if tunConf.TunAddressPrefix != nil {
tunAddress = *tunAddressPrefix tunAddress = *tunConf.TunAddressPrefix
} }
if !tunAddress.IsValid() || !tunAddress.Addr().Is4() { if !tunAddress.IsValid() || !tunAddress.Addr().Is4() {
tunAddress = netip.MustParsePrefix("198.18.0.1/16") tunAddress = netip.MustParsePrefix("198.18.0.1/16")
} }
process.AppendLocalIPs(tunAddress.Masked().Addr().Next())
// open tun device // open tun device
tunDevice, err = parseDevice(devName, uint32(mtu)) tunDevice, err = parseDevice(devName, uint32(mtu))
if err != nil { if err != nil {

View File

@ -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 Plus Pro %s %s %s with %s %s\n", C.Version, runtime.GOOS, runtime.GOARCH, runtime.Version(), C.BuildTime) fmt.Printf("Clash with tun deveice %s %s %s with %s %s\n", C.Version, runtime.GOOS, runtime.GOARCH, runtime.Version(), C.BuildTime)
return return
} }

View File

@ -51,14 +51,11 @@ func NewGEOSITE(country string, adapter string) (*GEOSITE, error) {
return nil, fmt.Errorf("load GeoSite data error, %s", err.Error()) return nil, fmt.Errorf("load GeoSite data error, %s", err.Error())
} }
cont := fmt.Sprintf("%d", recordsCount) count := fmt.Sprintf("%d", recordsCount)
if recordsCount == 0 { if recordsCount == 0 {
cont = "from cache" count = "from cache"
} }
if adapter == C.ScriptRuleGeoSiteTarget { log.Infoln("Start initial GeoSite rule %s => %s, records: %s", country, adapter, count)
adapter = "Script"
}
log.Infoln("Start initial GeoSite rule %s => %s, records: %s", country, adapter, cont)
geoSite := &GEOSITE{ geoSite := &GEOSITE{
Base: &Base{}, Base: &Base{},

View File

@ -37,8 +37,6 @@ func ParseRule(tp, payload, target string, params []string) (C.Rule, error) {
parsed, parseErr = NewProcess(payload, target, true) parsed, parseErr = NewProcess(payload, target, true)
case "PROCESS-PATH": case "PROCESS-PATH":
parsed, parseErr = NewProcess(payload, target, false) parsed, parseErr = NewProcess(payload, target, false)
case "SCRIPT":
parsed, parseErr = NewScript(payload, target)
case "USER-AGENT": case "USER-AGENT":
parsed, parseErr = NewUserAgent(payload, target) parsed, parseErr = NewUserAgent(payload, target)
case "MATCH": case "MATCH":

View File

@ -1,77 +0,0 @@
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)

View File

@ -8,7 +8,7 @@ require (
github.com/docker/go-connections v0.4.0 github.com/docker/go-connections v0.4.0
github.com/miekg/dns v1.1.49 github.com/miekg/dns v1.1.49
github.com/stretchr/testify v1.7.1 github.com/stretchr/testify v1.7.1
golang.org/x/net v0.0.0-20220531201128-c960675eff93 golang.org/x/net v0.0.0-20220526153639-5463443f8c37
) )
replace github.com/Dreamacro/clash => ../ replace github.com/Dreamacro/clash => ../
@ -37,20 +37,20 @@ require (
go.etcd.io/bbolt v1.3.6 // indirect go.etcd.io/bbolt v1.3.6 // indirect
go.uber.org/atomic v1.9.0 // indirect go.uber.org/atomic v1.9.0 // indirect
golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e // indirect golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e // indirect
golang.org/x/exp v0.0.0-20220602145555-4a0574d9293f // indirect golang.org/x/exp v0.0.0-20220518171630-0b5c67f07fdf // indirect
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 // indirect golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 // indirect
golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f // indirect golang.org/x/sync v0.0.0-20220513210516-0976fa681c29 // indirect
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a // indirect golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a // indirect
golang.org/x/text v0.3.8-0.20220124021120-d1c84af989ab // indirect golang.org/x/text v0.3.8-0.20220124021120-d1c84af989ab // indirect
golang.org/x/time v0.0.0-20220411224347-583f2d630306 // indirect golang.org/x/time v0.0.0-20220411224347-583f2d630306 // indirect
golang.org/x/tools v0.1.10 // indirect golang.org/x/tools v0.1.10 // indirect
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
golang.zx2c4.com/wintun v0.0.0-20211104114900-415007cec224 // indirect golang.zx2c4.com/wintun v0.0.0-20211104114900-415007cec224 // indirect
golang.zx2c4.com/wireguard v0.0.0-20220601130007-6a08d81f6bc4 // indirect golang.zx2c4.com/wireguard v0.0.0-20220407013110-ef5c587f782d // indirect
golang.zx2c4.com/wireguard/windows v0.5.4-0.20220328111914-004c22c5647e // indirect golang.zx2c4.com/wireguard/windows v0.5.4-0.20220328111914-004c22c5647e // indirect
google.golang.org/protobuf v1.28.0 // indirect google.golang.org/protobuf v1.28.0 // indirect
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
gotest.tools/v3 v3.1.0 // indirect gotest.tools/v3 v3.1.0 // indirect
gvisor.dev/gvisor v0.0.0-20220601233344-46e478629075 // indirect gvisor.dev/gvisor v0.0.0-20220527053002-8ab279227ac8 // indirect
) )

View File

@ -101,8 +101,8 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e h1:T8NU3HyQ8ClP4SEE+KbFlg6n0NhuTsN4MyznaarGsZM= golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e h1:T8NU3HyQ8ClP4SEE+KbFlg6n0NhuTsN4MyznaarGsZM=
golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/exp v0.0.0-20220602145555-4a0574d9293f h1:KK6mxegmt5hGJRcAnEDjSNLxIRhZxDcgwMbcO/lMCRM= golang.org/x/exp v0.0.0-20220518171630-0b5c67f07fdf h1:oXVg4h2qJDd9htKxb5SCpFBHLipW6hXmL3qpUixS2jw=
golang.org/x/exp v0.0.0-20220602145555-4a0574d9293f/go.mod h1:yh0Ynu2b5ZUe3MQfp2nM0ecK7wsgouWTDN0FNeJuIys= golang.org/x/exp v0.0.0-20220518171630-0b5c67f07fdf/go.mod h1:yh0Ynu2b5ZUe3MQfp2nM0ecK7wsgouWTDN0FNeJuIys=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
@ -122,14 +122,14 @@ golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwY
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220531201128-c960675eff93 h1:MYimHLfoXEpOhqd/zgoA/uoXzHB86AEky4LAx5ij9xA= golang.org/x/net v0.0.0-20220526153639-5463443f8c37 h1:lUkvobShwKsOesNfWWlCS5q7fnbG1MEliIzwu886fn8=
golang.org/x/net v0.0.0-20220531201128-c960675eff93/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.0.0-20220526153639-5463443f8c37/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f h1:Ax0t5p6N38Ga0dThY21weqDEyz2oklo4IvDkpigvkD8= golang.org/x/sync v0.0.0-20220513210516-0976fa681c29 h1:w8s32wxx3sY+OjLlv9qltkLU5yvJzxjjgiHWLjdIcw4=
golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220513210516-0976fa681c29/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190411185658-b44545bcd369/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190411185658-b44545bcd369/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@ -180,8 +180,8 @@ golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1N
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.zx2c4.com/wintun v0.0.0-20211104114900-415007cec224 h1:Ug9qvr1myri/zFN6xL17LSCBGFDnphBBhzmILHsM5TY= golang.zx2c4.com/wintun v0.0.0-20211104114900-415007cec224 h1:Ug9qvr1myri/zFN6xL17LSCBGFDnphBBhzmILHsM5TY=
golang.zx2c4.com/wintun v0.0.0-20211104114900-415007cec224/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI= golang.zx2c4.com/wintun v0.0.0-20211104114900-415007cec224/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI=
golang.zx2c4.com/wireguard v0.0.0-20220601130007-6a08d81f6bc4 h1:QlbNZ9SwDAepRQwgeWHLi3rfEMo/kVEU4SmgsNM7HmQ= golang.zx2c4.com/wireguard v0.0.0-20220407013110-ef5c587f782d h1:q4JksJ2n0fmbXC0Aj0eOs6E0AcPqnKglxWXWFqGD6x0=
golang.zx2c4.com/wireguard v0.0.0-20220601130007-6a08d81f6bc4/go.mod h1:bVQfyl2sCM/QIIGHpWbFGfHPuDvqnCNkT6MQLTCjO/U= golang.zx2c4.com/wireguard v0.0.0-20220407013110-ef5c587f782d/go.mod h1:bVQfyl2sCM/QIIGHpWbFGfHPuDvqnCNkT6MQLTCjO/U=
golang.zx2c4.com/wireguard/windows v0.5.4-0.20220328111914-004c22c5647e h1:yV04h6Tx19uDR6LvuEbR19cDU+3QrB9LuGjtF7F5G0w= golang.zx2c4.com/wireguard/windows v0.5.4-0.20220328111914-004c22c5647e h1:yV04h6Tx19uDR6LvuEbR19cDU+3QrB9LuGjtF7F5G0w=
golang.zx2c4.com/wireguard/windows v0.5.4-0.20220328111914-004c22c5647e/go.mod h1:1CeiatTZwcwSFA3cAtMm8CQoroviTldnxd7DOgM/vI4= golang.zx2c4.com/wireguard/windows v0.5.4-0.20220328111914-004c22c5647e/go.mod h1:1CeiatTZwcwSFA3cAtMm8CQoroviTldnxd7DOgM/vI4=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
@ -196,5 +196,5 @@ gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk= gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk=
gotest.tools/v3 v3.1.0 h1:rVV8Tcg/8jHUkPUorwjaMTtemIMVXfIPKiOqnhEhakk= gotest.tools/v3 v3.1.0 h1:rVV8Tcg/8jHUkPUorwjaMTtemIMVXfIPKiOqnhEhakk=
gotest.tools/v3 v3.1.0/go.mod h1:fHy7eyTmJFO5bQbUsEGQ1v4m2J3Jz9eWL54TP2/ZuYQ= gotest.tools/v3 v3.1.0/go.mod h1:fHy7eyTmJFO5bQbUsEGQ1v4m2J3Jz9eWL54TP2/ZuYQ=
gvisor.dev/gvisor v0.0.0-20220601233344-46e478629075 h1:ucwwit0X39HmMQ1iSeNCIXw4g/B8bi+O6TSxxDbPs9E= gvisor.dev/gvisor v0.0.0-20220527053002-8ab279227ac8 h1:K6RgHqNR+9t3sKVsfRFsvXryRL5kL6wtBPU5aPt1jLY=
gvisor.dev/gvisor v0.0.0-20220601233344-46e478629075/go.mod h1:TIvkJD0sxe8pIob3p6T8IzxXunlp6yfgktvTNp+DGNM= gvisor.dev/gvisor v0.0.0-20220527053002-8ab279227ac8/go.mod h1:TIvkJD0sxe8pIob3p6T8IzxXunlp6yfgktvTNp+DGNM=

View File

@ -6,6 +6,7 @@ import (
"errors" "errors"
"io" "io"
"net" "net"
"net/netip"
"strconv" "strconv"
"github.com/Dreamacro/clash/component/auth" "github.com/Dreamacro/clash/component/auth"
@ -398,6 +399,21 @@ func ParseAddrToSocksAddr(addr net.Addr) Addr {
return parsed return parsed
} }
func AddrFromStdAddrPort(addrPort netip.AddrPort) Addr {
addr := addrPort.Addr()
if addr.Is4() {
ip4 := addr.As4()
return []byte{AtypIPv4, ip4[0], ip4[1], ip4[2], ip4[3], byte(addrPort.Port() >> 8), byte(addrPort.Port())}
}
buf := make([]byte, 1+net.IPv6len+2)
buf[0] = AtypIPv6
copy(buf[1:], addr.AsSlice())
buf[1+net.IPv6len] = byte(addrPort.Port() >> 8)
buf[1+net.IPv6len+1] = byte(addrPort.Port())
return buf
}
// DecodeUDPPacket split `packet` to addr payload, and this function is mutable with `packet` // DecodeUDPPacket split `packet` to addr payload, and this function is mutable with `packet`
func DecodeUDPPacket(packet []byte) (addr Addr, payload []byte, err error) { func DecodeUDPPacket(packet []byte) (addr Addr, payload []byte, err error) {
if len(packet) < 5 { if len(packet) < 5 {

View File

@ -16,7 +16,7 @@ func handleUDPToRemote(packet C.UDPPacket, pc C.PacketConn, metadata *C.Metadata
// local resolve UDP dns // local resolve UDP dns
if !metadata.Resolved() { if !metadata.Resolved() {
ip, err := resolver.ResolveIP(metadata.Host) ip, err := resolver.ResolveFirstIP(metadata.Host)
if err != nil { if err != nil {
return err return err
} }
@ -32,19 +32,21 @@ func handleUDPToRemote(packet C.UDPPacket, pc C.PacketConn, metadata *C.Metadata
return err return err
} }
// reset timeout // reset timeout
pc.SetReadDeadline(time.Now().Add(udpTimeout)) _ = pc.SetReadDeadline(time.Now().Add(udpTimeout))
return nil return nil
} }
func handleUDPToLocal(packet C.UDPPacket, pc net.PacketConn, key string, fAddr net.Addr) { func handleUDPToLocal(packet C.UDPPacket, pc net.PacketConn, key string, fAddr net.Addr) {
buf := pool.Get(pool.UDPBufferSize) buf := pool.Get(pool.UDPBufferSize)
defer pool.Put(buf) defer func() {
defer natTable.Delete(key) _ = pc.Close()
defer pc.Close() natTable.Delete(key)
_ = pool.Put(buf)
}()
for { for {
pc.SetReadDeadline(time.Now().Add(udpTimeout)) _ = pc.SetReadDeadline(time.Now().Add(udpTimeout))
n, from, err := pc.ReadFrom(buf) n, from, err := pc.ReadFrom(buf)
if err != nil { if err != nil {
return return

View File

@ -12,14 +12,12 @@ 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
) )
@ -63,8 +61,6 @@ 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:

View File

@ -16,7 +16,6 @@ import (
"github.com/Dreamacro/clash/component/nat" "github.com/Dreamacro/clash/component/nat"
P "github.com/Dreamacro/clash/component/process" P "github.com/Dreamacro/clash/component/process"
"github.com/Dreamacro/clash/component/resolver" "github.com/Dreamacro/clash/component/resolver"
S "github.com/Dreamacro/clash/component/script"
"github.com/Dreamacro/clash/component/trie" "github.com/Dreamacro/clash/component/trie"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/constant/provider" "github.com/Dreamacro/clash/constant/provider"
@ -191,19 +190,6 @@ func preHandleMetadata(metadata *C.Metadata) error {
} }
} }
// pre resolve process name
srcPort, err := strconv.ParseUint(metadata.SrcPort, 10, 16)
if err == nil && P.ShouldFindProcess(metadata) {
path, err := P.FindProcessName(metadata.NetWork.String(), metadata.SrcIP, int(srcPort))
if err != nil {
log.Debugln("[Process] find process %s: %v", metadata.String(), err)
} else {
log.Debugln("[Process] %s from process %s", metadata.String(), path)
metadata.Process = filepath.Base(path)
metadata.ProcessPath = path
}
}
return nil return nil
} }
@ -219,8 +205,6 @@ func resolveMetadata(_ C.PlainContext, metadata *C.Metadata) (proxy C.Proxy, rul
proxy = proxies["DIRECT"] proxy = proxies["DIRECT"]
case Global: case Global:
proxy = proxies["GLOBAL"] proxy = proxies["GLOBAL"]
case Script:
proxy, err = matchScript(metadata)
// Rule // Rule
default: default:
proxy, rule, err = match(metadata) proxy, rule, err = match(metadata)
@ -302,8 +286,6 @@ 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:
@ -371,8 +353,6 @@ func handleTCPConn(connCtx C.ConnContext) {
break break
case rule != nil: case rule != nil:
log.Infoln("[TCP] %s(%s) --> %s match %s(%s) using %s", metadata.SourceAddress(), metadata.Process, metadata.RemoteAddress(), rule.RuleType().String(), rule.Payload(), remoteConn.Chains().String()) log.Infoln("[TCP] %s(%s) --> %s match %s(%s) using %s", metadata.SourceAddress(), metadata.Process, metadata.RemoteAddress(), rule.RuleType().String(), rule.Payload(), remoteConn.Chains().String())
case mode == Script:
log.Infoln("[TCP] %s(%s) --> %s using SCRIPT %s", metadata.SourceAddress(), metadata.Process, metadata.RemoteAddress(), remoteConn.Chains().String())
case mode == Global: case mode == Global:
log.Infoln("[TCP] %s(%s) --> %s using GLOBAL", metadata.SourceAddress(), metadata.Process, metadata.RemoteAddress()) log.Infoln("[TCP] %s(%s) --> %s using GLOBAL", metadata.SourceAddress(), metadata.Process, metadata.RemoteAddress())
case mode == Direct: case mode == Direct:
@ -392,7 +372,10 @@ func match(metadata *C.Metadata) (C.Proxy, C.Rule, error) {
configMux.RLock() configMux.RLock()
defer configMux.RUnlock() defer configMux.RUnlock()
var resolved bool var (
resolved bool
processFound bool
)
if node := resolver.DefaultHosts.Search(metadata.Host); node != nil { if node := resolver.DefaultHosts.Search(metadata.Host); node != nil {
metadata.DstIP = node.Data metadata.DstIP = node.Data
@ -411,6 +394,22 @@ func match(metadata *C.Metadata) (C.Proxy, C.Rule, error) {
resolved = true resolved = true
} }
if !processFound && rule.ShouldFindProcess() {
processFound = true
srcPort, err := strconv.ParseUint(metadata.SrcPort, 10, 16)
if err == nil {
path, err := P.FindProcessName(metadata.NetWork.String(), metadata.SrcIP, int(srcPort))
if err != nil {
log.Debugln("[Process] find process %s: %v", metadata.String(), err)
} else {
log.Debugln("[Process] %s from process %s", metadata.String(), path)
metadata.Process = filepath.Base(path)
metadata.ProcessPath = path
}
}
}
if rule.Match(metadata) { if rule.Match(metadata) {
adapter, ok := proxies[rule.Adapter()] adapter, ok := proxies[rule.Adapter()]
if !ok { if !ok {
@ -443,23 +442,3 @@ func match(metadata *C.Metadata) (C.Proxy, C.Rule, error) {
return proxies["REJECT"], nil, nil return proxies["REJECT"], nil, nil
} }
func matchScript(metadata *C.Metadata) (C.Proxy, error) {
configMux.RLock()
defer configMux.RUnlock()
if node := resolver.DefaultHosts.Search(metadata.Host); node != nil {
metadata.DstIP = node.Data
}
adapter, err := S.CallPyMainFunction(metadata)
if err != nil {
return nil, err
}
if _, ok := proxies[adapter]; !ok {
return nil, fmt.Errorf("proxy [%s] not found by script", adapter)
}
return proxies[adapter], nil
}