Compare commits
25 Commits
v1.12.3_tu
...
plus
Author | SHA1 | Date | |
---|---|---|---|
160e630f03 | |||
13d19ff101 | |||
934babca85 | |||
f23d1d5d7c | |||
4334b45e82 | |||
9ffcc9e352 | |||
392572d684 | |||
0321ddbb90 | |||
d74dd69329 | |||
c8bc4386dd | |||
1e7cbd6358 | |||
0a2701eef0 | |||
0d004bf6f3 | |||
053366c3e1 | |||
9d72bf2a36 | |||
571c34f140 | |||
05b4a326de | |||
d77ef6a525 | |||
4d9d8b28ec | |||
7973491625 | |||
edbc8ed972 | |||
bd123dddc6 | |||
ae493f1084 | |||
711b2bcf87 | |||
a45354fa08 |
75
.github/workflows/build-windows-amd.yml
vendored
Normal file
75
.github/workflows/build-windows-amd.yml
vendored
Normal file
@ -0,0 +1,75 @@
|
||||
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',$(git describe --tags --always)) | 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-windows-amd64.exe
|
||||
$env:GOAMD64="v3"; $env:CGO_ENABLED=1; go build -tags build_actions -trimpath -ldflags '-w -s -buildid=' -o bin/clash-windows-amd64-v3.exe
|
||||
|
||||
cd bin/
|
||||
Compress-Archive -Path clash-windows-amd64.exe -DestinationPath clash-plus-windows-amd64-$(git describe --tags --always)-$(Get-Date -Format 'yyyy.MM.dd').zip
|
||||
Compress-Archive -Path clash-windows-amd64-v3.exe -DestinationPath clash-plus-windows-amd64-v3-$(git describe --tags --always)-$(Get-Date -Format 'yyyy.MM.dd').zip
|
||||
Remove-Item -Force clash-windows-amd64.exe
|
||||
Remove-Item -Force clash-windows-amd64-v3.exe
|
||||
|
||||
- name: Upload files to Artifacts
|
||||
uses: actions/upload-artifact@v2
|
||||
if: startsWith(github.ref, 'refs/tags/') == false
|
||||
with:
|
||||
name: clash-windows-amd64-${{ steps.test.outputs.file_sha }}-${{ steps.test.outputs.file_date }}
|
||||
path: |
|
||||
bin/*
|
||||
|
||||
- name: Delete workflow runs
|
||||
uses: GitRML/delete-workflow-runs@main
|
||||
with:
|
||||
retain_days: 1
|
||||
keep_minimum_runs: 2
|
3
.github/workflows/docker.yml
vendored
3
.github/workflows/docker.yml
vendored
@ -3,7 +3,8 @@ on:
|
||||
push:
|
||||
branches:
|
||||
- rm
|
||||
|
||||
tags:
|
||||
- '*'
|
||||
jobs:
|
||||
|
||||
build:
|
||||
|
1
.github/workflows/linter.yml
vendored
1
.github/workflows/linter.yml
vendored
@ -20,3 +20,4 @@ jobs:
|
||||
uses: golangci/golangci-lint-action@v3
|
||||
with:
|
||||
version: latest
|
||||
args: --build-tags=build_local
|
||||
|
89
.github/workflows/release.yml
vendored
89
.github/workflows/release.yml
vendored
@ -1,5 +1,8 @@
|
||||
name: Release
|
||||
on: [push]
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- rm
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
@ -33,29 +36,63 @@ jobs:
|
||||
restore-keys: |
|
||||
${{ runner.os }}-go-
|
||||
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: '3.9'
|
||||
|
||||
- name: Get dependencies, run test
|
||||
run: |
|
||||
go test ./...
|
||||
# fetch python cross compile source files
|
||||
mkdir -p bin/python/
|
||||
cd bin/python/
|
||||
curl -LO https://raw.githubusercontent.com/yaling888/snack/main/python-3.9.7-darwin-amd64.tar.xz
|
||||
curl -LO https://raw.githubusercontent.com/yaling888/snack/main/python-3.9.7-darwin-arm64.tar.xz
|
||||
curl -LO https://raw.githubusercontent.com/yaling888/snack/main/python-3.9.7-windows-amd64.tar.xz
|
||||
curl -LO https://raw.githubusercontent.com/yaling888/snack/main/python-3.9.7-windows-386.tar.xz
|
||||
#curl -LO https://raw.githubusercontent.com/yaling888/snack/main/python-3.9.7-linux-amd64.tar.xz
|
||||
#curl -LO https://raw.githubusercontent.com/yaling888/snack/main/python-3.9.7-linux-arm64.tar.xz
|
||||
#curl -LO https://raw.githubusercontent.com/yaling888/snack/main/python-3.9.7-linux-386.tar.xz
|
||||
tar -Jxf python-3.9.7-darwin-amd64.tar.xz
|
||||
tar -Jxf python-3.9.7-darwin-arm64.tar.xz
|
||||
tar -Jxf python-3.9.7-windows-amd64.tar.xz
|
||||
tar -Jxf python-3.9.7-windows-386.tar.xz
|
||||
#tar -Jxf python-3.9.7-linux-amd64.tar.xz
|
||||
#tar -Jxf python-3.9.7-linux-arm64.tar.xz
|
||||
#tar -Jxf python-3.9.7-linux-386.tar.xz
|
||||
rm python-3.9.7-*.tar.xz
|
||||
cd ../../
|
||||
|
||||
# go test
|
||||
go test -tags build_local ./...
|
||||
|
||||
# init xgo
|
||||
docker pull techknowlogick/xgo:latest
|
||||
go install src.techknowlogick.com/xgo@latest
|
||||
|
||||
- name: Build
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
#if: startsWith(github.ref, 'refs/tags/')
|
||||
env:
|
||||
NAME: clash
|
||||
BINDIR: bin
|
||||
run: make -j releases
|
||||
run: |
|
||||
make -j releases
|
||||
#ls -lahF bin/python/
|
||||
|
||||
#- name: Prepare upload
|
||||
# run: |
|
||||
# echo "FILE_DATE=_$(date +"%Y%m%d%H%M")" >> $GITHUB_ENV
|
||||
# echo "FILE_SHA=$(git describe --tags --always 2>/dev/null)" >> $GITHUB_ENV
|
||||
#
|
||||
#- name: Upload files to Artifacts
|
||||
# uses: actions/upload-artifact@v2
|
||||
# if: startsWith(github.ref, 'refs/tags/') == false
|
||||
# with:
|
||||
# name: clash_${{ env.FILE_SHA }}${{ env.FILE_DATE }}
|
||||
# path: |
|
||||
# bin/*
|
||||
- name: Prepare upload
|
||||
if: startsWith(github.ref, 'refs/tags/') == false
|
||||
run: |
|
||||
rm -rf bin/python/
|
||||
echo "FILE_DATE=_$(date +"%Y%m%d%H%M")" >> $GITHUB_ENV
|
||||
echo "FILE_SHA=$(git describe --tags --always 2>/dev/null)" >> $GITHUB_ENV
|
||||
|
||||
- name: Upload files to Artifacts
|
||||
uses: actions/upload-artifact@v2
|
||||
if: startsWith(github.ref, 'refs/tags/') == false
|
||||
with:
|
||||
name: clash_${{ env.FILE_SHA }}${{ env.FILE_DATE }}
|
||||
path: |
|
||||
bin/*
|
||||
|
||||
- name: Upload Release
|
||||
uses: softprops/action-gh-release@v1
|
||||
@ -66,8 +103,18 @@ jobs:
|
||||
prerelease: true
|
||||
generate_release_notes: true
|
||||
|
||||
#- name: Delete workflow runs
|
||||
# uses: GitRML/delete-workflow-runs@main
|
||||
# with:
|
||||
# retain_days: 1
|
||||
# keep_minimum_runs: 2
|
||||
- name: Delete workflow runs
|
||||
uses: GitRML/delete-workflow-runs@main
|
||||
with:
|
||||
retain_days: 1
|
||||
keep_minimum_runs: 2
|
||||
|
||||
- name: Remove old Releases
|
||||
uses: dev-drprasad/delete-older-releases@v0.2.0
|
||||
if: startsWith(github.ref, 'refs/tags/') && !cancelled()
|
||||
with:
|
||||
keep_latest: 1
|
||||
delete_tags: true
|
||||
delete_tag_pattern: plus-pro
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
148
Makefile
148
Makefile
@ -1,119 +1,70 @@
|
||||
GOCMD=go
|
||||
XGOCMD=xgo -go=go-1.18.x
|
||||
GOBUILD=CGO_ENABLED=1 $(GOCMD) build -trimpath
|
||||
GOCLEAN=$(GOCMD) clean
|
||||
NAME=clash
|
||||
BINDIR=bin
|
||||
VERSION=$(shell git describe --tags --always 2>/dev/null || echo "unknown version")
|
||||
BINDIR=$(shell pwd)/bin
|
||||
VERSION=$(shell git describe --tags --always 2>/dev/null || date +%F)
|
||||
BUILDTIME=$(shell date -u)
|
||||
GOBUILD=CGO_ENABLED=0 go build -trimpath -ldflags '-X "github.com/Dreamacro/clash/constant.Version=$(VERSION)" \
|
||||
-X "github.com/Dreamacro/clash/constant.BuildTime=$(BUILDTIME)" \
|
||||
-w -s -buildid='
|
||||
BUILD_PACKAGE=.
|
||||
RELEASE_LDFLAGS='-X "github.com/Dreamacro/clash/constant.Version=$(VERSION)" \
|
||||
-X "github.com/Dreamacro/clash/constant.BuildTime=$(BUILDTIME)" \
|
||||
-w -s -buildid='
|
||||
STATIC_LDFLAGS='-X "github.com/Dreamacro/clash/constant.Version=$(VERSION)" \
|
||||
-X "github.com/Dreamacro/clash/constant.BuildTime=$(BUILDTIME)" \
|
||||
-extldflags "-static" \
|
||||
-w -s -buildid='
|
||||
|
||||
PLATFORM_LIST = \
|
||||
darwin-amd64 \
|
||||
darwin-amd64-v3 \
|
||||
darwin-arm64 \
|
||||
linux-386 \
|
||||
linux-amd64 \
|
||||
linux-amd64-v3 \
|
||||
linux-armv5 \
|
||||
linux-armv6 \
|
||||
linux-armv7 \
|
||||
linux-armv8 \
|
||||
linux-mips-softfloat \
|
||||
linux-mips-hardfloat \
|
||||
linux-mipsle-softfloat \
|
||||
linux-mipsle-hardfloat \
|
||||
linux-mips64 \
|
||||
linux-mips64le \
|
||||
freebsd-386 \
|
||||
freebsd-amd64 \
|
||||
freebsd-amd64-v3 \
|
||||
freebsd-arm64
|
||||
linux-amd64
|
||||
# linux-arm64
|
||||
# linux-386
|
||||
|
||||
WINDOWS_ARCH_LIST = \
|
||||
windows-386 \
|
||||
windows-amd64 \
|
||||
windows-amd64-v3 \
|
||||
windows-arm64 \
|
||||
windows-arm32v7
|
||||
windows-386
|
||||
# windows-arm64
|
||||
|
||||
all: linux-amd64 darwin-amd64 windows-amd64 # Most used
|
||||
|
||||
docker:
|
||||
$(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
||||
local:
|
||||
$(GOBUILD) -ldflags $(RELEASE_LDFLAGS) -tags build_local -o $(BINDIR)/$(NAME)-$@
|
||||
|
||||
local-v3:
|
||||
GOAMD64=v3 $(GOBUILD) -ldflags $(RELEASE_LDFLAGS) -tags build_local -o $(BINDIR)/$(NAME)-$@
|
||||
|
||||
darwin-amd64:
|
||||
GOARCH=amd64 GOOS=darwin $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
||||
|
||||
darwin-amd64-v3:
|
||||
GOARCH=amd64 GOOS=darwin GOAMD64=v3 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
||||
$(XGOCMD) -dest=$(BINDIR) -out=$(NAME) -trimpath=true -ldflags=$(RELEASE_LDFLAGS) -targets=darwin-10.12/amd64 $(BUILD_PACKAGE) && \
|
||||
mv $(BINDIR)/$(NAME)-darwin-10.12-amd64 $(BINDIR)/$(NAME)-darwin-amd64
|
||||
|
||||
darwin-arm64:
|
||||
GOARCH=arm64 GOOS=darwin $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
||||
$(XGOCMD) -dest=$(BINDIR) -out=$(NAME) -trimpath=true -ldflags=$(RELEASE_LDFLAGS) -targets=darwin-11.1/arm64 $(BUILD_PACKAGE) && \
|
||||
mv $(BINDIR)/$(NAME)-darwin-11.1-arm64 $(BINDIR)/$(NAME)-darwin-arm64
|
||||
|
||||
linux-386:
|
||||
GOARCH=386 GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
||||
$(XGOCMD) -dest=$(BINDIR) -out=$(NAME) -trimpath=true -ldflags=$(STATIC_LDFLAGS) -targets=linux/386 $(BUILD_PACKAGE)
|
||||
|
||||
linux-amd64:
|
||||
GOARCH=amd64 GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
||||
$(GOBUILD) -ldflags $(RELEASE_LDFLAGS) -o $(BINDIR)/$(NAME)-$@
|
||||
#GOARCH=amd64 GOOS=linux $(GOBUILD) -ldflags $(RELEASE_LDFLAGS) -o $(BINDIR)/$(NAME)-$@
|
||||
#$(XGOCMD) -dest=$(BINDIR) -out=$(NAME) -trimpath=true -ldflags=$(STATIC_LDFLAGS) -targets=linux/amd64 $(BUILD_PACKAGE)
|
||||
|
||||
linux-amd64-v3:
|
||||
GOARCH=amd64 GOOS=linux GOAMD64=v3 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
||||
|
||||
linux-armv5:
|
||||
GOARCH=arm GOOS=linux GOARM=5 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
||||
|
||||
linux-armv6:
|
||||
GOARCH=arm GOOS=linux GOARM=6 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
||||
|
||||
linux-armv7:
|
||||
GOARCH=arm GOOS=linux GOARM=7 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
||||
|
||||
linux-armv8:
|
||||
GOARCH=arm64 GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
||||
|
||||
linux-mips-softfloat:
|
||||
GOARCH=mips GOMIPS=softfloat GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
||||
|
||||
linux-mips-hardfloat:
|
||||
GOARCH=mips GOMIPS=hardfloat GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
||||
|
||||
linux-mipsle-softfloat:
|
||||
GOARCH=mipsle GOMIPS=softfloat GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
||||
|
||||
linux-mipsle-hardfloat:
|
||||
GOARCH=mipsle GOMIPS=hardfloat GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
||||
|
||||
linux-mips64:
|
||||
GOARCH=mips64 GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
||||
|
||||
linux-mips64le:
|
||||
GOARCH=mips64le GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
||||
|
||||
freebsd-386:
|
||||
GOARCH=386 GOOS=freebsd $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
||||
|
||||
freebsd-amd64:
|
||||
GOARCH=amd64 GOOS=freebsd $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
||||
|
||||
freebsd-amd64-v3:
|
||||
GOARCH=amd64 GOOS=freebsd GOAMD64=v3 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
||||
|
||||
freebsd-arm64:
|
||||
GOARCH=arm64 GOOS=freebsd $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
||||
linux-arm64:
|
||||
$(XGOCMD) -dest=$(BINDIR) -out=$(NAME) -trimpath=true -ldflags=$(STATIC_LDFLAGS) -targets=linux/arm64 $(BUILD_PACKAGE)
|
||||
|
||||
windows-386:
|
||||
GOARCH=386 GOOS=windows $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.exe
|
||||
$(XGOCMD) -dest=$(BINDIR) -out=$(NAME) -trimpath=true -ldflags=$(RELEASE_LDFLAGS) -targets=windows-6.0/386 $(BUILD_PACKAGE) && \
|
||||
mv $(BINDIR)/$(NAME)-windows-6.0-386.exe $(BINDIR)/$(NAME)-windows-386.exe
|
||||
|
||||
windows-amd64:
|
||||
GOARCH=amd64 GOOS=windows $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.exe
|
||||
$(XGOCMD) -dest=$(BINDIR) -out=$(NAME) -trimpath=true -ldflags=$(RELEASE_LDFLAGS) -targets=windows-6.0/amd64 $(BUILD_PACKAGE) && \
|
||||
mv $(BINDIR)/$(NAME)-windows-6.0-amd64.exe $(BINDIR)/$(NAME)-windows-amd64.exe
|
||||
|
||||
windows-amd64-v3:
|
||||
GOARCH=amd64 GOOS=windows GOAMD64=v3 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.exe
|
||||
|
||||
windows-arm64:
|
||||
GOARCH=arm64 GOOS=windows $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.exe
|
||||
|
||||
windows-arm32v7:
|
||||
GOARCH=arm GOOS=windows GOARM=7 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.exe
|
||||
#windows-arm64:
|
||||
# $(XGOCMD) -dest=$(BINDIR) -out=$(NAME) -trimpath=true -ldflags=$(RELEASE_LDFLAGS) -targets=windows/arm64 $(BUILD_PACKAGE)
|
||||
# mv $(NAME)-windows-4.0-arm64.exe $(NAME)-windows-arm64.exe
|
||||
|
||||
gz_releases=$(addsuffix .gz, $(PLATFORM_LIST))
|
||||
zip_releases=$(addsuffix .zip, $(WINDOWS_ARCH_LIST))
|
||||
@ -130,14 +81,17 @@ all-arch: $(PLATFORM_LIST) $(WINDOWS_ARCH_LIST)
|
||||
releases: $(gz_releases) $(zip_releases)
|
||||
|
||||
vet:
|
||||
go test ./...
|
||||
$(GOCMD) test -tags build_local ./...
|
||||
|
||||
lint:
|
||||
GOOS=darwin golangci-lint run ./...
|
||||
GOOS=windows golangci-lint run ./...
|
||||
GOOS=linux golangci-lint run ./...
|
||||
GOOS=freebsd golangci-lint run ./...
|
||||
GOOS=openbsd golangci-lint run ./...
|
||||
golangci-lint run --build-tags=build_local ./...
|
||||
|
||||
clean:
|
||||
rm -rf $(BINDIR)/*
|
||||
rm -rf $(BINDIR)/
|
||||
mkdir -p $(BINDIR)
|
||||
|
||||
cleancache:
|
||||
# go build cache may need to cleanup if changing C source code
|
||||
$(GOCLEAN) -cache
|
||||
rm -rf $(BINDIR)/
|
||||
mkdir -p $(BINDIR)
|
249
README.md
249
README.md
@ -13,11 +13,11 @@
|
||||
<img src="https://goreportcard.com/badge/github.com/Dreamacro/clash?style=flat-square">
|
||||
</a>
|
||||
<img src="https://img.shields.io/github/go-mod/go-version/Dreamacro/clash?style=flat-square">
|
||||
<a href="https://github.com/yaling888/clash/releases">
|
||||
<img src="https://img.shields.io/github/release/yaling888/clash/all.svg?style=flat-square">
|
||||
<a href="https://github.com/Dreamacro/clash/releases">
|
||||
<img src="https://img.shields.io/github/release/Dreamacro/clash/all.svg?style=flat-square">
|
||||
</a>
|
||||
<a href="https://github.com/yaling888/clash/releases/tag/plus_pro">
|
||||
<img src="https://img.shields.io/badge/release-Plus Pro-00b4f0?style=flat-square">
|
||||
<a href="https://github.com/Dreamacro/clash/releases/tag/premium">
|
||||
<img src="https://img.shields.io/badge/release-Premium-00b4f0?style=flat-square">
|
||||
</a>
|
||||
</p>
|
||||
|
||||
@ -36,11 +36,27 @@
|
||||
Documentations are now moved to [GitHub Wiki](https://github.com/Dreamacro/clash/wiki).
|
||||
|
||||
## Advanced usage for this branch
|
||||
### General configuration
|
||||
```yaml
|
||||
sniffing: true # Sniff TLS SNI
|
||||
### Build
|
||||
This branch requires cgo and Python3.9, so make sure you set up Python3.9 before building.
|
||||
|
||||
force-cert-verify: true # force verify TLS Certificate, prevent machine-in-the-middle attacks
|
||||
For example, build on macOS:
|
||||
```shell
|
||||
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 cleancache && make local
|
||||
# or make local-v3
|
||||
|
||||
ls bin/
|
||||
|
||||
# run
|
||||
sudo bin/clash-local
|
||||
```
|
||||
|
||||
### MITM configuration
|
||||
@ -76,7 +92,7 @@ mitm:
|
||||
```
|
||||
|
||||
### DNS configuration
|
||||
Support resolve ip with a proxy tunnel or interface.
|
||||
Support resolve ip with a proxy tunnel.
|
||||
|
||||
Support `geosite` with `fallback-filter`.
|
||||
|
||||
@ -96,8 +112,8 @@ Use `curl -X POST controllerip:port/cache/fakeip/flush` to flush persistence fak
|
||||
- https://doh.pub/dns-query
|
||||
- tls://223.5.5.5:853
|
||||
fallback:
|
||||
- 'tls://8.8.4.4:853#proxy or interface'
|
||||
- 'https://1.0.0.1/dns-query#Proxy' # append the proxy adapter name to the end of DNS URL with '#' prefix.
|
||||
- 'tls://8.8.4.4:853#Proxy'
|
||||
fallback-filter:
|
||||
geoip: false
|
||||
geosite:
|
||||
@ -109,59 +125,19 @@ Use `curl -X POST controllerip:port/cache/fakeip/flush` to flush persistence fak
|
||||
```
|
||||
|
||||
### TUN configuration
|
||||
Simply add the following to the main configuration:
|
||||
|
||||
#### NOTE:
|
||||
> auto-route and auto-detect-interface only available on macOS, Windows and Linux, receive IPv4 traffic
|
||||
Supports macOS, Linux and Windows.
|
||||
|
||||
On Windows, you should download the [Wintun](https://www.wintun.net) driver and copy `wintun.dll` into the system32 directory.
|
||||
```yaml
|
||||
# Enable the TUN listener
|
||||
tun:
|
||||
enable: true
|
||||
stack: system # or gvisor
|
||||
stack: gvisor # System or gVisor
|
||||
# device: tun://utun8 # or fd://xxx, it's optional
|
||||
# dns-hijack:
|
||||
# - 8.8.8.8:53
|
||||
# - tcp://8.8.8.8:53
|
||||
# - any:53
|
||||
# - tcp://any:53
|
||||
auto-route: true # auto set global route
|
||||
auto-detect-interface: true # conflict with interface-name
|
||||
```
|
||||
or
|
||||
```yaml
|
||||
interface-name: en0
|
||||
|
||||
tun:
|
||||
enable: true
|
||||
stack: system # or gvisor
|
||||
# dns-hijack:
|
||||
# - 8.8.8.8:53
|
||||
# - tcp://8.8.8.8:53
|
||||
dns-hijack:
|
||||
- 0.0.0.0:53 # hijack all public
|
||||
auto-route: true # auto set global route
|
||||
```
|
||||
It's recommended to use fake-ip mode for the DNS server.
|
||||
|
||||
Clash needs elevated permission to create TUN device:
|
||||
```sh
|
||||
$ sudo ./clash
|
||||
```
|
||||
Then manually create the default route and DNS server. If your device already has some TUN device, Clash TUN might not work. In this case, fake-ip-filter may helpful.
|
||||
|
||||
Enjoy! :)
|
||||
|
||||
#### For Windows:
|
||||
```yaml
|
||||
tun:
|
||||
enable: true
|
||||
stack: gvisor # or system
|
||||
dns-hijack:
|
||||
- 198.18.0.2:53 # when `fake-ip-range` is 198.18.0.1/16, should hijack 198.18.0.2:53
|
||||
auto-route: true # auto set global route for Windows
|
||||
# It is recommended to use `interface-name`
|
||||
auto-detect-interface: true # auto detect interface, conflict with `interface-name`
|
||||
```
|
||||
Finally, open the Clash
|
||||
|
||||
### Rules configuration
|
||||
- Support rule `GEOSITE`.
|
||||
- Support rule `USER-AGENT`.
|
||||
@ -174,7 +150,18 @@ The `GEOIP` databases via [https://github.com/Loyalsoldier/geoip](https://raw.gi
|
||||
|
||||
The `GEOSITE` databases via [https://github.com/Loyalsoldier/v2ray-rules-dat](https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat).
|
||||
```yaml
|
||||
mode: rule
|
||||
|
||||
script:
|
||||
shortcuts:
|
||||
quic: 'network == "udp" and dst_port == 443'
|
||||
privacy: '"analytics" in host or "adservice" in host or "firebase" in host or "safebrowsing" in host or "doubleclick" in host'
|
||||
|
||||
rules:
|
||||
# rule SCRIPT
|
||||
- SCRIPT,quic,REJECT # Disable QUIC, same as rule "DST-PORT,443,REJECT,udp"
|
||||
- SCRIPT,privacy,REJECT
|
||||
|
||||
# network condition for all rules
|
||||
- DOMAIN-SUFFIX,example.com,DIRECT,tcp
|
||||
- DOMAIN-SUFFIX,example.com,REJECT,udp
|
||||
@ -209,16 +196,87 @@ rules:
|
||||
- MATCH,PROXY
|
||||
```
|
||||
|
||||
### Script configuration
|
||||
Script enables users to programmatically select a policy for the packets with more flexibility.
|
||||
|
||||
```yaml
|
||||
mode: script
|
||||
|
||||
rules:
|
||||
# the rule GEOSITE just as a rule provider in mode script
|
||||
- GEOSITE,category-ads-all,Whatever
|
||||
- GEOSITE,youtube,Whatever
|
||||
- GEOSITE,geolocation-cn,Whatever
|
||||
|
||||
script:
|
||||
code: |
|
||||
def main(ctx, metadata):
|
||||
if metadata["process_name"] == 'apsd':
|
||||
return "DIRECT"
|
||||
|
||||
if metadata["network"] == 'udp' and metadata["dst_port"] == 443:
|
||||
return "REJECT"
|
||||
|
||||
host = metadata["host"]
|
||||
for kw in ['analytics', 'adservice', 'firebase', 'bugly', 'safebrowsing', 'doubleclick']:
|
||||
if kw in host:
|
||||
return "REJECT"
|
||||
|
||||
now = time.now()
|
||||
if (now.hour < 8 or now.hour > 17) and metadata["src_ip"] == '192.168.1.99':
|
||||
return "REJECT"
|
||||
|
||||
if ctx.rule_providers["geosite:category-ads-all"].match(metadata):
|
||||
return "REJECT"
|
||||
|
||||
if ctx.rule_providers["geosite:youtube"].match(metadata):
|
||||
ctx.log('[Script] domain %s matched youtube' % host)
|
||||
return "Proxy"
|
||||
|
||||
if ctx.rule_providers["geosite:geolocation-cn"].match(metadata):
|
||||
ctx.log('[Script] domain %s matched geolocation-cn' % host)
|
||||
return "DIRECT"
|
||||
|
||||
ip = metadata["dst_ip"]
|
||||
if host != "":
|
||||
ip = ctx.resolve_ip(host)
|
||||
if ip == "":
|
||||
return "Proxy"
|
||||
|
||||
code = ctx.geoip(ip)
|
||||
if code == "LAN" or code == "CN":
|
||||
return "DIRECT"
|
||||
|
||||
return "Proxy" # default policy for requests which are not matched by any other script
|
||||
```
|
||||
the context and metadata
|
||||
```ts
|
||||
interface Metadata {
|
||||
type: string // socks5、http
|
||||
network: string // tcp
|
||||
host: string
|
||||
process_name: string
|
||||
process_path: string
|
||||
src_ip: string
|
||||
src_port: int
|
||||
dst_ip: string
|
||||
dst_port: int
|
||||
}
|
||||
|
||||
interface Context {
|
||||
resolve_ip: (host: string) => string // ip string
|
||||
geoip: (ip: string) => string // country code
|
||||
log: (log: string) => void
|
||||
rule_providers: Record<string, { match: (metadata: Metadata) => boolean }>
|
||||
}
|
||||
```
|
||||
|
||||
### Proxies configuration
|
||||
Support outbound protocol `VLESS`.
|
||||
|
||||
Support `Trojan` with XTLS.
|
||||
|
||||
Support relay `UDP` traffic.
|
||||
|
||||
Support filtering proxy providers in proxy groups.
|
||||
|
||||
Support custom http request header, prefix name and V2Ray subscription URL in proxy providers.
|
||||
Currently XTLS only supports TCP transport.
|
||||
```yaml
|
||||
proxies:
|
||||
# VLESS
|
||||
@ -255,56 +313,6 @@ proxies:
|
||||
# udp: true
|
||||
# sni: example.com # aka server name
|
||||
# skip-cert-verify: true
|
||||
|
||||
proxy-groups:
|
||||
# Relay chains the proxies. proxies shall not contain a relay.
|
||||
# Support relay UDP traffic.
|
||||
# Traffic: clash <-> ss1 <-> trojan <-> vmess <-> ss2 <-> Internet
|
||||
- name: "relay-udp-over-tcp"
|
||||
type: relay
|
||||
proxies:
|
||||
- ss1
|
||||
- trojan
|
||||
- vmess
|
||||
- ss2
|
||||
|
||||
- name: "relay-raw-udp"
|
||||
type: relay
|
||||
proxies:
|
||||
- ss1
|
||||
- ss2
|
||||
- ss3
|
||||
|
||||
- name: "filtering-proxy-providers"
|
||||
type: url-test
|
||||
url: "http://www.gstatic.com/generate_204"
|
||||
interval: 300
|
||||
tolerance: 200
|
||||
# lazy: true
|
||||
filter: "XXX" # a regular expression
|
||||
use:
|
||||
- provider1
|
||||
|
||||
proxy-providers:
|
||||
provider1:
|
||||
type: http
|
||||
url: "url" # support V2Ray subscription URL
|
||||
interval: 3600
|
||||
path: ./providers/provider1.yaml
|
||||
# filter: "xxx"
|
||||
# prefix-name: "XXX-"
|
||||
header: # custom http request header
|
||||
User-Agent:
|
||||
- "Clash/v1.10.6"
|
||||
# Accept:
|
||||
# - 'application/vnd.github.v3.raw'
|
||||
# Authorization:
|
||||
# - ' token xxxxxxxxxxx'
|
||||
health-check:
|
||||
enable: false
|
||||
interval: 1200
|
||||
# lazy: false # default value is true
|
||||
url: http://www.gstatic.com/generate_204
|
||||
```
|
||||
|
||||
### IPTABLES configuration
|
||||
@ -321,7 +329,7 @@ iptables:
|
||||
Run Clash as a daemon.
|
||||
|
||||
Create the systemd configuration file at /etc/systemd/system/clash.service:
|
||||
```sh
|
||||
```shell
|
||||
[Unit]
|
||||
Description=Clash daemon, A rule-based proxy in Go.
|
||||
After=network.target
|
||||
@ -336,23 +344,22 @@ ExecStart=/usr/local/bin/clash -d /etc/clash
|
||||
WantedBy=multi-user.target
|
||||
```
|
||||
Launch clashd on system startup with:
|
||||
```sh
|
||||
```shell
|
||||
$ systemctl enable clash
|
||||
```
|
||||
Launch clashd immediately with:
|
||||
```sh
|
||||
```shell
|
||||
$ systemctl start clash
|
||||
```
|
||||
|
||||
### Display Process name
|
||||
To display process name online by click [http://yacd.clash-plus.cf](http://yacd.clash-plus.cf) for local API by Safari or [https://yacd.clash-plus.cf](https://yacd.clash-plus.cf) for local API by Chrome.
|
||||
To display process name online by click [https://yaling888.github.io/yacd/](https://yaling888.github.io/yacd/).
|
||||
|
||||
You can download the [Dashboard](https://github.com/yaling888/yacd/archive/gh-pages.zip) into Clash home directory:
|
||||
```sh
|
||||
$ cd ~/.config/clash
|
||||
$ curl -LJ https://github.com/yaling888/yacd/archive/gh-pages.zip -o yacd-gh-pages.zip
|
||||
$ unzip yacd-gh-pages.zip
|
||||
$ mv yacd-gh-pages dashboard
|
||||
```shell
|
||||
cd ~/.config/clash
|
||||
curl -LJ https://github.com/yaling888/yacd/archive/gh-pages.zip -o dashboard.zip
|
||||
unzip dashboard.zip
|
||||
```
|
||||
|
||||
Add to config file:
|
||||
@ -363,7 +370,7 @@ external-ui: dashboard
|
||||
Open [http://127.0.0.1:9090/ui/](http://127.0.0.1:9090/ui/) by web browser.
|
||||
|
||||
## Plus Pro Release
|
||||
[Release](https://github.com/yaling888/clash/releases/tag/plus_pro)
|
||||
[Release](https://github.com/yaling888/clash/releases/tag/plus)
|
||||
|
||||
## Development
|
||||
If you want to build an application that uses clash as a library, check out the the [GitHub Wiki](https://github.com/Dreamacro/clash/wiki/use-clash-as-a-library)
|
||||
|
@ -3,12 +3,14 @@ package adapter
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/netip"
|
||||
"net/url"
|
||||
"time"
|
||||
_ "unsafe"
|
||||
|
||||
"github.com/Dreamacro/clash/common/queue"
|
||||
"github.com/Dreamacro/clash/component/dialer"
|
||||
@ -17,6 +19,9 @@ import (
|
||||
"go.uber.org/atomic"
|
||||
)
|
||||
|
||||
//go:linkname errCanceled net.errCanceled
|
||||
var errCanceled error
|
||||
|
||||
type Proxy struct {
|
||||
C.ProxyAdapter
|
||||
history *queue.Queue[C.DelayHistory]
|
||||
@ -38,7 +43,7 @@ func (p *Proxy) Dial(metadata *C.Metadata) (C.Conn, error) {
|
||||
// DialContext implements C.ProxyAdapter
|
||||
func (p *Proxy) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.Conn, error) {
|
||||
conn, err := p.ProxyAdapter.DialContext(ctx, metadata, opts...)
|
||||
p.alive.Store(err == nil)
|
||||
p.alive.Store(err == nil || errors.Is(err, errCanceled))
|
||||
return conn, err
|
||||
}
|
||||
|
||||
|
@ -4,7 +4,6 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io"
|
||||
"net"
|
||||
|
||||
"github.com/Dreamacro/clash/component/dialer"
|
||||
@ -31,17 +30,12 @@ func (b *Base) Type() C.AdapterType {
|
||||
}
|
||||
|
||||
// StreamConn implements C.ProxyAdapter
|
||||
func (b *Base) StreamConn(c net.Conn, _ *C.Metadata) (net.Conn, error) {
|
||||
return c, errors.New("no support")
|
||||
}
|
||||
|
||||
// StreamPacketConn implements C.ProxyAdapter
|
||||
func (b *Base) StreamPacketConn(c net.Conn, _ *C.Metadata) (net.Conn, error) {
|
||||
func (b *Base) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
||||
return c, errors.New("no support")
|
||||
}
|
||||
|
||||
// ListenPacketContext implements C.ProxyAdapter
|
||||
func (b *Base) ListenPacketContext(_ context.Context, _ *C.Metadata, _ ...dialer.Option) (C.PacketConn, error) {
|
||||
func (b *Base) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) {
|
||||
return nil, errors.New("no support")
|
||||
}
|
||||
|
||||
@ -63,7 +57,7 @@ func (b *Base) Addr() string {
|
||||
}
|
||||
|
||||
// Unwrap implements C.ProxyAdapter
|
||||
func (b *Base) Unwrap(_ *C.Metadata) C.Proxy {
|
||||
func (b *Base) Unwrap(metadata *C.Metadata) C.Proxy {
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -139,40 +133,6 @@ func (c *packetConn) AppendToChains(a C.ProxyAdapter) {
|
||||
c.chain = append(c.chain, a.Name())
|
||||
}
|
||||
|
||||
func NewPacketConn(pc net.PacketConn, a C.ProxyAdapter) C.PacketConn {
|
||||
func newPacketConn(pc net.PacketConn, a C.ProxyAdapter) C.PacketConn {
|
||||
return &packetConn{pc, []string{a.Name()}}
|
||||
}
|
||||
|
||||
type wrapConn struct {
|
||||
net.PacketConn
|
||||
}
|
||||
|
||||
func (*wrapConn) Read([]byte) (int, error) {
|
||||
return 0, io.EOF
|
||||
}
|
||||
|
||||
func (*wrapConn) Write([]byte) (int, error) {
|
||||
return 0, io.EOF
|
||||
}
|
||||
|
||||
func (*wrapConn) RemoteAddr() net.Addr {
|
||||
return nil
|
||||
}
|
||||
|
||||
func WrapConn(packetConn net.PacketConn) net.Conn {
|
||||
return &wrapConn{
|
||||
PacketConn: packetConn,
|
||||
}
|
||||
}
|
||||
|
||||
func IsPacketConn(c net.Conn) bool {
|
||||
if _, ok := c.(net.PacketConn); !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
if ua, ok := c.LocalAddr().(*net.UnixAddr); ok {
|
||||
return ua.Net == "unixgram"
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
@ -3,7 +3,6 @@ package outbound
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"net/netip"
|
||||
|
||||
"github.com/Dreamacro/clash/component/dialer"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
@ -20,26 +19,18 @@ func (d *Direct) DialContext(ctx context.Context, metadata *C.Metadata, opts ...
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tcpKeepAlive(c)
|
||||
|
||||
if !metadata.Resolved() && c.RemoteAddr() != nil {
|
||||
if h, _, err := net.SplitHostPort(c.RemoteAddr().String()); err == nil {
|
||||
metadata.DstIP = netip.MustParseAddr(h)
|
||||
}
|
||||
}
|
||||
|
||||
return NewConn(c, d), nil
|
||||
}
|
||||
|
||||
// ListenPacketContext implements C.ProxyAdapter
|
||||
func (d *Direct) ListenPacketContext(ctx context.Context, _ *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) {
|
||||
func (d *Direct) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) {
|
||||
opts = append(opts, dialer.WithDirect())
|
||||
pc, err := dialer.ListenPacket(ctx, "udp", "", d.Base.DialOptions(opts...)...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return NewPacketConn(&directPacketConn{pc}, d), nil
|
||||
return newPacketConn(&directPacketConn{pc}, d), nil
|
||||
}
|
||||
|
||||
type directPacketConn struct {
|
||||
|
@ -24,7 +24,6 @@ func (m *Mitm) DialContext(_ context.Context, metadata *C.Metadata, _ ...dialer.
|
||||
|
||||
_ = c.SetKeepAlive(true)
|
||||
_ = c.SetKeepAlivePeriod(60 * time.Second)
|
||||
_ = c.SetLinger(0)
|
||||
|
||||
metadata.Type = C.MITM
|
||||
|
||||
|
@ -48,7 +48,7 @@ func (r *Reject) DialContext(ctx context.Context, metadata *C.Metadata, opts ...
|
||||
|
||||
// ListenPacketContext implements C.ProxyAdapter
|
||||
func (r *Reject) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) {
|
||||
return NewPacketConn(&nopPacketConn{}, r), nil
|
||||
return newPacketConn(&nopPacketConn{}, r), nil
|
||||
}
|
||||
|
||||
func NewReject() *Reject {
|
||||
|
@ -10,10 +10,11 @@ import (
|
||||
"github.com/Dreamacro/clash/common/structure"
|
||||
"github.com/Dreamacro/clash/component/dialer"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/transport/shadowsocks/core"
|
||||
obfs "github.com/Dreamacro/clash/transport/simple-obfs"
|
||||
"github.com/Dreamacro/clash/transport/socks5"
|
||||
v2rayObfs "github.com/Dreamacro/clash/transport/v2ray-plugin"
|
||||
|
||||
"github.com/Dreamacro/go-shadowsocks2/core"
|
||||
)
|
||||
|
||||
type ShadowSocks struct {
|
||||
@ -73,21 +74,6 @@ func (ss *ShadowSocks) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, e
|
||||
return c, err
|
||||
}
|
||||
|
||||
// StreamPacketConn implements C.ProxyAdapter
|
||||
func (ss *ShadowSocks) StreamPacketConn(c net.Conn, _ *C.Metadata) (net.Conn, error) {
|
||||
if !IsPacketConn(c) {
|
||||
return c, fmt.Errorf("%s connect error: can not convert net.Conn to net.PacketConn", ss.addr)
|
||||
}
|
||||
|
||||
addr, err := resolveUDPAddr("udp", ss.addr)
|
||||
if err != nil {
|
||||
return c, err
|
||||
}
|
||||
|
||||
pc := ss.cipher.PacketConn(c.(net.PacketConn))
|
||||
return WrapConn(&ssPacketConn{PacketConn: pc, rAddr: addr}), nil
|
||||
}
|
||||
|
||||
// DialContext implements C.ProxyAdapter
|
||||
func (ss *ShadowSocks) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) {
|
||||
c, err := dialer.DialContext(ctx, "tcp", ss.addr, ss.Base.DialOptions(opts...)...)
|
||||
@ -109,13 +95,14 @@ func (ss *ShadowSocks) ListenPacketContext(ctx context.Context, metadata *C.Meta
|
||||
return nil, err
|
||||
}
|
||||
|
||||
c, err := ss.StreamPacketConn(WrapConn(pc), metadata)
|
||||
addr, err := resolveUDPAddr("udp", ss.addr)
|
||||
if err != nil {
|
||||
_ = pc.Close()
|
||||
pc.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return NewPacketConn(c.(net.PacketConn), ss), nil
|
||||
pc = ss.cipher.PacketConn(pc)
|
||||
return newPacketConn(&ssPacketConn{PacketConn: pc, rAddr: addr}, ss), nil
|
||||
}
|
||||
|
||||
func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) {
|
||||
|
@ -8,11 +8,12 @@ import (
|
||||
|
||||
"github.com/Dreamacro/clash/component/dialer"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/transport/shadowsocks/core"
|
||||
"github.com/Dreamacro/clash/transport/shadowsocks/shadowaead"
|
||||
"github.com/Dreamacro/clash/transport/shadowsocks/shadowstream"
|
||||
"github.com/Dreamacro/clash/transport/ssr/obfs"
|
||||
"github.com/Dreamacro/clash/transport/ssr/protocol"
|
||||
|
||||
"github.com/Dreamacro/go-shadowsocks2/core"
|
||||
"github.com/Dreamacro/go-shadowsocks2/shadowaead"
|
||||
"github.com/Dreamacro/go-shadowsocks2/shadowstream"
|
||||
)
|
||||
|
||||
type ShadowSocksR struct {
|
||||
@ -58,22 +59,6 @@ func (ssr *ShadowSocksR) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn,
|
||||
return c, err
|
||||
}
|
||||
|
||||
// StreamPacketConn implements C.ProxyAdapter
|
||||
func (ssr *ShadowSocksR) StreamPacketConn(c net.Conn, _ *C.Metadata) (net.Conn, error) {
|
||||
if !IsPacketConn(c) {
|
||||
return c, fmt.Errorf("%s connect error: can not convert net.Conn to net.PacketConn", ssr.addr)
|
||||
}
|
||||
|
||||
addr, err := resolveUDPAddr("udp", ssr.addr)
|
||||
if err != nil {
|
||||
return c, err
|
||||
}
|
||||
|
||||
pc := ssr.cipher.PacketConn(c.(net.PacketConn))
|
||||
pc = ssr.protocol.PacketConn(pc)
|
||||
return WrapConn(&ssPacketConn{PacketConn: pc, rAddr: addr}), nil
|
||||
}
|
||||
|
||||
// DialContext implements C.ProxyAdapter
|
||||
func (ssr *ShadowSocksR) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) {
|
||||
c, err := dialer.DialContext(ctx, "tcp", ssr.addr, ssr.Base.DialOptions(opts...)...)
|
||||
@ -95,13 +80,15 @@ func (ssr *ShadowSocksR) ListenPacketContext(ctx context.Context, metadata *C.Me
|
||||
return nil, err
|
||||
}
|
||||
|
||||
c, err := ssr.StreamPacketConn(WrapConn(pc), metadata)
|
||||
addr, err := resolveUDPAddr("udp", ssr.addr)
|
||||
if err != nil {
|
||||
_ = pc.Close()
|
||||
pc.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return NewPacketConn(c.(net.PacketConn), ssr), nil
|
||||
pc = ssr.cipher.PacketConn(pc)
|
||||
pc = ssr.protocol.PacketConn(pc)
|
||||
return newPacketConn(&ssPacketConn{PacketConn: pc, rAddr: addr}, ssr), nil
|
||||
}
|
||||
|
||||
func NewShadowSocksR(option ShadowSocksROption) (*ShadowSocksR, error) {
|
||||
|
@ -58,18 +58,6 @@ func (s *Snell) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
||||
return c, err
|
||||
}
|
||||
|
||||
// StreamPacketConn implements C.ProxyAdapter
|
||||
func (s *Snell) StreamPacketConn(c net.Conn, _ *C.Metadata) (net.Conn, error) {
|
||||
c = streamConn(c, streamOption{s.psk, s.version, s.addr, s.obfsOption})
|
||||
|
||||
err := snell.WriteUDPHeader(c, s.version)
|
||||
if err != nil {
|
||||
return c, err
|
||||
}
|
||||
|
||||
return WrapConn(snell.PacketConn(c)), nil
|
||||
}
|
||||
|
||||
// DialContext implements C.ProxyAdapter
|
||||
func (s *Snell) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) {
|
||||
if s.version == snell.Version2 && len(opts) == 0 {
|
||||
@ -80,7 +68,7 @@ func (s *Snell) DialContext(ctx context.Context, metadata *C.Metadata, opts ...d
|
||||
|
||||
port, _ := strconv.ParseUint(metadata.DstPort, 10, 16)
|
||||
if err = snell.WriteHeader(c, metadata.String(), uint(port), s.version); err != nil {
|
||||
_ = c.Close()
|
||||
c.Close()
|
||||
return nil, err
|
||||
}
|
||||
return NewConn(c, s), err
|
||||
@ -105,14 +93,15 @@ func (s *Snell) ListenPacketContext(ctx context.Context, metadata *C.Metadata, o
|
||||
return nil, err
|
||||
}
|
||||
tcpKeepAlive(c)
|
||||
c = streamConn(c, streamOption{s.psk, s.version, s.addr, s.obfsOption})
|
||||
|
||||
pc, err := s.StreamPacketConn(c, metadata)
|
||||
err = snell.WriteUDPHeader(c, s.version)
|
||||
if err != nil {
|
||||
_ = c.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return NewPacketConn(pc.(net.PacketConn), s), nil
|
||||
pc := snell.PacketConn(c)
|
||||
return newPacketConn(pc, s), nil
|
||||
}
|
||||
|
||||
func NewSnell(option SnellOption) (*Snell, error) {
|
||||
|
@ -37,59 +37,12 @@ type Socks5Option struct {
|
||||
|
||||
// StreamConn implements C.ProxyAdapter
|
||||
func (ss *Socks5) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
||||
var err error
|
||||
c, _, err = ss.streamConn(c, metadata)
|
||||
|
||||
return c, err
|
||||
}
|
||||
|
||||
func (ss *Socks5) StreamSocks5PacketConn(c net.Conn, pc net.PacketConn, metadata *C.Metadata) (net.PacketConn, error) {
|
||||
if c == nil {
|
||||
return pc, fmt.Errorf("%s connect error: parameter net.Conn is nil", ss.addr)
|
||||
}
|
||||
|
||||
if pc == nil {
|
||||
return pc, fmt.Errorf("%s connect error: parameter net.PacketConn is nil", ss.addr)
|
||||
}
|
||||
|
||||
cc, bindAddr, err := ss.streamConn(c, metadata)
|
||||
if err != nil {
|
||||
return pc, err
|
||||
}
|
||||
|
||||
c = cc
|
||||
|
||||
go func() {
|
||||
_, _ = io.Copy(io.Discard, c)
|
||||
_ = c.Close()
|
||||
// A UDP association terminates when the TCP connection that the UDP
|
||||
// ASSOCIATE request arrived on terminates. RFC1928
|
||||
_ = pc.Close()
|
||||
}()
|
||||
|
||||
// Support unspecified UDP bind address.
|
||||
bindUDPAddr := bindAddr.UDPAddr()
|
||||
if bindUDPAddr == nil {
|
||||
return pc, errors.New("invalid UDP bind address")
|
||||
} else if bindUDPAddr.IP.IsUnspecified() {
|
||||
serverAddr, err := resolveUDPAddr("udp", ss.Addr())
|
||||
if err != nil {
|
||||
return pc, err
|
||||
}
|
||||
|
||||
bindUDPAddr.IP = serverAddr.IP
|
||||
}
|
||||
|
||||
return &socksPacketConn{PacketConn: pc, rAddr: bindUDPAddr, tcpConn: c}, nil
|
||||
}
|
||||
|
||||
func (ss *Socks5) streamConn(c net.Conn, metadata *C.Metadata) (_ net.Conn, bindAddr socks5.Addr, err error) {
|
||||
if ss.tls {
|
||||
cc := tls.Client(c, ss.tlsConfig)
|
||||
err := cc.Handshake()
|
||||
c = cc
|
||||
if err != nil {
|
||||
return c, nil, fmt.Errorf("%s connect error: %w", ss.addr, err)
|
||||
return nil, fmt.Errorf("%s connect error: %w", ss.addr, err)
|
||||
}
|
||||
}
|
||||
|
||||
@ -100,14 +53,10 @@ func (ss *Socks5) streamConn(c net.Conn, metadata *C.Metadata) (_ net.Conn, bind
|
||||
Password: ss.pass,
|
||||
}
|
||||
}
|
||||
|
||||
if metadata.NetWork == C.UDP {
|
||||
bindAddr, err = socks5.ClientHandshake(c, serializesSocksAddr(metadata), socks5.CmdUDPAssociate, user)
|
||||
} else {
|
||||
bindAddr, err = socks5.ClientHandshake(c, serializesSocksAddr(metadata), socks5.CmdConnect, user)
|
||||
if _, err := socks5.ClientHandshake(c, serializesSocksAddr(metadata), socks5.CmdConnect, user); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return c, bindAddr, err
|
||||
return c, nil
|
||||
}
|
||||
|
||||
// DialContext implements C.ProxyAdapter
|
||||
@ -132,24 +81,61 @@ func (ss *Socks5) DialContext(ctx context.Context, metadata *C.Metadata, opts ..
|
||||
func (ss *Socks5) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.PacketConn, err error) {
|
||||
c, err := dialer.DialContext(ctx, "tcp", ss.addr, ss.Base.DialOptions(opts...)...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s connect error: %w", ss.addr, err)
|
||||
err = fmt.Errorf("%s connect error: %w", ss.addr, err)
|
||||
return
|
||||
}
|
||||
|
||||
if ss.tls {
|
||||
cc := tls.Client(c, ss.tlsConfig)
|
||||
err = cc.Handshake()
|
||||
c = cc
|
||||
}
|
||||
|
||||
defer safeConnClose(c, err)
|
||||
|
||||
tcpKeepAlive(c)
|
||||
var user *socks5.User
|
||||
if ss.user != "" {
|
||||
user = &socks5.User{
|
||||
Username: ss.user,
|
||||
Password: ss.pass,
|
||||
}
|
||||
}
|
||||
|
||||
bindAddr, err := socks5.ClientHandshake(c, serializesSocksAddr(metadata), socks5.CmdUDPAssociate, user)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("client hanshake error: %w", err)
|
||||
return
|
||||
}
|
||||
|
||||
pc, err := dialer.ListenPacket(ctx, "udp", "", ss.Base.DialOptions(opts...)...)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
tcpKeepAlive(c)
|
||||
go func() {
|
||||
io.Copy(io.Discard, c)
|
||||
c.Close()
|
||||
// A UDP association terminates when the TCP connection that the UDP
|
||||
// ASSOCIATE request arrived on terminates. RFC1928
|
||||
pc.Close()
|
||||
}()
|
||||
|
||||
pc, err = ss.StreamSocks5PacketConn(c, pc, metadata)
|
||||
if err != nil {
|
||||
// Support unspecified UDP bind address.
|
||||
bindUDPAddr := bindAddr.UDPAddr()
|
||||
if bindUDPAddr == nil {
|
||||
err = errors.New("invalid UDP bind address")
|
||||
return
|
||||
} else if bindUDPAddr.IP.IsUnspecified() {
|
||||
serverAddr, err := resolveUDPAddr("udp", ss.Addr())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
bindUDPAddr.IP = serverAddr.IP
|
||||
}
|
||||
|
||||
return NewPacketConn(pc, ss), nil
|
||||
return newPacketConn(&socksPacketConn{PacketConn: pc, rAddr: bindUDPAddr, tcpConn: c}, ss), nil
|
||||
}
|
||||
|
||||
func NewSocks5(option Socks5Option) *Socks5 {
|
||||
@ -213,6 +199,6 @@ func (uc *socksPacketConn) ReadFrom(b []byte) (int, net.Addr, error) {
|
||||
}
|
||||
|
||||
func (uc *socksPacketConn) Close() error {
|
||||
_ = uc.tcpConn.Close()
|
||||
uc.tcpConn.Close()
|
||||
return uc.PacketConn.Close()
|
||||
}
|
||||
|
@ -72,7 +72,8 @@ func (t *Trojan) plainStream(c net.Conn) (net.Conn, error) {
|
||||
return t.instance.StreamConn(c)
|
||||
}
|
||||
|
||||
func (t *Trojan) trojanStream(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
||||
// StreamConn implements C.ProxyAdapter
|
||||
func (t *Trojan) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
||||
var err error
|
||||
if t.transport != nil {
|
||||
c, err = gun.StreamGunWithConn(c, t.gunTLSConfig, t.gunConfig)
|
||||
@ -84,63 +85,39 @@ func (t *Trojan) trojanStream(c net.Conn, metadata *C.Metadata) (net.Conn, error
|
||||
return nil, fmt.Errorf("%s connect error: %w", t.addr, err)
|
||||
}
|
||||
|
||||
c, err = t.instance.PrepareXTLSConn(c)
|
||||
c, err = t.instance.PresetXTLSConn(c)
|
||||
if err != nil {
|
||||
return c, err
|
||||
}
|
||||
|
||||
if metadata.NetWork == C.UDP {
|
||||
err = t.instance.WriteHeader(c, trojan.CommandUDP, serializesSocksAddr(metadata))
|
||||
return c, err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = t.instance.WriteHeader(c, trojan.CommandTCP, serializesSocksAddr(metadata))
|
||||
return c, err
|
||||
}
|
||||
|
||||
// StreamConn implements C.ProxyAdapter
|
||||
func (t *Trojan) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
||||
return t.trojanStream(c, metadata)
|
||||
}
|
||||
|
||||
// StreamPacketConn implements C.ProxyAdapter
|
||||
func (t *Trojan) StreamPacketConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
||||
var err error
|
||||
c, err = t.trojanStream(c, metadata)
|
||||
if err != nil {
|
||||
return c, err
|
||||
}
|
||||
|
||||
pc := t.instance.PacketConn(c)
|
||||
return WrapConn(pc), nil
|
||||
}
|
||||
|
||||
// DialContext implements C.ProxyAdapter
|
||||
func (t *Trojan) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) {
|
||||
var c net.Conn
|
||||
|
||||
// gun transport
|
||||
if t.transport != nil && len(opts) == 0 {
|
||||
c, err = gun.StreamGunWithTransport(t.transport, t.gunConfig)
|
||||
c, err := gun.StreamGunWithTransport(t.transport, t.gunConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer safeConnClose(c, err)
|
||||
|
||||
c, err = t.instance.PrepareXTLSConn(c)
|
||||
c, err = t.instance.PresetXTLSConn(c)
|
||||
if err != nil {
|
||||
c.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = t.instance.WriteHeader(c, trojan.CommandTCP, serializesSocksAddr(metadata)); err != nil {
|
||||
c.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return NewConn(c, t), nil
|
||||
}
|
||||
|
||||
c, err = dialer.DialContext(ctx, "tcp", t.addr, t.Base.DialOptions(opts...)...)
|
||||
c, err := dialer.DialContext(ctx, "tcp", t.addr, t.Base.DialOptions(opts...)...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s connect error: %w", t.addr, err)
|
||||
}
|
||||
@ -160,44 +137,33 @@ func (t *Trojan) DialContext(ctx context.Context, metadata *C.Metadata, opts ...
|
||||
func (t *Trojan) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.PacketConn, err error) {
|
||||
var c net.Conn
|
||||
|
||||
// gun transport
|
||||
// grpc transport
|
||||
if t.transport != nil && len(opts) == 0 {
|
||||
c, err = gun.StreamGunWithTransport(t.transport, t.gunConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, fmt.Errorf("%s connect error: %w", t.addr, err)
|
||||
}
|
||||
|
||||
defer safeConnClose(c, err)
|
||||
|
||||
c, err = t.instance.PrepareXTLSConn(c)
|
||||
} else {
|
||||
c, err = dialer.DialContext(ctx, "tcp", t.addr, t.Base.DialOptions(opts...)...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, fmt.Errorf("%s connect error: %w", t.addr, err)
|
||||
}
|
||||
|
||||
if err = t.instance.WriteHeader(c, trojan.CommandUDP, serializesSocksAddr(metadata)); err != nil {
|
||||
return nil, err
|
||||
defer safeConnClose(c, err)
|
||||
tcpKeepAlive(c)
|
||||
c, err = t.plainStream(c)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s connect error: %w", t.addr, err)
|
||||
}
|
||||
|
||||
pc := t.instance.PacketConn(c)
|
||||
|
||||
return NewPacketConn(pc, t), nil
|
||||
}
|
||||
|
||||
c, err = dialer.DialContext(ctx, "tcp", t.addr, t.Base.DialOptions(opts...)...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s connect error: %w", t.addr, err)
|
||||
}
|
||||
|
||||
tcpKeepAlive(c)
|
||||
|
||||
defer safeConnClose(c, err)
|
||||
|
||||
c, err = t.StreamPacketConn(c, metadata)
|
||||
err = t.instance.WriteHeader(c, trojan.CommandUDP, serializesSocksAddr(metadata))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return NewPacketConn(c.(net.PacketConn), t), nil
|
||||
pc := t.instance.PacketConn(c)
|
||||
return newPacketConn(pc, t), err
|
||||
}
|
||||
|
||||
func NewTrojan(option TrojanOption) (*Trojan, error) {
|
||||
|
@ -15,7 +15,6 @@ func tcpKeepAlive(c net.Conn) {
|
||||
if tcp, ok := c.(*net.TCPConn); ok {
|
||||
_ = tcp.SetKeepAlive(true)
|
||||
_ = tcp.SetKeepAlivePeriod(30 * time.Second)
|
||||
_ = tcp.SetLinger(0)
|
||||
}
|
||||
}
|
||||
|
||||
@ -53,7 +52,7 @@ func resolveUDPAddr(network, address string) (*net.UDPAddr, error) {
|
||||
}
|
||||
|
||||
func safeConnClose(c net.Conn, err error) {
|
||||
if err != nil && c != nil {
|
||||
if err != nil {
|
||||
_ = c.Close()
|
||||
}
|
||||
}
|
||||
|
@ -58,7 +58,6 @@ type VlessOption struct {
|
||||
ServerName string `proxy:"servername,omitempty"`
|
||||
}
|
||||
|
||||
// StreamConn implements C.ProxyAdapter
|
||||
func (v *Vless) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
||||
var err error
|
||||
switch v.option.Network {
|
||||
@ -148,26 +147,6 @@ func (v *Vless) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
||||
return v.client.StreamConn(c, parseVlessAddr(metadata))
|
||||
}
|
||||
|
||||
// StreamPacketConn implements C.ProxyAdapter
|
||||
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
|
||||
if !metadata.Resolved() {
|
||||
ip, err := resolver.ResolveFirstIP(metadata.Host)
|
||||
if err != nil {
|
||||
return nil, errors.New("can't resolve ip")
|
||||
}
|
||||
metadata.DstIP = ip
|
||||
}
|
||||
|
||||
var err error
|
||||
c, err = v.StreamConn(c, metadata)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("new vmess client error: %v", err)
|
||||
}
|
||||
|
||||
return WrapConn(&vlessPacketConn{Conn: c, rAddr: metadata.UDPAddr()}), nil
|
||||
}
|
||||
|
||||
func (v *Vless) streamTLSOrXTLSConn(conn net.Conn, isH2 bool) (net.Conn, error) {
|
||||
host, _, _ := net.SplitHostPort(v.addr)
|
||||
|
||||
@ -240,18 +219,18 @@ func (v *Vless) DialContext(ctx context.Context, metadata *C.Metadata, opts ...d
|
||||
|
||||
// ListenPacketContext implements C.ProxyAdapter
|
||||
func (v *Vless) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.PacketConn, err error) {
|
||||
// vless use stream-oriented udp with a special address, so we needs a net.UDPAddr
|
||||
if !metadata.Resolved() {
|
||||
ip, err := resolver.ResolveIP(metadata.Host)
|
||||
if err != nil {
|
||||
return nil, errors.New("can't resolve ip")
|
||||
}
|
||||
metadata.DstIP = ip
|
||||
}
|
||||
|
||||
var c net.Conn
|
||||
// gun transport
|
||||
if v.transport != nil && len(opts) == 0 {
|
||||
// vless use stream-oriented udp with a special address, so we needs a net.UDPAddr
|
||||
if !metadata.Resolved() {
|
||||
ip, err := resolver.ResolveFirstIP(metadata.Host)
|
||||
if err != nil {
|
||||
return nil, errors.New("can't resolve ip")
|
||||
}
|
||||
metadata.DstIP = ip
|
||||
}
|
||||
|
||||
c, err = gun.StreamGunWithTransport(v.transport, v.gunConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -259,27 +238,22 @@ func (v *Vless) ListenPacketContext(ctx context.Context, metadata *C.Metadata, o
|
||||
defer safeConnClose(c, err)
|
||||
|
||||
c, err = v.client.StreamConn(c, parseVlessAddr(metadata))
|
||||
} else {
|
||||
c, err = dialer.DialContext(ctx, "tcp", v.addr, v.Base.DialOptions(opts...)...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("new vless client error: %v", err)
|
||||
return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error())
|
||||
}
|
||||
tcpKeepAlive(c)
|
||||
defer safeConnClose(c, err)
|
||||
|
||||
return NewPacketConn(&vlessPacketConn{Conn: c, rAddr: metadata.UDPAddr()}, v), nil
|
||||
c, err = v.StreamConn(c, metadata)
|
||||
}
|
||||
|
||||
c, err = dialer.DialContext(ctx, "tcp", v.addr, v.Base.DialOptions(opts...)...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error())
|
||||
}
|
||||
|
||||
tcpKeepAlive(c)
|
||||
defer safeConnClose(c, err)
|
||||
|
||||
c, err = v.StreamPacketConn(c, metadata)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("new vless client error: %v", err)
|
||||
}
|
||||
|
||||
return NewPacketConn(c.(net.PacketConn), v), nil
|
||||
return newPacketConn(&vlessPacketConn{Conn: c, rAddr: metadata.UDPAddr()}, v), nil
|
||||
}
|
||||
|
||||
func parseVlessAddr(metadata *C.Metadata) *vless.DstAddr {
|
||||
@ -318,7 +292,7 @@ type vlessPacketConn struct {
|
||||
mux sync.Mutex
|
||||
}
|
||||
|
||||
func (vc *vlessPacketConn) WriteTo(b []byte, _ net.Addr) (int, error) {
|
||||
func (vc *vlessPacketConn) WriteTo(b []byte, addr net.Addr) (int, error) {
|
||||
total := len(b)
|
||||
if total == 0 {
|
||||
return 0, nil
|
||||
|
@ -3,13 +3,13 @@ package outbound
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/Dreamacro/clash/common/convert"
|
||||
"github.com/Dreamacro/clash/component/dialer"
|
||||
"github.com/Dreamacro/clash/component/resolver"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
@ -91,16 +91,17 @@ func (v *Vmess) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
||||
wsOpts := &vmess.WebsocketConfig{
|
||||
Host: host,
|
||||
Port: port,
|
||||
Headers: http.Header{},
|
||||
Path: v.option.WSOpts.Path,
|
||||
MaxEarlyData: v.option.WSOpts.MaxEarlyData,
|
||||
EarlyDataHeaderName: v.option.WSOpts.EarlyDataHeaderName,
|
||||
}
|
||||
|
||||
if len(v.option.WSOpts.Headers) != 0 {
|
||||
header := http.Header{}
|
||||
for key, value := range v.option.WSOpts.Headers {
|
||||
wsOpts.Headers.Add(key, value)
|
||||
header.Add(key, value)
|
||||
}
|
||||
wsOpts.Headers = header
|
||||
}
|
||||
|
||||
if v.option.TLS {
|
||||
@ -115,11 +116,6 @@ func (v *Vmess) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
||||
} else if host := wsOpts.Headers.Get("Host"); host != "" {
|
||||
wsOpts.TLSConfig.ServerName = host
|
||||
}
|
||||
} else {
|
||||
if wsOpts.Headers.Get("Host") == "" {
|
||||
wsOpts.Headers.Set("Host", convert.RandHost())
|
||||
}
|
||||
convert.SetUserAgent(wsOpts.Headers)
|
||||
}
|
||||
c, err = vmess.StreamWebsocketConn(c, wsOpts)
|
||||
case "http":
|
||||
@ -199,26 +195,6 @@ func (v *Vmess) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
||||
return v.client.StreamConn(c, parseVmessAddr(metadata))
|
||||
}
|
||||
|
||||
// StreamPacketConn implements C.ProxyAdapter
|
||||
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
|
||||
if !metadata.Resolved() {
|
||||
ip, err := resolver.ResolveFirstIP(metadata.Host)
|
||||
if err != nil {
|
||||
return c, fmt.Errorf("can't resolve ip: %w", err)
|
||||
}
|
||||
metadata.DstIP = ip
|
||||
}
|
||||
|
||||
var err error
|
||||
c, err = v.StreamConn(c, metadata)
|
||||
if err != nil {
|
||||
return c, fmt.Errorf("new vmess client error: %v", err)
|
||||
}
|
||||
|
||||
return WrapConn(&vmessPacketConn{Conn: c, rAddr: metadata.UDPAddr()}), nil
|
||||
}
|
||||
|
||||
// DialContext implements C.ProxyAdapter
|
||||
func (v *Vmess) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) {
|
||||
// gun transport
|
||||
@ -250,18 +226,18 @@ func (v *Vmess) DialContext(ctx context.Context, metadata *C.Metadata, opts ...d
|
||||
|
||||
// ListenPacketContext implements C.ProxyAdapter
|
||||
func (v *Vmess) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.PacketConn, err error) {
|
||||
// vmess use stream-oriented udp with a special address, so we needs a net.UDPAddr
|
||||
if !metadata.Resolved() {
|
||||
ip, err := resolver.ResolveIP(metadata.Host)
|
||||
if err != nil {
|
||||
return nil, errors.New("can't resolve ip")
|
||||
}
|
||||
metadata.DstIP = ip
|
||||
}
|
||||
|
||||
var c net.Conn
|
||||
// gun transport
|
||||
if v.transport != nil && len(opts) == 0 {
|
||||
// vmess use stream-oriented udp with a special address, so we needs a net.UDPAddr
|
||||
if !metadata.Resolved() {
|
||||
ip, err := resolver.ResolveFirstIP(metadata.Host)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("can't resolve ip: %w", err)
|
||||
}
|
||||
metadata.DstIP = ip
|
||||
}
|
||||
|
||||
c, err = gun.StreamGunWithTransport(v.transport, v.gunConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -269,27 +245,22 @@ func (v *Vmess) ListenPacketContext(ctx context.Context, metadata *C.Metadata, o
|
||||
defer safeConnClose(c, err)
|
||||
|
||||
c, err = v.client.StreamConn(c, parseVmessAddr(metadata))
|
||||
} else {
|
||||
c, err = dialer.DialContext(ctx, "tcp", v.addr, v.Base.DialOptions(opts...)...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("new vmess client error: %v", err)
|
||||
return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error())
|
||||
}
|
||||
tcpKeepAlive(c)
|
||||
defer safeConnClose(c, err)
|
||||
|
||||
return NewPacketConn(&vmessPacketConn{Conn: c, rAddr: metadata.UDPAddr()}, v), nil
|
||||
c, err = v.StreamConn(c, metadata)
|
||||
}
|
||||
|
||||
c, err = dialer.DialContext(ctx, "tcp", v.addr, v.Base.DialOptions(opts...)...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error())
|
||||
}
|
||||
|
||||
tcpKeepAlive(c)
|
||||
defer safeConnClose(c, err)
|
||||
|
||||
c, err = v.StreamPacketConn(c, metadata)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("new vmess client error: %v", err)
|
||||
}
|
||||
|
||||
return NewPacketConn(c.(net.PacketConn), v), nil
|
||||
return newPacketConn(&vmessPacketConn{Conn: c, rAddr: metadata.UDPAddr()}, v), nil
|
||||
}
|
||||
|
||||
func NewVmess(option VmessOption) (*Vmess, error) {
|
||||
@ -397,7 +368,7 @@ type vmessPacketConn struct {
|
||||
rAddr net.Addr
|
||||
}
|
||||
|
||||
func (uc *vmessPacketConn) WriteTo(b []byte, _ net.Addr) (int, error) {
|
||||
func (uc *vmessPacketConn) WriteTo(b []byte, addr net.Addr) (int, error) {
|
||||
return uc.Conn.Write(b)
|
||||
}
|
||||
|
||||
|
@ -3,8 +3,6 @@ package outboundgroup
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/Dreamacro/clash/adapter"
|
||||
"github.com/Dreamacro/clash/adapter/outbound"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/constant/provider"
|
||||
)
|
||||
@ -13,19 +11,14 @@ const (
|
||||
defaultGetProxiesDuration = time.Second * 5
|
||||
)
|
||||
|
||||
var defaultRejectProxy = adapter.NewProxy(outbound.NewReject())
|
||||
|
||||
func getProvidersProxies(providers []provider.ProxyProvider, touch bool) []C.Proxy {
|
||||
proxies := []C.Proxy{}
|
||||
for _, pd := range providers {
|
||||
for _, provider := range providers {
|
||||
if touch {
|
||||
proxies = append(proxies, pd.ProxiesWithTouch()...)
|
||||
proxies = append(proxies, provider.ProxiesWithTouch()...)
|
||||
} else {
|
||||
proxies = append(proxies, pd.Proxies()...)
|
||||
proxies = append(proxies, provider.Proxies()...)
|
||||
}
|
||||
}
|
||||
if len(proxies) == 0 {
|
||||
proxies = append(proxies, defaultRejectProxy)
|
||||
}
|
||||
return proxies
|
||||
}
|
||||
|
@ -3,7 +3,6 @@ package outboundgroup
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"regexp"
|
||||
|
||||
"github.com/Dreamacro/clash/adapter/outbound"
|
||||
"github.com/Dreamacro/clash/adapter/provider"
|
||||
@ -30,7 +29,6 @@ type GroupCommonOption struct {
|
||||
Interval int `group:"interval,omitempty"`
|
||||
Lazy bool `group:"lazy,omitempty"`
|
||||
DisableUDP bool `group:"disable-udp,omitempty"`
|
||||
Filter string `group:"filter,omitempty"`
|
||||
}
|
||||
|
||||
func ParseProxyGroup(config map[string]any, proxyMap map[string]C.Proxy, providersMap map[string]types.ProxyProvider) (C.ProxyAdapter, error) {
|
||||
@ -39,23 +37,10 @@ func ParseProxyGroup(config map[string]any, proxyMap map[string]C.Proxy, provide
|
||||
groupOption := &GroupCommonOption{
|
||||
Lazy: true,
|
||||
}
|
||||
|
||||
var (
|
||||
filterRegx *regexp.Regexp
|
||||
err error
|
||||
)
|
||||
|
||||
if err = decoder.Decode(config, groupOption); err != nil {
|
||||
if err := decoder.Decode(config, groupOption); err != nil {
|
||||
return nil, errFormat
|
||||
}
|
||||
|
||||
if groupOption.Filter != "" {
|
||||
filterRegx, err = regexp.Compile(groupOption.Filter)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid filter regex: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
if groupOption.Type == "" || groupOption.Name == "" {
|
||||
return nil, errFormat
|
||||
}
|
||||
@ -78,31 +63,38 @@ func ParseProxyGroup(config map[string]any, proxyMap map[string]C.Proxy, provide
|
||||
return nil, errDuplicateProvider
|
||||
}
|
||||
|
||||
hc, err := newHealthCheck(ps, groupOption)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// select don't need health check
|
||||
if groupOption.Type == "select" || groupOption.Type == "relay" {
|
||||
hc := provider.NewHealthCheck(ps, "", 0, true)
|
||||
pd, err := provider.NewCompatibleProvider(groupName, ps, hc)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pd, err := provider.NewCompatibleProvider(groupName, ps, hc)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
providers = append(providers, pd)
|
||||
providersMap[groupName] = pd
|
||||
} else {
|
||||
if groupOption.URL == "" || groupOption.Interval == 0 {
|
||||
return nil, errMissHealthCheck
|
||||
}
|
||||
|
||||
providers = append(providers, pd)
|
||||
providersMap[groupName] = pd
|
||||
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 {
|
||||
list, err := getProviders(providersMap, groupOption, filterRegx)
|
||||
list, err := getProviders(providersMap, groupOption.Use)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if groupOption.Type == "fallback" {
|
||||
providers = append(list, providers...)
|
||||
} else {
|
||||
providers = append(providers, list...)
|
||||
}
|
||||
providers = append(providers, list...)
|
||||
}
|
||||
|
||||
var group C.ProxyAdapter
|
||||
@ -138,13 +130,8 @@ func getProxies(mapping map[string]C.Proxy, list []string) ([]C.Proxy, error) {
|
||||
return ps, nil
|
||||
}
|
||||
|
||||
func getProviders(mapping map[string]types.ProxyProvider, groupOption *GroupCommonOption, filterRegx *regexp.Regexp) ([]types.ProxyProvider, error) {
|
||||
var (
|
||||
ps []types.ProxyProvider
|
||||
list = groupOption.Use
|
||||
groupName = groupOption.Name
|
||||
)
|
||||
|
||||
func getProviders(mapping map[string]types.ProxyProvider, list []string) ([]types.ProxyProvider, error) {
|
||||
var ps []types.ProxyProvider
|
||||
for _, name := range list {
|
||||
p, ok := mapping[name]
|
||||
if !ok {
|
||||
@ -154,39 +141,7 @@ func getProviders(mapping map[string]types.ProxyProvider, groupOption *GroupComm
|
||||
if p.VehicleType() == types.Compatible {
|
||||
return nil, fmt.Errorf("proxy group %s can't contains in `use`", name)
|
||||
}
|
||||
|
||||
if filterRegx != nil {
|
||||
hc, err := newHealthCheck([]C.Proxy{}, groupOption)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
gName := groupName
|
||||
if _, ok = mapping[gName]; ok {
|
||||
gName = groupName + " -> " + p.Name()
|
||||
}
|
||||
|
||||
pd := p.(*provider.ProxySetProvider)
|
||||
p = provider.NewProxyFilterProvider(gName, pd, hc, filterRegx)
|
||||
pd.RegisterProvidersInUse(p)
|
||||
}
|
||||
|
||||
ps = append(ps, p)
|
||||
}
|
||||
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
|
||||
}
|
||||
|
@ -4,14 +4,10 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/netip"
|
||||
|
||||
"github.com/Dreamacro/clash/adapter"
|
||||
"github.com/Dreamacro/clash/adapter/outbound"
|
||||
"github.com/Dreamacro/clash/common/singledo"
|
||||
"github.com/Dreamacro/clash/component/dialer"
|
||||
"github.com/Dreamacro/clash/component/resolver"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/constant/provider"
|
||||
)
|
||||
@ -38,12 +34,30 @@ func (r *Relay) DialContext(ctx context.Context, metadata *C.Metadata, opts ...d
|
||||
return proxies[0].DialContext(ctx, metadata, r.Base.DialOptions(opts...)...)
|
||||
}
|
||||
|
||||
c, err := r.streamContext(ctx, proxies, r.Base.DialOptions(opts...)...)
|
||||
first := proxies[0]
|
||||
last := proxies[len(proxies)-1]
|
||||
|
||||
c, err := dialer.DialContext(ctx, "tcp", first.Addr(), r.Base.DialOptions(opts...)...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, fmt.Errorf("%s connect error: %w", first.Addr(), err)
|
||||
}
|
||||
tcpKeepAlive(c)
|
||||
|
||||
var currentMeta *C.Metadata
|
||||
for _, proxy := range proxies[1:] {
|
||||
currentMeta, err = addrToMetadata(proxy.Addr())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
c, err = first.StreamConn(c, currentMeta)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s connect error: %w", first.Addr(), err)
|
||||
}
|
||||
|
||||
first = proxy
|
||||
}
|
||||
|
||||
last := proxies[len(proxies)-1]
|
||||
c, err = last.StreamConn(c, metadata)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s connect error: %w", last.Addr(), err)
|
||||
@ -52,147 +66,6 @@ func (r *Relay) DialContext(ctx context.Context, metadata *C.Metadata, opts ...d
|
||||
return outbound.NewConn(c, r), nil
|
||||
}
|
||||
|
||||
// ListenPacketContext implements C.ProxyAdapter
|
||||
func (r *Relay) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) {
|
||||
var proxies []C.Proxy
|
||||
for _, proxy := range r.proxies(metadata, true) {
|
||||
if proxy.Type() != C.Direct {
|
||||
proxies = append(proxies, proxy)
|
||||
}
|
||||
}
|
||||
|
||||
length := len(proxies)
|
||||
|
||||
switch length {
|
||||
case 0:
|
||||
return outbound.NewDirect().ListenPacketContext(ctx, metadata, r.Base.DialOptions(opts...)...)
|
||||
case 1:
|
||||
proxy := proxies[0]
|
||||
if !proxy.SupportUDP() {
|
||||
return nil, fmt.Errorf("%s connect error: proxy [%s] UDP is not supported", proxy.Addr(), proxy.Name())
|
||||
}
|
||||
return proxy.ListenPacketContext(ctx, metadata, r.Base.DialOptions(opts...)...)
|
||||
}
|
||||
|
||||
var (
|
||||
firstIndex = 0
|
||||
nextIndex = 1
|
||||
lastUDPOverTCPIndex = -1
|
||||
rawUDPRelay = false
|
||||
|
||||
first = proxies[firstIndex]
|
||||
last = proxies[length-1]
|
||||
|
||||
c net.Conn
|
||||
cc net.Conn
|
||||
err error
|
||||
currentMeta *C.Metadata
|
||||
)
|
||||
|
||||
if !last.SupportUDP() {
|
||||
return nil, fmt.Errorf("%s connect error: proxy [%s] UDP is not supported in relay chains", last.Addr(), last.Name())
|
||||
}
|
||||
|
||||
rawUDPRelay, lastUDPOverTCPIndex = isRawUDPRelay(proxies)
|
||||
|
||||
if first.Type() == C.Socks5 {
|
||||
cc1, err1 := dialer.DialContext(ctx, "tcp", first.Addr(), r.Base.DialOptions(opts...)...)
|
||||
if err1 != nil {
|
||||
return nil, fmt.Errorf("%s connect error: %w", first.Addr(), err)
|
||||
}
|
||||
cc = cc1
|
||||
tcpKeepAlive(cc)
|
||||
|
||||
var pc net.PacketConn
|
||||
pc, err = dialer.ListenPacket(ctx, "udp", "", r.Base.DialOptions(opts...)...)
|
||||
c = outbound.WrapConn(pc)
|
||||
} else if rawUDPRelay {
|
||||
var pc net.PacketConn
|
||||
pc, err = dialer.ListenPacket(ctx, "udp", "", r.Base.DialOptions(opts...)...)
|
||||
c = outbound.WrapConn(pc)
|
||||
} else {
|
||||
firstIndex = lastUDPOverTCPIndex
|
||||
nextIndex = firstIndex + 1
|
||||
first = proxies[firstIndex]
|
||||
c, err = r.streamContext(ctx, proxies[:nextIndex], r.Base.DialOptions(opts...)...)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s connect error: %w", first.Addr(), err)
|
||||
}
|
||||
|
||||
if nextIndex < length {
|
||||
for i, proxy := range proxies[nextIndex:] { // raw udp in loop
|
||||
currentMeta, err = addrToMetadata(proxy.Addr())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
currentMeta.NetWork = C.UDP
|
||||
|
||||
if !isRawUDP(first) && !first.SupportUDP() {
|
||||
return nil, fmt.Errorf("%s connect error: proxy [%s] UDP is not supported in relay chains", first.Addr(), first.Name())
|
||||
}
|
||||
|
||||
if needResolveIP(first, currentMeta) {
|
||||
var ip netip.Addr
|
||||
ip, err = resolver.ResolveProxyServerHost(currentMeta.Host)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("can't resolve ip: %w", err)
|
||||
}
|
||||
currentMeta.DstIP = ip
|
||||
}
|
||||
|
||||
if cc != nil { // socks5
|
||||
c, err = streamSocks5PacketConn(first, cc, c, currentMeta)
|
||||
cc = nil
|
||||
} else {
|
||||
c, err = first.StreamPacketConn(c, currentMeta)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s connect error: %w", first.Addr(), err)
|
||||
}
|
||||
|
||||
if proxy.Type() == C.Socks5 {
|
||||
endIndex := nextIndex + i + 1
|
||||
cc, err = r.streamContext(ctx, proxies[:endIndex], r.Base.DialOptions(opts...)...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s connect error: %w", first.Addr(), err)
|
||||
}
|
||||
}
|
||||
|
||||
first = proxy
|
||||
}
|
||||
}
|
||||
|
||||
if cc != nil {
|
||||
c, err = streamSocks5PacketConn(last, cc, c, metadata)
|
||||
} else {
|
||||
c, err = last.StreamPacketConn(c, metadata)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s connect error: %w", last.Addr(), err)
|
||||
}
|
||||
|
||||
return outbound.NewPacketConn(c.(net.PacketConn), r), nil
|
||||
}
|
||||
|
||||
// SupportUDP implements C.ProxyAdapter
|
||||
func (r *Relay) SupportUDP() bool {
|
||||
proxies := r.rawProxies(true)
|
||||
|
||||
l := len(proxies)
|
||||
|
||||
if l == 0 {
|
||||
return true
|
||||
}
|
||||
|
||||
last := proxies[l-1]
|
||||
|
||||
return isRawUDP(last) || last.SupportUDP()
|
||||
}
|
||||
|
||||
// MarshalJSON implements C.ProxyAdapter
|
||||
func (r *Relay) MarshalJSON() ([]byte, error) {
|
||||
var all []string
|
||||
@ -227,83 +100,6 @@ func (r *Relay) proxies(metadata *C.Metadata, touch bool) []C.Proxy {
|
||||
return proxies
|
||||
}
|
||||
|
||||
func (r *Relay) streamContext(ctx context.Context, proxies []C.Proxy, opts ...dialer.Option) (net.Conn, error) {
|
||||
first := proxies[0]
|
||||
|
||||
c, err := dialer.DialContext(ctx, "tcp", first.Addr(), opts...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s connect error: %w", first.Addr(), err)
|
||||
}
|
||||
tcpKeepAlive(c)
|
||||
|
||||
if len(proxies) > 1 {
|
||||
var currentMeta *C.Metadata
|
||||
for _, proxy := range proxies[1:] {
|
||||
currentMeta, err = addrToMetadata(proxy.Addr())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
c, err = first.StreamConn(c, currentMeta)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s connect error: %w", first.Addr(), err)
|
||||
}
|
||||
|
||||
first = proxy
|
||||
}
|
||||
}
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func streamSocks5PacketConn(proxy C.Proxy, cc, c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
||||
pc, err := proxy.(*adapter.Proxy).ProxyAdapter.(*outbound.Socks5).StreamSocks5PacketConn(cc, c.(net.PacketConn), metadata)
|
||||
return outbound.WrapConn(pc), err
|
||||
}
|
||||
|
||||
func isRawUDPRelay(proxies []C.Proxy) (bool, int) {
|
||||
var (
|
||||
lastIndex = len(proxies) - 1
|
||||
last = proxies[lastIndex]
|
||||
isLastRawUDP = isRawUDP(last)
|
||||
isUDPOverTCP = false
|
||||
lastUDPOverTCPIndex = -1
|
||||
)
|
||||
|
||||
for i := lastIndex; i >= 0; i-- {
|
||||
p := proxies[i]
|
||||
|
||||
isUDPOverTCP = isUDPOverTCP || !isRawUDP(p)
|
||||
|
||||
if isLastRawUDP && isUDPOverTCP && lastUDPOverTCPIndex == -1 {
|
||||
lastUDPOverTCPIndex = i
|
||||
}
|
||||
}
|
||||
|
||||
if !isLastRawUDP {
|
||||
lastUDPOverTCPIndex = lastIndex
|
||||
}
|
||||
|
||||
return !isUDPOverTCP, lastUDPOverTCPIndex
|
||||
}
|
||||
|
||||
func isRawUDP(proxy C.ProxyAdapter) bool {
|
||||
if proxy.Type() == C.Shadowsocks || proxy.Type() == C.ShadowsocksR || proxy.Type() == C.Socks5 {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func needResolveIP(proxy C.ProxyAdapter, metadata *C.Metadata) bool {
|
||||
if metadata.Resolved() {
|
||||
return false
|
||||
}
|
||||
if proxy.Type() != C.Vmess && proxy.Type() != C.Vless {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func NewRelay(option *GroupCommonOption, providers []provider.ProxyProvider) *Relay {
|
||||
return &Relay{
|
||||
Base: outbound.NewBase(outbound.BaseOption{
|
||||
|
@ -98,11 +98,7 @@ func (s *Selector) selectedProxy(touch bool) C.Proxy {
|
||||
}
|
||||
|
||||
func NewSelector(option *GroupCommonOption, providers []provider.ProxyProvider) *Selector {
|
||||
selected := "REJECT"
|
||||
if len(providers) != 0 && len(providers[0].Proxies()) != 0 {
|
||||
selected = providers[0].Proxies()[0].Name()
|
||||
}
|
||||
|
||||
selected := providers[0].Proxies()[0].Name()
|
||||
return &Selector{
|
||||
Base: outbound.NewBase(outbound.BaseOption{
|
||||
Name: option.Name,
|
||||
|
@ -24,7 +24,7 @@ func addrToMetadata(rawAddress string) (addr *C.Metadata, err error) {
|
||||
DstIP: netip.Addr{},
|
||||
DstPort: port,
|
||||
}
|
||||
return addr, nil
|
||||
return
|
||||
} else if ip.Is4() {
|
||||
addr = &C.Metadata{
|
||||
AddrType: C.AtypIPv4,
|
||||
@ -48,6 +48,5 @@ func tcpKeepAlive(c net.Conn) {
|
||||
if tcp, ok := c.(*net.TCPConn); ok {
|
||||
_ = tcp.SetKeepAlive(true)
|
||||
_ = tcp.SetKeepAlivePeriod(30 * time.Second)
|
||||
_ = tcp.SetLinger(0)
|
||||
}
|
||||
}
|
||||
|
@ -8,9 +8,9 @@ import (
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
)
|
||||
|
||||
func ParseProxy(mapping map[string]any, forceCertVerify bool) (C.Proxy, error) {
|
||||
func ParseProxy(mapping map[string]any) (C.Proxy, error) {
|
||||
decoder := structure.NewDecoder(structure.Option{TagName: "proxy", WeaklyTypedInput: true})
|
||||
proxyType, existType := mapping["type"]
|
||||
proxyType, existType := mapping["type"].(string)
|
||||
if !existType {
|
||||
return nil, fmt.Errorf("missing type")
|
||||
}
|
||||
@ -19,7 +19,7 @@ func ParseProxy(mapping map[string]any, forceCertVerify bool) (C.Proxy, error) {
|
||||
proxy C.ProxyAdapter
|
||||
err error
|
||||
)
|
||||
switch proxyType.(string) {
|
||||
switch proxyType {
|
||||
case "ss":
|
||||
ssOption := &outbound.ShadowSocksOption{}
|
||||
err = decoder.Decode(mapping, ssOption)
|
||||
@ -40,9 +40,6 @@ func ParseProxy(mapping map[string]any, forceCertVerify bool) (C.Proxy, error) {
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
if forceCertVerify {
|
||||
socksOption.SkipCertVerify = false
|
||||
}
|
||||
proxy = outbound.NewSocks5(*socksOption)
|
||||
case "http":
|
||||
httpOption := &outbound.HttpOption{}
|
||||
@ -50,25 +47,18 @@ func ParseProxy(mapping map[string]any, forceCertVerify bool) (C.Proxy, error) {
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
if forceCertVerify {
|
||||
httpOption.SkipCertVerify = false
|
||||
}
|
||||
proxy = outbound.NewHttp(*httpOption)
|
||||
case "vmess":
|
||||
vmessOption := &outbound.VmessOption{
|
||||
HTTPOpts: outbound.HTTPOptions{
|
||||
Method: "GET",
|
||||
Path: []string{"/"},
|
||||
Headers: make(map[string][]string),
|
||||
Method: "GET",
|
||||
Path: []string{"/"},
|
||||
},
|
||||
}
|
||||
err = decoder.Decode(mapping, vmessOption)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
if forceCertVerify {
|
||||
vmessOption.SkipCertVerify = false
|
||||
}
|
||||
proxy, err = outbound.NewVmess(*vmessOption)
|
||||
case "vless":
|
||||
vlessOption := &outbound.VlessOption{}
|
||||
@ -76,9 +66,6 @@ func ParseProxy(mapping map[string]any, forceCertVerify bool) (C.Proxy, error) {
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
if forceCertVerify {
|
||||
vlessOption.SkipCertVerify = false
|
||||
}
|
||||
proxy, err = outbound.NewVless(*vlessOption)
|
||||
case "snell":
|
||||
snellOption := &outbound.SnellOption{}
|
||||
@ -93,9 +80,6 @@ func ParseProxy(mapping map[string]any, forceCertVerify bool) (C.Proxy, error) {
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
if forceCertVerify {
|
||||
trojanOption.SkipCertVerify = false
|
||||
}
|
||||
proxy, err = outbound.NewTrojan(*trojanOption)
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupport proxy type: %s", proxyType)
|
||||
|
@ -16,28 +16,28 @@ var (
|
||||
dirMode os.FileMode = 0o755
|
||||
)
|
||||
|
||||
type parser[V any] func([]byte) (V, error)
|
||||
type parser = func([]byte) (any, error)
|
||||
|
||||
type fetcher[V any] struct {
|
||||
type fetcher struct {
|
||||
name string
|
||||
vehicle types.Vehicle
|
||||
updatedAt *time.Time
|
||||
ticker *time.Ticker
|
||||
done chan struct{}
|
||||
hash [16]byte
|
||||
parser parser[V]
|
||||
onUpdate func(V)
|
||||
parser parser
|
||||
onUpdate func(any)
|
||||
}
|
||||
|
||||
func (f *fetcher[V]) Name() string {
|
||||
func (f *fetcher) Name() string {
|
||||
return f.name
|
||||
}
|
||||
|
||||
func (f *fetcher[V]) VehicleType() types.VehicleType {
|
||||
func (f *fetcher) VehicleType() types.VehicleType {
|
||||
return f.vehicle.Type()
|
||||
}
|
||||
|
||||
func (f *fetcher[V]) Initial() (V, error) {
|
||||
func (f *fetcher) Initial() (any, error) {
|
||||
var (
|
||||
buf []byte
|
||||
err error
|
||||
@ -53,24 +53,24 @@ func (f *fetcher[V]) Initial() (V, error) {
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return getZero[V](), err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
proxies, err := f.parser(buf)
|
||||
if err != nil {
|
||||
if !isLocal {
|
||||
return getZero[V](), err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// parse local file error, fallback to remote
|
||||
buf, err = f.vehicle.Read()
|
||||
if err != nil {
|
||||
return getZero[V](), err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
proxies, err = f.parser(buf)
|
||||
if err != nil {
|
||||
return getZero[V](), err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
isLocal = false
|
||||
@ -78,7 +78,7 @@ func (f *fetcher[V]) Initial() (V, error) {
|
||||
|
||||
if f.vehicle.Type() != types.File && !isLocal {
|
||||
if err := safeWrite(f.vehicle.Path(), buf); err != nil {
|
||||
return getZero[V](), err
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
@ -92,28 +92,28 @@ func (f *fetcher[V]) Initial() (V, error) {
|
||||
return proxies, nil
|
||||
}
|
||||
|
||||
func (f *fetcher[V]) Update() (V, bool, error) {
|
||||
func (f *fetcher) Update() (any, bool, error) {
|
||||
buf, err := f.vehicle.Read()
|
||||
if err != nil {
|
||||
return getZero[V](), false, err
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
hash := md5.Sum(buf)
|
||||
if bytes.Equal(f.hash[:], hash[:]) {
|
||||
f.updatedAt = &now
|
||||
_ = os.Chtimes(f.vehicle.Path(), now, now)
|
||||
return getZero[V](), true, nil
|
||||
os.Chtimes(f.vehicle.Path(), now, now)
|
||||
return nil, true, nil
|
||||
}
|
||||
|
||||
proxies, err := f.parser(buf)
|
||||
if err != nil {
|
||||
return getZero[V](), false, err
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
if f.vehicle.Type() != types.File {
|
||||
if err := safeWrite(f.vehicle.Path(), buf); err != nil {
|
||||
return getZero[V](), false, err
|
||||
return nil, false, err
|
||||
}
|
||||
}
|
||||
|
||||
@ -123,14 +123,14 @@ func (f *fetcher[V]) Update() (V, bool, error) {
|
||||
return proxies, false, nil
|
||||
}
|
||||
|
||||
func (f *fetcher[V]) Destroy() error {
|
||||
func (f *fetcher) Destroy() error {
|
||||
if f.ticker != nil {
|
||||
f.done <- struct{}{}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *fetcher[V]) pullLoop() {
|
||||
func (f *fetcher) pullLoop() {
|
||||
for {
|
||||
select {
|
||||
case <-f.ticker.C:
|
||||
@ -168,13 +168,13 @@ func safeWrite(path string, buf []byte) error {
|
||||
return os.WriteFile(path, buf, fileMode)
|
||||
}
|
||||
|
||||
func newFetcher[V any](name string, interval time.Duration, vehicle types.Vehicle, parser parser[V], onUpdate func(V)) *fetcher[V] {
|
||||
func newFetcher(name string, interval time.Duration, vehicle types.Vehicle, parser parser, onUpdate func(any)) *fetcher {
|
||||
var ticker *time.Ticker
|
||||
if interval != 0 {
|
||||
ticker = time.NewTicker(interval)
|
||||
}
|
||||
|
||||
return &fetcher[V]{
|
||||
return &fetcher{
|
||||
name: name,
|
||||
ticker: ticker,
|
||||
vehicle: vehicle,
|
||||
@ -183,8 +183,3 @@ func newFetcher[V any](name string, interval time.Duration, vehicle types.Vehicl
|
||||
onUpdate: onUpdate,
|
||||
}
|
||||
}
|
||||
|
||||
func getZero[V any]() V {
|
||||
var result V
|
||||
return result
|
||||
}
|
||||
|
@ -25,16 +25,10 @@ type HealthCheck struct {
|
||||
interval uint
|
||||
lazy bool
|
||||
lastTouch *atomic.Int64
|
||||
running *atomic.Bool
|
||||
done chan struct{}
|
||||
}
|
||||
|
||||
func (hc *HealthCheck) process() {
|
||||
if hc.running.Load() {
|
||||
return
|
||||
}
|
||||
hc.running.Store(true)
|
||||
|
||||
ticker := time.NewTicker(time.Duration(hc.interval) * time.Second)
|
||||
|
||||
go func() {
|
||||
@ -71,13 +65,8 @@ func (hc *HealthCheck) touch() {
|
||||
}
|
||||
|
||||
func (hc *HealthCheck) check() {
|
||||
proxies := hc.proxies
|
||||
if len(proxies) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
b, _ := batch.New[bool](context.Background(), batch.WithConcurrencyNum[bool](10))
|
||||
for _, proxy := range proxies {
|
||||
for _, proxy := range hc.proxies {
|
||||
p := proxy
|
||||
b.Go(p.Name(), func() (bool, error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), defaultURLTestTimeout)
|
||||
@ -90,10 +79,6 @@ func (hc *HealthCheck) check() {
|
||||
}
|
||||
|
||||
func (hc *HealthCheck) close() {
|
||||
if !hc.running.Load() {
|
||||
return
|
||||
}
|
||||
hc.running.Store(false)
|
||||
hc.done <- struct{}{}
|
||||
}
|
||||
|
||||
@ -104,7 +89,6 @@ func NewHealthCheck(proxies []C.Proxy, url string, interval uint, lazy bool) *He
|
||||
interval: interval,
|
||||
lazy: lazy,
|
||||
lastTouch: atomic.NewInt64(0),
|
||||
running: atomic.NewBool(false),
|
||||
done: make(chan struct{}, 1),
|
||||
}
|
||||
}
|
||||
|
@ -20,18 +20,15 @@ type healthCheckSchema struct {
|
||||
}
|
||||
|
||||
type proxyProviderSchema struct {
|
||||
Type string `provider:"type"`
|
||||
Path string `provider:"path"`
|
||||
URL string `provider:"url,omitempty"`
|
||||
Interval int `provider:"interval,omitempty"`
|
||||
Filter string `provider:"filter,omitempty"`
|
||||
HealthCheck healthCheckSchema `provider:"health-check,omitempty"`
|
||||
ForceCertVerify bool `provider:"force-cert-verify,omitempty"`
|
||||
PrefixName string `provider:"prefix-name,omitempty"`
|
||||
Header map[string][]string `provider:"header,omitempty"`
|
||||
Type string `provider:"type"`
|
||||
Path string `provider:"path"`
|
||||
URL string `provider:"url,omitempty"`
|
||||
Interval int `provider:"interval,omitempty"`
|
||||
Filter string `provider:"filter,omitempty"`
|
||||
HealthCheck healthCheckSchema `provider:"health-check,omitempty"`
|
||||
}
|
||||
|
||||
func ParseProxyProvider(name string, mapping map[string]any, forceCertVerify bool) (types.ProxyProvider, error) {
|
||||
func ParseProxyProvider(name string, mapping map[string]any) (types.ProxyProvider, error) {
|
||||
decoder := structure.NewDecoder(structure.Option{TagName: "provider", WeaklyTypedInput: true})
|
||||
|
||||
schema := &proxyProviderSchema{
|
||||
@ -39,11 +36,6 @@ func ParseProxyProvider(name string, mapping map[string]any, forceCertVerify boo
|
||||
Lazy: true,
|
||||
},
|
||||
}
|
||||
|
||||
if forceCertVerify {
|
||||
schema.ForceCertVerify = true
|
||||
}
|
||||
|
||||
if err := decoder.Decode(mapping, schema); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -61,12 +53,12 @@ func ParseProxyProvider(name string, mapping map[string]any, forceCertVerify boo
|
||||
case "file":
|
||||
vehicle = NewFileVehicle(path)
|
||||
case "http":
|
||||
vehicle = NewHTTPVehicle(schema.URL, path, schema.Header)
|
||||
vehicle = NewHTTPVehicle(schema.URL, path)
|
||||
default:
|
||||
return nil, fmt.Errorf("%w: %s", errVehicleType, schema.Type)
|
||||
}
|
||||
|
||||
interval := time.Duration(uint(schema.Interval)) * time.Second
|
||||
filter := schema.Filter
|
||||
return NewProxySetProvider(name, interval, filter, vehicle, hc, schema.ForceCertVerify, schema.PrefixName)
|
||||
return NewProxySetProvider(name, interval, filter, vehicle, hc)
|
||||
}
|
||||
|
@ -9,11 +9,10 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/Dreamacro/clash/adapter"
|
||||
"github.com/Dreamacro/clash/common/convert"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
types "github.com/Dreamacro/clash/constant/provider"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -24,16 +23,15 @@ type ProxySchema struct {
|
||||
Proxies []map[string]any `yaml:"proxies"`
|
||||
}
|
||||
|
||||
// ProxySetProvider for auto gc
|
||||
// for auto gc
|
||||
type ProxySetProvider struct {
|
||||
*proxySetProvider
|
||||
}
|
||||
|
||||
type proxySetProvider struct {
|
||||
*fetcher[[]C.Proxy]
|
||||
proxies []C.Proxy
|
||||
healthCheck *HealthCheck
|
||||
providersInUse []types.ProxyProvider
|
||||
*fetcher
|
||||
proxies []C.Proxy
|
||||
healthCheck *HealthCheck
|
||||
}
|
||||
|
||||
func (pp *proxySetProvider) MarshalJSON() ([]byte, error) {
|
||||
@ -88,22 +86,17 @@ func (pp *proxySetProvider) ProxiesWithTouch() []C.Proxy {
|
||||
func (pp *proxySetProvider) setProxies(proxies []C.Proxy) {
|
||||
pp.proxies = proxies
|
||||
pp.healthCheck.setProxy(proxies)
|
||||
|
||||
for _, use := range pp.providersInUse {
|
||||
_ = use.Update()
|
||||
if pp.healthCheck.auto() {
|
||||
go pp.healthCheck.check()
|
||||
}
|
||||
}
|
||||
|
||||
func (pp *proxySetProvider) RegisterProvidersInUse(providers ...types.ProxyProvider) {
|
||||
pp.providersInUse = append(pp.providersInUse, providers...)
|
||||
}
|
||||
|
||||
func stopProxyProvider(pd *ProxySetProvider) {
|
||||
pd.healthCheck.close()
|
||||
_ = pd.fetcher.Destroy()
|
||||
pd.fetcher.Destroy()
|
||||
}
|
||||
|
||||
func NewProxySetProvider(name string, interval time.Duration, filter string, vehicle types.Vehicle, hc *HealthCheck, forceCertVerify bool, prefixName string) (*ProxySetProvider, error) {
|
||||
func NewProxySetProvider(name string, interval time.Duration, filter string, vehicle types.Vehicle, hc *HealthCheck) (*ProxySetProvider, error) {
|
||||
filterReg, err := regexp.Compile(filter)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid filter regex: %w", err)
|
||||
@ -118,7 +111,45 @@ func NewProxySetProvider(name string, interval time.Duration, filter string, veh
|
||||
healthCheck: hc,
|
||||
}
|
||||
|
||||
fetcher := newFetcher[[]C.Proxy](name, interval, vehicle, proxiesParseAndFilter(filter, filterReg, forceCertVerify, prefixName), proxiesOnUpdate(pd))
|
||||
onUpdate := func(elm any) {
|
||||
ret := elm.([]C.Proxy)
|
||||
pd.setProxies(ret)
|
||||
}
|
||||
|
||||
proxiesParseAndFilter := func(buf []byte) (any, error) {
|
||||
schema := &ProxySchema{}
|
||||
|
||||
if err := yaml.Unmarshal(buf, schema); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if schema.Proxies == nil {
|
||||
return nil, errors.New("file must have a `proxies` field")
|
||||
}
|
||||
|
||||
proxies := []C.Proxy{}
|
||||
for idx, mapping := range schema.Proxies {
|
||||
if name, ok := mapping["name"]; ok && len(filter) > 0 && !filterReg.MatchString(name.(string)) {
|
||||
continue
|
||||
}
|
||||
proxy, err := adapter.ParseProxy(mapping)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("proxy %d error: %w", idx, err)
|
||||
}
|
||||
proxies = append(proxies, proxy)
|
||||
}
|
||||
|
||||
if len(proxies) == 0 {
|
||||
if len(filter) > 0 {
|
||||
return nil, errors.New("doesn't match any proxy, please check your filter")
|
||||
}
|
||||
return nil, errors.New("file doesn't have any proxy")
|
||||
}
|
||||
|
||||
return proxies, nil
|
||||
}
|
||||
|
||||
fetcher := newFetcher(name, interval, vehicle, proxiesParseAndFilter, onUpdate)
|
||||
pd.fetcher = fetcher
|
||||
|
||||
wrapper := &ProxySetProvider{pd}
|
||||
@ -126,7 +157,7 @@ func NewProxySetProvider(name string, interval time.Duration, filter string, veh
|
||||
return wrapper, nil
|
||||
}
|
||||
|
||||
// CompatibleProvider for auto gc
|
||||
// for auto gc
|
||||
type CompatibleProvider struct {
|
||||
*compatibleProvider
|
||||
}
|
||||
@ -202,147 +233,3 @@ func NewCompatibleProvider(name string, proxies []C.Proxy, hc *HealthCheck) (*Co
|
||||
runtime.SetFinalizer(wrapper, stopCompatibleProvider)
|
||||
return wrapper, nil
|
||||
}
|
||||
|
||||
// ProxyFilterProvider for filter provider
|
||||
type ProxyFilterProvider struct {
|
||||
*proxyFilterProvider
|
||||
}
|
||||
|
||||
type proxyFilterProvider struct {
|
||||
name string
|
||||
psd *ProxySetProvider
|
||||
proxies []C.Proxy
|
||||
filter *regexp.Regexp
|
||||
healthCheck *HealthCheck
|
||||
}
|
||||
|
||||
func (pf *proxyFilterProvider) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(map[string]any{
|
||||
"name": pf.Name(),
|
||||
"type": pf.Type().String(),
|
||||
"vehicleType": pf.VehicleType().String(),
|
||||
"proxies": pf.Proxies(),
|
||||
})
|
||||
}
|
||||
|
||||
func (pf *proxyFilterProvider) Name() string {
|
||||
return pf.name
|
||||
}
|
||||
|
||||
func (pf *proxyFilterProvider) HealthCheck() {
|
||||
pf.healthCheck.check()
|
||||
}
|
||||
|
||||
func (pf *proxyFilterProvider) Update() error {
|
||||
pf.healthCheck.close()
|
||||
|
||||
proxies := []C.Proxy{}
|
||||
if pf.filter != nil {
|
||||
for _, proxy := range pf.psd.Proxies() {
|
||||
if !pf.filter.MatchString(proxy.Name()) {
|
||||
continue
|
||||
}
|
||||
proxies = append(proxies, proxy)
|
||||
}
|
||||
} else {
|
||||
proxies = pf.psd.Proxies()
|
||||
}
|
||||
|
||||
pf.proxies = proxies
|
||||
pf.healthCheck.setProxy(proxies)
|
||||
|
||||
if len(proxies) != 0 && pf.healthCheck.auto() {
|
||||
go pf.healthCheck.process()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (pf *proxyFilterProvider) Initial() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (pf *proxyFilterProvider) VehicleType() types.VehicleType {
|
||||
return pf.psd.VehicleType()
|
||||
}
|
||||
|
||||
func (pf *proxyFilterProvider) Type() types.ProviderType {
|
||||
return types.Proxy
|
||||
}
|
||||
|
||||
func (pf *proxyFilterProvider) Proxies() []C.Proxy {
|
||||
return pf.proxies
|
||||
}
|
||||
|
||||
func (pf *proxyFilterProvider) ProxiesWithTouch() []C.Proxy {
|
||||
pf.healthCheck.touch()
|
||||
return pf.Proxies()
|
||||
}
|
||||
|
||||
func stopProxyFilterProvider(pf *ProxyFilterProvider) {
|
||||
pf.healthCheck.close()
|
||||
}
|
||||
|
||||
func NewProxyFilterProvider(name string, psd *ProxySetProvider, hc *HealthCheck, filterRegx *regexp.Regexp) *ProxyFilterProvider {
|
||||
pd := &proxyFilterProvider{
|
||||
psd: psd,
|
||||
name: name,
|
||||
healthCheck: hc,
|
||||
filter: filterRegx,
|
||||
}
|
||||
|
||||
_ = pd.Update()
|
||||
|
||||
wrapper := &ProxyFilterProvider{pd}
|
||||
runtime.SetFinalizer(wrapper, stopProxyFilterProvider)
|
||||
return wrapper
|
||||
}
|
||||
|
||||
func proxiesOnUpdate(pd *proxySetProvider) func([]C.Proxy) {
|
||||
return func(elm []C.Proxy) {
|
||||
pd.setProxies(elm)
|
||||
}
|
||||
}
|
||||
|
||||
func proxiesParseAndFilter(filter string, filterReg *regexp.Regexp, forceCertVerify bool, prefixName string) parser[[]C.Proxy] {
|
||||
return func(buf []byte) ([]C.Proxy, error) {
|
||||
schema := &ProxySchema{}
|
||||
|
||||
if err := yaml.Unmarshal(buf, schema); err != nil {
|
||||
proxies, err1 := convert.ConvertsV2Ray(buf)
|
||||
if err1 != nil {
|
||||
return nil, fmt.Errorf("%w, %s", err, err1.Error())
|
||||
}
|
||||
schema.Proxies = proxies
|
||||
}
|
||||
|
||||
if schema.Proxies == nil {
|
||||
return nil, errors.New("file must have a `proxies` field")
|
||||
}
|
||||
|
||||
proxies := []C.Proxy{}
|
||||
for idx, mapping := range schema.Proxies {
|
||||
if name, ok := mapping["name"]; ok && len(filter) > 0 && !filterReg.MatchString(name.(string)) {
|
||||
continue
|
||||
}
|
||||
|
||||
if prefixName != "" {
|
||||
mapping["name"] = prefixName + mapping["name"].(string)
|
||||
}
|
||||
|
||||
proxy, err := adapter.ParseProxy(mapping, forceCertVerify)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("proxy %d error: %w", idx, err)
|
||||
}
|
||||
proxies = append(proxies, proxy)
|
||||
}
|
||||
|
||||
if len(proxies) == 0 {
|
||||
if len(filter) > 0 {
|
||||
return nil, errors.New("doesn't match any proxy, please check your filter")
|
||||
}
|
||||
return nil, errors.New("file doesn't have any proxy")
|
||||
}
|
||||
|
||||
return proxies, nil
|
||||
}
|
||||
}
|
||||
|
@ -9,7 +9,6 @@ import (
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/Dreamacro/clash/common/convert"
|
||||
"github.com/Dreamacro/clash/component/dialer"
|
||||
types "github.com/Dreamacro/clash/constant/provider"
|
||||
)
|
||||
@ -35,9 +34,8 @@ func NewFileVehicle(path string) *FileVehicle {
|
||||
}
|
||||
|
||||
type HTTPVehicle struct {
|
||||
url string
|
||||
path string
|
||||
header http.Header
|
||||
url string
|
||||
path string
|
||||
}
|
||||
|
||||
func (h *HTTPVehicle) Type() types.VehicleType {
|
||||
@ -62,17 +60,11 @@ func (h *HTTPVehicle) Read() ([]byte, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if h.header != nil {
|
||||
req.Header = h.header
|
||||
}
|
||||
|
||||
if user := uri.User; user != nil {
|
||||
password, _ := user.Password()
|
||||
req.SetBasicAuth(user.Username(), password)
|
||||
}
|
||||
|
||||
convert.SetUserAgent(req.Header)
|
||||
|
||||
req = req.WithContext(ctx)
|
||||
|
||||
transport := &http.Transport{
|
||||
@ -82,10 +74,7 @@ func (h *HTTPVehicle) Read() ([]byte, error) {
|
||||
TLSHandshakeTimeout: 10 * time.Second,
|
||||
ExpectContinueTimeout: 1 * time.Second,
|
||||
DialContext: func(ctx context.Context, network, address string) (net.Conn, error) {
|
||||
if req.URL.Scheme == "https" {
|
||||
return (&net.Dialer{}).DialContext(ctx, network, address) // forward to tun if tun enabled
|
||||
}
|
||||
return dialer.DialContext(ctx, network, address, dialer.WithDirect()) // with direct
|
||||
return dialer.DialContext(ctx, network, address)
|
||||
},
|
||||
}
|
||||
|
||||
@ -94,9 +83,7 @@ func (h *HTTPVehicle) Read() ([]byte, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func() {
|
||||
_ = resp.Body.Close()
|
||||
}()
|
||||
defer resp.Body.Close()
|
||||
|
||||
buf, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
@ -106,6 +93,6 @@ func (h *HTTPVehicle) Read() ([]byte, error) {
|
||||
return buf, nil
|
||||
}
|
||||
|
||||
func NewHTTPVehicle(url string, path string, header http.Header) *HTTPVehicle {
|
||||
return &HTTPVehicle{url, path, header}
|
||||
func NewHTTPVehicle(url string, path string) *HTTPVehicle {
|
||||
return &HTTPVehicle{url, path}
|
||||
}
|
||||
|
2
common/cache/cache.go
vendored
2
common/cache/cache.go
vendored
@ -61,7 +61,7 @@ func (c *cache[K, V]) GetWithExpire(key K) (payload V, expired time.Time) {
|
||||
|
||||
func (c *cache[K, V]) cleanup() {
|
||||
c.mapping.Range(func(k, v any) bool {
|
||||
key := k
|
||||
key := k.(string)
|
||||
elm := v.(*element[V])
|
||||
if time.Since(elm.Expired) > 0 {
|
||||
c.mapping.Delete(key)
|
||||
|
@ -1,347 +0,0 @@
|
||||
package convert
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var enc = base64.StdEncoding
|
||||
|
||||
func DecodeBase64(buf []byte) ([]byte, error) {
|
||||
dBuf := make([]byte, enc.DecodedLen(len(buf)))
|
||||
n, err := enc.Decode(dBuf, buf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return dBuf[:n], nil
|
||||
}
|
||||
|
||||
func DecodeRawBase64(buf []byte) ([]byte, error) {
|
||||
dBuf := make([]byte, base64.RawStdEncoding.DecodedLen(len(buf)))
|
||||
n, err := base64.RawStdEncoding.Decode(dBuf, buf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return dBuf[:n], nil
|
||||
}
|
||||
|
||||
// ConvertsV2Ray convert V2Ray subscribe proxies data to clash proxies config
|
||||
func ConvertsV2Ray(buf []byte) ([]map[string]any, error) {
|
||||
data, err := DecodeBase64(buf)
|
||||
if err != nil {
|
||||
data, err = DecodeRawBase64(buf)
|
||||
if err != nil {
|
||||
data = buf
|
||||
}
|
||||
}
|
||||
|
||||
arr := strings.Split(string(data), "\n")
|
||||
|
||||
proxies := make([]map[string]any, 0, len(arr))
|
||||
names := make(map[string]int, 200)
|
||||
|
||||
for _, line := range arr {
|
||||
line = strings.TrimRight(line, " \r")
|
||||
if line == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
scheme, body, found := strings.Cut(line, "://")
|
||||
if !found {
|
||||
continue
|
||||
}
|
||||
|
||||
scheme = strings.ToLower(scheme)
|
||||
switch scheme {
|
||||
case "trojan":
|
||||
urlTrojan, err := url.Parse(line)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
query := urlTrojan.Query()
|
||||
|
||||
name := uniqueName(names, urlTrojan.Fragment)
|
||||
trojan := make(map[string]any, 20)
|
||||
|
||||
trojan["name"] = name
|
||||
trojan["type"] = scheme
|
||||
trojan["server"] = urlTrojan.Hostname()
|
||||
trojan["port"] = urlTrojan.Port()
|
||||
trojan["password"] = urlTrojan.User.Username()
|
||||
trojan["udp"] = true
|
||||
trojan["skip-cert-verify"] = false
|
||||
|
||||
sni := query.Get("sni")
|
||||
if sni != "" {
|
||||
trojan["sni"] = sni
|
||||
}
|
||||
|
||||
network := strings.ToLower(query.Get("type"))
|
||||
if network != "" {
|
||||
trojan["network"] = network
|
||||
}
|
||||
|
||||
if network == "ws" {
|
||||
headers := make(map[string]any)
|
||||
wsOpts := make(map[string]any)
|
||||
|
||||
headers["Host"] = RandHost()
|
||||
headers["User-Agent"] = RandUserAgent()
|
||||
|
||||
wsOpts["path"] = query.Get("path")
|
||||
wsOpts["headers"] = headers
|
||||
|
||||
trojan["ws-opts"] = wsOpts
|
||||
}
|
||||
|
||||
proxies = append(proxies, trojan)
|
||||
case "vmess":
|
||||
dcBuf, err := enc.DecodeString(body)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
jsonDc := json.NewDecoder(bytes.NewReader(dcBuf))
|
||||
values := make(map[string]any, 20)
|
||||
|
||||
if jsonDc.Decode(&values) != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
name := uniqueName(names, values["ps"].(string))
|
||||
vmess := make(map[string]any, 20)
|
||||
|
||||
vmess["name"] = name
|
||||
vmess["type"] = scheme
|
||||
vmess["server"] = values["add"]
|
||||
vmess["port"] = values["port"]
|
||||
vmess["uuid"] = values["id"]
|
||||
vmess["alterId"] = values["aid"]
|
||||
vmess["cipher"] = "auto"
|
||||
vmess["udp"] = true
|
||||
vmess["skip-cert-verify"] = false
|
||||
|
||||
host := values["host"]
|
||||
network := strings.ToLower(values["net"].(string))
|
||||
|
||||
vmess["network"] = network
|
||||
|
||||
tls := strings.ToLower(values["tls"].(string))
|
||||
if tls != "" && tls != "0" && tls != "null" {
|
||||
if host != nil {
|
||||
vmess["servername"] = host
|
||||
}
|
||||
vmess["tls"] = true
|
||||
}
|
||||
|
||||
if network == "ws" {
|
||||
headers := make(map[string]any)
|
||||
wsOpts := make(map[string]any)
|
||||
|
||||
headers["Host"] = RandHost()
|
||||
headers["User-Agent"] = RandUserAgent()
|
||||
|
||||
if values["path"] != nil {
|
||||
wsOpts["path"] = values["path"]
|
||||
}
|
||||
wsOpts["headers"] = headers
|
||||
|
||||
vmess["ws-opts"] = wsOpts
|
||||
}
|
||||
|
||||
proxies = append(proxies, vmess)
|
||||
case "ss":
|
||||
urlSS, err := url.Parse(line)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
name := uniqueName(names, urlSS.Fragment)
|
||||
port := urlSS.Port()
|
||||
|
||||
if port == "" {
|
||||
dcBuf, err := enc.DecodeString(urlSS.Host)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
urlSS, err = url.Parse("ss://" + string(dcBuf))
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
cipher = urlSS.User.Username()
|
||||
password string
|
||||
)
|
||||
|
||||
if password, found = urlSS.User.Password(); !found {
|
||||
dcBuf, err := enc.DecodeString(cipher)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
cipher, password, found = strings.Cut(string(dcBuf), ":")
|
||||
if !found {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
ss := make(map[string]any, 20)
|
||||
|
||||
ss["name"] = name
|
||||
ss["type"] = scheme
|
||||
ss["server"] = urlSS.Hostname()
|
||||
ss["port"] = urlSS.Port()
|
||||
ss["cipher"] = cipher
|
||||
ss["password"] = password
|
||||
ss["udp"] = true
|
||||
|
||||
proxies = append(proxies, ss)
|
||||
case "ssr":
|
||||
dcBuf, err := enc.DecodeString(body)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// ssr://host:port:protocol:method:obfs:urlsafebase64pass/?obfsparam=urlsafebase64&protoparam=&remarks=urlsafebase64&group=urlsafebase64&udpport=0&uot=1
|
||||
|
||||
before, after, ok := strings.Cut(string(dcBuf), "/?")
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
beforeArr := strings.Split(before, ":")
|
||||
|
||||
if len(beforeArr) != 6 {
|
||||
continue
|
||||
}
|
||||
|
||||
host := beforeArr[0]
|
||||
port := beforeArr[1]
|
||||
protocol := beforeArr[2]
|
||||
method := beforeArr[3]
|
||||
obfs := beforeArr[4]
|
||||
password := decodeUrlSafe(urlSafe(beforeArr[5]))
|
||||
|
||||
query, err := url.ParseQuery(urlSafe(after))
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
remarks := decodeUrlSafe(query.Get("remarks"))
|
||||
name := uniqueName(names, remarks)
|
||||
|
||||
obfsParam := decodeUrlSafe(query.Get("obfsparam"))
|
||||
protocolParam := query.Get("protoparam")
|
||||
|
||||
ssr := make(map[string]any, 20)
|
||||
|
||||
ssr["name"] = name
|
||||
ssr["type"] = scheme
|
||||
ssr["server"] = host
|
||||
ssr["port"] = port
|
||||
ssr["cipher"] = method
|
||||
ssr["password"] = password
|
||||
ssr["obfs"] = obfs
|
||||
ssr["protocol"] = protocol
|
||||
ssr["udp"] = true
|
||||
|
||||
if obfsParam != "" {
|
||||
ssr["obfs-param"] = obfsParam
|
||||
}
|
||||
|
||||
if protocolParam != "" {
|
||||
ssr["protocol-param"] = protocolParam
|
||||
}
|
||||
|
||||
proxies = append(proxies, ssr)
|
||||
case "vless":
|
||||
urlVless, err := url.Parse(line)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
query := urlVless.Query()
|
||||
|
||||
name := uniqueName(names, urlVless.Fragment)
|
||||
vless := make(map[string]any, 20)
|
||||
|
||||
vless["name"] = name
|
||||
vless["type"] = scheme
|
||||
vless["server"] = urlVless.Hostname()
|
||||
vless["port"] = urlVless.Port()
|
||||
vless["uuid"] = urlVless.User.Username()
|
||||
vless["udp"] = true
|
||||
vless["skip-cert-verify"] = false
|
||||
|
||||
sni := query.Get("sni")
|
||||
if sni != "" {
|
||||
vless["servername"] = sni
|
||||
}
|
||||
|
||||
flow := strings.ToLower(query.Get("flow"))
|
||||
if flow != "" {
|
||||
vless["flow"] = flow
|
||||
}
|
||||
|
||||
network := strings.ToLower(query.Get("type"))
|
||||
if network != "" {
|
||||
vless["network"] = network
|
||||
}
|
||||
|
||||
if network == "ws" {
|
||||
headers := make(map[string]any)
|
||||
wsOpts := make(map[string]any)
|
||||
|
||||
headers["Host"] = RandHost()
|
||||
headers["User-Agent"] = RandUserAgent()
|
||||
|
||||
wsOpts["path"] = query.Get("path")
|
||||
wsOpts["headers"] = headers
|
||||
|
||||
vless["ws-opts"] = wsOpts
|
||||
}
|
||||
|
||||
proxies = append(proxies, vless)
|
||||
}
|
||||
}
|
||||
|
||||
if len(proxies) == 0 {
|
||||
return nil, fmt.Errorf("convert v2ray subscribe error: format invalid")
|
||||
}
|
||||
|
||||
return proxies, nil
|
||||
}
|
||||
|
||||
func urlSafe(data string) string {
|
||||
return strings.ReplaceAll(strings.ReplaceAll(data, "+", "-"), "/", "_")
|
||||
}
|
||||
|
||||
func decodeUrlSafe(data string) string {
|
||||
dcBuf, err := base64.URLEncoding.DecodeString(data)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return string(dcBuf)
|
||||
}
|
||||
|
||||
func uniqueName(names map[string]int, name string) string {
|
||||
if index, ok := names[name]; ok {
|
||||
index++
|
||||
names[name] = index
|
||||
name = fmt.Sprintf("%s-%02d", name, index)
|
||||
} else {
|
||||
index = 0
|
||||
names[name] = index
|
||||
}
|
||||
return name
|
||||
}
|
@ -1,315 +0,0 @@
|
||||
package convert
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/gofrs/uuid"
|
||||
)
|
||||
|
||||
var hostsSuffix = []string{
|
||||
"-cdn.aliyuncs.com",
|
||||
".alicdn.com",
|
||||
".pan.baidu.com",
|
||||
".tbcache.com",
|
||||
".aliyuncdn.com",
|
||||
".vod.miguvideo.com",
|
||||
".cibntv.net",
|
||||
".myqcloud.com",
|
||||
".smtcdns.com",
|
||||
".alikunlun.com",
|
||||
".smtcdns.net",
|
||||
".apcdns.net",
|
||||
".cdn-go.cn",
|
||||
".cdntip.com",
|
||||
".cdntips.com",
|
||||
".alidayu.com",
|
||||
".alidns.com",
|
||||
".cdngslb.com",
|
||||
".mxhichina.com",
|
||||
}
|
||||
|
||||
var userAgents = []string{
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.162 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.85 Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 7.0; Moto C Build/NRD90M.059) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 6.0.1; SM-G532M Build/MMB29T; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/55.0.2883.91 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.80 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.101 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.2; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/40.0.2214.111 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.94 Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 5.1.1; SM-J120M Build/LMY47X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.80 Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 7.0; Moto G (5) Build/NPPS25.137-93-14) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.106 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 7.0; SM-G570M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.80 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 5.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.80 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.112 Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 6.0; CAM-L03 Build/HUAWEICAM-L03) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.76 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.117 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/534.3 (KHTML, like Gecko) Chrome/6.0.472.63 Safari/534.3",
|
||||
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.106 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/534.7 (KHTML, like Gecko) Chrome/7.0.517.44 Safari/534.7",
|
||||
"Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.75 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.71 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/534.3 (KHTML, like Gecko) Chrome/6.0.472.63 Safari/534.3",
|
||||
"Mozilla/5.0 (Linux; Android 8.0.0; FIG-LX3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.80 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/40.0.2214.115 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/534.10 (KHTML, like Gecko) Chrome/8.0.552.237 Safari/534.10",
|
||||
"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36",
|
||||
"Mozilla/5.0 (X11; U; Linux x86_64; en-US) AppleWebKit/533.2 (KHTML, like Gecko) Chrome/5.0.342.1 Safari/533.2",
|
||||
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.110 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.89 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.81 Safari/537.36",
|
||||
"Mozilla/5.0 (X11; Datanyze; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.71 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.1916.153 Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 5.1.1; SM-J111M Build/LMY47V) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.106 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/37.0.2062.120 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.84 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.99 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/32.0.1700.107 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 6.0.1; SM-J700M Build/MMB29K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.63 Safari/537.36",
|
||||
"Mozilla/5.0 (X11; Linux i686) AppleWebKit/534.30 (KHTML, like Gecko) Slackware/Chrome/12.0.742.100 Safari/534.30",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.86 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.167 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/48.0.2564.116 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.102 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.143 Safari/537.36",
|
||||
"Mozilla/5.0 (X11; Linux i686) AppleWebKit/534.30 (KHTML, like Gecko) Chrome/12.0.742.100 Safari/534.30",
|
||||
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 8.0.0; WAS-LX3 Build/HUAWEIWAS-LX3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.99 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.87 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.102 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.57 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.101 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.1805 Safari/537.36 MVisionPlayer/1.0.0.0",
|
||||
"Mozilla/5.0 (Linux; Android 7.0; TRT-LX3 Build/HUAWEITRT-LX3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.89 Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 6.0; vivo 1610 Build/MMB29M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.124 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.2; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.87 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.111 Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 4.4.2; de-de; SAMSUNG GT-I9195 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Version/1.5 Chrome/28.0.1500.94 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.95 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.90 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.110 Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 8.0.0; ANE-LX3 Build/HUAWEIANE-LX3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.112 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.87 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.143 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36",
|
||||
"Mozilla/5.0 (X11; U; Linux i586; en-US) AppleWebKit/533.2 (KHTML, like Gecko) Chrome/5.0.342.1 Safari/533.2",
|
||||
"Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/43.0.2357.65 Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 7.0; SM-G610M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.80 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 6.0.1; SM-J500M Build/MMB29M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/534.7 (KHTML, like Gecko) Chrome/7.0.517.44 Safari/534.7",
|
||||
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.104 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 6.0; vivo 1606 Build/MMB29M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.124 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36",
|
||||
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.135 Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 7.0; SM-G610M Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 7.1; vivo 1716 Build/N2G47H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.98 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.93 Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 7.0; SM-G570M Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 6.0; MYA-L22 Build/HUAWEIMYA-L22) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.84 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 5.1; A1601 Build/LMY47I) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.98 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 7.0; TRT-LX2 Build/HUAWEITRT-LX2; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/59.0.3071.125 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.2; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 5.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/534.17 (KHTML, like Gecko) Chrome/10.0.649.0 Safari/534.17",
|
||||
"Mozilla/5.0 (Linux; Android 6.0; CAM-L21 Build/HUAWEICAM-L21; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/62.0.3202.84 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.94 Safari/537.36",
|
||||
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/534.24 (KHTML, like Gecko) Chrome/11.0.696.3 Safari/534.24",
|
||||
"Mozilla/5.0 (Linux; Android 7.1.2; Redmi 4X Build/N2G47H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.111 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.106 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.181 Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 4.4.2; SM-G7102 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.84 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/48.0.2564.109 Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 5.1; HUAWEI CUN-L22 Build/HUAWEICUN-L22; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/62.0.3202.84 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 5.1.1; A37fw Build/LMY47V) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.84 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 7.0; SM-J730GM Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.111 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 7.0; SM-G610F Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.111 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.101 Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 7.1.2; Redmi Note 5A Build/N2G47H; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/63.0.3239.111 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 7.0; Redmi Note 4 Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.111 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.106 Safari/537.36",
|
||||
"Mozilla/5.0 (Unknown; Linux) AppleWebKit/538.1 (KHTML, like Gecko) Chrome/v1.0.0 Safari/538.1",
|
||||
"Mozilla/5.0 (Linux; Android 7.0; BLL-L22 Build/HUAWEIBLL-L22) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.91 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 7.0; SM-J710F Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.84 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 6.0.1; SM-G532M Build/MMB29T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.91 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 7.1.1; CPH1723 Build/N6F26Q) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.98 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.118 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.79 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.101 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.94 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.139 Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 8.0.0; FIG-LX3 Build/HUAWEIFIG-LX3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Windows; U; Windows NT 6.1; de-DE) AppleWebKit/534.17 (KHTML, like Gecko) Chrome/10.0.649.0 Safari/534.17",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.63 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2228.0 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.67 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.94 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.99 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.67 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.67 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.67 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/43.0.2357.65 Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 7.1; Mi A1 Build/N2G47H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.83 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.117 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/533.4 (KHTML, like Gecko) Chrome/5.0.375.99 Safari/533.4",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/36.0.1985.125 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.89 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.111 Safari/537.36 MVisionPlayer/1.0.0.0",
|
||||
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.103 Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 5.1; A37f Build/LMY47V) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/43.0.2357.93 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.2; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.100 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.86 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.76 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 5.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.80 Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 6.0.1; CPH1607 Build/MMB29M; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/63.0.3239.111 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.99 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.95 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.100 Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 6.0.1; vivo 1603 Build/MMB29M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.83 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.186 Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 6.0.1; SM-G532M Build/MMB29T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.87 Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 6.0.1; Redmi 4A Build/MMB29M; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/60.0.3112.116 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.103 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.112 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36",
|
||||
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/44.0.2403.157 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.71 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 5.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.2; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.90 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.100 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.31 (KHTML, like Gecko) Chrome/26.0.1410.64 Safari/537.31",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.84 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.186 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.80 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/36.0.1985.143 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.112 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.67 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.181 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.106 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 6.0.1; SM-G532G Build/MMB29T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.83 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/48.0.2564.109 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.117 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.139 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.67 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/34.0.1847.131 Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 6.0; vivo 1713 Build/MRA58K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.124 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.89 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.80 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.101 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.67 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36",
|
||||
}
|
||||
|
||||
var (
|
||||
hostsLen = len(hostsSuffix)
|
||||
uaLen = len(userAgents)
|
||||
)
|
||||
|
||||
func RandHost() string {
|
||||
id, _ := uuid.NewV4()
|
||||
base := strings.ToLower(base64.RawURLEncoding.EncodeToString(id.Bytes()))
|
||||
base = strings.ReplaceAll(base, "-", "")
|
||||
base = strings.ReplaceAll(base, "_", "")
|
||||
buf := []byte(base)
|
||||
prefix := string(buf[:3]) + "---"
|
||||
prefix += string(buf[6:8]) + "-"
|
||||
prefix += string(buf[len(buf)-8:])
|
||||
|
||||
return prefix + hostsSuffix[rand.Intn(hostsLen)]
|
||||
}
|
||||
|
||||
func RandUserAgent() string {
|
||||
return userAgents[rand.Intn(uaLen)]
|
||||
}
|
||||
|
||||
func SetUserAgent(header http.Header) {
|
||||
if header.Get("User-Agent") != "" {
|
||||
return
|
||||
}
|
||||
userAgent := RandUserAgent()
|
||||
header.Set("User-Agent", userAgent)
|
||||
}
|
@ -4,6 +4,8 @@ import (
|
||||
"io"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/Dreamacro/clash/common/pool"
|
||||
)
|
||||
|
||||
// Relay copies between left and right bidirectionally.
|
||||
@ -14,14 +16,18 @@ func Relay(leftConn, rightConn net.Conn) {
|
||||
tcpKeepAlive(rightConn)
|
||||
|
||||
go func() {
|
||||
buf := pool.Get(pool.RelayBufferSize)
|
||||
// Wrapping to avoid using *net.TCPConn.(ReadFrom)
|
||||
// See also https://github.com/Dreamacro/clash/pull/1209
|
||||
_, err := io.Copy(WriteOnlyWriter{Writer: leftConn}, ReadOnlyReader{Reader: rightConn})
|
||||
_, err := io.CopyBuffer(WriteOnlyWriter{Writer: leftConn}, ReadOnlyReader{Reader: rightConn}, buf)
|
||||
_ = pool.Put(buf)
|
||||
_ = leftConn.SetReadDeadline(time.Now())
|
||||
ch <- err
|
||||
}()
|
||||
|
||||
_, _ = io.Copy(WriteOnlyWriter{Writer: rightConn}, ReadOnlyReader{Reader: leftConn})
|
||||
buf := pool.Get(pool.RelayBufferSize)
|
||||
_, _ = io.CopyBuffer(WriteOnlyWriter{Writer: rightConn}, ReadOnlyReader{Reader: leftConn}, buf)
|
||||
_ = pool.Put(buf)
|
||||
_ = rightConn.SetReadDeadline(time.Now())
|
||||
<-ch
|
||||
}
|
||||
|
@ -52,8 +52,8 @@ func (alloc *Allocator) Put(buf []byte) error {
|
||||
return errors.New("allocator Put() incorrect buffer size")
|
||||
}
|
||||
|
||||
//nolint
|
||||
//lint:ignore SA6002 ignore temporarily
|
||||
//nolint
|
||||
alloc.buffers[bits].Put(buf)
|
||||
return nil
|
||||
}
|
||||
|
@ -25,6 +25,7 @@ type Result[T any] struct {
|
||||
}
|
||||
|
||||
// Do single.Do likes sync.singleFlight
|
||||
//lint:ignore ST1008 it likes sync.singleFlight
|
||||
func (s *Single[T]) Do(fn func() (T, error)) (v T, err error, shared bool) {
|
||||
s.mux.Lock()
|
||||
now := time.Now()
|
||||
|
@ -2,11 +2,9 @@ package geodata
|
||||
|
||||
import (
|
||||
"github.com/Dreamacro/clash/component/geodata/router"
|
||||
|
||||
"golang.org/x/exp/maps"
|
||||
)
|
||||
|
||||
func loadGeoSiteMatcher(countryCode string) (*router.DomainMatcher, int, error) {
|
||||
func LoadGeoSiteMatcher(countryCode string) (*router.DomainMatcher, int, error) {
|
||||
geoLoaderName := "standard"
|
||||
geoLoader, err := GetGeoDataLoader(geoLoaderName)
|
||||
if err != nil {
|
||||
@ -30,33 +28,3 @@ func loadGeoSiteMatcher(countryCode string) (*router.DomainMatcher, int, error)
|
||||
|
||||
return matcher, len(domains), nil
|
||||
}
|
||||
|
||||
var ruleProviders = make(map[string]*router.DomainMatcher)
|
||||
|
||||
// HasProvider has geo site provider by county code
|
||||
func HasProvider(countyCode string) (ok bool) {
|
||||
_, ok = ruleProviders[countyCode]
|
||||
return ok
|
||||
}
|
||||
|
||||
// GetProvidersList get geo site providers
|
||||
func GetProvidersList(countyCode string) []*router.DomainMatcher {
|
||||
return maps.Values(ruleProviders)
|
||||
}
|
||||
|
||||
// GetProviderByCode get geo site provider by county code
|
||||
func GetProviderByCode(countyCode string) (matcher *router.DomainMatcher, ok bool) {
|
||||
matcher, ok = ruleProviders[countyCode]
|
||||
return
|
||||
}
|
||||
|
||||
func LoadProviderByCode(countyCode string) (matcher *router.DomainMatcher, count int, err error) {
|
||||
var ok bool
|
||||
matcher, ok = ruleProviders[countyCode]
|
||||
if !ok {
|
||||
if matcher, count, err = loadGeoSiteMatcher(countyCode); err == nil {
|
||||
ruleProviders[countyCode] = matcher
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
@ -28,9 +28,6 @@ func ShouldFindProcess(metadata *C.Metadata) bool {
|
||||
if metadata.Process != "" {
|
||||
return false
|
||||
}
|
||||
if metadata.SrcIP.IsUnspecified() {
|
||||
return true
|
||||
}
|
||||
for _, ip := range localIPs {
|
||||
if ip == metadata.SrcIP {
|
||||
return true
|
||||
@ -44,7 +41,7 @@ func AppendLocalIPs(ip ...netip.Addr) {
|
||||
}
|
||||
|
||||
func getLocalIPs() []netip.Addr {
|
||||
var ips []netip.Addr
|
||||
ips := []netip.Addr{netip.IPv4Unspecified(), netip.IPv6Unspecified()}
|
||||
|
||||
netInterfaces, err := net.Interfaces()
|
||||
if err != nil {
|
||||
|
@ -37,17 +37,17 @@ var (
|
||||
)
|
||||
|
||||
type Resolver interface {
|
||||
ResolveIP(host string, random bool) (ip netip.Addr, err error)
|
||||
ResolveIPv4(host string, random bool) (ip netip.Addr, err error)
|
||||
ResolveIPv6(host string, random bool) (ip netip.Addr, err error)
|
||||
ResolveIP(host string) (ip netip.Addr, err error)
|
||||
ResolveIPv4(host string) (ip netip.Addr, err error)
|
||||
ResolveIPv6(host string) (ip netip.Addr, err error)
|
||||
}
|
||||
|
||||
// ResolveIPv4 with a host, return ipv4
|
||||
func ResolveIPv4(host string) (netip.Addr, error) {
|
||||
return resolveIPv4(host, true)
|
||||
return ResolveIPv4WithResolver(host, DefaultResolver)
|
||||
}
|
||||
|
||||
func ResolveIPv4WithResolver(host string, r Resolver, random bool) (netip.Addr, error) {
|
||||
func ResolveIPv4WithResolver(host string, r Resolver) (netip.Addr, error) {
|
||||
if node := DefaultHosts.Search(host); node != nil {
|
||||
if ip := node.Data; ip.Is4() {
|
||||
return ip, nil
|
||||
@ -56,7 +56,6 @@ func ResolveIPv4WithResolver(host string, r Resolver, random bool) (netip.Addr,
|
||||
|
||||
ip, err := netip.ParseAddr(host)
|
||||
if err == nil {
|
||||
ip = ip.Unmap()
|
||||
if ip.Is4() {
|
||||
return ip, nil
|
||||
}
|
||||
@ -64,7 +63,7 @@ func ResolveIPv4WithResolver(host string, r Resolver, random bool) (netip.Addr,
|
||||
}
|
||||
|
||||
if r != nil {
|
||||
return r.ResolveIPv4(host, random)
|
||||
return r.ResolveIPv4(host)
|
||||
}
|
||||
|
||||
if DefaultResolver == nil {
|
||||
@ -77,11 +76,7 @@ func ResolveIPv4WithResolver(host string, r Resolver, random bool) (netip.Addr,
|
||||
return netip.Addr{}, ErrIPNotFound
|
||||
}
|
||||
|
||||
index := 0
|
||||
if random {
|
||||
index = rand.Intn(len(ipAddrs))
|
||||
}
|
||||
ip := ipAddrs[index].To4()
|
||||
ip := ipAddrs[rand.Intn(len(ipAddrs))].To4()
|
||||
if ip == nil {
|
||||
return netip.Addr{}, ErrIPVersion
|
||||
}
|
||||
@ -94,10 +89,10 @@ func ResolveIPv4WithResolver(host string, r Resolver, random bool) (netip.Addr,
|
||||
|
||||
// ResolveIPv6 with a host, return ipv6
|
||||
func ResolveIPv6(host string) (netip.Addr, error) {
|
||||
return ResolveIPv6WithResolver(host, DefaultResolver, true)
|
||||
return ResolveIPv6WithResolver(host, DefaultResolver)
|
||||
}
|
||||
|
||||
func ResolveIPv6WithResolver(host string, r Resolver, random bool) (netip.Addr, error) {
|
||||
func ResolveIPv6WithResolver(host string, r Resolver) (netip.Addr, error) {
|
||||
if DisableIPv6 {
|
||||
return netip.Addr{}, ErrIPv6Disabled
|
||||
}
|
||||
@ -117,7 +112,7 @@ func ResolveIPv6WithResolver(host string, r Resolver, random bool) (netip.Addr,
|
||||
}
|
||||
|
||||
if r != nil {
|
||||
return r.ResolveIPv6(host, random)
|
||||
return r.ResolveIPv6(host)
|
||||
}
|
||||
|
||||
if DefaultResolver == nil {
|
||||
@ -130,29 +125,25 @@ func ResolveIPv6WithResolver(host string, r Resolver, random bool) (netip.Addr,
|
||||
return netip.Addr{}, ErrIPNotFound
|
||||
}
|
||||
|
||||
index := 0
|
||||
if random {
|
||||
index = rand.Intn(len(ipAddrs))
|
||||
}
|
||||
return netip.AddrFrom16(*(*[16]byte)(ipAddrs[index])), nil
|
||||
return netip.AddrFrom16(*(*[16]byte)(ipAddrs[rand.Intn(len(ipAddrs))])), nil
|
||||
}
|
||||
|
||||
return netip.Addr{}, ErrIPNotFound
|
||||
}
|
||||
|
||||
// ResolveIPWithResolver same as ResolveIP, but with a resolver
|
||||
func ResolveIPWithResolver(host string, r Resolver, random bool) (netip.Addr, error) {
|
||||
func ResolveIPWithResolver(host string, r Resolver) (netip.Addr, error) {
|
||||
if node := DefaultHosts.Search(host); node != nil {
|
||||
return node.Data, nil
|
||||
}
|
||||
|
||||
if r != nil {
|
||||
if DisableIPv6 {
|
||||
return r.ResolveIPv4(host, random)
|
||||
return r.ResolveIPv4(host)
|
||||
}
|
||||
return r.ResolveIP(host, random)
|
||||
return r.ResolveIP(host)
|
||||
} else if DisableIPv6 {
|
||||
return resolveIPv4(host, random)
|
||||
return ResolveIPv4(host)
|
||||
}
|
||||
|
||||
ip, err := netip.ParseAddr(host)
|
||||
@ -174,18 +165,13 @@ func ResolveIPWithResolver(host string, r Resolver, random bool) (netip.Addr, er
|
||||
|
||||
// ResolveIP with a host, return ip
|
||||
func ResolveIP(host string) (netip.Addr, error) {
|
||||
return resolveIP(host, true)
|
||||
}
|
||||
|
||||
// ResolveFirstIP with a host, return ip
|
||||
func ResolveFirstIP(host string) (netip.Addr, error) {
|
||||
return resolveIP(host, false)
|
||||
return ResolveIPWithResolver(host, DefaultResolver)
|
||||
}
|
||||
|
||||
// ResolveIPv4ProxyServerHost proxies server host only
|
||||
func ResolveIPv4ProxyServerHost(host string) (netip.Addr, error) {
|
||||
if ProxyServerHostResolver != nil {
|
||||
return ResolveIPv4WithResolver(host, ProxyServerHostResolver, true)
|
||||
return ResolveIPv4WithResolver(host, ProxyServerHostResolver)
|
||||
}
|
||||
return ResolveIPv4(host)
|
||||
}
|
||||
@ -193,7 +179,7 @@ func ResolveIPv4ProxyServerHost(host string) (netip.Addr, error) {
|
||||
// ResolveIPv6ProxyServerHost proxies server host only
|
||||
func ResolveIPv6ProxyServerHost(host string) (netip.Addr, error) {
|
||||
if ProxyServerHostResolver != nil {
|
||||
return ResolveIPv6WithResolver(host, ProxyServerHostResolver, true)
|
||||
return ResolveIPv6WithResolver(host, ProxyServerHostResolver)
|
||||
}
|
||||
return ResolveIPv6(host)
|
||||
}
|
||||
@ -201,15 +187,7 @@ func ResolveIPv6ProxyServerHost(host string) (netip.Addr, error) {
|
||||
// ResolveProxyServerHost proxies server host only
|
||||
func ResolveProxyServerHost(host string) (netip.Addr, error) {
|
||||
if ProxyServerHostResolver != nil {
|
||||
return ResolveIPWithResolver(host, ProxyServerHostResolver, true)
|
||||
return ResolveIPWithResolver(host, ProxyServerHostResolver)
|
||||
}
|
||||
return ResolveIP(host)
|
||||
}
|
||||
|
||||
func resolveIP(host string, random bool) (netip.Addr, error) {
|
||||
return ResolveIPWithResolver(host, DefaultResolver, random)
|
||||
}
|
||||
|
||||
func resolveIPv4(host string, random bool) (netip.Addr, error) {
|
||||
return ResolveIPv4WithResolver(host, DefaultResolver, random)
|
||||
}
|
||||
|
10
component/script/build_actions.go
Normal file
10
component/script/build_actions.go
Normal file
@ -0,0 +1,10 @@
|
||||
//go:build build_actions
|
||||
|
||||
package script
|
||||
|
||||
/*
|
||||
#cgo windows,amd64 CFLAGS: -ID:/python-amd64/include -DMS_WIN64
|
||||
|
||||
#cgo windows,amd64 LDFLAGS: -LD:/python-amd64/libs -lpython39 -lpthread -lm
|
||||
*/
|
||||
import "C"
|
8
component/script/build_local.go
Normal file
8
component/script/build_local.go
Normal file
@ -0,0 +1,8 @@
|
||||
//go:build build_local
|
||||
|
||||
package script
|
||||
|
||||
/*
|
||||
#cgo pkg-config: python3-embed
|
||||
*/
|
||||
import "C"
|
24
component/script/build_xgo.go
Normal file
24
component/script/build_xgo.go
Normal file
@ -0,0 +1,24 @@
|
||||
//go:build !build_local
|
||||
|
||||
package script
|
||||
|
||||
/*
|
||||
#cgo linux,amd64 pkg-config: python3-embed
|
||||
|
||||
#cgo darwin,amd64 CFLAGS: -I/build/python/python-3.9.7-darwin-amd64/include/python3.9
|
||||
#cgo darwin,arm64 CFLAGS: -I/build/python/python-3.9.7-darwin-arm64/include/python3.9
|
||||
#cgo windows,amd64 CFLAGS: -I/build/python/python-3.9.7-windows-amd64/include -DMS_WIN64
|
||||
#cgo windows,386 CFLAGS: -I/build/python/python-3.9.7-windows-386/include
|
||||
//#cgo linux,amd64 CFLAGS: -I/home/runner/work/clash/clash/bin/python/python-3.9.7-linux-amd64/include/python3.9
|
||||
//#cgo linux,arm64 CFLAGS: -I/build/python/python-3.9.7-linux-arm64/include/python3.9
|
||||
//#cgo linux,386 CFLAGS: -I/build/python/python-3.9.7-linux-386/include/python3.9
|
||||
|
||||
#cgo darwin,amd64 LDFLAGS: -L/build/python/python-3.9.7-darwin-amd64/lib -lpython3.9 -ldl -framework CoreFoundation
|
||||
#cgo darwin,arm64 LDFLAGS: -L/build/python/python-3.9.7-darwin-arm64/lib -lpython3.9 -ldl -framework CoreFoundation
|
||||
#cgo windows,amd64 LDFLAGS: -L/build/python/python-3.9.7-windows-amd64/lib -lpython39 -lpthread -lm
|
||||
#cgo windows,386 LDFLAGS: -L/build/python/python-3.9.7-windows-386/lib -lpython39 -lpthread -lm
|
||||
//#cgo linux,amd64 LDFLAGS: -L/home/runner/work/clash/clash/bin/python/python-3.9.7-linux-amd64/lib -lpython3.9 -lpthread -ldl -lutil -lm
|
||||
//#cgo linux,arm64 LDFLAGS: -L/build/python/python-3.9.7-linux-arm64/lib -lpython3.9 -lpthread -ldl -lutil -lm
|
||||
//#cgo linux,386 LDFLAGS: -L/build/python/python-3.9.7-linux-386/lib -lpython3.9 -lpthread -ldl -lutil -lm
|
||||
*/
|
||||
import "C"
|
742
component/script/clash_module.c
Normal file
742
component/script/clash_module.c
Normal file
@ -0,0 +1,742 @@
|
||||
#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:H, s:s, s:H}",
|
||||
"ctx", clash_context,
|
||||
"network", network,
|
||||
"process_name", process_name,
|
||||
"process_path", process_path,
|
||||
"host", host,
|
||||
"src_ip", src_ip,
|
||||
"src_port", src_port,
|
||||
"dst_ip", dst_ip,
|
||||
"dst_port", dst_port); //Return value: New reference.
|
||||
|
||||
if (args == NULL) {
|
||||
PyErr_SetString(PyExc_TypeError,
|
||||
"Py_BuildValue failure");
|
||||
return -1;
|
||||
}
|
||||
|
||||
PyObject *tupleArgs = PyTuple_New(0); //Return value: New reference.
|
||||
|
||||
Py_INCREF(clash_context);
|
||||
Py_INCREF(shortcut_fn);
|
||||
result = PyObject_Call(shortcut_fn, tupleArgs, args); //Return value: New reference.
|
||||
Py_DECREF(shortcut_fn);
|
||||
Py_DECREF(clash_context);
|
||||
Py_DECREF(tupleArgs);
|
||||
Py_DECREF(args);
|
||||
|
||||
if (result == NULL) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!PyBool_Check(result)) {
|
||||
Py_DECREF(result);
|
||||
PyErr_SetString(PyExc_TypeError,
|
||||
"script shortcut return value must be as boolean");
|
||||
return -1;
|
||||
}
|
||||
|
||||
int rs = (result == Py_True) ? 1 : 0;
|
||||
|
||||
Py_DECREF(result);
|
||||
|
||||
return rs;
|
||||
}
|
||||
|
||||
void finalize_Python() {
|
||||
Py_CLEAR(main_fn);
|
||||
Py_CLEAR(clash_context);
|
||||
Py_CLEAR(clash_module);
|
||||
Py_FinalizeEx();
|
||||
|
||||
clash_module = NULL;
|
||||
main_fn = NULL;
|
||||
clash_context = NULL;
|
||||
}
|
||||
|
||||
/* --------------------------------------------------------------------- */
|
338
component/script/clash_module.go
Normal file
338
component/script/clash_module.go
Normal file
@ -0,0 +1,338 @@
|
||||
package script
|
||||
|
||||
/*
|
||||
#include "clash_module.h"
|
||||
|
||||
extern const char *resolveIPCallbackFn(const char *host);
|
||||
|
||||
void
|
||||
go_set_resolve_ip_callback() {
|
||||
set_resolve_ip_callback(resolveIPCallbackFn);
|
||||
}
|
||||
|
||||
extern const char *geoipCallbackFn(const char *ip);
|
||||
|
||||
void
|
||||
go_set_geoip_callback() {
|
||||
set_geoip_callback(geoipCallbackFn);
|
||||
}
|
||||
|
||||
extern const int ruleProviderCallbackFn(const char *provider_name, struct Metadata *metadata);
|
||||
|
||||
void
|
||||
go_set_rule_provider_callback() {
|
||||
set_rule_provider_callback(ruleProviderCallbackFn);
|
||||
}
|
||||
|
||||
extern void logCallbackFn(const char *msg);
|
||||
|
||||
void
|
||||
go_set_log_callback() {
|
||||
set_log_callback(logCallbackFn);
|
||||
}
|
||||
*/
|
||||
import "C"
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
"github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/log"
|
||||
)
|
||||
|
||||
const ClashScriptModuleName = C.CLASH_SCRIPT_MODULE_NAME
|
||||
|
||||
var lock sync.Mutex
|
||||
|
||||
type PyObject C.PyObject
|
||||
|
||||
func togo(cobject *C.PyObject) *PyObject {
|
||||
return (*PyObject)(cobject)
|
||||
}
|
||||
|
||||
func toc(object *PyObject) *C.PyObject {
|
||||
return (*C.PyObject)(object)
|
||||
}
|
||||
|
||||
func (pyObject *PyObject) IncRef() {
|
||||
C.Py_IncRef(toc(pyObject))
|
||||
}
|
||||
|
||||
func (pyObject *PyObject) DecRef() {
|
||||
C.Py_DecRef(toc(pyObject))
|
||||
}
|
||||
|
||||
func (pyObject *PyObject) Clear() {
|
||||
C.py_clear(toc(pyObject))
|
||||
}
|
||||
|
||||
// Py_Initialize initialize Python3
|
||||
func Py_Initialize(program string, path string) error {
|
||||
lock.Lock()
|
||||
defer lock.Unlock()
|
||||
|
||||
if C.Py_IsInitialized() != 0 {
|
||||
if pyThreadState != nil {
|
||||
PyEval_RestoreThread(pyThreadState)
|
||||
}
|
||||
C.finalize_Python()
|
||||
}
|
||||
|
||||
path = strings.ReplaceAll(path, "\\", "/")
|
||||
cPath := C.CString(path)
|
||||
|
||||
C.init_python(C.CString(program), cPath)
|
||||
err := PyLastError()
|
||||
|
||||
if err != nil {
|
||||
if C.Py_IsInitialized() != 0 {
|
||||
C.finalize_Python()
|
||||
_ = os.RemoveAll(constant.Path.ScriptDir())
|
||||
}
|
||||
return err
|
||||
} else if C.Py_IsInitialized() == 0 {
|
||||
err = errors.New("initialized script module failure")
|
||||
return err
|
||||
}
|
||||
|
||||
initPython3Callback()
|
||||
return nil
|
||||
}
|
||||
|
||||
func Py_IsInitialized() bool {
|
||||
lock.Lock()
|
||||
defer lock.Unlock()
|
||||
|
||||
return C.Py_IsInitialized() != 0
|
||||
}
|
||||
|
||||
func Py_Finalize() {
|
||||
lock.Lock()
|
||||
defer lock.Unlock()
|
||||
|
||||
if C.Py_IsInitialized() != 0 {
|
||||
if pyThreadState != nil {
|
||||
PyEval_RestoreThread(pyThreadState)
|
||||
}
|
||||
C.finalize_Python()
|
||||
_ = os.RemoveAll(constant.Path.ScriptDir())
|
||||
log.Warnln("Clash clean up script mode.")
|
||||
}
|
||||
}
|
||||
|
||||
//Py_GetVersion get
|
||||
func Py_GetVersion() string {
|
||||
cversion := C.Py_GetVersion()
|
||||
return strings.Split(C.GoString(cversion), "\n")[0]
|
||||
}
|
||||
|
||||
// loadPyFunc loads a Python function by module and function name
|
||||
func loadPyFunc(moduleName, funcName string) (*C.PyObject, error) {
|
||||
// Convert names to C char*
|
||||
cMod := C.CString(moduleName)
|
||||
cFunc := C.CString(funcName)
|
||||
|
||||
// Free memory allocated by C.CString
|
||||
defer func() {
|
||||
C.free(unsafe.Pointer(cMod))
|
||||
C.free(unsafe.Pointer(cFunc))
|
||||
}()
|
||||
|
||||
fnc := C.load_func(cMod, cFunc)
|
||||
if fnc == nil {
|
||||
return nil, PyLastError()
|
||||
}
|
||||
|
||||
return fnc, nil
|
||||
}
|
||||
|
||||
//PyLastError python last error
|
||||
func PyLastError() error {
|
||||
cp := C.py_last_error()
|
||||
if cp == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return errors.New(C.GoString(cp))
|
||||
}
|
||||
|
||||
func LoadShortcutFunction(shortcut string) (*PyObject, error) {
|
||||
fnc, err := loadPyFunc(ClashScriptModuleName, shortcut)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return togo(fnc), nil
|
||||
}
|
||||
|
||||
func LoadMainFunction() error {
|
||||
C.load_main_func()
|
||||
err := PyLastError()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
//CallPyMainFunction call python script main function
|
||||
//return the proxy adapter name.
|
||||
func CallPyMainFunction(mtd *constant.Metadata) (string, error) {
|
||||
_type := C.CString(mtd.Type.String())
|
||||
network := C.CString(mtd.NetWork.String())
|
||||
processName := C.CString(mtd.Process)
|
||||
processPath := C.CString(mtd.ProcessPath)
|
||||
host := C.CString(mtd.Host)
|
||||
|
||||
srcPortGo, _ := strconv.ParseUint(mtd.SrcPort, 10, 16)
|
||||
dstPortGo, _ := strconv.ParseUint(mtd.DstPort, 10, 16)
|
||||
srcPort := C.ushort(srcPortGo)
|
||||
dstPort := C.ushort(dstPortGo)
|
||||
|
||||
dstIpGo := ""
|
||||
srcIpGo := ""
|
||||
if mtd.SrcIP.IsValid() {
|
||||
srcIpGo = mtd.SrcIP.String()
|
||||
}
|
||||
if mtd.DstIP.IsValid() {
|
||||
dstIpGo = mtd.DstIP.String()
|
||||
}
|
||||
srcIp := C.CString(srcIpGo)
|
||||
dstIp := C.CString(dstIpGo)
|
||||
|
||||
defer func() {
|
||||
C.free(unsafe.Pointer(_type))
|
||||
C.free(unsafe.Pointer(network))
|
||||
C.free(unsafe.Pointer(processName))
|
||||
C.free(unsafe.Pointer(processPath))
|
||||
C.free(unsafe.Pointer(host))
|
||||
C.free(unsafe.Pointer(srcIp))
|
||||
C.free(unsafe.Pointer(dstIp))
|
||||
}()
|
||||
|
||||
runtime.LockOSThread()
|
||||
gilState := PyGILState_Ensure()
|
||||
defer PyGILState_Release(gilState)
|
||||
|
||||
cRs := C.call_main(_type, network, processName, processPath, host, srcIp, srcPort, dstIp, dstPort)
|
||||
|
||||
rs := C.GoString(cRs)
|
||||
if rs == "-1" {
|
||||
err := PyLastError()
|
||||
if err != nil {
|
||||
log.Errorln("[Script] script code error: %v", err)
|
||||
killSelf()
|
||||
return "", fmt.Errorf("script code error: %w", err)
|
||||
} else {
|
||||
return "", fmt.Errorf("script code error, result: %v", rs)
|
||||
}
|
||||
}
|
||||
|
||||
return rs, nil
|
||||
}
|
||||
|
||||
//CallPyShortcut call python script shortcuts function
|
||||
//param: shortcut name
|
||||
//return the match result.
|
||||
func CallPyShortcut(fn *PyObject, mtd *constant.Metadata) (bool, error) {
|
||||
_type := C.CString(mtd.Type.String())
|
||||
network := C.CString(mtd.NetWork.String())
|
||||
processName := C.CString(mtd.Process)
|
||||
processPath := C.CString(mtd.ProcessPath)
|
||||
host := C.CString(mtd.Host)
|
||||
|
||||
srcPortGo, _ := strconv.ParseUint(mtd.SrcPort, 10, 16)
|
||||
dstPortGo, _ := strconv.ParseUint(mtd.DstPort, 10, 16)
|
||||
srcPort := C.ushort(srcPortGo)
|
||||
dstPort := C.ushort(dstPortGo)
|
||||
|
||||
dstIpGo := ""
|
||||
srcIpGo := ""
|
||||
if mtd.SrcIP.IsValid() {
|
||||
srcIpGo = mtd.SrcIP.String()
|
||||
}
|
||||
if mtd.DstIP.IsValid() {
|
||||
dstIpGo = mtd.DstIP.String()
|
||||
}
|
||||
srcIp := C.CString(srcIpGo)
|
||||
dstIp := C.CString(dstIpGo)
|
||||
|
||||
defer func() {
|
||||
C.free(unsafe.Pointer(_type))
|
||||
C.free(unsafe.Pointer(network))
|
||||
C.free(unsafe.Pointer(processName))
|
||||
C.free(unsafe.Pointer(processPath))
|
||||
C.free(unsafe.Pointer(host))
|
||||
C.free(unsafe.Pointer(srcIp))
|
||||
C.free(unsafe.Pointer(dstIp))
|
||||
}()
|
||||
|
||||
runtime.LockOSThread()
|
||||
gilState := PyGILState_Ensure()
|
||||
defer PyGILState_Release(gilState)
|
||||
|
||||
cRs := C.call_shortcut(toc(fn), _type, network, processName, processPath, host, srcIp, srcPort, dstIp, dstPort)
|
||||
|
||||
rs := int(cRs)
|
||||
if rs == -1 {
|
||||
err := PyLastError()
|
||||
if err != nil {
|
||||
log.Errorln("[Script] script shortcut code error: %v", err)
|
||||
killSelf()
|
||||
return false, fmt.Errorf("script shortcut code error: %w", err)
|
||||
} else {
|
||||
return false, fmt.Errorf("script shortcut code error: result: %d", rs)
|
||||
}
|
||||
}
|
||||
|
||||
if rs == 1 {
|
||||
return true, nil
|
||||
} else {
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
|
||||
func initPython3Callback() {
|
||||
C.go_set_resolve_ip_callback()
|
||||
C.go_set_geoip_callback()
|
||||
C.go_set_rule_provider_callback()
|
||||
C.go_set_log_callback()
|
||||
}
|
||||
|
||||
//NewClashPyContext new clash context for python
|
||||
func NewClashPyContext(ruleProvidersName []string) error {
|
||||
length := len(ruleProvidersName)
|
||||
cStringArr := make([]*C.char, length)
|
||||
for i, v := range ruleProvidersName {
|
||||
cStringArr[i] = C.CString(v)
|
||||
defer C.free(unsafe.Pointer(cStringArr[i]))
|
||||
}
|
||||
|
||||
cArrPointer := unsafe.Pointer(nil)
|
||||
if length > 0 {
|
||||
cArrPointer = unsafe.Pointer(&cStringArr[0])
|
||||
}
|
||||
|
||||
rs := C.new_clash_py_context((**C.char)(cArrPointer), C.int(length))
|
||||
|
||||
if int(rs) == 0 {
|
||||
err := PyLastError()
|
||||
return fmt.Errorf("new script module context failure: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func killSelf() {
|
||||
p, err := os.FindProcess(os.Getpid())
|
||||
if err != nil {
|
||||
os.Exit(int(syscall.SIGINT))
|
||||
return
|
||||
}
|
||||
_ = p.Signal(syscall.SIGINT)
|
||||
}
|
65
component/script/clash_module.h
Normal file
65
component/script/clash_module.h
Normal file
@ -0,0 +1,65 @@
|
||||
#ifndef CLASH_CALLBACK_MODULE_H__
|
||||
#define CLASH_CALLBACK_MODULE_H__
|
||||
|
||||
#include <Python.h>
|
||||
|
||||
#define CLASH_SCRIPT_MODULE_NAME "clash_script"
|
||||
|
||||
struct Metadata {
|
||||
const char *type; /* type socks5/http */
|
||||
const char *network; /* network tcp/udp */
|
||||
const char *process_name;
|
||||
const char *process_path;
|
||||
const char *host;
|
||||
const char *src_ip;
|
||||
unsigned short src_port;
|
||||
const char *dst_ip;
|
||||
unsigned short dst_port;
|
||||
};
|
||||
|
||||
/** callback function, that call go function by python3 script. **/
|
||||
typedef const char *(*resolve_ip_callback)(const char *host);
|
||||
typedef const char *(*geoip_callback)(const char *ip);
|
||||
typedef const int (*rule_provider_callback)(const char *provider_name, struct Metadata *metadata);
|
||||
typedef void (*log_callback)(const char *msg);
|
||||
|
||||
void set_resolve_ip_callback(resolve_ip_callback cb);
|
||||
void set_geoip_callback(geoip_callback cb);
|
||||
void set_rule_provider_callback(rule_provider_callback cb);
|
||||
void set_log_callback(log_callback cb);
|
||||
/*---------------------------------------------------------------*/
|
||||
|
||||
void append_inittab();
|
||||
void init_python(const char *program, const char *path);
|
||||
void load_main_func();
|
||||
void finalize_Python();
|
||||
void py_clear(PyObject *obj);
|
||||
const char *py_last_error();
|
||||
|
||||
PyObject *load_func(const char *module_name, char *func_name);
|
||||
|
||||
int new_clash_py_context(const char *provider_name_arr[], int size);
|
||||
|
||||
const char *call_main(
|
||||
const char *type,
|
||||
const char *network,
|
||||
const char *process_name,
|
||||
const char *process_path,
|
||||
const char *host,
|
||||
const char *src_ip,
|
||||
unsigned short src_port,
|
||||
const char *dst_ip,
|
||||
unsigned short dst_port);
|
||||
|
||||
int call_shortcut(PyObject *shortcut_fn,
|
||||
const char *type,
|
||||
const char *network,
|
||||
const char *process_name,
|
||||
const char *process_path,
|
||||
const char *host,
|
||||
const char *src_ip,
|
||||
unsigned short src_port,
|
||||
const char *dst_ip,
|
||||
unsigned short dst_port);
|
||||
|
||||
#endif // CLASH_CALLBACK_MODULE_H__
|
145
component/script/clash_module_export.go
Normal file
145
component/script/clash_module_export.go
Normal file
@ -0,0 +1,145 @@
|
||||
package script
|
||||
|
||||
/*
|
||||
#include "clash_module.h"
|
||||
*/
|
||||
import "C"
|
||||
import (
|
||||
"net/netip"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unsafe"
|
||||
|
||||
"github.com/Dreamacro/clash/component/mmdb"
|
||||
"github.com/Dreamacro/clash/component/resolver"
|
||||
"github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/log"
|
||||
)
|
||||
|
||||
var (
|
||||
ruleProviders = map[string]constant.Rule{}
|
||||
pyThreadState *PyThreadState
|
||||
)
|
||||
|
||||
func UpdateRuleProviders(rpd map[string]constant.Rule) {
|
||||
ruleProviders = rpd
|
||||
if Py_IsInitialized() {
|
||||
pyThreadState = PyEval_SaveThread()
|
||||
}
|
||||
}
|
||||
|
||||
//export resolveIPCallbackFn
|
||||
func resolveIPCallbackFn(cHost *C.char) *C.char {
|
||||
host := C.GoString(cHost)
|
||||
if len(host) == 0 {
|
||||
cip := C.CString("")
|
||||
defer C.free(unsafe.Pointer(cip))
|
||||
return cip
|
||||
}
|
||||
if ip, err := resolver.ResolveIP(host); err == nil {
|
||||
cip := C.CString(ip.String())
|
||||
defer C.free(unsafe.Pointer(cip))
|
||||
return cip
|
||||
} else {
|
||||
log.Errorln("[Script] resolve ip error: %s", err.Error())
|
||||
cip := C.CString("")
|
||||
defer C.free(unsafe.Pointer(cip))
|
||||
return cip
|
||||
}
|
||||
}
|
||||
|
||||
//export geoipCallbackFn
|
||||
func geoipCallbackFn(cIP *C.char) *C.char {
|
||||
dstIP, err := netip.ParseAddr(C.GoString(cIP))
|
||||
|
||||
if err != nil {
|
||||
emptyC := C.CString("")
|
||||
defer C.free(unsafe.Pointer(emptyC))
|
||||
|
||||
return emptyC
|
||||
}
|
||||
|
||||
if dstIP.IsPrivate() ||
|
||||
dstIP.IsUnspecified() ||
|
||||
dstIP.IsLoopback() ||
|
||||
dstIP.IsMulticast() ||
|
||||
dstIP.IsLinkLocalUnicast() ||
|
||||
resolver.IsFakeBroadcastIP(dstIP) {
|
||||
|
||||
lanC := C.CString("LAN")
|
||||
defer C.free(unsafe.Pointer(lanC))
|
||||
|
||||
return lanC
|
||||
}
|
||||
|
||||
record, _ := mmdb.Instance().Country(dstIP.AsSlice())
|
||||
|
||||
rc := C.CString(strings.ToUpper(record.Country.IsoCode))
|
||||
defer C.free(unsafe.Pointer(rc))
|
||||
|
||||
return rc
|
||||
}
|
||||
|
||||
//export ruleProviderCallbackFn
|
||||
func ruleProviderCallbackFn(cProviderName *C.char, cMetadata *C.struct_Metadata) C.int {
|
||||
//_type := C.GoString(cMetadata._type)
|
||||
//network := C.GoString(cMetadata.network)
|
||||
processName := C.GoString(cMetadata.process_name)
|
||||
processPath := C.GoString(cMetadata.process_path)
|
||||
host := C.GoString(cMetadata.host)
|
||||
srcIp := C.GoString(cMetadata.src_ip)
|
||||
srcPort := strconv.Itoa(int(cMetadata.src_port))
|
||||
dstIp := C.GoString(cMetadata.dst_ip)
|
||||
dstPort := strconv.Itoa(int(cMetadata.dst_port))
|
||||
|
||||
addrType := constant.AtypDomainName
|
||||
if h, err := netip.ParseAddr(host); err == nil {
|
||||
if h.Is4() {
|
||||
addrType = constant.AtypIPv4
|
||||
} else {
|
||||
addrType = constant.AtypIPv6
|
||||
}
|
||||
}
|
||||
|
||||
src, _ := netip.ParseAddr(srcIp)
|
||||
dst, _ := netip.ParseAddr(dstIp)
|
||||
|
||||
metadata := &constant.Metadata{
|
||||
AddrType: addrType,
|
||||
SrcIP: src,
|
||||
DstIP: dst,
|
||||
SrcPort: srcPort,
|
||||
DstPort: dstPort,
|
||||
Host: host,
|
||||
Process: processName,
|
||||
ProcessPath: processPath,
|
||||
}
|
||||
|
||||
providerName := C.GoString(cProviderName)
|
||||
|
||||
rule, ok := ruleProviders[providerName]
|
||||
if !ok {
|
||||
log.Warnln("[Script] rule provider [%s] not found", providerName)
|
||||
return C.int(0)
|
||||
}
|
||||
|
||||
if strings.HasPrefix(providerName, "geosite:") {
|
||||
if len(host) == 0 {
|
||||
return C.int(0)
|
||||
}
|
||||
metadata.AddrType = constant.AtypDomainName
|
||||
}
|
||||
|
||||
rs := rule.Match(metadata)
|
||||
|
||||
if rs {
|
||||
return C.int(1)
|
||||
}
|
||||
return C.int(0)
|
||||
}
|
||||
|
||||
//export logCallbackFn
|
||||
func logCallbackFn(msg *C.char) {
|
||||
|
||||
log.Infoln(C.GoString(msg))
|
||||
}
|
52
component/script/thread.go
Normal file
52
component/script/thread.go
Normal file
@ -0,0 +1,52 @@
|
||||
package script
|
||||
|
||||
/*
|
||||
#include "Python.h"
|
||||
*/
|
||||
import "C"
|
||||
|
||||
//PyThreadState : https://docs.python.org/3/c-api/init.html#c.PyThreadState
|
||||
type PyThreadState C.PyThreadState
|
||||
|
||||
//PyGILState is an opaque “handle” to the thread state when PyGILState_Ensure() was called, and must be passed to PyGILState_Release() to ensure Python is left in the same state
|
||||
type PyGILState C.PyGILState_STATE
|
||||
|
||||
//PyEval_SaveThread : https://docs.python.org/3/c-api/init.html#c.PyEval_SaveThread
|
||||
func PyEval_SaveThread() *PyThreadState {
|
||||
return (*PyThreadState)(C.PyEval_SaveThread())
|
||||
}
|
||||
|
||||
//PyEval_RestoreThread : https://docs.python.org/3/c-api/init.html#c.PyEval_RestoreThread
|
||||
func PyEval_RestoreThread(tstate *PyThreadState) {
|
||||
C.PyEval_RestoreThread((*C.PyThreadState)(tstate))
|
||||
}
|
||||
|
||||
//PyThreadState_Get : https://docs.python.org/3/c-api/init.html#c.PyThreadState_Get
|
||||
func PyThreadState_Get() *PyThreadState {
|
||||
return (*PyThreadState)(C.PyThreadState_Get())
|
||||
}
|
||||
|
||||
//PyThreadState_Swap : https://docs.python.org/3/c-api/init.html#c.PyThreadState_Swap
|
||||
func PyThreadState_Swap(tstate *PyThreadState) *PyThreadState {
|
||||
return (*PyThreadState)(C.PyThreadState_Swap((*C.PyThreadState)(tstate)))
|
||||
}
|
||||
|
||||
//PyGILState_Ensure : https://docs.python.org/3/c-api/init.html#c.PyGILState_Ensure
|
||||
func PyGILState_Ensure() PyGILState {
|
||||
return PyGILState(C.PyGILState_Ensure())
|
||||
}
|
||||
|
||||
//PyGILState_Release : https://docs.python.org/3/c-api/init.html#c.PyGILState_Release
|
||||
func PyGILState_Release(state PyGILState) {
|
||||
C.PyGILState_Release(C.PyGILState_STATE(state))
|
||||
}
|
||||
|
||||
//PyGILState_GetThisThreadState : https://docs.python.org/3/c-api/init.html#c.PyGILState_GetThisThreadState
|
||||
func PyGILState_GetThisThreadState() *PyThreadState {
|
||||
return (*PyThreadState)(C.PyGILState_GetThisThreadState())
|
||||
}
|
||||
|
||||
//PyGILState_Check : https://docs.python.org/3/c-api/init.html#c.PyGILState_Check
|
||||
func PyGILState_Check() bool {
|
||||
return C.PyGILState_Check() == 1
|
||||
}
|
323
config/config.go
323
config/config.go
@ -7,6 +7,7 @@ import (
|
||||
"net/netip"
|
||||
"net/url"
|
||||
"os"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
@ -19,7 +20,7 @@ import (
|
||||
"github.com/Dreamacro/clash/component/fakeip"
|
||||
"github.com/Dreamacro/clash/component/geodata"
|
||||
"github.com/Dreamacro/clash/component/geodata/router"
|
||||
_ "github.com/Dreamacro/clash/component/geodata/standard"
|
||||
S "github.com/Dreamacro/clash/component/script"
|
||||
"github.com/Dreamacro/clash/component/trie"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
providerTypes "github.com/Dreamacro/clash/constant/provider"
|
||||
@ -30,7 +31,7 @@ import (
|
||||
R "github.com/Dreamacro/clash/rule"
|
||||
T "github.com/Dreamacro/clash/tunnel"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
// General config
|
||||
@ -40,7 +41,6 @@ type General struct {
|
||||
Mode T.TunnelMode `json:"mode"`
|
||||
LogLevel log.LogLevel `json:"log-level"`
|
||||
IPv6 bool `json:"ipv6"`
|
||||
Sniffing bool `json:"sniffing"`
|
||||
Interface string `json:"-"`
|
||||
RoutingMark int `json:"-"`
|
||||
Tun Tun `json:"tun"`
|
||||
@ -99,12 +99,17 @@ type Profile struct {
|
||||
|
||||
// Tun config
|
||||
type Tun struct {
|
||||
Enable bool `yaml:"enable" json:"enable"`
|
||||
Device string `yaml:"device" json:"device"`
|
||||
Stack C.TUNStack `yaml:"stack" json:"stack"`
|
||||
DNSHijack []C.DNSUrl `yaml:"dns-hijack" json:"dns-hijack"`
|
||||
AutoRoute bool `yaml:"auto-route" json:"auto-route"`
|
||||
AutoDetectInterface bool `yaml:"auto-detect-interface" json:"auto-detect-interface"`
|
||||
Enable bool `yaml:"enable" json:"enable"`
|
||||
Device string `yaml:"device" json:"device"`
|
||||
Stack C.TUNStack `yaml:"stack" json:"stack"`
|
||||
DNSHijack []netip.AddrPort `yaml:"dns-hijack" json:"dns-hijack"`
|
||||
AutoRoute bool `yaml:"auto-route" json:"auto-route"`
|
||||
}
|
||||
|
||||
// Script config
|
||||
type Script struct {
|
||||
MainCode string `yaml:"code" json:"code"`
|
||||
ShortcutsCode map[string]string `yaml:"shortcuts" json:"shortcuts"`
|
||||
}
|
||||
|
||||
// IPTables config
|
||||
@ -124,17 +129,19 @@ type Experimental struct{}
|
||||
|
||||
// Config is clash config manager
|
||||
type Config struct {
|
||||
General *General
|
||||
IPTables *IPTables
|
||||
Mitm *Mitm
|
||||
DNS *DNS
|
||||
Experimental *Experimental
|
||||
Hosts *trie.DomainTrie[netip.Addr]
|
||||
Profile *Profile
|
||||
Rules []C.Rule
|
||||
Users []auth.AuthUser
|
||||
Proxies map[string]C.Proxy
|
||||
Providers map[string]providerTypes.ProxyProvider
|
||||
General *General
|
||||
Tun *Tun
|
||||
IPTables *IPTables
|
||||
Mitm *Mitm
|
||||
DNS *DNS
|
||||
Experimental *Experimental
|
||||
Hosts *trie.DomainTrie[netip.Addr]
|
||||
Profile *Profile
|
||||
Rules []C.Rule
|
||||
RuleProviders map[string]C.Rule
|
||||
Users []auth.AuthUser
|
||||
Proxies map[string]C.Proxy
|
||||
Providers map[string]providerTypes.ProxyProvider
|
||||
}
|
||||
|
||||
type RawDNS struct {
|
||||
@ -161,6 +168,14 @@ type RawFallbackFilter struct {
|
||||
GeoSite []string `yaml:"geosite"`
|
||||
}
|
||||
|
||||
type RawTun struct {
|
||||
Enable bool `yaml:"enable" json:"enable"`
|
||||
Device string `yaml:"device" json:"device"`
|
||||
Stack C.TUNStack `yaml:"stack" json:"stack"`
|
||||
DNSHijack []string `yaml:"dns-hijack" json:"dns-hijack"`
|
||||
AutoRoute bool `yaml:"auto-route" json:"auto-route"`
|
||||
}
|
||||
|
||||
type RawMitm struct {
|
||||
Hosts []string `yaml:"hosts" json:"hosts"`
|
||||
Rules []string `yaml:"rules" json:"rules"`
|
||||
@ -184,13 +199,11 @@ type RawConfig struct {
|
||||
Secret string `yaml:"secret"`
|
||||
Interface string `yaml:"interface-name"`
|
||||
RoutingMark int `yaml:"routing-mark"`
|
||||
Sniffing bool `yaml:"sniffing"`
|
||||
ForceCertVerify bool `yaml:"force-cert-verify"`
|
||||
|
||||
ProxyProvider map[string]map[string]any `yaml:"proxy-providers"`
|
||||
Hosts map[string]string `yaml:"hosts"`
|
||||
DNS RawDNS `yaml:"dns"`
|
||||
Tun Tun `yaml:"tun"`
|
||||
Tun RawTun `yaml:"tun"`
|
||||
IPTables IPTables `yaml:"iptables"`
|
||||
MITM RawMitm `yaml:"mitm"`
|
||||
Experimental Experimental `yaml:"experimental"`
|
||||
@ -198,6 +211,7 @@ type RawConfig struct {
|
||||
Proxy []map[string]any `yaml:"proxies"`
|
||||
ProxyGroup []map[string]any `yaml:"proxy-groups"`
|
||||
Rule []string `yaml:"rules"`
|
||||
Script Script `yaml:"script"`
|
||||
}
|
||||
|
||||
// Parse config
|
||||
@ -213,37 +227,21 @@ func Parse(buf []byte) (*Config, error) {
|
||||
func UnmarshalRawConfig(buf []byte) (*RawConfig, error) {
|
||||
// config with default value
|
||||
rawCfg := &RawConfig{
|
||||
AllowLan: false,
|
||||
Sniffing: false,
|
||||
ForceCertVerify: false,
|
||||
BindAddress: "*",
|
||||
Mode: T.Rule,
|
||||
Authentication: []string{},
|
||||
LogLevel: log.INFO,
|
||||
Hosts: map[string]string{},
|
||||
Rule: []string{},
|
||||
Proxy: []map[string]any{},
|
||||
ProxyGroup: []map[string]any{},
|
||||
Tun: Tun{
|
||||
Enable: false,
|
||||
Device: "",
|
||||
Stack: C.TunGvisor,
|
||||
DNSHijack: []C.DNSUrl{ // default hijack all dns lookup
|
||||
{
|
||||
Network: "udp",
|
||||
AddrPort: C.DNSAddrPort{
|
||||
AddrPort: netip.MustParseAddrPort("0.0.0.0:53"),
|
||||
},
|
||||
},
|
||||
{
|
||||
Network: "tcp",
|
||||
AddrPort: C.DNSAddrPort{
|
||||
AddrPort: netip.MustParseAddrPort("0.0.0.0:53"),
|
||||
},
|
||||
},
|
||||
},
|
||||
AutoRoute: false,
|
||||
AutoDetectInterface: false,
|
||||
AllowLan: false,
|
||||
BindAddress: "*",
|
||||
Mode: T.Rule,
|
||||
Authentication: []string{},
|
||||
LogLevel: log.INFO,
|
||||
Hosts: map[string]string{},
|
||||
Rule: []string{},
|
||||
Proxy: []map[string]any{},
|
||||
ProxyGroup: []map[string]any{},
|
||||
Tun: RawTun{
|
||||
Enable: false,
|
||||
Device: "",
|
||||
Stack: C.TunGvisor,
|
||||
DNSHijack: []string{"0.0.0.0:53"}, // default hijack all dns query
|
||||
AutoRoute: true,
|
||||
},
|
||||
IPTables: IPTables{
|
||||
Enable: false,
|
||||
@ -265,7 +263,7 @@ func UnmarshalRawConfig(buf []byte) (*RawConfig, error) {
|
||||
"223.5.5.5",
|
||||
},
|
||||
NameServer: []string{ // default if user not set
|
||||
"https://120.53.53.53/dns-query",
|
||||
"https://doh.pub/dns-query",
|
||||
"tls://223.5.5.5:853",
|
||||
},
|
||||
},
|
||||
@ -298,6 +296,14 @@ func ParseRawConfig(rawCfg *RawConfig) (*Config, error) {
|
||||
}
|
||||
config.General = general
|
||||
|
||||
tunCfg, err := parseTun(rawCfg.Tun, config.General)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
config.Tun = tunCfg
|
||||
|
||||
dialer.DefaultInterface.Store(config.General.Interface)
|
||||
|
||||
proxies, providers, err := parseProxies(rawCfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -305,11 +311,16 @@ func ParseRawConfig(rawCfg *RawConfig) (*Config, error) {
|
||||
config.Proxies = proxies
|
||||
config.Providers = providers
|
||||
|
||||
rules, err := parseRules(rawCfg, proxies)
|
||||
if err = parseScript(rawCfg.Script); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rules, ruleProviders, err := parseRules(rawCfg, proxies)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
config.Rules = rules
|
||||
config.RuleProviders = ruleProviders
|
||||
|
||||
hosts, err := parseHosts(rawCfg)
|
||||
if err != nil {
|
||||
@ -317,7 +328,7 @@ func ParseRawConfig(rawCfg *RawConfig) (*Config, error) {
|
||||
}
|
||||
config.Hosts = hosts
|
||||
|
||||
dnsCfg, err := parseDNS(rawCfg, hosts)
|
||||
dnsCfg, err := parseDNS(rawCfg, hosts, rules)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -346,19 +357,6 @@ func parseGeneral(cfg *RawConfig) (*General, error) {
|
||||
}
|
||||
}
|
||||
|
||||
if cfg.Tun.Enable && cfg.Tun.AutoDetectInterface {
|
||||
outboundInterface, err := commons.GetAutoDetectInterface()
|
||||
if err != nil && cfg.Interface == "" {
|
||||
return nil, fmt.Errorf("get auto detect interface fail: %w", err)
|
||||
}
|
||||
|
||||
if outboundInterface != "" {
|
||||
cfg.Interface = outboundInterface
|
||||
}
|
||||
}
|
||||
|
||||
dialer.DefaultInterface.Store(cfg.Interface)
|
||||
|
||||
return &General{
|
||||
Inbound: Inbound{
|
||||
Port: cfg.Port,
|
||||
@ -380,8 +378,6 @@ func parseGeneral(cfg *RawConfig) (*General, error) {
|
||||
IPv6: cfg.IPv6,
|
||||
Interface: cfg.Interface,
|
||||
RoutingMark: cfg.RoutingMark,
|
||||
Sniffing: cfg.Sniffing,
|
||||
Tun: cfg.Tun,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@ -391,7 +387,6 @@ func parseProxies(cfg *RawConfig) (proxies map[string]C.Proxy, providersMap map[
|
||||
proxiesConfig := cfg.Proxy
|
||||
groupsConfig := cfg.ProxyGroup
|
||||
providersConfig := cfg.ProxyProvider
|
||||
forceCertVerify := cfg.ForceCertVerify
|
||||
|
||||
var proxyList []string
|
||||
|
||||
@ -401,7 +396,7 @@ func parseProxies(cfg *RawConfig) (proxies map[string]C.Proxy, providersMap map[
|
||||
|
||||
// parse proxy
|
||||
for idx, mapping := range proxiesConfig {
|
||||
proxy, err := adapter.ParseProxy(mapping, forceCertVerify)
|
||||
proxy, err := adapter.ParseProxy(mapping)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("proxy %d: %w", idx, err)
|
||||
}
|
||||
@ -415,11 +410,11 @@ func parseProxies(cfg *RawConfig) (proxies map[string]C.Proxy, providersMap map[
|
||||
|
||||
// keep the original order of ProxyGroups in config file
|
||||
for idx, mapping := range groupsConfig {
|
||||
groupName, existName := mapping["name"]
|
||||
groupName, existName := mapping["name"].(string)
|
||||
if !existName {
|
||||
return nil, nil, fmt.Errorf("proxy group %d: missing name", idx)
|
||||
}
|
||||
proxyList = append(proxyList, groupName.(string))
|
||||
proxyList = append(proxyList, groupName)
|
||||
}
|
||||
|
||||
// check if any loop exists and sort the ProxyGroups
|
||||
@ -433,7 +428,7 @@ func parseProxies(cfg *RawConfig) (proxies map[string]C.Proxy, providersMap map[
|
||||
return nil, nil, fmt.Errorf("can not defined a provider called `%s`", provider.ReservedName)
|
||||
}
|
||||
|
||||
pd, err := provider.ParseProxyProvider(name, mapping, forceCertVerify)
|
||||
pd, err := provider.ParseProxyProvider(name, mapping)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("parse proxy provider %s error: %w", name, err)
|
||||
}
|
||||
@ -442,7 +437,7 @@ func parseProxies(cfg *RawConfig) (proxies map[string]C.Proxy, providersMap map[
|
||||
}
|
||||
|
||||
for _, proxyProvider := range providersMap {
|
||||
log.Infoln("Start initial proxy provider %s", proxyProvider.Name())
|
||||
log.Infoln("Start initial provider %s", proxyProvider.Name())
|
||||
if err := proxyProvider.Initial(); err != nil {
|
||||
return nil, nil, fmt.Errorf("initial proxy provider %s error: %w", proxyProvider.Name(), err)
|
||||
}
|
||||
@ -493,10 +488,15 @@ func parseProxies(cfg *RawConfig) (proxies map[string]C.Proxy, providersMap map[
|
||||
return proxies, providersMap, nil
|
||||
}
|
||||
|
||||
func parseRules(cfg *RawConfig, proxies map[string]C.Proxy) ([]C.Rule, error) {
|
||||
rulesConfig := cfg.Rule
|
||||
func parseRules(cfg *RawConfig, proxies map[string]C.Proxy) ([]C.Rule, map[string]C.Rule, error) {
|
||||
var (
|
||||
rules []C.Rule
|
||||
providerNames []string
|
||||
|
||||
var rules []C.Rule
|
||||
ruleProviders = map[string]C.Rule{}
|
||||
rulesConfig = cfg.Rule
|
||||
mode = cfg.Mode
|
||||
)
|
||||
|
||||
// parse rules
|
||||
for idx, line := range rulesConfig {
|
||||
@ -511,7 +511,7 @@ func parseRules(cfg *RawConfig, proxies map[string]C.Proxy) ([]C.Rule, error) {
|
||||
l := len(rule)
|
||||
|
||||
if l < 2 {
|
||||
return nil, fmt.Errorf("rules[%d] [%s] error: format invalid", idx, line)
|
||||
return nil, nil, fmt.Errorf("rules[%d] [%s] error: format invalid", idx, line)
|
||||
}
|
||||
|
||||
if l < 4 {
|
||||
@ -530,23 +530,35 @@ func parseRules(cfg *RawConfig, proxies map[string]C.Proxy) ([]C.Rule, error) {
|
||||
target = rule[l-1]
|
||||
params = rule[l:]
|
||||
|
||||
if _, ok := proxies[target]; !ok {
|
||||
return nil, fmt.Errorf("rules[%d] [%s] error: proxy [%s] not found", idx, line, target)
|
||||
if _, ok := proxies[target]; !ok && (mode != T.Script || ruleName != "GEOSITE") {
|
||||
return nil, nil, fmt.Errorf("rules[%d] [%s] error: proxy [%s] not found", idx, line, target)
|
||||
}
|
||||
|
||||
params = trimArr(params)
|
||||
|
||||
parsed, parseErr := R.ParseRule(ruleName, payload, target, params)
|
||||
if parseErr != nil {
|
||||
return nil, fmt.Errorf("rules[%d] [%s] error: %s", idx, line, parseErr.Error())
|
||||
return nil, nil, fmt.Errorf("rules[%d] [%s] error: %s", idx, line, parseErr.Error())
|
||||
}
|
||||
|
||||
if ruleName == "GEOSITE" {
|
||||
pvName := "geosite:" + strings.ToLower(payload)
|
||||
providerNames = append(providerNames, pvName)
|
||||
ruleProviders[pvName] = parsed
|
||||
}
|
||||
|
||||
rules = append(rules, parsed)
|
||||
}
|
||||
|
||||
if err := S.NewClashPyContext(providerNames); err != nil {
|
||||
return nil, nil, err
|
||||
} else {
|
||||
log.Infoln("Start initial script context successful, provider records: %v", len(providerNames))
|
||||
}
|
||||
|
||||
runtime.GC()
|
||||
|
||||
return rules, nil
|
||||
return rules, ruleProviders, nil
|
||||
}
|
||||
|
||||
func parseHosts(cfg *RawConfig) (*trie.DomainTrie[netip.Addr], error) {
|
||||
@ -675,27 +687,37 @@ func parseFallbackIPCIDR(ips []string) ([]*netip.Prefix, error) {
|
||||
return ipNets, nil
|
||||
}
|
||||
|
||||
func parseFallbackGeoSite(countries []string) ([]*router.DomainMatcher, error) {
|
||||
func parseFallbackGeoSite(countries []string, rules []C.Rule) ([]*router.DomainMatcher, error) {
|
||||
var sites []*router.DomainMatcher
|
||||
|
||||
for _, country := range countries {
|
||||
matcher, recordsCount, err := geodata.LoadProviderByCode(country)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
found := false
|
||||
for _, rule := range rules {
|
||||
if rule.RuleType() == C.GEOSITE {
|
||||
if strings.EqualFold(country, rule.Payload()) {
|
||||
found = true
|
||||
sites = append(sites, rule.(C.RuleGeoSite).GetDomainMatcher())
|
||||
log.Infoln("Start initial GeoSite dns fallback filter from rule `%s`", country)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sites = append(sites, matcher)
|
||||
if !found {
|
||||
matcher, recordsCount, err := geodata.LoadGeoSiteMatcher(country)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cont := fmt.Sprintf("%d", recordsCount)
|
||||
if recordsCount == 0 {
|
||||
cont = "from cache"
|
||||
sites = append(sites, matcher)
|
||||
|
||||
log.Infoln("Start initial GeoSite dns fallback filter `%s`, records: %d", country, recordsCount)
|
||||
}
|
||||
log.Infoln("Start initial GeoSite dns fallback filter `%s`, records: %s", country, cont)
|
||||
}
|
||||
runtime.GC()
|
||||
return sites, nil
|
||||
}
|
||||
|
||||
func parseDNS(rawCfg *RawConfig, hosts *trie.DomainTrie[netip.Addr]) (*DNS, error) {
|
||||
func parseDNS(rawCfg *RawConfig, hosts *trie.DomainTrie[netip.Addr], rules []C.Rule) (*DNS, error) {
|
||||
cfg := rawCfg.DNS
|
||||
if cfg.Enable && len(cfg.NameServer) == 0 {
|
||||
return nil, fmt.Errorf("if DNS configuration is turned on, NameServer cannot be empty")
|
||||
@ -789,7 +811,7 @@ func parseDNS(rawCfg *RawConfig, hosts *trie.DomainTrie[netip.Addr]) (*DNS, erro
|
||||
dnsCfg.FallbackFilter.IPCIDR = fallbackip
|
||||
}
|
||||
dnsCfg.FallbackFilter.Domain = cfg.FallbackFilter.Domain
|
||||
fallbackGeoSite, err := parseFallbackGeoSite(cfg.FallbackFilter.GeoSite)
|
||||
fallbackGeoSite, err := parseFallbackGeoSite(cfg.FallbackFilter.GeoSite, rules)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("load GeoSite dns fallback filter error, %w", err)
|
||||
}
|
||||
@ -804,7 +826,7 @@ func parseDNS(rawCfg *RawConfig, hosts *trie.DomainTrie[netip.Addr]) (*DNS, erro
|
||||
}
|
||||
|
||||
func parseAuthentication(rawRecords []string) []auth.AuthUser {
|
||||
var users []auth.AuthUser
|
||||
users := []auth.AuthUser{}
|
||||
for _, line := range rawRecords {
|
||||
if user, pass, found := strings.Cut(line, ":"); found {
|
||||
users = append(users, auth.AuthUser{User: user, Pass: pass})
|
||||
@ -813,6 +835,111 @@ func parseAuthentication(rawRecords []string) []auth.AuthUser {
|
||||
return users
|
||||
}
|
||||
|
||||
func parseTun(rawTun RawTun, general *General) (*Tun, error) {
|
||||
if (rawTun.Enable || general.TProxyPort != 0) && general.Interface == "" {
|
||||
autoDetectInterfaceName, err := commons.GetAutoDetectInterface()
|
||||
if err != nil || autoDetectInterfaceName == "" {
|
||||
return nil, fmt.Errorf("can not find auto detect interface: %w. you must be detect `interface-name` if tun set to enable or `tproxy-port` isn't zore", err)
|
||||
}
|
||||
|
||||
general.Interface = autoDetectInterfaceName
|
||||
}
|
||||
|
||||
var dnsHijack []netip.AddrPort
|
||||
|
||||
for _, d := range rawTun.DNSHijack {
|
||||
if _, after, ok := strings.Cut(d, "://"); ok {
|
||||
d = after
|
||||
}
|
||||
|
||||
addrPort, err := netip.ParseAddrPort(d)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parse dns-hijack url error: %w", err)
|
||||
}
|
||||
|
||||
dnsHijack = append(dnsHijack, addrPort)
|
||||
}
|
||||
|
||||
return &Tun{
|
||||
Enable: rawTun.Enable,
|
||||
Device: rawTun.Device,
|
||||
Stack: rawTun.Stack,
|
||||
DNSHijack: dnsHijack,
|
||||
AutoRoute: rawTun.AutoRoute,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func parseScript(script Script) error {
|
||||
mainCode := script.MainCode
|
||||
shortcutsCode := script.ShortcutsCode
|
||||
|
||||
if strings.TrimSpace(mainCode) == "" {
|
||||
mainCode = `
|
||||
def main(ctx, metadata):
|
||||
return "DIRECT"
|
||||
`
|
||||
} else {
|
||||
mainCode = cleanPyKeywords(mainCode)
|
||||
}
|
||||
|
||||
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()
|
||||
|
||||
`
|
||||
|
||||
content += mainCode + "\n\n"
|
||||
|
||||
for k, v := range shortcutsCode {
|
||||
v = cleanPyKeywords(v)
|
||||
v = strings.TrimSpace(v)
|
||||
if v == "" {
|
||||
return fmt.Errorf("initialized rule SCRIPT failure, shortcut [%s] code invalid syntax", k)
|
||||
}
|
||||
|
||||
content += "def " + strings.ToLower(k) + "(ctx, network, process_name, process_path, host, src_ip, src_port, dst_ip, dst_port):\n return " + v + "\n\n"
|
||||
}
|
||||
|
||||
err := os.WriteFile(C.Path.Script(), []byte(content), 0o644)
|
||||
if err != nil {
|
||||
return fmt.Errorf("initialized script module failure, %w", err)
|
||||
}
|
||||
|
||||
if err = S.Py_Initialize(C.Path.GetExecutableFullPath(), C.Path.ScriptDir()); err != nil {
|
||||
return fmt.Errorf("initialized script module failure, %w", err)
|
||||
}
|
||||
|
||||
if err = S.LoadMainFunction(); err != nil {
|
||||
return fmt.Errorf("initialized script module failure, %w", err)
|
||||
}
|
||||
|
||||
log.Infoln("Start initial script module successful, version: %s", S.Py_GetVersion())
|
||||
|
||||
return 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 parseMitm(rawMitm RawMitm) (*Mitm, error) {
|
||||
var (
|
||||
req []C.Rewrite
|
||||
|
@ -6,14 +6,13 @@ import (
|
||||
"net/http"
|
||||
"os"
|
||||
|
||||
"github.com/Dreamacro/clash/common/convert"
|
||||
"github.com/Dreamacro/clash/component/mmdb"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/log"
|
||||
)
|
||||
|
||||
func downloadMMDB(path string) (err error) {
|
||||
resp, err := doGet("https://raw.githubusercontent.com/Loyalsoldier/geoip/release/Country.mmdb")
|
||||
resp, err := http.Get("https://cdn.jsdelivr.net/gh/Loyalsoldier/geoip@release/Country.mmdb")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@ -33,18 +32,18 @@ func initMMDB() error {
|
||||
if _, err := os.Stat(C.Path.MMDB()); os.IsNotExist(err) {
|
||||
log.Infoln("Can't find MMDB, start download")
|
||||
if err := downloadMMDB(C.Path.MMDB()); err != nil {
|
||||
return fmt.Errorf("can't download MMDB: %w", err)
|
||||
return fmt.Errorf("can't download MMDB: %s", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
if !mmdb.Verify() {
|
||||
log.Warnln("MMDB invalid, remove and download")
|
||||
if err := os.Remove(C.Path.MMDB()); err != nil {
|
||||
return fmt.Errorf("can't remove invalid MMDB: %w", err)
|
||||
return fmt.Errorf("can't remove invalid MMDB: %s", err.Error())
|
||||
}
|
||||
|
||||
if err := downloadMMDB(C.Path.MMDB()); err != nil {
|
||||
return fmt.Errorf("can't download MMDB: %w", err)
|
||||
return fmt.Errorf("can't download MMDB: %s", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
@ -52,7 +51,7 @@ func initMMDB() error {
|
||||
}
|
||||
|
||||
func downloadGeoSite(path string) (err error) {
|
||||
resp, err := doGet("https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat")
|
||||
resp, err := http.Get("https://cdn.jsdelivr.net/gh/Loyalsoldier/v2ray-rules-dat@release/geosite.dat")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@ -72,7 +71,7 @@ func initGeoSite() error {
|
||||
if _, err := os.Stat(C.Path.GeoSite()); os.IsNotExist(err) {
|
||||
log.Infoln("Can't find GeoSite.dat, start download")
|
||||
if err := downloadGeoSite(C.Path.GeoSite()); err != nil {
|
||||
return fmt.Errorf("can't download GeoSite.dat: %w", err)
|
||||
return fmt.Errorf("can't download GeoSite.dat: %s", err.Error())
|
||||
}
|
||||
log.Infoln("Download GeoSite.dat finish")
|
||||
}
|
||||
@ -85,7 +84,7 @@ func Init(dir string) error {
|
||||
// initial homedir
|
||||
if _, err := os.Stat(dir); os.IsNotExist(err) {
|
||||
if err := os.MkdirAll(dir, 0o777); err != nil {
|
||||
return fmt.Errorf("can't create config directory %s: %w", dir, err)
|
||||
return fmt.Errorf("can't create config directory %s: %s", dir, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
@ -94,10 +93,10 @@ func Init(dir string) error {
|
||||
log.Infoln("Can't find config, create a initial config file")
|
||||
f, err := os.OpenFile(C.Path.Config(), os.O_CREATE|os.O_WRONLY, 0o644)
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't create file %s: %w", C.Path.Config(), err)
|
||||
return fmt.Errorf("can't create file %s: %s", C.Path.Config(), err.Error())
|
||||
}
|
||||
_, _ = f.Write([]byte(`mixed-port: 7890`))
|
||||
_ = f.Close()
|
||||
f.Write([]byte(`mixed-port: 7890`))
|
||||
f.Close()
|
||||
}
|
||||
|
||||
// initial mmdb
|
||||
@ -111,16 +110,3 @@ func Init(dir string) error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func doGet(url string) (resp *http.Response, err error) {
|
||||
var req *http.Request
|
||||
req, err = http.NewRequest("GET", url, nil)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
convert.SetUserAgent(req.Header)
|
||||
|
||||
resp, err = http.DefaultClient.Do(req)
|
||||
return
|
||||
}
|
||||
|
@ -1,68 +0,0 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"runtime"
|
||||
|
||||
"github.com/Dreamacro/clash/component/geodata"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
|
||||
"github.com/oschwald/geoip2-golang"
|
||||
)
|
||||
|
||||
func UpdateGeoDatabases() error {
|
||||
var (
|
||||
tmpMMDB = C.Path.Resolve("temp_country.mmdb")
|
||||
tmpGeoSite = C.Path.Resolve("temp_geosite.dat")
|
||||
)
|
||||
|
||||
if err := downloadMMDB(tmpMMDB); err != nil {
|
||||
return fmt.Errorf("can't download MMDB database file: %w", err)
|
||||
}
|
||||
|
||||
if err := verifyMMDB(tmpMMDB); err != nil {
|
||||
_ = os.Remove(tmpMMDB)
|
||||
return fmt.Errorf("invalid MMDB database file, %w", err)
|
||||
}
|
||||
|
||||
if err := os.Rename(tmpMMDB, C.Path.MMDB()); err != nil {
|
||||
return fmt.Errorf("can't rename MMDB database file: %w", err)
|
||||
}
|
||||
|
||||
if err := downloadGeoSite(tmpGeoSite); err != nil {
|
||||
return fmt.Errorf("can't download GeoSite database file: %w", err)
|
||||
}
|
||||
|
||||
if err := verifyGeoSite(tmpGeoSite); err != nil {
|
||||
_ = os.Remove(tmpGeoSite)
|
||||
return fmt.Errorf("invalid GeoSite database file, %w", err)
|
||||
}
|
||||
|
||||
if err := os.Rename(tmpGeoSite, C.Path.GeoSite()); err != nil {
|
||||
return fmt.Errorf("can't rename GeoSite database file: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func verifyMMDB(path string) error {
|
||||
instance, err := geoip2.Open(path)
|
||||
if err == nil {
|
||||
_ = instance.Close()
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func verifyGeoSite(path string) error {
|
||||
geoLoader, err := geodata.GetGeoDataLoader("standard")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = geoLoader.LoadSite(path, "cn")
|
||||
|
||||
runtime.GC()
|
||||
|
||||
return err
|
||||
}
|
@ -93,14 +93,9 @@ type ProxyAdapter interface {
|
||||
// a new session (if any)
|
||||
StreamConn(c net.Conn, metadata *Metadata) (net.Conn, error)
|
||||
|
||||
// StreamPacketConn wraps a UDP protocol around net.Conn with Metadata.
|
||||
StreamPacketConn(c net.Conn, metadata *Metadata) (net.Conn, error)
|
||||
|
||||
// DialContext return a C.Conn with protocol which
|
||||
// contains multiplexing-related reuse logic (if any)
|
||||
DialContext(ctx context.Context, metadata *Metadata, opts ...dialer.Option) (Conn, error)
|
||||
|
||||
// ListenPacketContext listen for a PacketConn
|
||||
ListenPacketContext(ctx context.Context, metadata *Metadata, opts ...dialer.Option) (PacketConn, error)
|
||||
|
||||
// Unwrap extracts the proxy from a proxy-group. It returns nil when nothing to extract.
|
||||
|
@ -101,8 +101,8 @@ func (m *Metadata) Resolved() bool {
|
||||
|
||||
// Pure is used to solve unexpected behavior
|
||||
// when dialing proxy connection in DNSMapping mode.
|
||||
func (m *Metadata) Pure(isMitmOutbound bool) *Metadata {
|
||||
if !isMitmOutbound && m.DNSMode == DNSMapping && m.DstIP.IsValid() {
|
||||
func (m *Metadata) Pure() *Metadata {
|
||||
if m.DNSMode == DNSMapping && m.DstIP.IsValid() {
|
||||
copyM := *m
|
||||
copyM.Host = ""
|
||||
if copyM.DstIP.Is4() {
|
||||
|
@ -22,6 +22,7 @@ var Path = func() *path {
|
||||
type path struct {
|
||||
homeDir string
|
||||
configFile string
|
||||
scriptDir string
|
||||
}
|
||||
|
||||
// SetHomeDir is used to set the configuration path
|
||||
@ -71,6 +72,32 @@ func (p *path) GeoSite() string {
|
||||
return P.Join(p.homeDir, "geosite.dat")
|
||||
}
|
||||
|
||||
func (p *path) ScriptDir() string {
|
||||
if len(p.scriptDir) != 0 {
|
||||
return p.scriptDir
|
||||
}
|
||||
if dir, err := os.MkdirTemp("", Name+"-"); err == nil {
|
||||
p.scriptDir = dir
|
||||
} else {
|
||||
p.scriptDir = P.Join(os.TempDir(), Name)
|
||||
_ = os.MkdirAll(p.scriptDir, 0o644)
|
||||
}
|
||||
return p.scriptDir
|
||||
}
|
||||
|
||||
func (p *path) Script() string {
|
||||
return P.Join(p.ScriptDir(), "clash_script.py")
|
||||
}
|
||||
|
||||
func (p *path) GetExecutableFullPath() string {
|
||||
exePath, err := os.Executable()
|
||||
if err != nil {
|
||||
return "clash"
|
||||
}
|
||||
res, _ := filepath.EvalSymlinks(exePath)
|
||||
return res
|
||||
}
|
||||
|
||||
func (p *path) RootCA() string {
|
||||
return p.Resolve("mitm_ca.crt")
|
||||
}
|
||||
|
@ -13,6 +13,7 @@ const (
|
||||
DstPort
|
||||
Process
|
||||
ProcessPath
|
||||
Script
|
||||
UserAgent
|
||||
MATCH
|
||||
)
|
||||
@ -43,6 +44,8 @@ func (rt RuleType) String() string {
|
||||
return "Process"
|
||||
case ProcessPath:
|
||||
return "ProcessPath"
|
||||
case Script:
|
||||
return "Script"
|
||||
case UserAgent:
|
||||
return "UserAgent"
|
||||
case MATCH:
|
||||
|
127
constant/tun.go
127
constant/tun.go
@ -3,11 +3,7 @@ package constant
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"net/netip"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
var StackTypeMapping = map[string]TUNStack{
|
||||
@ -18,21 +14,10 @@ var StackTypeMapping = map[string]TUNStack{
|
||||
const (
|
||||
TunGvisor TUNStack = iota
|
||||
TunSystem
|
||||
|
||||
TunDisabled TUNState = iota
|
||||
TunEnabled
|
||||
TunPaused
|
||||
)
|
||||
|
||||
type TUNStack int
|
||||
|
||||
type TUNState int
|
||||
|
||||
type TUNChangeCallback interface {
|
||||
Pause()
|
||||
Resume()
|
||||
}
|
||||
|
||||
// UnmarshalYAML unserialize TUNStack with yaml
|
||||
func (e *TUNStack) UnmarshalYAML(unmarshal func(any) error) error {
|
||||
var tp string
|
||||
@ -55,7 +40,7 @@ func (e TUNStack) MarshalYAML() (any, error) {
|
||||
// UnmarshalJSON unserialize TUNStack with json
|
||||
func (e *TUNStack) UnmarshalJSON(data []byte) error {
|
||||
var tp string
|
||||
_ = json.Unmarshal(data, &tp)
|
||||
json.Unmarshal(data, &tp)
|
||||
mode, exist := StackTypeMapping[strings.ToUpper(tp)]
|
||||
if !exist {
|
||||
return errors.New("invalid tun stack")
|
||||
@ -79,113 +64,3 @@ func (e TUNStack) String() string {
|
||||
return "unknown"
|
||||
}
|
||||
}
|
||||
|
||||
type DNSAddrPort struct {
|
||||
netip.AddrPort
|
||||
}
|
||||
|
||||
func (p *DNSAddrPort) UnmarshalText(text []byte) error {
|
||||
if len(text) == 0 {
|
||||
*p = DNSAddrPort{}
|
||||
return nil
|
||||
}
|
||||
|
||||
addrPort := string(text)
|
||||
if strings.HasPrefix(addrPort, "any") {
|
||||
_, port, _ := strings.Cut(addrPort, "any")
|
||||
addrPort = "0.0.0.0" + port
|
||||
}
|
||||
|
||||
ap, err := netip.ParseAddrPort(addrPort)
|
||||
*p = DNSAddrPort{AddrPort: ap}
|
||||
return err
|
||||
}
|
||||
|
||||
func (p DNSAddrPort) String() string {
|
||||
addrPort := p.AddrPort.String()
|
||||
if p.AddrPort.Addr().IsUnspecified() {
|
||||
addrPort = "any:" + strconv.Itoa(int(p.AddrPort.Port()))
|
||||
}
|
||||
return addrPort
|
||||
}
|
||||
|
||||
type DNSUrl struct {
|
||||
Network string
|
||||
AddrPort DNSAddrPort
|
||||
}
|
||||
|
||||
func (d *DNSUrl) UnmarshalYAML(unmarshal func(any) error) error {
|
||||
var text string
|
||||
if err := unmarshal(&text); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
text = strings.ToLower(text)
|
||||
network := "udp"
|
||||
if before, after, found := strings.Cut(text, "://"); found {
|
||||
network = before
|
||||
text = after
|
||||
}
|
||||
|
||||
if network != "udp" && network != "tcp" {
|
||||
return errors.New("invalid dns url schema")
|
||||
}
|
||||
|
||||
ap := &DNSAddrPort{}
|
||||
if err := ap.UnmarshalText([]byte(text)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
*d = DNSUrl{Network: network, AddrPort: *ap}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d DNSUrl) MarshalYAML() (any, error) {
|
||||
return d.String(), nil
|
||||
}
|
||||
|
||||
func (d *DNSUrl) UnmarshalJSON(data []byte) error {
|
||||
var text string
|
||||
if err := json.Unmarshal(data, &text); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
text = strings.ToLower(text)
|
||||
network := "udp"
|
||||
if before, after, found := strings.Cut(text, "://"); found {
|
||||
network = before
|
||||
text = after
|
||||
}
|
||||
|
||||
if network != "udp" && network != "tcp" {
|
||||
return errors.New("invalid dns url schema")
|
||||
}
|
||||
|
||||
ap := &DNSAddrPort{}
|
||||
if err := ap.UnmarshalText([]byte(text)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
*d = DNSUrl{Network: network, AddrPort: *ap}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d DNSUrl) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(d.String())
|
||||
}
|
||||
|
||||
func (d DNSUrl) String() string {
|
||||
return d.Network + "://" + d.AddrPort.String()
|
||||
}
|
||||
|
||||
func RemoveDuplicateDNSUrl(slice []DNSUrl) []DNSUrl {
|
||||
slices.SortFunc[DNSUrl](slice, func(a, b DNSUrl) bool {
|
||||
return a.Network < b.Network || (a.Network == b.Network && a.AddrPort.Addr().Less(b.AddrPort.Addr()))
|
||||
})
|
||||
|
||||
return slices.CompactFunc[[]DNSUrl, DNSUrl](slice, func(a, b DNSUrl) bool {
|
||||
return a.Network == b.Network && a.AddrPort == b.AddrPort
|
||||
})
|
||||
}
|
||||
|
@ -36,7 +36,7 @@ func (c *client) ExchangeContext(ctx context.Context, m *D.Msg) (*D.Msg, error)
|
||||
if c.r == nil {
|
||||
return nil, fmt.Errorf("dns %s not a valid ip", c.host)
|
||||
} else {
|
||||
if ip, err = resolver.ResolveIPWithResolver(c.host, c.r, true); err != nil {
|
||||
if ip, err = resolver.ResolveIPWithResolver(c.host, c.r); err != nil {
|
||||
return nil, fmt.Errorf("use default dns resolve failed: %w", err)
|
||||
}
|
||||
c.host = ip.String()
|
||||
@ -54,14 +54,10 @@ func (c *client) ExchangeContext(ctx context.Context, m *D.Msg) (*D.Msg, error)
|
||||
}
|
||||
|
||||
var conn net.Conn
|
||||
if c.proxyAdapter != "" {
|
||||
conn, err = dialContextWithProxyAdapter(ctx, c.proxyAdapter, network, ip, c.port, options...)
|
||||
if err == errProxyNotFound {
|
||||
options = append(options[:0], dialer.WithInterface(c.proxyAdapter), dialer.WithRoutingMark(0))
|
||||
conn, err = dialer.DialContext(ctx, network, net.JoinHostPort(ip.String(), c.port), options...)
|
||||
}
|
||||
} else {
|
||||
if c.proxyAdapter == "" {
|
||||
conn, err = dialer.DialContext(ctx, network, net.JoinHostPort(ip.String(), c.port), options...)
|
||||
} else {
|
||||
conn, err = dialContextWithProxyAdapter(ctx, c.proxyAdapter, network, ip, c.port, options...)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
|
20
dns/doh.go
20
dns/doh.go
@ -69,9 +69,7 @@ func (dc *dohClient) doRequest(req *http.Request) (msg *D.Msg, err error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func() {
|
||||
_ = resp.Body.Close()
|
||||
}()
|
||||
defer resp.Body.Close()
|
||||
|
||||
buf, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
@ -94,22 +92,16 @@ func newDoHClient(url string, r *Resolver, proxyAdapter string) *dohClient {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ip, err := resolver.ResolveIPWithResolver(host, r, true)
|
||||
ip, err := resolver.ResolveIPWithResolver(host, r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if proxyAdapter != "" {
|
||||
var conn net.Conn
|
||||
conn, err = dialContextWithProxyAdapter(ctx, proxyAdapter, "tcp", ip, port)
|
||||
if err == errProxyNotFound {
|
||||
options := []dialer.Option{dialer.WithInterface(proxyAdapter), dialer.WithRoutingMark(0)}
|
||||
conn, err = dialer.DialContext(ctx, "tcp", net.JoinHostPort(ip.String(), port), options...)
|
||||
}
|
||||
return conn, err
|
||||
if proxyAdapter == "" {
|
||||
return dialer.DialContext(ctx, "tcp", net.JoinHostPort(ip.String(), port))
|
||||
} else {
|
||||
return dialContextWithProxyAdapter(ctx, proxyAdapter, "tcp", ip, port)
|
||||
}
|
||||
|
||||
return dialer.DialContext(ctx, "tcp", net.JoinHostPort(ip.String(), port))
|
||||
},
|
||||
},
|
||||
}
|
||||
|
@ -21,8 +21,6 @@ import (
|
||||
"golang.org/x/sync/singleflight"
|
||||
)
|
||||
|
||||
var _ resolver.Resolver = (*Resolver)(nil)
|
||||
|
||||
type dnsClient interface {
|
||||
Exchange(m *D.Msg) (msg *D.Msg, err error)
|
||||
ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg, err error)
|
||||
@ -47,18 +45,18 @@ type Resolver struct {
|
||||
}
|
||||
|
||||
// ResolveIP request with TypeA and TypeAAAA, priority return TypeA
|
||||
func (r *Resolver) ResolveIP(host string, random bool) (ip netip.Addr, err error) {
|
||||
func (r *Resolver) ResolveIP(host string) (ip netip.Addr, err error) {
|
||||
ch := make(chan netip.Addr, 1)
|
||||
go func() {
|
||||
defer close(ch)
|
||||
ip, err := r.resolveIP(host, D.TypeAAAA, random)
|
||||
ip, err := r.resolveIP(host, D.TypeAAAA)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
ch <- ip
|
||||
}()
|
||||
|
||||
ip, err = r.resolveIP(host, D.TypeA, random)
|
||||
ip, err = r.resolveIP(host, D.TypeA)
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
@ -72,13 +70,13 @@ func (r *Resolver) ResolveIP(host string, random bool) (ip netip.Addr, err error
|
||||
}
|
||||
|
||||
// ResolveIPv4 request with TypeA
|
||||
func (r *Resolver) ResolveIPv4(host string, random bool) (ip netip.Addr, err error) {
|
||||
return r.resolveIP(host, D.TypeA, random)
|
||||
func (r *Resolver) ResolveIPv4(host string) (ip netip.Addr, err error) {
|
||||
return r.resolveIP(host, D.TypeA)
|
||||
}
|
||||
|
||||
// ResolveIPv6 request with TypeAAAA
|
||||
func (r *Resolver) ResolveIPv6(host string, random bool) (ip netip.Addr, err error) {
|
||||
return r.resolveIP(host, D.TypeAAAA, random)
|
||||
func (r *Resolver) ResolveIPv6(host string) (ip netip.Addr, err error) {
|
||||
return r.resolveIP(host, D.TypeAAAA)
|
||||
}
|
||||
|
||||
func (r *Resolver) shouldIPFallback(ip netip.Addr) bool {
|
||||
@ -257,10 +255,9 @@ func (r *Resolver) ipExchange(ctx context.Context, m *D.Msg) (msg *D.Msg, err er
|
||||
return
|
||||
}
|
||||
|
||||
func (r *Resolver) resolveIP(host string, dnsType uint16, random bool) (ip netip.Addr, err error) {
|
||||
func (r *Resolver) resolveIP(host string, dnsType uint16) (ip netip.Addr, err error) {
|
||||
ip, err = netip.ParseAddr(host)
|
||||
if err == nil {
|
||||
ip = ip.Unmap()
|
||||
isIPv4 := ip.Is4()
|
||||
if dnsType == D.TypeAAAA && !isIPv4 {
|
||||
return ip, nil
|
||||
@ -285,12 +282,7 @@ func (r *Resolver) resolveIP(host string, dnsType uint16, random bool) (ip netip
|
||||
return netip.Addr{}, resolver.ErrIPNotFound
|
||||
}
|
||||
|
||||
index := 0
|
||||
if random {
|
||||
index = rand.Intn(ipLength)
|
||||
}
|
||||
|
||||
ip = ips[index]
|
||||
ip = ips[rand.Intn(ipLength)]
|
||||
return
|
||||
}
|
||||
|
||||
|
29
dns/util.go
29
dns/util.go
@ -3,13 +3,11 @@ package dns
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/netip"
|
||||
"time"
|
||||
|
||||
"github.com/Dreamacro/clash/adapter"
|
||||
"github.com/Dreamacro/clash/common/cache"
|
||||
"github.com/Dreamacro/clash/common/nnip"
|
||||
"github.com/Dreamacro/clash/component/dialer"
|
||||
@ -20,8 +18,6 @@ import (
|
||||
D "github.com/miekg/dns"
|
||||
)
|
||||
|
||||
var errProxyNotFound = errors.New("proxy adapter not found")
|
||||
|
||||
func putMsgToCache(c *cache.LruCache[string, *D.Msg], key string, msg *D.Msg) {
|
||||
var ttl uint32
|
||||
switch {
|
||||
@ -136,13 +132,16 @@ func (wpc *wrapPacketConn) RemoteAddr() net.Addr {
|
||||
}
|
||||
|
||||
func dialContextWithProxyAdapter(ctx context.Context, adapterName string, network string, dstIP netip.Addr, port string, opts ...dialer.Option) (net.Conn, error) {
|
||||
proxy, ok := tunnel.Proxies()[adapterName]
|
||||
adapter, ok := tunnel.Proxies()[adapterName]
|
||||
if !ok {
|
||||
return nil, errProxyNotFound
|
||||
return nil, fmt.Errorf("proxy adapter [%s] not found", adapterName)
|
||||
}
|
||||
|
||||
networkType := C.TCP
|
||||
if network == "udp" {
|
||||
if !adapter.SupportUDP() {
|
||||
return nil, fmt.Errorf("proxy adapter [%s] UDP is not supported", adapterName)
|
||||
}
|
||||
networkType = C.UDP
|
||||
}
|
||||
|
||||
@ -159,14 +158,8 @@ func dialContextWithProxyAdapter(ctx context.Context, adapterName string, networ
|
||||
DstPort: port,
|
||||
}
|
||||
|
||||
rawAdapter := fetchRawProxyAdapter(proxy.(*adapter.Proxy).ProxyAdapter, metadata)
|
||||
|
||||
if networkType == C.UDP {
|
||||
if !rawAdapter.SupportUDP() {
|
||||
return nil, fmt.Errorf("proxy adapter [%s] UDP is not supported", rawAdapter.Name())
|
||||
}
|
||||
|
||||
packetConn, err := rawAdapter.ListenPacketContext(ctx, metadata, opts...)
|
||||
packetConn, err := adapter.ListenPacketContext(ctx, metadata, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -177,13 +170,5 @@ func dialContextWithProxyAdapter(ctx context.Context, adapterName string, networ
|
||||
}, nil
|
||||
}
|
||||
|
||||
return rawAdapter.DialContext(ctx, metadata, opts...)
|
||||
}
|
||||
|
||||
func fetchRawProxyAdapter(proxyAdapter C.ProxyAdapter, metadata *C.Metadata) C.ProxyAdapter {
|
||||
if p := proxyAdapter.Unwrap(metadata); p != nil {
|
||||
return fetchRawProxyAdapter(p.(*adapter.Proxy).ProxyAdapter, metadata)
|
||||
}
|
||||
|
||||
return proxyAdapter
|
||||
return adapter.DialContext(ctx, metadata, opts...)
|
||||
}
|
||||
|
27
go.mod
27
go.mod
@ -3,32 +3,32 @@ module github.com/Dreamacro/clash
|
||||
go 1.18
|
||||
|
||||
require (
|
||||
github.com/Dreamacro/go-shadowsocks2 v0.1.8
|
||||
github.com/go-chi/chi/v5 v5.0.7
|
||||
github.com/go-chi/cors v1.2.1
|
||||
github.com/go-chi/render v1.0.1
|
||||
github.com/gofrs/uuid v4.2.0+incompatible
|
||||
github.com/gorilla/websocket v1.5.0
|
||||
github.com/insomniacslk/dhcp v0.0.0-20220504074936-1ca156eafb9f
|
||||
github.com/miekg/dns v1.1.50
|
||||
github.com/miekg/dns v1.1.48
|
||||
github.com/oschwald/geoip2-golang v1.7.0
|
||||
github.com/sirupsen/logrus v1.8.1
|
||||
github.com/stretchr/testify v1.7.2
|
||||
github.com/stretchr/testify v1.7.1
|
||||
github.com/xtls/go v0.0.0-20210920065950-d4af136d3672
|
||||
go.etcd.io/bbolt v1.3.6
|
||||
go.uber.org/atomic v1.9.0
|
||||
go.uber.org/automaxprocs v1.5.1
|
||||
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d
|
||||
golang.org/x/exp v0.0.0-20220613132600-b0d781184e0d
|
||||
golang.org/x/net v0.0.0-20220622184535-263ec571b305
|
||||
golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f
|
||||
golang.org/x/sys v0.0.0-20220622161953-175b2fd9d664
|
||||
golang.org/x/crypto v0.0.0-20220507011949-2cf3adece122
|
||||
golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
|
||||
golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6
|
||||
golang.org/x/text v0.3.8-0.20220124021120-d1c84af989ab
|
||||
golang.org/x/time v0.0.0-20220609170525-579cf78fd858
|
||||
golang.zx2c4.com/wireguard v0.0.0-20220601130007-6a08d81f6bc4
|
||||
golang.org/x/time v0.0.0-20220411224347-583f2d630306
|
||||
golang.zx2c4.com/wireguard v0.0.0-20220407013110-ef5c587f782d
|
||||
golang.zx2c4.com/wireguard/windows v0.5.4-0.20220328111914-004c22c5647e
|
||||
google.golang.org/protobuf v1.28.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
gvisor.dev/gvisor v0.0.0-20220616232550-d004a30ec069
|
||||
gopkg.in/yaml.v2 v2.4.0
|
||||
gvisor.dev/gvisor v0.0.0-20220506231117-8ef340c14150
|
||||
)
|
||||
|
||||
require (
|
||||
@ -39,8 +39,9 @@ require (
|
||||
github.com/oschwald/maxminddb-golang v1.9.0 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/u-root/uio v0.0.0-20210528114334-82958018845c // indirect
|
||||
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 // indirect
|
||||
golang.org/x/tools v0.1.10 // indirect
|
||||
golang.org/x/mod v0.6.0-dev.0.20211013180041-c96bc1413d57 // indirect
|
||||
golang.org/x/tools v0.1.9 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
|
||||
golang.zx2c4.com/wintun v0.0.0-20211104114900-415007cec224 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
|
||||
)
|
||||
|
55
go.sum
55
go.sum
@ -1,3 +1,5 @@
|
||||
github.com/Dreamacro/go-shadowsocks2 v0.1.8 h1:Ixejp5JscEc866gAvm/l6TFd7BOBvDviKgwb1quWw3g=
|
||||
github.com/Dreamacro/go-shadowsocks2 v0.1.8/go.mod h1:51y4Q6tJoCE7e8TmYXcQRqfoxPfE9Cvn79V6pB6Df7Y=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
@ -20,7 +22,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.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.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
|
||||
github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
|
||||
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
@ -45,8 +47,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/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/miekg/dns v1.1.50 h1:DQUfb9uc6smULcREF09Uc+/Gd46YWqJd5DbpPE9xkcA=
|
||||
github.com/miekg/dns v1.1.50/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME=
|
||||
github.com/miekg/dns v1.1.48 h1:Ucfr7IIVyMBz4lRE8qmGUuZ4Wt3/ZGu9hmcMT3Uu4tQ=
|
||||
github.com/miekg/dns v1.1.48/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/go.mod h1:mdI/C7iK7NVMcIDDtf4bCKMJ7r0o7UwGeCo9eiitCMQ=
|
||||
github.com/oschwald/maxminddb-golang v1.9.0 h1:tIk4nv6VT9OiPyrnDAfJS1s1xKDQMZOsGojab6EjC1Y=
|
||||
@ -62,8 +64,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.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.7.2 h1:4jaiDzPyXQvSd7D0EjG45355tLlV3VOECpq10pLC+8s=
|
||||
github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals=
|
||||
github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/u-root/uio v0.0.0-20210528114334-82958018845c h1:BFvcl34IGnw8yvJi8hlqLFo9EshRInwWBs2M5fGWzQA=
|
||||
github.com/u-root/uio v0.0.0-20210528114334-82958018845c/go.mod h1:LpEX5FO/cB+WF4TYGY1V5qktpaZLkKkSegbr0V4eYXA=
|
||||
github.com/xtls/go v0.0.0-20210920065950-d4af136d3672 h1:4mkzGhKqt3JO1BWYjtD3iRFyAx4ow67hmSqOcGjuxqQ=
|
||||
@ -78,13 +80,11 @@ 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-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-20220622213112-05595931fe9d h1:sK3txAijHtOK88l68nt020reeT1ZdKLIYetKl95FzVY=
|
||||
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/exp v0.0.0-20220613132600-b0d781184e0d h1:vtUKgx8dahOomfFzLREU8nSv25YHnTgLBn4rDnWZdU0=
|
||||
golang.org/x/exp v0.0.0-20220613132600-b0d781184e0d/go.mod h1:Kr81I6Kryrl9sr8s2FK3vxD90NdsKWRuOIl2O4CvYbA=
|
||||
golang.org/x/crypto v0.0.0-20220507011949-2cf3adece122 h1:NvGWuYG8dkDHFSKksI1P9faiVJ9rayE6l0+ouWVIDs8=
|
||||
golang.org/x/crypto v0.0.0-20220507011949-2cf3adece122/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
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/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY=
|
||||
golang.org/x/mod v0.6.0-dev.0.20211013180041-c96bc1413d57 h1:LQmS1nU0twXLA96Kt7U9qtHJEbBk3z6Q0V4UXjZkpr4=
|
||||
golang.org/x/mod v0.6.0-dev.0.20211013180041-c96bc1413d57/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190419010253-1f3472d942ba/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
@ -97,12 +97,11 @@ 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-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-20220622184535-263ec571b305 h1:dAgbJ2SP4jD6XYfMNLVj0BF21jo2PjChrtGaAvF5M3I=
|
||||
golang.org/x/net v0.0.0-20220622184535-263ec571b305/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4 h1:HVyaeDAYux4pnY+D/SiwmLOR36ewZ4iGQIIrtnuCjFA=
|
||||
golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
|
||||
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/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190411185658-b44545bcd369/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
@ -123,22 +122,22 @@ 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-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-20220622161953-175b2fd9d664 h1:wEZYwx+kK+KlZ0hpvP2Ls1Xr4+RWnlzGFwPP0aiDjIU=
|
||||
golang.org/x/sys v0.0.0-20220622161953-175b2fd9d664/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6 h1:nonptSpoQ4vQjyraW20DXPAglgQfVnM9ZC6MmNLMR60=
|
||||
golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
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.3/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/go.mod h1:EFNZuWvGYxIRUEX+K8UmCFwYmZjqcrnq15ZuVldZkZ0=
|
||||
golang.org/x/time v0.0.0-20220609170525-579cf78fd858 h1:Dpdu/EMxGMFgq0CeYMh4fazTD2vtlZRYE7wyynxJb9U=
|
||||
golang.org/x/time v0.0.0-20220609170525-579cf78fd858/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20220411224347-583f2d630306 h1:+gHMid33q6pen7kv9xvT+JRinntgeXO2AeZVd0AWD3w=
|
||||
golang.org/x/time v0.0.0-20220411224347-583f2d630306/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-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.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.10 h1:QjFRCZxdOhBJ/UNgnBZLbNV13DlbnK0quyivTnXJM20=
|
||||
golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E=
|
||||
golang.org/x/tools v0.1.9 h1:j9KsMiaP1c3B0OTQGth0/k+miLGTgLsAFUCrF2vLcF8=
|
||||
golang.org/x/tools v0.1.9/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
@ -146,8 +145,8 @@ golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1N
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.zx2c4.com/wintun v0.0.0-20211104114900-415007cec224 h1:Ug9qvr1myri/zFN6xL17LSCBGFDnphBBhzmILHsM5TY=
|
||||
golang.zx2c4.com/wintun v0.0.0-20211104114900-415007cec224/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI=
|
||||
golang.zx2c4.com/wireguard v0.0.0-20220601130007-6a08d81f6bc4 h1:QlbNZ9SwDAepRQwgeWHLi3rfEMo/kVEU4SmgsNM7HmQ=
|
||||
golang.zx2c4.com/wireguard v0.0.0-20220601130007-6a08d81f6bc4/go.mod h1:bVQfyl2sCM/QIIGHpWbFGfHPuDvqnCNkT6MQLTCjO/U=
|
||||
golang.zx2c4.com/wireguard v0.0.0-20220407013110-ef5c587f782d h1:q4JksJ2n0fmbXC0Aj0eOs6E0AcPqnKglxWXWFqGD6x0=
|
||||
golang.zx2c4.com/wireguard v0.0.0-20220407013110-ef5c587f782d/go.mod h1:bVQfyl2sCM/QIIGHpWbFGfHPuDvqnCNkT6MQLTCjO/U=
|
||||
golang.zx2c4.com/wireguard/windows v0.5.4-0.20220328111914-004c22c5647e h1:yV04h6Tx19uDR6LvuEbR19cDU+3QrB9LuGjtF7F5G0w=
|
||||
golang.zx2c4.com/wireguard/windows v0.5.4-0.20220328111914-004c22c5647e/go.mod h1:1CeiatTZwcwSFA3cAtMm8CQoroviTldnxd7DOgM/vI4=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
@ -155,8 +154,10 @@ google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscL
|
||||
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
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/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gvisor.dev/gvisor v0.0.0-20220616232550-d004a30ec069 h1:Ly12hwbYd06NuYM2/nssGPgQ9ZVw7xaNs2lc087VW1w=
|
||||
gvisor.dev/gvisor v0.0.0-20220616232550-d004a30ec069/go.mod h1:TIvkJD0sxe8pIob3p6T8IzxXunlp6yfgktvTNp+DGNM=
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gvisor.dev/gvisor v0.0.0-20220506231117-8ef340c14150 h1:bspdBY1iCLtW6JXold8yhXHkAiE9UoWfmHShNkTc9JA=
|
||||
gvisor.dev/gvisor v0.0.0-20220506231117-8ef340c14150/go.mod h1:tWwEcFvJavs154OdjFCw78axNrsDlz4Zh8jvPqwcpGI=
|
||||
|
@ -15,6 +15,7 @@ import (
|
||||
"github.com/Dreamacro/clash/component/profile"
|
||||
"github.com/Dreamacro/clash/component/profile/cachefile"
|
||||
"github.com/Dreamacro/clash/component/resolver"
|
||||
S "github.com/Dreamacro/clash/component/script"
|
||||
"github.com/Dreamacro/clash/component/trie"
|
||||
"github.com/Dreamacro/clash/config"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
@ -79,12 +80,14 @@ func ApplyConfig(cfg *config.Config, force bool) {
|
||||
updateUsers(cfg.Users)
|
||||
updateProxies(cfg.Proxies, cfg.Providers)
|
||||
updateRules(cfg.Rules)
|
||||
updateRuleProviders(cfg.RuleProviders)
|
||||
updateHosts(cfg.Hosts)
|
||||
updateMitm(cfg.Mitm)
|
||||
updateProfile(cfg)
|
||||
updateDNS(cfg.DNS, cfg.General.Tun)
|
||||
updateDNS(cfg.DNS, cfg.Tun)
|
||||
updateGeneral(cfg.General, force)
|
||||
updateIPTables(cfg)
|
||||
updateTun(cfg.Tun, cfg.DNS)
|
||||
updateExperimental(cfg)
|
||||
|
||||
log.SetLevel(cfg.General.LogLevel)
|
||||
@ -93,8 +96,8 @@ func ApplyConfig(cfg *config.Config, force bool) {
|
||||
func GetGeneral() *config.General {
|
||||
ports := P.GetPorts()
|
||||
authenticator := []string{}
|
||||
if authM := authStore.Authenticator(); authM != nil {
|
||||
authenticator = authM.Users()
|
||||
if auth := authStore.Authenticator(); auth != nil {
|
||||
authenticator = auth.Users()
|
||||
}
|
||||
|
||||
general := &config.General{
|
||||
@ -112,16 +115,15 @@ func GetGeneral() *config.General {
|
||||
Mode: tunnel.Mode(),
|
||||
LogLevel: log.Level(),
|
||||
IPv6: !resolver.DisableIPv6,
|
||||
Sniffing: tunnel.Sniffing(),
|
||||
Tun: P.GetTunConf(),
|
||||
}
|
||||
|
||||
return general
|
||||
}
|
||||
|
||||
func updateExperimental(_ *config.Config) {}
|
||||
func updateExperimental(c *config.Config) {}
|
||||
|
||||
func updateDNS(c *config.DNS, t config.Tun) {
|
||||
func updateDNS(c *config.DNS, t *config.Tun) {
|
||||
cfg := dns.Config{
|
||||
Main: c.NameServer,
|
||||
Fallback: c.Fallback,
|
||||
@ -141,11 +143,6 @@ func updateDNS(c *config.DNS, t config.Tun) {
|
||||
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)
|
||||
pr := dns.NewProxyServerHostResolver(r)
|
||||
m := dns.NewEnhancer(cfg)
|
||||
@ -177,10 +174,6 @@ func updateDNS(c *config.DNS, t config.Tun) {
|
||||
}
|
||||
dns.ReCreateServer("", nil, nil)
|
||||
}
|
||||
|
||||
if cfg.Pool != nil {
|
||||
P.SetTunAddressPrefix(cfg.Pool.IPNet())
|
||||
}
|
||||
}
|
||||
|
||||
func updateHosts(tree *trie.DomainTrie[netip.Addr]) {
|
||||
@ -195,6 +188,19 @@ func updateRules(rules []C.Rule) {
|
||||
tunnel.UpdateRules(rules)
|
||||
}
|
||||
|
||||
func updateRuleProviders(providers map[string]C.Rule) {
|
||||
S.UpdateRuleProviders(providers)
|
||||
}
|
||||
|
||||
func updateTun(tun *config.Tun, dns *config.DNS) {
|
||||
var tunAddressPrefix *netip.Prefix
|
||||
if dns.FakeIPRange != nil {
|
||||
tunAddressPrefix = dns.FakeIPRange.IPNet()
|
||||
}
|
||||
|
||||
P.ReCreateTun(tun, tunAddressPrefix, tunnel.TCPIn(), tunnel.UDPIn())
|
||||
}
|
||||
|
||||
func updateGeneral(general *config.General, force bool) {
|
||||
tunnel.SetMode(general.Mode)
|
||||
resolver.DisableIPv6 = !general.IPv6
|
||||
@ -223,11 +229,6 @@ func updateGeneral(general *config.General, force bool) {
|
||||
bindAddress := general.BindAddress
|
||||
P.SetBindAddress(bindAddress)
|
||||
|
||||
sniffing := general.Sniffing
|
||||
tunnel.SetSniffing(sniffing)
|
||||
|
||||
log.Infoln("Use TLS SNI sniffer: %v", sniffing)
|
||||
|
||||
tcpIn := tunnel.TCPIn()
|
||||
udpIn := tunnel.UDPIn()
|
||||
|
||||
@ -237,7 +238,6 @@ func updateGeneral(general *config.General, force bool) {
|
||||
P.ReCreateTProxy(general.TProxyPort, tcpIn, udpIn)
|
||||
P.ReCreateMixed(general.MixedPort, tcpIn, udpIn)
|
||||
P.ReCreateMitm(general.MitmPort, tcpIn)
|
||||
P.ReCreateTun(&general.Tun, tcpIn, udpIn)
|
||||
}
|
||||
|
||||
func updateUsers(users []auth.AuthUser) {
|
||||
@ -279,7 +279,7 @@ func patchSelectGroup(proxies map[string]C.Proxy) {
|
||||
continue
|
||||
}
|
||||
|
||||
_ = selector.Set(selected)
|
||||
selector.Set(selected)
|
||||
}
|
||||
}
|
||||
|
||||
@ -343,6 +343,7 @@ func updateMitm(mitm *config.Mitm) {
|
||||
|
||||
func Shutdown() {
|
||||
P.Cleanup()
|
||||
S.Py_Finalize()
|
||||
tproxy.CleanupTProxyIPTables()
|
||||
resolver.StoreFakePoolState()
|
||||
|
||||
|
@ -1,17 +1,15 @@
|
||||
package route
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/netip"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/Dreamacro/clash/component/dialer"
|
||||
"github.com/Dreamacro/clash/component/resolver"
|
||||
"github.com/Dreamacro/clash/config"
|
||||
"github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/hub/executor"
|
||||
P "github.com/Dreamacro/clash/listener"
|
||||
"github.com/Dreamacro/clash/listener/tun/ipstack/commons"
|
||||
"github.com/Dreamacro/clash/log"
|
||||
"github.com/Dreamacro/clash/tunnel"
|
||||
|
||||
@ -28,28 +26,26 @@ func configRouter() http.Handler {
|
||||
}
|
||||
|
||||
type configSchema struct {
|
||||
Port *int `json:"port,omitempty"`
|
||||
SocksPort *int `json:"socks-port,omitempty"`
|
||||
RedirPort *int `json:"redir-port,omitempty"`
|
||||
TProxyPort *int `json:"tproxy-port,omitempty"`
|
||||
MixedPort *int `json:"mixed-port,omitempty"`
|
||||
MitmPort *int `json:"mitm-port,omitempty"`
|
||||
AllowLan *bool `json:"allow-lan,omitempty"`
|
||||
BindAddress *string `json:"bind-address,omitempty"`
|
||||
Mode *tunnel.TunnelMode `json:"mode,omitempty"`
|
||||
LogLevel *log.LogLevel `json:"log-level,omitempty"`
|
||||
IPv6 *bool `json:"ipv6,omitempty"`
|
||||
Sniffing *bool `json:"sniffing,omitempty"`
|
||||
Tun *tunConfigSchema `json:"tun,omitempty"`
|
||||
Port *int `json:"port"`
|
||||
SocksPort *int `json:"socks-port"`
|
||||
RedirPort *int `json:"redir-port"`
|
||||
TProxyPort *int `json:"tproxy-port"`
|
||||
MixedPort *int `json:"mixed-port"`
|
||||
MitmPort *int `json:"mitm-port"`
|
||||
AllowLan *bool `json:"allow-lan"`
|
||||
BindAddress *string `json:"bind-address"`
|
||||
Mode *tunnel.TunnelMode `json:"mode"`
|
||||
LogLevel *log.LogLevel `json:"log-level"`
|
||||
IPv6 *bool `json:"ipv6"`
|
||||
Tun *tunConfigSchema `json:"tun"`
|
||||
}
|
||||
|
||||
type tunConfigSchema struct {
|
||||
Enable *bool `json:"enable,omitempty"`
|
||||
Device *string `json:"device,omitempty"`
|
||||
Stack *constant.TUNStack `json:"stack,omitempty"`
|
||||
DNSHijack *[]constant.DNSUrl `json:"dns-hijack,omitempty"`
|
||||
AutoRoute *bool `json:"auto-route,omitempty"`
|
||||
AutoDetectInterface *bool `json:"auto-detect-interface,omitempty"`
|
||||
Enable *bool `json:"enable"`
|
||||
Device *string `json:"device"`
|
||||
Stack *constant.TUNStack `json:"stack"`
|
||||
DNSHijack *[]netip.AddrPort `json:"dns-hijack"`
|
||||
AutoRoute *bool `json:"auto-route"`
|
||||
}
|
||||
|
||||
func getConfigs(w http.ResponseWriter, r *http.Request) {
|
||||
@ -105,10 +101,6 @@ func patchConfigs(w http.ResponseWriter, r *http.Request) {
|
||||
resolver.DisableIPv6 = !*general.IPv6
|
||||
}
|
||||
|
||||
if general.Sniffing != nil {
|
||||
tunnel.SetSniffing(*general.Sniffing)
|
||||
}
|
||||
|
||||
if general.Tun != nil {
|
||||
tunSchema := general.Tun
|
||||
tunConf := P.GetTunConf()
|
||||
@ -128,26 +120,10 @@ func patchConfigs(w http.ResponseWriter, r *http.Request) {
|
||||
if tunSchema.AutoRoute != nil {
|
||||
tunConf.AutoRoute = *tunSchema.AutoRoute
|
||||
}
|
||||
if tunSchema.AutoDetectInterface != nil {
|
||||
tunConf.AutoDetectInterface = *tunSchema.AutoDetectInterface
|
||||
}
|
||||
|
||||
if dialer.DefaultInterface.Load() == "" && tunConf.Enable {
|
||||
outboundInterface, err := commons.GetAutoDetectInterface()
|
||||
if err != nil {
|
||||
render.Status(r, http.StatusBadRequest)
|
||||
render.JSON(w, r, newError("Get auto detect interface fail: "+err.Error()))
|
||||
return
|
||||
}
|
||||
dialer.DefaultInterface.Store(outboundInterface)
|
||||
}
|
||||
|
||||
P.ReCreateTun(&tunConf, tcpIn, udpIn)
|
||||
P.ReCreateTun(&tunConf, nil, tcpIn, udpIn)
|
||||
}
|
||||
|
||||
msg, _ := json.Marshal(general)
|
||||
log.Warnln("[REST-API] patch config by: %s", string(msg))
|
||||
|
||||
render.NoContent(w, r)
|
||||
}
|
||||
|
||||
@ -169,7 +145,6 @@ func updateConfigs(w http.ResponseWriter, r *http.Request) {
|
||||
var err error
|
||||
|
||||
if req.Payload != "" {
|
||||
log.Warnln("[REST-API] update config by payload")
|
||||
cfg, err = executor.ParseWithBytes([]byte(req.Payload))
|
||||
if err != nil {
|
||||
render.Status(r, http.StatusBadRequest)
|
||||
@ -186,7 +161,6 @@ func updateConfigs(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
log.Warnln("[REST-API] reload config from path: %s", req.Path)
|
||||
cfg, err = executor.ParseWithPath(req.Path)
|
||||
if err != nil {
|
||||
render.Status(r, http.StatusBadRequest)
|
||||
|
@ -1,64 +0,0 @@
|
||||
package route
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"sync"
|
||||
|
||||
"github.com/Dreamacro/clash/config"
|
||||
"github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/hub/executor"
|
||||
"github.com/Dreamacro/clash/log"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/go-chi/render"
|
||||
)
|
||||
|
||||
var (
|
||||
updatingGeo bool
|
||||
updateGeoMux sync.Mutex
|
||||
)
|
||||
|
||||
func configGeoRouter() http.Handler {
|
||||
r := chi.NewRouter()
|
||||
r.Post("/", updateGeoDatabases)
|
||||
return r
|
||||
}
|
||||
|
||||
func updateGeoDatabases(w http.ResponseWriter, r *http.Request) {
|
||||
updateGeoMux.Lock()
|
||||
|
||||
if updatingGeo {
|
||||
updateGeoMux.Unlock()
|
||||
render.Status(r, http.StatusBadRequest)
|
||||
render.JSON(w, r, newError("updating..."))
|
||||
return
|
||||
}
|
||||
|
||||
updatingGeo = true
|
||||
updateGeoMux.Unlock()
|
||||
|
||||
go func() {
|
||||
defer func() {
|
||||
updatingGeo = false
|
||||
}()
|
||||
|
||||
log.Warnln("[REST-API] updating GEO databases...")
|
||||
|
||||
if err := config.UpdateGeoDatabases(); err != nil {
|
||||
log.Errorln("[REST-API] update GEO databases failed: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
log.Warnln("[REST-API] update GEO databases successful, apply config...")
|
||||
|
||||
cfg, err := executor.ParseWithPath(constant.Path.Config())
|
||||
if err != nil {
|
||||
log.Errorln("[REST-API] update GEO databases failed: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
executor.ApplyConfig(cfg, false)
|
||||
}()
|
||||
|
||||
render.NoContent(w, r)
|
||||
}
|
@ -11,7 +11,6 @@ import (
|
||||
"github.com/Dreamacro/clash/adapter/outboundgroup"
|
||||
"github.com/Dreamacro/clash/component/profile/cachefile"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/constant/provider"
|
||||
"github.com/Dreamacro/clash/tunnel"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
@ -44,10 +43,6 @@ func findProxyByName(next http.Handler) http.Handler {
|
||||
name := r.Context().Value(CtxKeyProxyName).(string)
|
||||
proxies := tunnel.Proxies()
|
||||
proxy, exist := proxies[name]
|
||||
if !exist {
|
||||
proxy, exist = findProxyInNonCompatibleProviderByName(name)
|
||||
}
|
||||
|
||||
if !exist {
|
||||
render.Status(r, http.StatusNotFound)
|
||||
render.JSON(w, r, ErrNotFound)
|
||||
@ -133,20 +128,3 @@ func getProxyDelay(w http.ResponseWriter, r *http.Request) {
|
||||
"delay": delay,
|
||||
})
|
||||
}
|
||||
|
||||
func findProxyInNonCompatibleProviderByName(name string) (proxy C.Proxy, found bool) {
|
||||
providers := tunnel.Providers()
|
||||
for _, pd := range providers {
|
||||
if pd.VehicleType() == provider.Compatible {
|
||||
continue
|
||||
}
|
||||
for _, pp := range pd.Proxies() {
|
||||
found = pp.Name() == name
|
||||
if found {
|
||||
proxy = pp
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
@ -67,7 +67,6 @@ func Start(addr string, secret string) {
|
||||
r.Get("/traffic", traffic)
|
||||
r.Get("/version", version)
|
||||
r.Mount("/configs", configRouter())
|
||||
r.Mount("/configs/geo", configGeoRouter())
|
||||
r.Mount("/proxies", proxyRouter())
|
||||
r.Mount("/rules", ruleRouter())
|
||||
r.Mount("/connections", connectionRouter())
|
||||
@ -132,7 +131,7 @@ func authentication(next http.Handler) http.Handler {
|
||||
}
|
||||
|
||||
func hello(w http.ResponseWriter, r *http.Request) {
|
||||
render.JSON(w, r, render.M{"hello": "clash with tun"})
|
||||
render.JSON(w, r, render.M{"hello": "clash plus pro"})
|
||||
}
|
||||
|
||||
func traffic(w http.ResponseWriter, r *http.Request) {
|
||||
|
@ -13,15 +13,7 @@ import (
|
||||
)
|
||||
|
||||
func isUpgradeRequest(req *http.Request) bool {
|
||||
for _, header := range req.Header["Connection"] {
|
||||
for _, elm := range strings.Split(header, ",") {
|
||||
if strings.EqualFold(strings.TrimSpace(elm), "Upgrade") {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
return strings.EqualFold(req.Header.Get("Connection"), "Upgrade")
|
||||
}
|
||||
|
||||
func HandleUpgrade(localConn net.Conn, serverConn *N.BufferedConn, request *http.Request, in chan<- C.ConnContext) (resp *http.Response) {
|
||||
|
@ -8,6 +8,7 @@ import (
|
||||
"net"
|
||||
"net/netip"
|
||||
"os"
|
||||
"sort"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
@ -35,7 +36,6 @@ var (
|
||||
bindAddress = "*"
|
||||
lastTunConf *config.Tun
|
||||
lastTunAddressPrefix *netip.Prefix
|
||||
tunAddressPrefix *netip.Prefix
|
||||
|
||||
socksListener *socks.Listener
|
||||
socksUDPListener *socks.UDPListener
|
||||
@ -70,24 +70,11 @@ type Ports struct {
|
||||
|
||||
func GetTunConf() config.Tun {
|
||||
if lastTunConf == nil {
|
||||
addrPort := C.DNSAddrPort{
|
||||
AddrPort: netip.MustParseAddrPort("0.0.0.0:53"),
|
||||
}
|
||||
return config.Tun{
|
||||
Enable: false,
|
||||
Stack: C.TunGvisor,
|
||||
DNSHijack: []C.DNSUrl{ // default hijack all dns query
|
||||
{
|
||||
Network: "udp",
|
||||
AddrPort: addrPort,
|
||||
},
|
||||
{
|
||||
Network: "tcp",
|
||||
AddrPort: addrPort,
|
||||
},
|
||||
},
|
||||
AutoRoute: true,
|
||||
AutoDetectInterface: false,
|
||||
Enable: false,
|
||||
Stack: C.TunGvisor,
|
||||
DNSHijack: []netip.AddrPort{netip.MustParseAddrPort("0.0.0.0:53")},
|
||||
AutoRoute: true,
|
||||
}
|
||||
}
|
||||
return *lastTunConf
|
||||
@ -109,10 +96,6 @@ func SetBindAddress(host string) {
|
||||
bindAddress = host
|
||||
}
|
||||
|
||||
func SetTunAddressPrefix(tunAddress *netip.Prefix) {
|
||||
tunAddressPrefix = tunAddress
|
||||
}
|
||||
|
||||
func ReCreateHTTP(port int, tcpIn chan<- C.ConnContext) {
|
||||
httpMux.Lock()
|
||||
defer httpMux.Unlock()
|
||||
@ -352,7 +335,7 @@ func ReCreateMixed(port int, tcpIn chan<- C.ConnContext, udpIn chan<- *inbound.P
|
||||
log.Infoln("Mixed(http+socks) proxy listening at: %s", mixedListener.Address())
|
||||
}
|
||||
|
||||
func ReCreateTun(tunConf *config.Tun, tcpIn chan<- C.ConnContext, udpIn chan<- *inbound.PacketAdapter) {
|
||||
func ReCreateTun(tunConf *config.Tun, tunAddressPrefix *netip.Prefix, tcpIn chan<- C.ConnContext, udpIn chan<- *inbound.PacketAdapter) {
|
||||
tunMux.Lock()
|
||||
defer tunMux.Unlock()
|
||||
|
||||
@ -367,8 +350,6 @@ func ReCreateTun(tunConf *config.Tun, tcpIn chan<- C.ConnContext, udpIn chan<- *
|
||||
tunAddressPrefix = lastTunAddressPrefix
|
||||
}
|
||||
|
||||
tunConf.DNSHijack = C.RemoveDuplicateDNSUrl(tunConf.DNSHijack)
|
||||
|
||||
if tunStackListener != nil {
|
||||
if !hasTunConfigChange(tunConf, tunAddressPrefix) {
|
||||
return
|
||||
@ -385,13 +366,7 @@ func ReCreateTun(tunConf *config.Tun, tcpIn chan<- C.ConnContext, udpIn chan<- *
|
||||
return
|
||||
}
|
||||
|
||||
callback := &tunChangeCallback{
|
||||
tunConf: *tunConf,
|
||||
tcpIn: tcpIn,
|
||||
udpIn: udpIn,
|
||||
}
|
||||
|
||||
tunStackListener, err = tun.New(tunConf, tunAddressPrefix, tcpIn, udpIn, callback)
|
||||
tunStackListener, err = tun.New(tunConf, tunAddressPrefix, tcpIn, udpIn)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@ -544,6 +519,14 @@ func hasTunConfigChange(tunConf *config.Tun, tunAddressPrefix *netip.Prefix) boo
|
||||
return true
|
||||
}
|
||||
|
||||
sort.Slice(lastTunConf.DNSHijack, func(i, j int) bool {
|
||||
return lastTunConf.DNSHijack[i].Addr().Less(lastTunConf.DNSHijack[j].Addr())
|
||||
})
|
||||
|
||||
sort.Slice(tunConf.DNSHijack, func(i, j int) bool {
|
||||
return tunConf.DNSHijack[i].Addr().Less(tunConf.DNSHijack[j].Addr())
|
||||
})
|
||||
|
||||
for i, dns := range tunConf.DNSHijack {
|
||||
if dns != lastTunConf.DNSHijack[i] {
|
||||
return true
|
||||
@ -553,8 +536,7 @@ func hasTunConfigChange(tunConf *config.Tun, tunAddressPrefix *netip.Prefix) boo
|
||||
if lastTunConf.Enable != tunConf.Enable ||
|
||||
lastTunConf.Device != tunConf.Device ||
|
||||
lastTunConf.Stack != tunConf.Stack ||
|
||||
lastTunConf.AutoRoute != tunConf.AutoRoute ||
|
||||
lastTunConf.AutoDetectInterface != tunConf.AutoDetectInterface {
|
||||
lastTunConf.AutoRoute != tunConf.AutoRoute {
|
||||
return true
|
||||
}
|
||||
|
||||
@ -569,24 +551,6 @@ func hasTunConfigChange(tunConf *config.Tun, tunAddressPrefix *netip.Prefix) boo
|
||||
return false
|
||||
}
|
||||
|
||||
type tunChangeCallback struct {
|
||||
tunConf config.Tun
|
||||
tcpIn chan<- C.ConnContext
|
||||
udpIn chan<- *inbound.PacketAdapter
|
||||
}
|
||||
|
||||
func (t *tunChangeCallback) Pause() {
|
||||
conf := t.tunConf
|
||||
conf.Enable = false
|
||||
ReCreateTun(&conf, t.tcpIn, t.udpIn)
|
||||
}
|
||||
|
||||
func (t *tunChangeCallback) Resume() {
|
||||
conf := t.tunConf
|
||||
conf.Enable = true
|
||||
ReCreateTun(&conf, t.tcpIn, t.udpIn)
|
||||
}
|
||||
|
||||
func initCert() error {
|
||||
if _, err := os.Stat(C.Path.RootCA()); os.IsNotExist(err) {
|
||||
log.Infoln("Can't find mitm_ca.crt, start generate")
|
||||
|
@ -2,13 +2,12 @@ package tproxy
|
||||
|
||||
import (
|
||||
"net"
|
||||
"net/netip"
|
||||
|
||||
"github.com/Dreamacro/clash/common/pool"
|
||||
)
|
||||
|
||||
type packet struct {
|
||||
lAddr netip.AddrPort
|
||||
lAddr *net.UDPAddr
|
||||
buf []byte
|
||||
}
|
||||
|
||||
@ -18,21 +17,21 @@ func (c *packet) Data() []byte {
|
||||
|
||||
// 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) {
|
||||
tc, err := dialUDP("udp", addr.(*net.UDPAddr).AddrPort(), c.lAddr)
|
||||
tc, err := dialUDP("udp", addr.(*net.UDPAddr), c.lAddr)
|
||||
if err != nil {
|
||||
n = 0
|
||||
return
|
||||
}
|
||||
n, err = tc.Write(b)
|
||||
_ = tc.Close()
|
||||
tc.Close()
|
||||
return
|
||||
}
|
||||
|
||||
// LocalAddr returns the source IP/Port of UDP Packet
|
||||
func (c *packet) LocalAddr() net.Addr {
|
||||
return net.UDPAddrFromAddrPort(c.lAddr)
|
||||
return c.lAddr
|
||||
}
|
||||
|
||||
func (c *packet) Drop() {
|
||||
_ = pool.Put(c.buf)
|
||||
pool.Put(c.buf)
|
||||
}
|
||||
|
@ -32,7 +32,7 @@ func (l *Listener) Close() error {
|
||||
|
||||
func (l *Listener) handleTProxy(conn net.Conn, in chan<- C.ConnContext) {
|
||||
target := socks5.ParseAddrToSocksAddr(conn.LocalAddr())
|
||||
_ = conn.(*net.TCPConn).SetKeepAlive(true)
|
||||
conn.(*net.TCPConn).SetKeepAlive(true)
|
||||
in <- inbound.NewSocket(target, conn, C.TPROXY)
|
||||
}
|
||||
|
@ -162,12 +162,15 @@ func CleanupTProxyIPTables() {
|
||||
func addLocalnetworkToChain(chain string) {
|
||||
execCmd(fmt.Sprintf("iptables -t mangle -A %s -d 0.0.0.0/8 -j RETURN", chain))
|
||||
execCmd(fmt.Sprintf("iptables -t mangle -A %s -d 10.0.0.0/8 -j RETURN", chain))
|
||||
execCmd(fmt.Sprintf("iptables -t mangle -A %s -d 100.64.0.0/10 -j RETURN", chain))
|
||||
execCmd(fmt.Sprintf("iptables -t mangle -A %s -d 127.0.0.0/8 -j RETURN", chain))
|
||||
execCmd(fmt.Sprintf("iptables -t mangle -A %s -d 169.254.0.0/16 -j RETURN", chain))
|
||||
execCmd(fmt.Sprintf("iptables -t mangle -A %s -d 172.16.0.0/12 -j RETURN", chain))
|
||||
execCmd(fmt.Sprintf("iptables -t mangle -A %s -d 192.0.0.0/24 -j RETURN", chain))
|
||||
execCmd(fmt.Sprintf("iptables -t mangle -A %s -d 192.0.2.0/24 -j RETURN", chain))
|
||||
execCmd(fmt.Sprintf("iptables -t mangle -A %s -d 192.88.99.0/24 -j RETURN", chain))
|
||||
execCmd(fmt.Sprintf("iptables -t mangle -A %s -d 192.168.0.0/16 -j RETURN", chain))
|
||||
execCmd(fmt.Sprintf("iptables -t mangle -A %s -d 198.51.100.0/24 -j RETURN", chain))
|
||||
execCmd(fmt.Sprintf("iptables -t mangle -A %s -d 203.0.113.0/24 -j RETURN", chain))
|
||||
execCmd(fmt.Sprintf("iptables -t mangle -A %s -d 224.0.0.0/4 -j RETURN", chain))
|
||||
execCmd(fmt.Sprintf("iptables -t mangle -A %s -d 240.0.0.0/4 -j RETURN", chain))
|
@ -2,7 +2,6 @@ package tproxy
|
||||
|
||||
import (
|
||||
"net"
|
||||
"net/netip"
|
||||
|
||||
"github.com/Dreamacro/clash/adapter/inbound"
|
||||
"github.com/Dreamacro/clash/common/pool"
|
||||
@ -59,28 +58,28 @@ func NewUDP(addr string, in chan<- *inbound.PacketAdapter) (*UDPListener, error)
|
||||
oob := make([]byte, 1024)
|
||||
for {
|
||||
buf := pool.Get(pool.UDPBufferSize)
|
||||
n, oobn, _, lAddr, err := c.ReadMsgUDPAddrPort(buf, oob)
|
||||
n, oobn, _, lAddr, err := c.ReadMsgUDP(buf, oob)
|
||||
if err != nil {
|
||||
_ = pool.Put(buf)
|
||||
pool.Put(buf)
|
||||
if rl.closed {
|
||||
break
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
rAddr, err := getOrigDst(oob[:oobn])
|
||||
rAddr, err := getOrigDst(oob, oobn)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
handlePacketConn(in, buf[:n], lAddr, rAddr)
|
||||
handlePacketConn(l, in, buf[:n], lAddr, rAddr)
|
||||
}
|
||||
}()
|
||||
|
||||
return rl, nil
|
||||
}
|
||||
|
||||
func handlePacketConn(in chan<- *inbound.PacketAdapter, buf []byte, lAddr, rAddr netip.AddrPort) {
|
||||
target := socks5.AddrFromStdAddrPort(rAddr)
|
||||
func handlePacketConn(pc net.PacketConn, in chan<- *inbound.PacketAdapter, buf []byte, lAddr *net.UDPAddr, rAddr *net.UDPAddr) {
|
||||
target := socks5.ParseAddrToSocksAddr(rAddr)
|
||||
pkt := &packet{
|
||||
lAddr: lAddr,
|
||||
buf: buf,
|
||||
|
@ -3,14 +3,13 @@
|
||||
package tproxy
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/netip"
|
||||
"os"
|
||||
"strconv"
|
||||
"syscall"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -20,7 +19,7 @@ const (
|
||||
|
||||
// dialUDP acts like net.DialUDP for transparent proxy.
|
||||
// It binds to a non-local address(`lAddr`).
|
||||
func dialUDP(network string, lAddr, rAddr netip.AddrPort) (uc *net.UDPConn, err error) {
|
||||
func dialUDP(network string, lAddr *net.UDPAddr, rAddr *net.UDPAddr) (*net.UDPConn, error) {
|
||||
rSockAddr, err := udpAddrToSockAddr(rAddr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -36,25 +35,23 @@ func dialUDP(network string, lAddr, rAddr netip.AddrPort) (uc *net.UDPConn, err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if err != nil {
|
||||
syscall.Close(fd)
|
||||
}
|
||||
}()
|
||||
|
||||
if err = syscall.SetsockoptInt(fd, syscall.SOL_SOCKET, syscall.SO_REUSEADDR, 1); err != nil {
|
||||
syscall.Close(fd)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = syscall.SetsockoptInt(fd, syscall.SOL_IP, syscall.IP_TRANSPARENT, 1); err != nil {
|
||||
syscall.Close(fd)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = syscall.Bind(fd, lSockAddr); err != nil {
|
||||
syscall.Close(fd)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = syscall.Connect(fd, rSockAddr); err != nil {
|
||||
syscall.Close(fd)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@ -63,26 +60,35 @@ func dialUDP(network string, lAddr, rAddr netip.AddrPort) (uc *net.UDPConn, err
|
||||
|
||||
c, err := net.FileConn(fdFile)
|
||||
if err != nil {
|
||||
syscall.Close(fd)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return c.(*net.UDPConn), nil
|
||||
}
|
||||
|
||||
func udpAddrToSockAddr(addr netip.AddrPort) (syscall.Sockaddr, error) {
|
||||
if addr.Addr().Is4() {
|
||||
return &syscall.SockaddrInet4{Addr: addr.Addr().As4(), Port: int(addr.Port())}, nil
|
||||
}
|
||||
func udpAddrToSockAddr(addr *net.UDPAddr) (syscall.Sockaddr, error) {
|
||||
switch {
|
||||
case addr.IP.To4() != nil:
|
||||
ip := [4]byte{}
|
||||
copy(ip[:], addr.IP.To4())
|
||||
|
||||
zoneID, err := strconv.ParseUint(addr.Addr().Zone(), 10, 32)
|
||||
if err != nil {
|
||||
zoneID = 0
|
||||
}
|
||||
return &syscall.SockaddrInet4{Addr: ip, Port: addr.Port}, nil
|
||||
|
||||
return &syscall.SockaddrInet6{Addr: addr.Addr().As16(), Port: int(addr.Port()), ZoneId: uint32(zoneID)}, nil
|
||||
default:
|
||||
ip := [16]byte{}
|
||||
copy(ip[:], addr.IP.To16())
|
||||
|
||||
zoneID, err := strconv.ParseUint(addr.Zone, 10, 32)
|
||||
if err != nil {
|
||||
zoneID = 0
|
||||
}
|
||||
|
||||
return &syscall.SockaddrInet6{Addr: ip, Port: addr.Port, ZoneId: uint32(zoneID)}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func udpAddrFamily(net string, lAddr, rAddr netip.AddrPort) int {
|
||||
func udpAddrFamily(net string, lAddr, rAddr *net.UDPAddr) int {
|
||||
switch net[len(net)-1] {
|
||||
case '4':
|
||||
return syscall.AF_INET
|
||||
@ -90,35 +96,29 @@ func udpAddrFamily(net string, lAddr, rAddr netip.AddrPort) int {
|
||||
return syscall.AF_INET6
|
||||
}
|
||||
|
||||
if lAddr.Addr().Is4() && rAddr.Addr().Is4() {
|
||||
if (lAddr == nil || lAddr.IP.To4() != nil) && (rAddr == nil || lAddr.IP.To4() != nil) {
|
||||
return syscall.AF_INET
|
||||
}
|
||||
return syscall.AF_INET6
|
||||
}
|
||||
|
||||
func getOrigDst(oob []byte) (netip.AddrPort, error) {
|
||||
// oob contains socket control messages which we need to parse.
|
||||
scms, err := unix.ParseSocketControlMessage(oob)
|
||||
func getOrigDst(oob []byte, oobn int) (*net.UDPAddr, error) {
|
||||
msgs, err := syscall.ParseSocketControlMessage(oob[:oobn])
|
||||
if err != nil {
|
||||
return netip.AddrPort{}, fmt.Errorf("parse control message: %w", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// retrieve the destination address from the SCM.
|
||||
sa, err := unix.ParseOrigDstAddr(&scms[0])
|
||||
if err != nil {
|
||||
return netip.AddrPort{}, fmt.Errorf("retrieve destination: %w", err)
|
||||
for _, msg := range msgs {
|
||||
if msg.Header.Level == syscall.SOL_IP && msg.Header.Type == syscall.IP_RECVORIGDSTADDR {
|
||||
ip := net.IP(msg.Data[4:8])
|
||||
port := binary.BigEndian.Uint16(msg.Data[2:4])
|
||||
return &net.UDPAddr{IP: ip, Port: int(port)}, nil
|
||||
} else if msg.Header.Level == syscall.SOL_IPV6 && msg.Header.Type == IPV6_RECVORIGDSTADDR {
|
||||
ip := net.IP(msg.Data[8:24])
|
||||
port := binary.BigEndian.Uint16(msg.Data[2:4])
|
||||
return &net.UDPAddr{IP: ip, Port: int(port)}, nil
|
||||
}
|
||||
}
|
||||
|
||||
// encode the destination address into a cmsg.
|
||||
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
|
||||
return nil, errors.New("cannot find origDst")
|
||||
}
|
||||
|
@ -5,13 +5,12 @@ package tproxy
|
||||
import (
|
||||
"errors"
|
||||
"net"
|
||||
"net/netip"
|
||||
)
|
||||
|
||||
func getOrigDst(oob []byte) (netip.AddrPort, error) {
|
||||
return netip.AddrPort{}, errors.New("UDP redir not supported on current platform")
|
||||
}
|
||||
|
||||
func dialUDP(network string, lAddr, rAddr netip.AddrPort) (*net.UDPConn, error) {
|
||||
func getOrigDst(oob []byte, oobn int) (*net.UDPAddr, error) {
|
||||
return nil, errors.New("UDP redir not supported on current platform")
|
||||
}
|
||||
|
||||
func dialUDP(network string, lAddr *net.UDPAddr, rAddr *net.UDPAddr) (*net.UDPConn, error) {
|
||||
return nil, errors.New("UDP redir not supported on current platform")
|
||||
}
|
||||
|
@ -8,10 +8,8 @@ import (
|
||||
"io"
|
||||
"sync"
|
||||
|
||||
"github.com/Dreamacro/clash/common/pool"
|
||||
|
||||
"gvisor.dev/gvisor/pkg/buffer"
|
||||
"gvisor.dev/gvisor/pkg/tcpip"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/buffer"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/header"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/link/channel"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/stack"
|
||||
@ -95,29 +93,23 @@ func (e *Endpoint) dispatchLoop(cancel context.CancelFunc) {
|
||||
|
||||
mtu := int(e.mtu)
|
||||
for {
|
||||
data := pool.Get(mtu)
|
||||
data := make([]byte, mtu)
|
||||
|
||||
n, err := e.rw.Read(data)
|
||||
if err != nil {
|
||||
_ = pool.Put(data)
|
||||
break
|
||||
}
|
||||
|
||||
if n == 0 || n > mtu {
|
||||
_ = pool.Put(data)
|
||||
continue
|
||||
}
|
||||
|
||||
if !e.IsAttached() {
|
||||
_ = pool.Put(data)
|
||||
continue /* unattached, drop packet */
|
||||
}
|
||||
|
||||
pkt := stack.NewPacketBuffer(stack.PacketBufferOptions{
|
||||
Payload: buffer.NewWithData(data[:n]),
|
||||
OnRelease: func() {
|
||||
_ = pool.Put(data)
|
||||
},
|
||||
Data: buffer.View(data[:n]).ToVectorisedView(),
|
||||
})
|
||||
|
||||
switch header.IPVersion(data) {
|
||||
@ -125,8 +117,6 @@ func (e *Endpoint) dispatchLoop(cancel context.CancelFunc) {
|
||||
e.InjectInbound(header.IPv4ProtocolNumber, pkt)
|
||||
case header.IPv6Version:
|
||||
e.InjectInbound(header.IPv6ProtocolNumber, pkt)
|
||||
default:
|
||||
_ = pool.Put(data)
|
||||
}
|
||||
pkt.DecRef()
|
||||
}
|
||||
@ -148,8 +138,11 @@ func (e *Endpoint) outboundLoop(ctx context.Context) {
|
||||
func (e *Endpoint) writePacket(pkt *stack.PacketBuffer) tcpip.Error {
|
||||
defer pkt.DecRef()
|
||||
|
||||
buf := pkt.Buffer()
|
||||
if _, err := e.rw.Write(buf.Flatten()); err != nil {
|
||||
size := pkt.Size()
|
||||
views := pkt.Views()
|
||||
|
||||
vView := buffer.NewVectorisedView(size, views)
|
||||
if _, err := e.rw.Write(vView.ToView()); err != nil {
|
||||
return &tcpip.ErrInvalidEndpointState{}
|
||||
}
|
||||
return nil
|
||||
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -1,216 +0,0 @@
|
||||
/* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package driver
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
"github.com/Dreamacro/clash/log"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
"golang.zx2c4.com/wireguard/windows/driver/memmod"
|
||||
)
|
||||
|
||||
//go:linkname modwintun golang.zx2c4.com/wintun.modwintun
|
||||
|
||||
//go:linkname procWintunCreateAdapter golang.zx2c4.com/wintun.procWintunCreateAdapter
|
||||
|
||||
//go:linkname procWintunOpenAdapter golang.zx2c4.com/wintun.procWintunOpenAdapter
|
||||
|
||||
//go:linkname procWintunCloseAdapter golang.zx2c4.com/wintun.procWintunCloseAdapter
|
||||
|
||||
//go:linkname procWintunDeleteDriver golang.zx2c4.com/wintun.procWintunDeleteDriver
|
||||
|
||||
//go:linkname procWintunGetAdapterLUID golang.zx2c4.com/wintun.procWintunGetAdapterLUID
|
||||
|
||||
//go:linkname procWintunGetRunningDriverVersion golang.zx2c4.com/wintun.procWintunGetRunningDriverVersion
|
||||
|
||||
//go:linkname procWintunAllocateSendPacket golang.zx2c4.com/wintun.procWintunAllocateSendPacket
|
||||
|
||||
//go:linkname procWintunEndSession golang.zx2c4.com/wintun.procWintunEndSession
|
||||
|
||||
//go:linkname procWintunGetReadWaitEvent golang.zx2c4.com/wintun.procWintunGetReadWaitEvent
|
||||
|
||||
//go:linkname procWintunReceivePacket golang.zx2c4.com/wintun.procWintunReceivePacket
|
||||
|
||||
//go:linkname procWintunReleaseReceivePacket golang.zx2c4.com/wintun.procWintunReleaseReceivePacket
|
||||
|
||||
//go:linkname procWintunSendPacket golang.zx2c4.com/wintun.procWintunSendPacket
|
||||
|
||||
//go:linkname procWintunStartSession golang.zx2c4.com/wintun.procWintunStartSession
|
||||
|
||||
var (
|
||||
modwintun = newLazyDLL("wintun.dll", setupLogger)
|
||||
procWintunCreateAdapter = modwintun.NewProc("WintunCreateAdapter")
|
||||
procWintunOpenAdapter = modwintun.NewProc("WintunOpenAdapter")
|
||||
procWintunCloseAdapter = modwintun.NewProc("WintunCloseAdapter")
|
||||
procWintunDeleteDriver = modwintun.NewProc("WintunDeleteDriver")
|
||||
procWintunGetAdapterLUID = modwintun.NewProc("WintunGetAdapterLUID")
|
||||
procWintunGetRunningDriverVersion = modwintun.NewProc("WintunGetRunningDriverVersion")
|
||||
procWintunAllocateSendPacket = modwintun.NewProc("WintunAllocateSendPacket")
|
||||
procWintunEndSession = modwintun.NewProc("WintunEndSession")
|
||||
procWintunGetReadWaitEvent = modwintun.NewProc("WintunGetReadWaitEvent")
|
||||
procWintunReceivePacket = modwintun.NewProc("WintunReceivePacket")
|
||||
procWintunReleaseReceivePacket = modwintun.NewProc("WintunReleaseReceivePacket")
|
||||
procWintunSendPacket = modwintun.NewProc("WintunSendPacket")
|
||||
procWintunStartSession = modwintun.NewProc("WintunStartSession")
|
||||
)
|
||||
|
||||
type loggerLevel int
|
||||
|
||||
const (
|
||||
logInfo loggerLevel = iota
|
||||
logWarn
|
||||
logErr
|
||||
)
|
||||
|
||||
func InitWintun() (err error) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
err = fmt.Errorf("init wintun.dll error: %v", r)
|
||||
}
|
||||
}()
|
||||
|
||||
if err = modwintun.Load(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
procWintunCreateAdapter.Addr()
|
||||
procWintunOpenAdapter.Addr()
|
||||
procWintunCloseAdapter.Addr()
|
||||
procWintunDeleteDriver.Addr()
|
||||
procWintunGetAdapterLUID.Addr()
|
||||
procWintunGetRunningDriverVersion.Addr()
|
||||
procWintunAllocateSendPacket.Addr()
|
||||
procWintunEndSession.Addr()
|
||||
procWintunGetReadWaitEvent.Addr()
|
||||
procWintunReceivePacket.Addr()
|
||||
procWintunReleaseReceivePacket.Addr()
|
||||
procWintunSendPacket.Addr()
|
||||
procWintunStartSession.Addr()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func newLazyDLL(name string, onLoad func(d *lazyDLL)) *lazyDLL {
|
||||
return &lazyDLL{Name: name, onLoad: onLoad}
|
||||
}
|
||||
|
||||
func logMessage(level loggerLevel, _ uint64, msg *uint16) int {
|
||||
switch level {
|
||||
case logInfo:
|
||||
log.Infoln("[TUN] %s", windows.UTF16PtrToString(msg))
|
||||
case logWarn:
|
||||
log.Warnln("[TUN] %s", windows.UTF16PtrToString(msg))
|
||||
case logErr:
|
||||
log.Errorln("[TUN] %s", windows.UTF16PtrToString(msg))
|
||||
default:
|
||||
log.Debugln("[TUN] %s", windows.UTF16PtrToString(msg))
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func setupLogger(dll *lazyDLL) {
|
||||
var callback uintptr
|
||||
if runtime.GOARCH == "386" {
|
||||
callback = windows.NewCallback(func(level loggerLevel, _, _ uint32, msg *uint16) int {
|
||||
return logMessage(level, 0, msg)
|
||||
})
|
||||
} else if runtime.GOARCH == "arm" {
|
||||
callback = windows.NewCallback(func(level loggerLevel, _, _, _ uint32, msg *uint16) int {
|
||||
return logMessage(level, 0, msg)
|
||||
})
|
||||
} else if runtime.GOARCH == "amd64" || runtime.GOARCH == "arm64" {
|
||||
callback = windows.NewCallback(logMessage)
|
||||
}
|
||||
_, _, _ = syscall.SyscallN(dll.NewProc("WintunSetLogger").Addr(), callback)
|
||||
}
|
||||
|
||||
func (d *lazyDLL) NewProc(name string) *lazyProc {
|
||||
return &lazyProc{dll: d, Name: name}
|
||||
}
|
||||
|
||||
type lazyProc struct {
|
||||
Name string
|
||||
mu sync.Mutex
|
||||
dll *lazyDLL
|
||||
addr uintptr
|
||||
}
|
||||
|
||||
func (p *lazyProc) Find() error {
|
||||
if atomic.LoadPointer((*unsafe.Pointer)(unsafe.Pointer(&p.addr))) != nil {
|
||||
return nil
|
||||
}
|
||||
p.mu.Lock()
|
||||
defer p.mu.Unlock()
|
||||
if p.addr != 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
err := p.dll.Load()
|
||||
if err != nil {
|
||||
return fmt.Errorf("error loading DLL: %s, MODULE: %s, error: %w", p.dll.Name, p.Name, err)
|
||||
}
|
||||
addr, err := p.nameToAddr()
|
||||
if err != nil {
|
||||
return fmt.Errorf("error getting %s address: %w", p.Name, err)
|
||||
}
|
||||
|
||||
atomic.StorePointer((*unsafe.Pointer)(unsafe.Pointer(&p.addr)), unsafe.Pointer(addr))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *lazyProc) Addr() uintptr {
|
||||
err := p.Find()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return p.addr
|
||||
}
|
||||
|
||||
func (p *lazyProc) Load() error {
|
||||
return p.dll.Load()
|
||||
}
|
||||
|
||||
type lazyDLL struct {
|
||||
Name string
|
||||
Base windows.Handle
|
||||
mu sync.Mutex
|
||||
module *memmod.Module
|
||||
onLoad func(d *lazyDLL)
|
||||
}
|
||||
|
||||
func (d *lazyDLL) Load() error {
|
||||
if atomic.LoadPointer((*unsafe.Pointer)(unsafe.Pointer(&d.module))) != nil {
|
||||
return nil
|
||||
}
|
||||
d.mu.Lock()
|
||||
defer d.mu.Unlock()
|
||||
if d.module != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
module, err := memmod.LoadLibrary(dllData)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to load library: %w", err)
|
||||
}
|
||||
d.Base = windows.Handle(module.BaseAddr())
|
||||
|
||||
atomic.StorePointer((*unsafe.Pointer)(unsafe.Pointer(&d.module)), unsafe.Pointer(module))
|
||||
if d.onLoad != nil {
|
||||
d.onLoad(d)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *lazyProc) nameToAddr() (uintptr, error) {
|
||||
return p.dll.module.ProcAddressByName(p.Name)
|
||||
}
|
@ -1,13 +0,0 @@
|
||||
/* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package driver
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
)
|
||||
|
||||
//go:embed x86/wintun.dll
|
||||
var dllData []byte
|
@ -1,13 +0,0 @@
|
||||
/* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package driver
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
)
|
||||
|
||||
//go:embed amd64/wintun.dll
|
||||
var dllData []byte
|
@ -1,13 +0,0 @@
|
||||
/* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package driver
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
)
|
||||
|
||||
//go:embed arm/wintun.dll
|
||||
var dllData []byte
|
@ -1,13 +0,0 @@
|
||||
/* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package driver
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
)
|
||||
|
||||
//go:embed arm64/wintun.dll
|
||||
var dllData []byte
|
@ -1,10 +0,0 @@
|
||||
//go:build windows
|
||||
|
||||
// https://git.zx2c4.com/wireguard-go/tree/tun/wintun
|
||||
|
||||
/* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Copyright (C) 2019-2021 WireGuard LLC. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package driver
|
Binary file not shown.
@ -3,6 +3,7 @@
|
||||
package tun
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"runtime"
|
||||
@ -42,11 +43,11 @@ func Open(name string, mtu uint32) (_ device.Device, err error) {
|
||||
forcedMTU = int(t.mtu)
|
||||
}
|
||||
|
||||
nt, err := newDevice(t.name, forcedMTU) // forcedMTU do not work on wintun, need to be setting by other way
|
||||
nt, err := tun.CreateTUN(t.name, forcedMTU) // forcedMTU do not work on wintun, need to be setting by other way
|
||||
|
||||
// retry if abnormal exit at last time on Windows
|
||||
if err != nil && runtime.GOOS == "windows" && os.IsExist(err) {
|
||||
nt, err = newDevice(t.name, forcedMTU)
|
||||
// retry if abnormal exit on Windows at last time
|
||||
if err != nil && runtime.GOOS == "windows" && errors.Is(err, os.ErrExist) {
|
||||
nt, err = tun.CreateTUN(t.name, forcedMTU)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
@ -87,11 +88,7 @@ func (t *TUN) Write(packet []byte) (int, error) {
|
||||
|
||||
packet = append(t.cache[:t.offset], packet...)
|
||||
|
||||
n, err := t.nt.Write(packet, t.offset)
|
||||
if n < t.offset {
|
||||
return 0, err
|
||||
}
|
||||
return n - t.offset, err
|
||||
return t.nt.Write(packet, t.offset)
|
||||
}
|
||||
|
||||
func (t *TUN) Close() error {
|
||||
|
@ -2,13 +2,7 @@
|
||||
|
||||
package tun
|
||||
|
||||
import "golang.zx2c4.com/wireguard/tun"
|
||||
|
||||
const (
|
||||
offset = 4 /* 4 bytes TUN_PI */
|
||||
defaultMTU = 1500
|
||||
)
|
||||
|
||||
func newDevice(name string, mtu int) (tun.Device, error) {
|
||||
return tun.CreateTUN(name, mtu)
|
||||
}
|
||||
|
@ -1,8 +1,6 @@
|
||||
package tun
|
||||
|
||||
import (
|
||||
"github.com/Dreamacro/clash/listener/tun/device/tun/driver"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
"golang.zx2c4.com/wireguard/tun"
|
||||
)
|
||||
@ -22,11 +20,3 @@ func init() {
|
||||
func (t *TUN) LUID() uint64 {
|
||||
return t.nt.LUID()
|
||||
}
|
||||
|
||||
func newDevice(name string, mtu int) (nt tun.Device, err error) {
|
||||
if err = driver.InitWintun(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return tun.CreateTUN(name, mtu)
|
||||
}
|
||||
|
@ -5,16 +5,15 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/Dreamacro/clash/component/resolver"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
|
||||
D "github.com/miekg/dns"
|
||||
)
|
||||
|
||||
const DefaultDnsReadTimeout = time.Second * 10
|
||||
|
||||
func ShouldHijackDns(dnsHijack []C.DNSUrl, targetAddr netip.AddrPort, network string) bool {
|
||||
for _, dns := range dnsHijack {
|
||||
if dns.Network == network && (dns.AddrPort.AddrPort == targetAddr || (dns.AddrPort.Addr().IsUnspecified() && dns.AddrPort.Port() == targetAddr.Port())) {
|
||||
func ShouldHijackDns(dnsAdds []netip.AddrPort, targetAddr netip.AddrPort) bool {
|
||||
for _, addrPort := range dnsAdds {
|
||||
if addrPort == targetAddr || (addrPort.Addr().IsUnspecified() && targetAddr.Port() == 53) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
@ -1,26 +1,18 @@
|
||||
package commons
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/component/dialer"
|
||||
"github.com/Dreamacro/clash/log"
|
||||
)
|
||||
|
||||
var (
|
||||
defaultRoutes = []string{"1.0.0.0/8", "2.0.0.0/7", "4.0.0.0/6", "8.0.0.0/5", "16.0.0.0/4", "32.0.0.0/3", "64.0.0.0/2", "128.0.0.0/1"}
|
||||
|
||||
monitorDuration = 10 * time.Second
|
||||
monitorStarted = false
|
||||
monitorStop = make(chan struct{}, 2)
|
||||
monitorMux sync.Mutex
|
||||
|
||||
tunStatus = C.TunDisabled
|
||||
tunChangeCallback C.TUNChangeCallback
|
||||
errInterfaceNotFound = errors.New("default interface not found")
|
||||
defaultInterfaceMonitorDuration = 20 * time.Second
|
||||
)
|
||||
|
||||
func ipv4MaskString(bits int) string {
|
||||
@ -32,6 +24,26 @@ func ipv4MaskString(bits int) string {
|
||||
return fmt.Sprintf("%d.%d.%d.%d", m[0], m[1], m[2], m[3])
|
||||
}
|
||||
|
||||
func SetTunChangeCallback(callback C.TUNChangeCallback) {
|
||||
tunChangeCallback = callback
|
||||
func defaultInterfaceChangeMonitor() {
|
||||
t := time.NewTicker(defaultInterfaceMonitorDuration)
|
||||
defer t.Stop()
|
||||
|
||||
for {
|
||||
<-t.C
|
||||
|
||||
interfaceName, err := GetAutoDetectInterface()
|
||||
if err != nil {
|
||||
log.Warnln("[TUN] default interface monitor exited, cause: %v", err)
|
||||
break
|
||||
}
|
||||
|
||||
old := dialer.DefaultInterface.Load()
|
||||
if interfaceName == old {
|
||||
continue
|
||||
}
|
||||
|
||||
dialer.DefaultInterface.Store(interfaceName)
|
||||
|
||||
log.Warnln("[TUN] default interface changed by monitor, %s => %s", old, interfaceName)
|
||||
}
|
||||
}
|
||||
|
@ -2,30 +2,17 @@ package commons
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"net/netip"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/Dreamacro/clash/common/cmd"
|
||||
"github.com/Dreamacro/clash/component/dialer"
|
||||
"github.com/Dreamacro/clash/component/iface"
|
||||
"github.com/Dreamacro/clash/listener/tun/device"
|
||||
"github.com/Dreamacro/clash/log"
|
||||
|
||||
"golang.org/x/net/route"
|
||||
)
|
||||
|
||||
func GetAutoDetectInterface() (string, error) {
|
||||
ifaceM, err := defaultRouteInterface()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return ifaceM.Name, nil
|
||||
return cmd.ExecCmd("/bin/bash -c /sbin/route -n get default | grep 'interface:' | awk -F ' ' 'NR==1{print $2}' | xargs echo -n")
|
||||
}
|
||||
|
||||
func ConfigInterfaceAddress(dev device.Device, addr netip.Prefix, _ int, autoRoute bool) error {
|
||||
func ConfigInterfaceAddress(dev device.Device, addr netip.Prefix, forceMTU int, autoRoute bool) error {
|
||||
if !addr.Addr().Is4() {
|
||||
return fmt.Errorf("supported ipv4 only")
|
||||
}
|
||||
@ -67,6 +54,8 @@ func configInterfaceRouting(interfaceName string, addr netip.Prefix) error {
|
||||
}
|
||||
}
|
||||
|
||||
go defaultInterfaceChangeMonitor()
|
||||
|
||||
return execRouterCmd("add", "-inet6", "2000::/3", interfaceName)
|
||||
}
|
||||
|
||||
@ -74,95 +63,3 @@ func execRouterCmd(action, inet, route string, interfaceName string) error {
|
||||
_, err := cmd.ExecCmd(fmt.Sprintf("/sbin/route %s %s %s -interface %s", action, inet, route, interfaceName))
|
||||
return err
|
||||
}
|
||||
|
||||
func defaultRouteInterface() (*net.Interface, error) {
|
||||
rib, err := route.FetchRIB(syscall.AF_UNSPEC, syscall.NET_RT_DUMP2, 0)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("route.FetchRIB: %w", err)
|
||||
}
|
||||
|
||||
msgs, err := route.ParseRIB(syscall.NET_RT_IFLIST2, rib)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("route.ParseRIB: %w", err)
|
||||
}
|
||||
|
||||
for _, message := range msgs {
|
||||
routeMessage := message.(*route.RouteMessage)
|
||||
if routeMessage.Flags&(syscall.RTF_UP|syscall.RTF_GATEWAY|syscall.RTF_STATIC) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
addresses := routeMessage.Addrs
|
||||
|
||||
if (addresses[0].Family() == syscall.AF_INET && addresses[0].(*route.Inet4Addr).IP != *(*[4]byte)(net.IPv4zero)) ||
|
||||
(addresses[0].Family() == syscall.AF_INET6 && addresses[0].(*route.Inet6Addr).IP != *(*[16]byte)(net.IPv6zero)) {
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
ifaceM, err1 := net.InterfaceByIndex(routeMessage.Index)
|
||||
if err1 != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if strings.HasPrefix(ifaceM.Name, "utun") {
|
||||
continue
|
||||
}
|
||||
|
||||
return ifaceM, nil
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("ambiguous gateway interfaces found")
|
||||
}
|
||||
|
||||
func StartDefaultInterfaceChangeMonitor() {
|
||||
monitorMux.Lock()
|
||||
if monitorStarted {
|
||||
monitorMux.Unlock()
|
||||
return
|
||||
}
|
||||
monitorStarted = true
|
||||
monitorMux.Unlock()
|
||||
|
||||
select {
|
||||
case <-monitorStop:
|
||||
default:
|
||||
}
|
||||
|
||||
t := time.NewTicker(monitorDuration)
|
||||
defer t.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-t.C:
|
||||
interfaceName, err := GetAutoDetectInterface()
|
||||
if err != nil {
|
||||
log.Warnln("[TUN] default interface monitor err: %v", err)
|
||||
continue
|
||||
}
|
||||
|
||||
old := dialer.DefaultInterface.Load()
|
||||
if interfaceName == old {
|
||||
continue
|
||||
}
|
||||
|
||||
dialer.DefaultInterface.Store(interfaceName)
|
||||
|
||||
iface.FlushCache()
|
||||
|
||||
log.Warnln("[TUN] default interface changed by monitor, %s => %s", old, interfaceName)
|
||||
case <-monitorStop:
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func StopDefaultInterfaceChangeMonitor() {
|
||||
monitorMux.Lock()
|
||||
defer monitorMux.Unlock()
|
||||
|
||||
if monitorStarted {
|
||||
monitorStop <- struct{}{}
|
||||
monitorStarted = false
|
||||
}
|
||||
}
|
||||
|
@ -3,20 +3,16 @@ package commons
|
||||
import (
|
||||
"fmt"
|
||||
"net/netip"
|
||||
"time"
|
||||
|
||||
"github.com/Dreamacro/clash/common/cmd"
|
||||
"github.com/Dreamacro/clash/component/dialer"
|
||||
"github.com/Dreamacro/clash/component/iface"
|
||||
"github.com/Dreamacro/clash/listener/tun/device"
|
||||
"github.com/Dreamacro/clash/log"
|
||||
)
|
||||
|
||||
func GetAutoDetectInterface() (string, error) {
|
||||
return cmd.ExecCmd("bash -c ip route show | grep 'default via' | awk -F ' ' 'NR==1{print $5}' | xargs echo -n")
|
||||
}
|
||||
|
||||
func ConfigInterfaceAddress(dev device.Device, addr netip.Prefix, _ int, autoRoute bool) error {
|
||||
func ConfigInterfaceAddress(dev device.Device, addr netip.Prefix, forceMTU int, autoRoute bool) error {
|
||||
var (
|
||||
interfaceName = dev.Name()
|
||||
ip = addr.Masked().Addr().Next()
|
||||
@ -46,6 +42,8 @@ func configInterfaceRouting(interfaceName string, addr netip.Prefix) error {
|
||||
}
|
||||
}
|
||||
|
||||
go defaultInterfaceChangeMonitor()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -55,55 +53,3 @@ func execRouterCmd(action, route string, interfaceName string, linkIP string) er
|
||||
_, err := cmd.ExecCmd(cmdStr)
|
||||
return err
|
||||
}
|
||||
|
||||
func StartDefaultInterfaceChangeMonitor() {
|
||||
monitorMux.Lock()
|
||||
if monitorStarted {
|
||||
monitorMux.Unlock()
|
||||
return
|
||||
}
|
||||
monitorStarted = true
|
||||
monitorMux.Unlock()
|
||||
|
||||
select {
|
||||
case <-monitorStop:
|
||||
default:
|
||||
}
|
||||
|
||||
t := time.NewTicker(monitorDuration)
|
||||
defer t.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-t.C:
|
||||
interfaceName, err := GetAutoDetectInterface()
|
||||
if err != nil {
|
||||
log.Warnln("[TUN] default interface monitor err: %v", err)
|
||||
continue
|
||||
}
|
||||
|
||||
old := dialer.DefaultInterface.Load()
|
||||
if interfaceName == old {
|
||||
continue
|
||||
}
|
||||
|
||||
dialer.DefaultInterface.Store(interfaceName)
|
||||
|
||||
iface.FlushCache()
|
||||
|
||||
log.Warnln("[TUN] default interface changed by monitor, %s => %s", old, interfaceName)
|
||||
case <-monitorStop:
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func StopDefaultInterfaceChangeMonitor() {
|
||||
monitorMux.Lock()
|
||||
defer monitorMux.Unlock()
|
||||
|
||||
if monitorStarted {
|
||||
monitorStop <- struct{}{}
|
||||
monitorStarted = false
|
||||
}
|
||||
}
|
||||
|
@ -17,7 +17,3 @@ func GetAutoDetectInterface() (string, error) {
|
||||
func ConfigInterfaceAddress(device.Device, netip.Prefix, int, bool) error {
|
||||
return fmt.Errorf("unsupported on this OS: %s", runtime.GOOS)
|
||||
}
|
||||
|
||||
func StartDefaultInterfaceChangeMonitor() {}
|
||||
|
||||
func StopDefaultInterfaceChangeMonitor() {}
|
||||
|
@ -1,16 +1,12 @@
|
||||
package commons
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/netip"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/Dreamacro/clash/common/nnip"
|
||||
"github.com/Dreamacro/clash/component/dialer"
|
||||
"github.com/Dreamacro/clash/component/iface"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/listener/tun/device"
|
||||
"github.com/Dreamacro/clash/listener/tun/device/tun"
|
||||
"github.com/Dreamacro/clash/log"
|
||||
@ -20,11 +16,7 @@ import (
|
||||
"golang.zx2c4.com/wireguard/windows/tunnel/winipcfg"
|
||||
)
|
||||
|
||||
var (
|
||||
wintunInterfaceName string
|
||||
unicastAddressChangeCallback *winipcfg.UnicastAddressChangeCallback
|
||||
unicastAddressChangeLock sync.Mutex
|
||||
)
|
||||
var wintunInterfaceName string
|
||||
|
||||
func GetAutoDetectInterface() (string, error) {
|
||||
ifname, err := getAutoDetectInterfaceByFamily(winipcfg.AddressFamily(windows.AF_INET))
|
||||
@ -213,6 +205,8 @@ startOver:
|
||||
|
||||
wintunInterfaceName = dev.Name()
|
||||
|
||||
go defaultInterfaceChangeMonitor()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -228,15 +222,15 @@ func cleanupAddressesOnDisconnectedInterfaces(family winipcfg.AddressFamily, add
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
for _, ifaceM := range interfaces {
|
||||
if ifaceM.OperStatus == winipcfg.IfOperStatusUp {
|
||||
for _, iface := range interfaces {
|
||||
if iface.OperStatus == winipcfg.IfOperStatusUp {
|
||||
continue
|
||||
}
|
||||
for address := ifaceM.FirstUnicastAddress; address != nil; address = address.Next {
|
||||
for address := iface.FirstUnicastAddress; address != nil; address = address.Next {
|
||||
if ip := nnip.IpToAddr(address.Address.IP()); addrHash[ip] {
|
||||
prefix := netip.PrefixFrom(ip, int(address.OnLinkPrefixLength))
|
||||
log.Infoln("[TUN] cleaning up stale address %s from interface ‘%s’", prefix.String(), ifaceM.FriendlyName())
|
||||
_ = ifaceM.LUID.DeleteIPAddress(prefix)
|
||||
log.Infoln("[TUN] cleaning up stale address %s from interface ‘%s’", prefix.String(), iface.FriendlyName())
|
||||
_ = iface.LUID.DeleteIPAddress(prefix)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -245,7 +239,7 @@ func cleanupAddressesOnDisconnectedInterfaces(family winipcfg.AddressFamily, add
|
||||
func getAutoDetectInterfaceByFamily(family winipcfg.AddressFamily) (string, error) {
|
||||
interfaces, err := winipcfg.GetAdaptersAddresses(family, winipcfg.GAAFlagIncludeGateways)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("get default interface failure. %w", err)
|
||||
return "", fmt.Errorf("get ethernet interface failure. %w", err)
|
||||
}
|
||||
|
||||
var destination netip.Prefix
|
||||
@ -255,96 +249,25 @@ func getAutoDetectInterfaceByFamily(family winipcfg.AddressFamily) (string, erro
|
||||
destination = netip.PrefixFrom(netip.IPv6Unspecified(), 0)
|
||||
}
|
||||
|
||||
for _, ifaceM := range interfaces {
|
||||
if ifaceM.OperStatus != winipcfg.IfOperStatusUp {
|
||||
for _, iface := range interfaces {
|
||||
if iface.OperStatus != winipcfg.IfOperStatusUp {
|
||||
continue
|
||||
}
|
||||
|
||||
ifname := ifaceM.FriendlyName()
|
||||
ifname := iface.FriendlyName()
|
||||
|
||||
if wintunInterfaceName == ifname {
|
||||
continue
|
||||
}
|
||||
|
||||
for gatewayAddress := ifaceM.FirstGatewayAddress; gatewayAddress != nil; gatewayAddress = gatewayAddress.Next {
|
||||
for gatewayAddress := iface.FirstGatewayAddress; gatewayAddress != nil; gatewayAddress = gatewayAddress.Next {
|
||||
nextHop := nnip.IpToAddr(gatewayAddress.Address.IP())
|
||||
|
||||
if _, err = ifaceM.LUID.Route(destination, nextHop); err == nil {
|
||||
if _, err = iface.LUID.Route(destination, nextHop); err == nil {
|
||||
return ifname, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return "", errInterfaceNotFound
|
||||
}
|
||||
|
||||
func unicastAddressChange(_ winipcfg.MibNotificationType, unicastAddress *winipcfg.MibUnicastIPAddressRow) {
|
||||
unicastAddressChangeLock.Lock()
|
||||
defer unicastAddressChangeLock.Unlock()
|
||||
|
||||
interfaceName, err := GetAutoDetectInterface()
|
||||
if err != nil {
|
||||
if err == errInterfaceNotFound && tunStatus == C.TunEnabled {
|
||||
log.Warnln("[TUN] lost the default interface, pause tun adapter")
|
||||
|
||||
tunStatus = C.TunPaused
|
||||
tunChangeCallback.Pause()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
ifaceM, err := net.InterfaceByIndex(int(unicastAddress.InterfaceIndex))
|
||||
if err != nil {
|
||||
log.Warnln("[TUN] default interface monitor err: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
newName := ifaceM.Name
|
||||
|
||||
if newName != interfaceName {
|
||||
return
|
||||
}
|
||||
|
||||
dialer.DefaultInterface.Store(interfaceName)
|
||||
|
||||
iface.FlushCache()
|
||||
|
||||
if tunStatus == C.TunPaused {
|
||||
log.Warnln("[TUN] found interface %s(%s), resume tun adapter", interfaceName, unicastAddress.Address.Addr())
|
||||
|
||||
tunStatus = C.TunEnabled
|
||||
tunChangeCallback.Resume()
|
||||
return
|
||||
}
|
||||
|
||||
log.Warnln("[TUN] default interface changed to %s(%s) by monitor", interfaceName, unicastAddress.Address.Addr())
|
||||
}
|
||||
|
||||
func StartDefaultInterfaceChangeMonitor() {
|
||||
if unicastAddressChangeCallback != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var err error
|
||||
unicastAddressChangeCallback, err = winipcfg.RegisterUnicastAddressChangeCallback(unicastAddressChange)
|
||||
|
||||
if err != nil {
|
||||
log.Errorln("[TUN] register uni-cast address change callback failed: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
tunStatus = C.TunEnabled
|
||||
|
||||
log.Infoln("[TUN] register uni-cast address change callback")
|
||||
}
|
||||
|
||||
func StopDefaultInterfaceChangeMonitor() {
|
||||
if unicastAddressChangeCallback == nil || tunStatus == C.TunPaused {
|
||||
return
|
||||
}
|
||||
|
||||
_ = unicastAddressChangeCallback.Unregister()
|
||||
unicastAddressChangeCallback = nil
|
||||
tunChangeCallback = nil
|
||||
tunStatus = C.TunDisabled
|
||||
return "", errors.New("ethernet interface not found")
|
||||
}
|
||||
|
@ -2,15 +2,23 @@ package adapter
|
||||
|
||||
import (
|
||||
"net"
|
||||
|
||||
"gvisor.dev/gvisor/pkg/tcpip/stack"
|
||||
)
|
||||
|
||||
// TCPConn implements the net.Conn interface.
|
||||
type TCPConn interface {
|
||||
net.Conn
|
||||
|
||||
// ID returns the transport endpoint id of TCPConn.
|
||||
ID() *stack.TransportEndpointID
|
||||
}
|
||||
|
||||
// UDPConn implements net.Conn and net.PacketConn.
|
||||
type UDPConn interface {
|
||||
net.Conn
|
||||
net.PacketConn
|
||||
|
||||
// ID returns the transport endpoint id of UDPConn.
|
||||
ID() *stack.TransportEndpointID
|
||||
}
|
||||
|
@ -7,6 +7,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/Dreamacro/clash/adapter/inbound"
|
||||
"github.com/Dreamacro/clash/common/nnip"
|
||||
"github.com/Dreamacro/clash/common/pool"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
D "github.com/Dreamacro/clash/listener/tun/ipstack/commons"
|
||||
@ -19,23 +20,31 @@ var _ adapter.Handler = (*gvHandler)(nil)
|
||||
|
||||
type gvHandler struct {
|
||||
gateway netip.Addr
|
||||
dnsHijack []C.DNSUrl
|
||||
dnsHijack []netip.AddrPort
|
||||
|
||||
tcpIn chan<- C.ConnContext
|
||||
udpIn chan<- *inbound.PacketAdapter
|
||||
}
|
||||
|
||||
func (gh *gvHandler) HandleTCP(tunConn adapter.TCPConn) {
|
||||
rAddrPort := tunConn.LocalAddr().(*net.TCPAddr).AddrPort()
|
||||
id := tunConn.ID()
|
||||
|
||||
if D.ShouldHijackDns(gh.dnsHijack, rAddrPort, "tcp") {
|
||||
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) {
|
||||
go func() {
|
||||
log.Debugln("[TUN] hijack dns tcp: %s", rAddrPort.String())
|
||||
|
||||
buf := pool.Get(pool.UDPBufferSize)
|
||||
defer func() {
|
||||
_ = tunConn.Close()
|
||||
_ = pool.Put(buf)
|
||||
_ = tunConn.Close()
|
||||
}()
|
||||
|
||||
for {
|
||||
@ -69,18 +78,26 @@ func (gh *gvHandler) HandleTCP(tunConn adapter.TCPConn) {
|
||||
return
|
||||
}
|
||||
|
||||
gh.tcpIn <- inbound.NewSocket(socks5.AddrFromStdAddrPort(rAddrPort), tunConn, C.TUN)
|
||||
gh.tcpIn <- inbound.NewSocket(socks5.ParseAddrToSocksAddr(rAddr), tunConn, C.TUN)
|
||||
}
|
||||
|
||||
func (gh *gvHandler) HandleUDP(tunConn adapter.UDPConn) {
|
||||
rAddrPort := tunConn.LocalAddr().(*net.UDPAddr).AddrPort()
|
||||
id := tunConn.ID()
|
||||
|
||||
rAddr := &net.UDPAddr{
|
||||
IP: net.IP(id.LocalAddress),
|
||||
Port: int(id.LocalPort),
|
||||
Zone: "",
|
||||
}
|
||||
|
||||
rAddrPort := netip.AddrPortFrom(nnip.IpToAddr(rAddr.IP), id.LocalPort)
|
||||
|
||||
if rAddrPort.Addr() == gh.gateway {
|
||||
_ = tunConn.Close()
|
||||
return
|
||||
}
|
||||
|
||||
target := socks5.AddrFromStdAddrPort(rAddrPort)
|
||||
target := socks5.ParseAddrToSocksAddr(rAddr)
|
||||
|
||||
go func() {
|
||||
for {
|
||||
@ -92,20 +109,22 @@ func (gh *gvHandler) HandleUDP(tunConn adapter.UDPConn) {
|
||||
break
|
||||
}
|
||||
|
||||
if D.ShouldHijackDns(gh.dnsHijack, rAddrPort, "udp") {
|
||||
payload := buf[:n]
|
||||
|
||||
if D.ShouldHijackDns(gh.dnsHijack, rAddrPort) {
|
||||
go func() {
|
||||
defer func() {
|
||||
_ = pool.Put(buf)
|
||||
}()
|
||||
|
||||
msg, err1 := D.RelayDnsPacket(buf[:n])
|
||||
msg, err1 := D.RelayDnsPacket(payload)
|
||||
if err1 != nil {
|
||||
return
|
||||
}
|
||||
|
||||
_, _ = tunConn.WriteTo(msg, addr)
|
||||
|
||||
log.Debugln("[TUN] hijack dns udp: %s", rAddrPort.String())
|
||||
log.Debugln("[TUN] hijack dns udp: %s", rAddr.String())
|
||||
}()
|
||||
|
||||
continue
|
||||
@ -114,14 +133,12 @@ func (gh *gvHandler) HandleUDP(tunConn adapter.UDPConn) {
|
||||
gvPacket := &packet{
|
||||
pc: tunConn,
|
||||
rAddr: addr,
|
||||
payload: buf,
|
||||
offset: n,
|
||||
payload: payload,
|
||||
}
|
||||
|
||||
select {
|
||||
case gh.udpIn <- inbound.NewPacket(target, gvPacket, C.TUN):
|
||||
default:
|
||||
gvPacket.Drop()
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
@ -8,7 +8,6 @@ import (
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/listener/tun/device"
|
||||
"github.com/Dreamacro/clash/listener/tun/ipstack"
|
||||
"github.com/Dreamacro/clash/listener/tun/ipstack/commons"
|
||||
"github.com/Dreamacro/clash/listener/tun/ipstack/gvisor/option"
|
||||
|
||||
"gvisor.dev/gvisor/pkg/tcpip"
|
||||
@ -26,8 +25,6 @@ type gvStack struct {
|
||||
}
|
||||
|
||||
func (s *gvStack) Close() error {
|
||||
commons.StopDefaultInterfaceChangeMonitor()
|
||||
|
||||
var err error
|
||||
if s.device != nil {
|
||||
err = s.device.Close()
|
||||
@ -40,7 +37,7 @@ func (s *gvStack) Close() error {
|
||||
}
|
||||
|
||||
// New allocates a new *gvStack with given options.
|
||||
func New(device device.Device, dnsHijack []C.DNSUrl, tunAddress netip.Prefix, tcpIn chan<- C.ConnContext, udpIn chan<- *inbound.PacketAdapter, opts ...option.Option) (ipstack.Stack, error) {
|
||||
func New(device device.Device, dnsHijack []netip.AddrPort, tunAddress netip.Prefix, tcpIn chan<- C.ConnContext, udpIn chan<- *inbound.PacketAdapter, opts ...option.Option) (ipstack.Stack, error) {
|
||||
s := &gvStack{
|
||||
Stack: stack.New(stack.Options{
|
||||
NetworkProtocols: []stack.NetworkProtocolFactory{
|
||||
|
@ -70,6 +70,7 @@ func withTCPHandler(handle adapter.TCPHandleFunc) option.Option {
|
||||
|
||||
conn := &tcpConn{
|
||||
TCPConn: gonet.NewTCPConn(&wq, ep),
|
||||
id: id,
|
||||
}
|
||||
handle(conn)
|
||||
})
|
||||
@ -112,4 +113,9 @@ func setSocketOptions(s *stack.Stack, ep tcpip.Endpoint) tcpip.Error {
|
||||
|
||||
type tcpConn struct {
|
||||
*gonet.TCPConn
|
||||
id stack.TransportEndpointID
|
||||
}
|
||||
|
||||
func (c *tcpConn) ID() *stack.TransportEndpointID {
|
||||
return &c.id
|
||||
}
|
||||
|
@ -29,6 +29,7 @@ func withUDPHandler(handle adapter.UDPHandleFunc) option.Option {
|
||||
|
||||
conn := &udpConn{
|
||||
UDPConn: gonet.NewUDPConn(s, &wq, ep),
|
||||
id: id,
|
||||
}
|
||||
handle(conn)
|
||||
})
|
||||
@ -39,17 +40,21 @@ func withUDPHandler(handle adapter.UDPHandleFunc) option.Option {
|
||||
|
||||
type udpConn struct {
|
||||
*gonet.UDPConn
|
||||
id stack.TransportEndpointID
|
||||
}
|
||||
|
||||
func (c *udpConn) ID() *stack.TransportEndpointID {
|
||||
return &c.id
|
||||
}
|
||||
|
||||
type packet struct {
|
||||
pc adapter.UDPConn
|
||||
rAddr net.Addr
|
||||
payload []byte
|
||||
offset int
|
||||
}
|
||||
|
||||
func (c *packet) Data() []byte {
|
||||
return c.payload[:c.offset]
|
||||
return c.payload
|
||||
}
|
||||
|
||||
// WriteBack write UDP packet with source(ip, port) = `addr`
|
||||
|
@ -5,8 +5,8 @@ import (
|
||||
"net"
|
||||
"net/netip"
|
||||
|
||||
"github.com/Dreamacro/clash/common/pool"
|
||||
"github.com/Dreamacro/clash/listener/tun/ipstack/system/mars/tcpip"
|
||||
"github.com/Dreamacro/clash/log"
|
||||
)
|
||||
|
||||
func Start(device io.ReadWriter, gateway, portal, broadcast netip.Addr) (*TCP, *UDP, error) {
|
||||
@ -22,7 +22,7 @@ func Start(device io.ReadWriter, gateway, portal, broadcast netip.Addr) (*TCP, *
|
||||
tab := newTable()
|
||||
udp := &UDP{
|
||||
device: device,
|
||||
buf: [0xffff]byte{},
|
||||
buf: [pool.UDPBufferSize]byte{},
|
||||
}
|
||||
tcp := &TCP{
|
||||
listener: listener,
|
||||
@ -38,7 +38,7 @@ func Start(device io.ReadWriter, gateway, portal, broadcast netip.Addr) (*TCP, *
|
||||
_ = udp.Close()
|
||||
}()
|
||||
|
||||
buf := make([]byte, 0xffff)
|
||||
buf := make([]byte, pool.RelayBufferSize)
|
||||
|
||||
for {
|
||||
n, err := device.Read(buf)
|
||||
@ -133,11 +133,7 @@ func Start(device io.ReadWriter, gateway, portal, broadcast netip.Addr) (*TCP, *
|
||||
continue
|
||||
}
|
||||
|
||||
port, err = tab.newConn(tup)
|
||||
if err != nil {
|
||||
log.Warnln("[STACK] drop tcp packet by system stack: %v", err)
|
||||
continue
|
||||
}
|
||||
port = tab.newConn(tup)
|
||||
}
|
||||
|
||||
ip.SetSourceIP(portal)
|
||||
@ -156,7 +152,7 @@ func Start(device io.ReadWriter, gateway, portal, broadcast netip.Addr) (*TCP, *
|
||||
continue
|
||||
}
|
||||
|
||||
go udp.handleUDPPacket(ip, u)
|
||||
udp.handleUDPPacket(ip, u)
|
||||
case tcpip.ICMP:
|
||||
i := tcpip.ICMPPacket(ip.Payload())
|
||||
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user