mirror of
https://gitclone.com/github.com/MetaCubeX/Clash.Meta
synced 2024-11-15 05:31:18 +08:00
commit
cec14db4a8
100
.github/ISSUE_TEMPLATE/bug_report.md
vendored
100
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@ -1,100 +0,0 @@
|
|||||||
---
|
|
||||||
name: Bug report
|
|
||||||
about: Create a report to help us improve
|
|
||||||
title: "[Bug]"
|
|
||||||
labels: ''
|
|
||||||
assignees: ''
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
<!--
|
|
||||||
感谢你向 Clash Core 提交 issue!
|
|
||||||
在提交之前,请确认:
|
|
||||||
|
|
||||||
- [ ] 如果你可以自己 debug 并解决的话,提交 PR 吧!
|
|
||||||
- [ ] 我已经在 [Issue Tracker](……/) 中找过我要提出的问题
|
|
||||||
- [ ] 我已经使用 dev 分支版本测试过,问题依旧存在
|
|
||||||
- [ ] 我已经仔细看过 [Documentation](https://github.com/Dreamacro/clash/wiki/) 并无法自行解决问题
|
|
||||||
- [ ] 这是 Clash 核心的问题,并非我所使用的 Clash 衍生版本(如 OpenClash、KoolClash 等)的特定问题
|
|
||||||
|
|
||||||
请注意,如果你并没有遵照这个 issue template 填写内容,我们将直接关闭这个 issue。
|
|
||||||
|
|
||||||
Thanks for opening an issue towards the Clash core!
|
|
||||||
But before so, please do the following checklist:
|
|
||||||
|
|
||||||
- [ ] Is this something you can **debug and fix**? Send a pull request! Bug fixes and documentation fixes are welcome.
|
|
||||||
- [ ] I have searched on the [issue tracker](……/) for a related issue.
|
|
||||||
- [ ] I have tested using the dev branch, and the issue still exists.
|
|
||||||
- [ ] I have read the [documentation](https://github.com/Dreamacro/clash/wiki/) and was unable to solve the issue
|
|
||||||
- [ ] This is an issue of the Clash core *per se*, not to the derivatives of Clash, like OpenClash or KoolClash
|
|
||||||
|
|
||||||
Please understand that we close issues that fail to follow this issue template.
|
|
||||||
-->
|
|
||||||
|
|
||||||
------------------------------------------------------------------
|
|
||||||
|
|
||||||
<!--
|
|
||||||
请附上任何可以帮助我们解决这个问题的信息,如果我们收到的信息不足,我们将对这个 issue 加上 *Needs more information* 标记并在收到更多资讯之前关闭 issue。
|
|
||||||
Make sure to add **all the information needed to understand the bug** so that someone can help. If the info is missing we'll add the 'Needs more information' label and close the issue until there is enough information.
|
|
||||||
-->
|
|
||||||
|
|
||||||
### Clash config
|
|
||||||
<!--
|
|
||||||
在下方附上 Clash core 脱敏后配置文件的内容
|
|
||||||
Paste the Clash core configuration below.
|
|
||||||
-->
|
|
||||||
<details>
|
|
||||||
<summary>config.yaml</summary>
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
……
|
|
||||||
```
|
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
### Clash log
|
|
||||||
<!--
|
|
||||||
在下方附上 Clash Core 的日志,log level 使用 DEBUG
|
|
||||||
Paste the Clash core log below with the log level set to `DEBUG`.
|
|
||||||
-->
|
|
||||||
```
|
|
||||||
……
|
|
||||||
```
|
|
||||||
|
|
||||||
### 环境 Environment
|
|
||||||
|
|
||||||
* 操作系统 (the OS that the Clash core is running on)
|
|
||||||
……
|
|
||||||
* 网路环境或拓扑 (network conditions/topology)
|
|
||||||
……
|
|
||||||
* iptables,如果适用 (if applicable)
|
|
||||||
……
|
|
||||||
* ISP 有没有进行 DNS 污染 (is your ISP performing DNS pollution?)
|
|
||||||
……
|
|
||||||
* 其他 (any other information that would be useful)
|
|
||||||
……
|
|
||||||
|
|
||||||
### 说明 Description
|
|
||||||
|
|
||||||
<!--
|
|
||||||
请详细、清晰地表达你要提出的论述,例如这个问题如何影响到你?你想实现什么功能?
|
|
||||||
-->
|
|
||||||
|
|
||||||
### 重现问题的具体布骤 Steps to Reproduce
|
|
||||||
|
|
||||||
1. [First Step]
|
|
||||||
2. [Second Step]
|
|
||||||
3. ……
|
|
||||||
|
|
||||||
**我预期会发生……?**
|
|
||||||
<!-- **Expected behavior:** [What you expected to happen] -->
|
|
||||||
|
|
||||||
**实际上发生了什么?**
|
|
||||||
<!-- **Actual behavior:** [What actually happened] -->
|
|
||||||
|
|
||||||
### 可能的解决方案 Possible Solution
|
|
||||||
<!-- 此项非必须,但是如果你有想法的话欢迎提出。 -->
|
|
||||||
<!-- Not obligatory, but suggest a fix/reason for the bug, -->
|
|
||||||
<!-- or ideas how to implement the addition or change -->
|
|
||||||
|
|
||||||
### 更多信息 More Information
|
|
76
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
Normal file
76
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
name: Bug report
|
||||||
|
description: Create a report to help us improve
|
||||||
|
title: "[Bug] "
|
||||||
|
body:
|
||||||
|
- type: checkboxes
|
||||||
|
id: ensure
|
||||||
|
attributes:
|
||||||
|
label: Verify steps
|
||||||
|
description: "
|
||||||
|
在提交之前,请确认
|
||||||
|
Please verify that you've followed these steps
|
||||||
|
"
|
||||||
|
options:
|
||||||
|
- label: "
|
||||||
|
如果你可以自己 debug 并解决的话,提交 PR 吧
|
||||||
|
Is this something you can **debug and fix**? Send a pull request! Bug fixes and documentation fixes are welcome.
|
||||||
|
"
|
||||||
|
required: true
|
||||||
|
- label: "
|
||||||
|
我已经在 [Issue Tracker](……/) 中找过我要提出的问题
|
||||||
|
I have searched on the [issue tracker](……/) for a related issue.
|
||||||
|
"
|
||||||
|
required: true
|
||||||
|
- label: "
|
||||||
|
我已经使用 dev 分支版本测试过,问题依旧存在
|
||||||
|
I have tested using the dev branch, and the issue still exists.
|
||||||
|
"
|
||||||
|
required: true
|
||||||
|
- label: "
|
||||||
|
我已经仔细看过 [Documentation](https://github.com/Dreamacro/clash/wiki/) 并无法自行解决问题
|
||||||
|
I have read the [documentation](https://github.com/Dreamacro/clash/wiki/) and was unable to solve the issue.
|
||||||
|
"
|
||||||
|
required: true
|
||||||
|
- label: "
|
||||||
|
这是 Clash 核心的问题,并非我所使用的 Clash 衍生版本(如 OpenClash、KoolClash 等)的特定问题
|
||||||
|
This is an issue of the Clash core *per se*, not to the derivatives of Clash, like OpenClash or KoolClash.
|
||||||
|
"
|
||||||
|
required: true
|
||||||
|
- type: input
|
||||||
|
attributes:
|
||||||
|
label: Clash version
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: dropdown
|
||||||
|
id: os
|
||||||
|
attributes:
|
||||||
|
label: What OS are you seeing the problem on?
|
||||||
|
multiple: true
|
||||||
|
options:
|
||||||
|
- macOS
|
||||||
|
- Windows
|
||||||
|
- Linux
|
||||||
|
- OpenBSD/FreeBSD
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
render: yaml
|
||||||
|
label: "Clash config"
|
||||||
|
description: "
|
||||||
|
在下方附上 Clash core 脱敏后配置文件的内容
|
||||||
|
Paste the Clash core configuration below.
|
||||||
|
"
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
render: shell
|
||||||
|
label: Clash log
|
||||||
|
description: "
|
||||||
|
在下方附上 Clash Core 的日志,log level 使用 DEBUG
|
||||||
|
Paste the Clash core log below with the log level set to `DEBUG`.
|
||||||
|
"
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: Description
|
||||||
|
validations:
|
||||||
|
required: true
|
6
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
6
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
blank_issues_enabled: false
|
||||||
|
|
||||||
|
contact_links:
|
||||||
|
- name: Get help in GitHub Discussions
|
||||||
|
url: https://github.com/Dreamacro/clash/discussions
|
||||||
|
about: Have a question? Not sure if your issue affects everyone reproducibly? The quickest way to get help is on Clash's GitHub Discussions!
|
78
.github/ISSUE_TEMPLATE/feature_request.md
vendored
78
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@ -1,78 +0,0 @@
|
|||||||
---
|
|
||||||
name: Feature request
|
|
||||||
about: Suggest an idea for this project
|
|
||||||
title: "[Feature]"
|
|
||||||
labels: ''
|
|
||||||
assignees: ''
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
<!-- The English version is available. -->
|
|
||||||
感谢你向 Clash Core 提交 Feature Request!
|
|
||||||
在提交之前,请确认:
|
|
||||||
|
|
||||||
- [ ] 我已经在 [Issue Tracker](……/) 中找过我要提出的请求
|
|
||||||
|
|
||||||
请注意,如果你并没有遵照这个 issue template 填写内容,我们将直接关闭这个 issue。
|
|
||||||
|
|
||||||
<!--
|
|
||||||
Thanks for submitting a feature request towards the Clash core!
|
|
||||||
But before so, please do the following checklist:
|
|
||||||
|
|
||||||
- [ ] I have searched on the [issue tracker](……/) before creating the issue.
|
|
||||||
|
|
||||||
Please understand that we close issues that fail to follow the issue template.
|
|
||||||
-->
|
|
||||||
|
|
||||||
我都确认过了,我要继续提交。
|
|
||||||
<!-- None of the above, create a feature request -->
|
|
||||||
------------------------------------------------------------------
|
|
||||||
|
|
||||||
请附上任何可以帮助我们解决这个问题的信息,如果我们收到的信息不足,我们将对这个 issue 加上 *Needs more information* 标记并在收到更多资讯之前关闭 issue。
|
|
||||||
<!-- Make sure to add **all the information needed to understand the bug** so that someone can help. If the info is missing we'll add the 'Needs more information' label and close the issue until there is enough information. -->
|
|
||||||
|
|
||||||
### Clash core config
|
|
||||||
<!--
|
|
||||||
在下方附上 Clash Core 脱敏后的配置内容
|
|
||||||
Paste the Clash core configuration below.
|
|
||||||
-->
|
|
||||||
```
|
|
||||||
……
|
|
||||||
```
|
|
||||||
|
|
||||||
### Clash log
|
|
||||||
<!--
|
|
||||||
在下方附上 Clash Core 的日志,log level 请使用 DEBUG
|
|
||||||
Paste the Clash core log below with the log level set to `DEBUG`.
|
|
||||||
-->
|
|
||||||
```
|
|
||||||
……
|
|
||||||
```
|
|
||||||
|
|
||||||
### 环境 Environment
|
|
||||||
|
|
||||||
* Clash Core 的操作系统 (the OS that the Clash core is running on)
|
|
||||||
……
|
|
||||||
* 使用者的操作系统 (the OS running on the client)
|
|
||||||
……
|
|
||||||
* 网路环境或拓扑 (network conditions/topology)
|
|
||||||
……
|
|
||||||
* iptables,如果适用 (if applicable)
|
|
||||||
……
|
|
||||||
* ISP 有没有进行 DNS 污染 (is your ISP performing DNS pollution?)
|
|
||||||
……
|
|
||||||
* 其他
|
|
||||||
……
|
|
||||||
|
|
||||||
### 说明 Description
|
|
||||||
|
|
||||||
<!--
|
|
||||||
请详细、清晰地表达你要提出的论述,例如这个问题如何影响到你?你想实现什么功能?目前 Clash Core 的行为是什麽?
|
|
||||||
-->
|
|
||||||
|
|
||||||
### 可能的解决方案 Possible Solution
|
|
||||||
<!-- 此项非必须,但是如果你有想法的话欢迎提出。 -->
|
|
||||||
<!-- Not obligatory, but suggest a fix/reason for the bug, -->
|
|
||||||
<!-- or ideas how to implement the addition or change -->
|
|
||||||
|
|
||||||
### 更多信息 More Information
|
|
36
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
Normal file
36
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
name: Feature request
|
||||||
|
description: Suggest an idea for this project
|
||||||
|
title: "[Feature] "
|
||||||
|
body:
|
||||||
|
- type: checkboxes
|
||||||
|
id: ensure
|
||||||
|
attributes:
|
||||||
|
label: Verify steps
|
||||||
|
description: "
|
||||||
|
在提交之前,请确认
|
||||||
|
Please verify that you've followed these steps
|
||||||
|
"
|
||||||
|
options:
|
||||||
|
- label: "
|
||||||
|
我已经在 [Issue Tracker](……/) 中找过我要提出的请求
|
||||||
|
I have searched on the [issue tracker](……/) for a related feature request.
|
||||||
|
"
|
||||||
|
required: true
|
||||||
|
- label: "
|
||||||
|
我已经仔细看过 [Documentation](https://github.com/Dreamacro/clash/wiki/) 并无法自行解决问题
|
||||||
|
I have read the [documentation](https://github.com/Dreamacro/clash/wiki/) and was unable to solve the issue.
|
||||||
|
"
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: Description
|
||||||
|
description: 请详细、清晰地表达你要提出的论述,例如这个问题如何影响到你?你想实现什么功能?目前 Clash Core 的行为是什麽?
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: Possible Solution
|
||||||
|
description: "
|
||||||
|
此项非必须,但是如果你有想法的话欢迎提出。
|
||||||
|
Not obligatory, but suggest a fix/reason for the bug, or ideas how to implement the addition or change
|
||||||
|
"
|
2
.github/workflows/docker.yml
vendored
2
.github/workflows/docker.yml
vendored
@ -52,7 +52,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Get all docker tags
|
- name: Get all docker tags
|
||||||
if: startsWith(github.ref, 'refs/tags/')
|
if: startsWith(github.ref, 'refs/tags/')
|
||||||
uses: actions/github-script@v3
|
uses: actions/github-script@v4
|
||||||
id: tags
|
id: tags
|
||||||
with:
|
with:
|
||||||
script: |
|
script: |
|
||||||
|
12
.github/workflows/linter.yml
vendored
Normal file
12
.github/workflows/linter.yml
vendored
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
name: Linter
|
||||||
|
on: [push, pull_request]
|
||||||
|
jobs:
|
||||||
|
lint:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- name: golangci-lint
|
||||||
|
uses: golangci/golangci-lint-action@v2
|
||||||
|
with:
|
||||||
|
version: latest
|
||||||
|
args: --disable-all -E govet -E gofumpt -E megacheck ./...
|
@ -1,15 +1,18 @@
|
|||||||
name: Go
|
name: Release
|
||||||
on: [push, pull_request]
|
on: [push]
|
||||||
jobs:
|
jobs:
|
||||||
|
|
||||||
build:
|
build:
|
||||||
name: Build
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
|
- name: Get latest go version
|
||||||
|
id: version
|
||||||
|
run: |
|
||||||
|
echo ::set-output name=go_version::$(curl -s https://raw.githubusercontent.com/actions/go-versions/main/versions-manifest.json | grep -oE '"version": "[0-9]{1}.[0-9]{1,}(.[0-9]{1,})?"' | head -1 | cut -d':' -f2 | sed 's/ //g; s/"//g')
|
||||||
|
|
||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@v2
|
uses: actions/setup-go@v2
|
||||||
with:
|
with:
|
||||||
go-version: 1.16
|
go-version: ${{ steps.version.outputs.go_version }}
|
||||||
|
|
||||||
- name: Check out code into the Go module directory
|
- name: Check out code into the Go module directory
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v2
|
||||||
@ -22,12 +25,9 @@ jobs:
|
|||||||
restore-keys: |
|
restore-keys: |
|
||||||
${{ runner.os }}-go-
|
${{ runner.os }}-go-
|
||||||
|
|
||||||
- name: Get dependencies, run test and static check
|
- name: Get dependencies, run test
|
||||||
run: |
|
run: |
|
||||||
go test ./...
|
go test ./...
|
||||||
go vet ./...
|
|
||||||
go install honnef.co/go/tools/cmd/staticcheck@latest
|
|
||||||
staticcheck -- $(go list ./...)
|
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
if: startsWith(github.ref, 'refs/tags/')
|
if: startsWith(github.ref, 'refs/tags/')
|
||||||
@ -39,8 +39,6 @@ jobs:
|
|||||||
- name: Upload Release
|
- name: Upload Release
|
||||||
uses: softprops/action-gh-release@v1
|
uses: softprops/action-gh-release@v1
|
||||||
if: startsWith(github.ref, 'refs/tags/')
|
if: startsWith(github.ref, 'refs/tags/')
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
with:
|
with:
|
||||||
files: bin/*
|
files: bin/*
|
||||||
draft: true
|
draft: true
|
3
.github/workflows/stale.yml
vendored
3
.github/workflows/stale.yml
vendored
@ -11,9 +11,8 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/stale@v3
|
- uses: actions/stale@v4
|
||||||
with:
|
with:
|
||||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
stale-issue-message: 'This issue is stale because it has been open 60 days with no activity. Remove stale label or comment or this will be closed in 5 days'
|
stale-issue-message: 'This issue is stale because it has been open 60 days with no activity. Remove stale label or comment or this will be closed in 5 days'
|
||||||
days-before-stale: 60
|
days-before-stale: 60
|
||||||
days-before-close: 5
|
days-before-close: 5
|
||||||
|
5
.gitignore
vendored
5
.gitignore
vendored
@ -12,7 +12,7 @@ bin/*
|
|||||||
# Output of the go coverage tool, specifically when used with LiteIDE
|
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||||
*.out
|
*.out
|
||||||
|
|
||||||
# dep
|
# go mod vendor
|
||||||
vendor
|
vendor
|
||||||
|
|
||||||
# GoLand
|
# GoLand
|
||||||
@ -20,3 +20,6 @@ vendor
|
|||||||
|
|
||||||
# macOS file
|
# macOS file
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
|
||||||
|
# test suite
|
||||||
|
test/config/cache*
|
||||||
|
10
Makefile
10
Makefile
@ -28,6 +28,7 @@ PLATFORM_LIST = \
|
|||||||
WINDOWS_ARCH_LIST = \
|
WINDOWS_ARCH_LIST = \
|
||||||
windows-386 \
|
windows-386 \
|
||||||
windows-amd64 \
|
windows-amd64 \
|
||||||
|
windows-arm64 \
|
||||||
windows-arm32v7
|
windows-arm32v7
|
||||||
|
|
||||||
all: linux-amd64 darwin-amd64 windows-amd64 # Most used
|
all: linux-amd64 darwin-amd64 windows-amd64 # Most used
|
||||||
@ -91,7 +92,10 @@ windows-386:
|
|||||||
|
|
||||||
windows-amd64:
|
windows-amd64:
|
||||||
GOARCH=amd64 GOOS=windows $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.exe
|
GOARCH=amd64 GOOS=windows $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.exe
|
||||||
|
|
||||||
|
windows-arm64:
|
||||||
|
GOARCH=arm64 GOOS=windows $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.exe
|
||||||
|
|
||||||
windows-arm32v7:
|
windows-arm32v7:
|
||||||
GOARCH=arm GOOS=windows GOARM=7 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.exe
|
GOARCH=arm GOOS=windows GOARM=7 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.exe
|
||||||
|
|
||||||
@ -108,5 +112,9 @@ $(zip_releases): %.zip : %
|
|||||||
all-arch: $(PLATFORM_LIST) $(WINDOWS_ARCH_LIST)
|
all-arch: $(PLATFORM_LIST) $(WINDOWS_ARCH_LIST)
|
||||||
|
|
||||||
releases: $(gz_releases) $(zip_releases)
|
releases: $(gz_releases) $(zip_releases)
|
||||||
|
|
||||||
|
lint:
|
||||||
|
golangci-lint run --disable-all -E govet -E gofumpt -E megacheck ./...
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm $(BINDIR)/*
|
rm $(BINDIR)/*
|
||||||
|
17
README.md
17
README.md
@ -12,9 +12,13 @@
|
|||||||
<a href="https://goreportcard.com/report/github.com/Dreamacro/clash">
|
<a href="https://goreportcard.com/report/github.com/Dreamacro/clash">
|
||||||
<img src="https://goreportcard.com/badge/github.com/Dreamacro/clash?style=flat-square">
|
<img src="https://goreportcard.com/badge/github.com/Dreamacro/clash?style=flat-square">
|
||||||
</a>
|
</a>
|
||||||
|
<img src="https://img.shields.io/github/go-mod/go-version/Dreamacro/clash?style=flat-square">
|
||||||
<a href="https://github.com/Dreamacro/clash/releases">
|
<a href="https://github.com/Dreamacro/clash/releases">
|
||||||
<img src="https://img.shields.io/github/release/Dreamacro/clash/all.svg?style=flat-square">
|
<img src="https://img.shields.io/github/release/Dreamacro/clash/all.svg?style=flat-square">
|
||||||
</a>
|
</a>
|
||||||
|
<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>
|
</p>
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
@ -22,7 +26,7 @@
|
|||||||
- Local HTTP/HTTPS/SOCKS server with authentication support
|
- Local HTTP/HTTPS/SOCKS server with authentication support
|
||||||
- VMess, Shadowsocks, Trojan, Snell protocol support for remote connections
|
- VMess, Shadowsocks, Trojan, Snell protocol support for remote connections
|
||||||
- Built-in DNS server that aims to minimize DNS pollution attack impact, supports DoH/DoT upstream and fake IP.
|
- Built-in DNS server that aims to minimize DNS pollution attack impact, supports DoH/DoT upstream and fake IP.
|
||||||
- Rules based off domains, GEOIP, IP CIDR or ports to forward packets to different nodes
|
- Rules based off domains, GEOIP, IPCIDR or Process to forward packets to different nodes
|
||||||
- Remote groups allow users to implement powerful rules. Supports automatic fallback, load balancing or auto select node based off latency
|
- Remote groups allow users to implement powerful rules. Supports automatic fallback, load balancing or auto select node based off latency
|
||||||
- Remote providers, allowing users to get node lists remotely instead of hardcoding in config
|
- Remote providers, allowing users to get node lists remotely instead of hardcoding in config
|
||||||
- Netfilter TCP redirecting. Deploy Clash on your Internet gateway with `iptables`.
|
- Netfilter TCP redirecting. Deploy Clash on your Internet gateway with `iptables`.
|
||||||
@ -40,20 +44,17 @@ Documentations are now moved to [GitHub Wiki](https://github.com/Dreamacro/clash
|
|||||||
## Premium Release
|
## Premium Release
|
||||||
[Release](https://github.com/Dreamacro/clash/releases/tag/premium)
|
[Release](https://github.com/Dreamacro/clash/releases/tag/premium)
|
||||||
|
|
||||||
|
## 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)
|
||||||
|
|
||||||
## Credits
|
## Credits
|
||||||
|
|
||||||
* [riobard/go-shadowsocks2](https://github.com/riobard/go-shadowsocks2)
|
* [riobard/go-shadowsocks2](https://github.com/riobard/go-shadowsocks2)
|
||||||
* [v2ray/v2ray-core](https://github.com/v2ray/v2ray-core)
|
* [v2ray/v2ray-core](https://github.com/v2ray/v2ray-core)
|
||||||
|
* [WireGuard/wireguard-go](https://github.com/WireGuard/wireguard-go)
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
This software is released under the GPL-3.0 license.
|
This software is released under the GPL-3.0 license.
|
||||||
|
|
||||||
[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2FDreamacro%2Fclash.svg?type=large)](https://app.fossa.io/projects/git%2Bgithub.com%2FDreamacro%2Fclash?ref=badge_large)
|
[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2FDreamacro%2Fclash.svg?type=large)](https://app.fossa.io/projects/git%2Bgithub.com%2FDreamacro%2Fclash?ref=badge_large)
|
||||||
|
|
||||||
## TODO
|
|
||||||
|
|
||||||
- [x] Complementing the necessary rule operators
|
|
||||||
- [x] Redir proxy
|
|
||||||
- [x] UDP support
|
|
||||||
- [x] Connection manager
|
|
||||||
|
@ -1,110 +1,21 @@
|
|||||||
package outbound
|
package adapter
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/url"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/common/queue"
|
"github.com/Dreamacro/clash/common/queue"
|
||||||
|
"github.com/Dreamacro/clash/component/dialer"
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
|
|
||||||
"go.uber.org/atomic"
|
"go.uber.org/atomic"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Base struct {
|
|
||||||
name string
|
|
||||||
addr string
|
|
||||||
tp C.AdapterType
|
|
||||||
udp bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// Name implements C.ProxyAdapter
|
|
||||||
func (b *Base) Name() string {
|
|
||||||
return b.name
|
|
||||||
}
|
|
||||||
|
|
||||||
// Type implements C.ProxyAdapter
|
|
||||||
func (b *Base) Type() C.AdapterType {
|
|
||||||
return b.tp
|
|
||||||
}
|
|
||||||
|
|
||||||
// StreamConn implements C.ProxyAdapter
|
|
||||||
func (b *Base) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
|
||||||
return c, errors.New("no support")
|
|
||||||
}
|
|
||||||
|
|
||||||
// DialUDP implements C.ProxyAdapter
|
|
||||||
func (b *Base) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
|
|
||||||
return nil, errors.New("no support")
|
|
||||||
}
|
|
||||||
|
|
||||||
// SupportUDP implements C.ProxyAdapter
|
|
||||||
func (b *Base) SupportUDP() bool {
|
|
||||||
return b.udp
|
|
||||||
}
|
|
||||||
|
|
||||||
// MarshalJSON implements C.ProxyAdapter
|
|
||||||
func (b *Base) MarshalJSON() ([]byte, error) {
|
|
||||||
return json.Marshal(map[string]string{
|
|
||||||
"type": b.Type().String(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Addr implements C.ProxyAdapter
|
|
||||||
func (b *Base) Addr() string {
|
|
||||||
return b.addr
|
|
||||||
}
|
|
||||||
|
|
||||||
// Unwrap implements C.ProxyAdapter
|
|
||||||
func (b *Base) Unwrap(metadata *C.Metadata) C.Proxy {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewBase(name string, addr string, tp C.AdapterType, udp bool) *Base {
|
|
||||||
return &Base{name, addr, tp, udp}
|
|
||||||
}
|
|
||||||
|
|
||||||
type conn struct {
|
|
||||||
net.Conn
|
|
||||||
chain C.Chain
|
|
||||||
}
|
|
||||||
|
|
||||||
// Chains implements C.Connection
|
|
||||||
func (c *conn) Chains() C.Chain {
|
|
||||||
return c.chain
|
|
||||||
}
|
|
||||||
|
|
||||||
// AppendToChains implements C.Connection
|
|
||||||
func (c *conn) AppendToChains(a C.ProxyAdapter) {
|
|
||||||
c.chain = append(c.chain, a.Name())
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewConn(c net.Conn, a C.ProxyAdapter) C.Conn {
|
|
||||||
return &conn{c, []string{a.Name()}}
|
|
||||||
}
|
|
||||||
|
|
||||||
type packetConn struct {
|
|
||||||
net.PacketConn
|
|
||||||
chain C.Chain
|
|
||||||
}
|
|
||||||
|
|
||||||
// Chains implements C.Connection
|
|
||||||
func (c *packetConn) Chains() C.Chain {
|
|
||||||
return c.chain
|
|
||||||
}
|
|
||||||
|
|
||||||
// AppendToChains implements C.Connection
|
|
||||||
func (c *packetConn) AppendToChains(a C.ProxyAdapter) {
|
|
||||||
c.chain = append(c.chain, a.Name())
|
|
||||||
}
|
|
||||||
|
|
||||||
func newPacketConn(pc net.PacketConn, a C.ProxyAdapter) C.PacketConn {
|
|
||||||
return &packetConn{pc, []string{a.Name()}}
|
|
||||||
}
|
|
||||||
|
|
||||||
type Proxy struct {
|
type Proxy struct {
|
||||||
C.ProxyAdapter
|
C.ProxyAdapter
|
||||||
history *queue.Queue
|
history *queue.Queue
|
||||||
@ -118,20 +29,32 @@ func (p *Proxy) Alive() bool {
|
|||||||
|
|
||||||
// Dial implements C.Proxy
|
// Dial implements C.Proxy
|
||||||
func (p *Proxy) Dial(metadata *C.Metadata) (C.Conn, error) {
|
func (p *Proxy) Dial(metadata *C.Metadata) (C.Conn, error) {
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), tcpTimeout)
|
ctx, cancel := context.WithTimeout(context.Background(), C.DefaultTCPTimeout)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
return p.DialContext(ctx, metadata)
|
return p.DialContext(ctx, metadata)
|
||||||
}
|
}
|
||||||
|
|
||||||
// DialContext implements C.ProxyAdapter
|
// DialContext implements C.ProxyAdapter
|
||||||
func (p *Proxy) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
|
func (p *Proxy) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.Conn, error) {
|
||||||
conn, err := p.ProxyAdapter.DialContext(ctx, metadata)
|
conn, err := p.ProxyAdapter.DialContext(ctx, metadata, opts...)
|
||||||
if err != nil {
|
p.alive.Store(err == nil)
|
||||||
p.alive.Store(false)
|
|
||||||
}
|
|
||||||
return conn, err
|
return conn, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DialUDP implements C.ProxyAdapter
|
||||||
|
func (p *Proxy) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), C.DefaultUDPTimeout)
|
||||||
|
defer cancel()
|
||||||
|
return p.ListenPacketContext(ctx, metadata)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListenPacketContext implements C.ProxyAdapter
|
||||||
|
func (p *Proxy) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) {
|
||||||
|
pc, err := p.ProxyAdapter.ListenPacketContext(ctx, metadata, opts...)
|
||||||
|
p.alive.Store(err == nil)
|
||||||
|
return pc, err
|
||||||
|
}
|
||||||
|
|
||||||
// DelayHistory implements C.Proxy
|
// DelayHistory implements C.Proxy
|
||||||
func (p *Proxy) DelayHistory() []C.DelayHistory {
|
func (p *Proxy) DelayHistory() []C.DelayHistory {
|
||||||
queue := p.history.Copy()
|
queue := p.history.Copy()
|
||||||
@ -172,6 +95,7 @@ func (p *Proxy) MarshalJSON() ([]byte, error) {
|
|||||||
json.Unmarshal(inner, &mapping)
|
json.Unmarshal(inner, &mapping)
|
||||||
mapping["history"] = p.DelayHistory()
|
mapping["history"] = p.DelayHistory()
|
||||||
mapping["name"] = p.Name()
|
mapping["name"] = p.Name()
|
||||||
|
mapping["udp"] = p.SupportUDP()
|
||||||
return json.Marshal(mapping)
|
return json.Marshal(mapping)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -225,6 +149,8 @@ func (p *Proxy) URLTest(ctx context.Context, url string) (t uint16, err error) {
|
|||||||
return http.ErrUseLastResponse
|
return http.ErrUseLastResponse
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
defer client.CloseIdleConnections()
|
||||||
|
|
||||||
resp, err := client.Do(req)
|
resp, err := client.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
@ -237,3 +163,31 @@ func (p *Proxy) URLTest(ctx context.Context, url string) (t uint16, err error) {
|
|||||||
func NewProxy(adapter C.ProxyAdapter) *Proxy {
|
func NewProxy(adapter C.ProxyAdapter) *Proxy {
|
||||||
return &Proxy{adapter, queue.New(10), atomic.NewBool(true)}
|
return &Proxy{adapter, queue.New(10), atomic.NewBool(true)}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func urlToMetadata(rawURL string) (addr C.Metadata, err error) {
|
||||||
|
u, err := url.Parse(rawURL)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
port := u.Port()
|
||||||
|
if port == "" {
|
||||||
|
switch u.Scheme {
|
||||||
|
case "https":
|
||||||
|
port = "443"
|
||||||
|
case "http":
|
||||||
|
port = "80"
|
||||||
|
default:
|
||||||
|
err = fmt.Errorf("%s scheme not Support", rawURL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
addr = C.Metadata{
|
||||||
|
AddrType: C.AtypDomainName,
|
||||||
|
Host: u.Hostname(),
|
||||||
|
DstIP: nil,
|
||||||
|
DstPort: port,
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
21
adapter/inbound/http.go
Normal file
21
adapter/inbound/http.go
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
package inbound
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
|
||||||
|
C "github.com/Dreamacro/clash/constant"
|
||||||
|
"github.com/Dreamacro/clash/context"
|
||||||
|
"github.com/Dreamacro/clash/transport/socks5"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewHTTP receive normal http request and return HTTPContext
|
||||||
|
func NewHTTP(target socks5.Addr, source net.Addr, conn net.Conn) *context.ConnContext {
|
||||||
|
metadata := parseSocksAddr(target)
|
||||||
|
metadata.NetWork = C.TCP
|
||||||
|
metadata.Type = C.HTTP
|
||||||
|
if ip, port, err := parseAddr(source.String()); err == nil {
|
||||||
|
metadata.SrcIP = ip
|
||||||
|
metadata.SrcPort = port
|
||||||
|
}
|
||||||
|
return context.NewConnContext(conn, metadata)
|
||||||
|
}
|
@ -1,8 +1,8 @@
|
|||||||
package inbound
|
package inbound
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/Dreamacro/clash/component/socks5"
|
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
|
"github.com/Dreamacro/clash/transport/socks5"
|
||||||
)
|
)
|
||||||
|
|
||||||
// PacketAdapter is a UDP Packet adapter for socks/redir/tun
|
// PacketAdapter is a UDP Packet adapter for socks/redir/tun
|
@ -3,9 +3,9 @@ package inbound
|
|||||||
import (
|
import (
|
||||||
"net"
|
"net"
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/component/socks5"
|
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
"github.com/Dreamacro/clash/context"
|
"github.com/Dreamacro/clash/context"
|
||||||
|
"github.com/Dreamacro/clash/transport/socks5"
|
||||||
)
|
)
|
||||||
|
|
||||||
// NewSocket receive TCP inbound and return ConnContext
|
// NewSocket receive TCP inbound and return ConnContext
|
@ -6,8 +6,8 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/component/socks5"
|
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
|
"github.com/Dreamacro/clash/transport/socks5"
|
||||||
)
|
)
|
||||||
|
|
||||||
func parseSocksAddr(target socks5.Addr) *C.Metadata {
|
func parseSocksAddr(target socks5.Addr) *C.Metadata {
|
138
adapter/outbound/base.go
Normal file
138
adapter/outbound/base.go
Normal file
@ -0,0 +1,138 @@
|
|||||||
|
package outbound
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"net"
|
||||||
|
|
||||||
|
"github.com/Dreamacro/clash/component/dialer"
|
||||||
|
C "github.com/Dreamacro/clash/constant"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Base struct {
|
||||||
|
name string
|
||||||
|
addr string
|
||||||
|
iface string
|
||||||
|
tp C.AdapterType
|
||||||
|
udp bool
|
||||||
|
rmark int
|
||||||
|
}
|
||||||
|
|
||||||
|
// Name implements C.ProxyAdapter
|
||||||
|
func (b *Base) Name() string {
|
||||||
|
return b.name
|
||||||
|
}
|
||||||
|
|
||||||
|
// Type implements C.ProxyAdapter
|
||||||
|
func (b *Base) Type() C.AdapterType {
|
||||||
|
return b.tp
|
||||||
|
}
|
||||||
|
|
||||||
|
// StreamConn implements C.ProxyAdapter
|
||||||
|
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(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) {
|
||||||
|
return nil, errors.New("no support")
|
||||||
|
}
|
||||||
|
|
||||||
|
// SupportUDP implements C.ProxyAdapter
|
||||||
|
func (b *Base) SupportUDP() bool {
|
||||||
|
return b.udp
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalJSON implements C.ProxyAdapter
|
||||||
|
func (b *Base) MarshalJSON() ([]byte, error) {
|
||||||
|
return json.Marshal(map[string]string{
|
||||||
|
"type": b.Type().String(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Addr implements C.ProxyAdapter
|
||||||
|
func (b *Base) Addr() string {
|
||||||
|
return b.addr
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unwrap implements C.ProxyAdapter
|
||||||
|
func (b *Base) Unwrap(metadata *C.Metadata) C.Proxy {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DialOptions return []dialer.Option from struct
|
||||||
|
func (b *Base) DialOptions(opts ...dialer.Option) []dialer.Option {
|
||||||
|
if b.iface != "" {
|
||||||
|
opts = append(opts, dialer.WithInterface(b.iface))
|
||||||
|
}
|
||||||
|
|
||||||
|
if b.rmark != 0 {
|
||||||
|
opts = append(opts, dialer.WithRoutingMark(b.rmark))
|
||||||
|
}
|
||||||
|
|
||||||
|
return opts
|
||||||
|
}
|
||||||
|
|
||||||
|
type BasicOption struct {
|
||||||
|
Interface string `proxy:"interface-name,omitempty" group:"interface-name,omitempty"`
|
||||||
|
RoutingMark int `proxy:"routing-mark,omitempty" group:"routing-mark,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type BaseOption struct {
|
||||||
|
Name string
|
||||||
|
Addr string
|
||||||
|
Type C.AdapterType
|
||||||
|
UDP bool
|
||||||
|
Interface string
|
||||||
|
RoutingMark int
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewBase(opt BaseOption) *Base {
|
||||||
|
return &Base{
|
||||||
|
name: opt.Name,
|
||||||
|
addr: opt.Addr,
|
||||||
|
tp: opt.Type,
|
||||||
|
udp: opt.UDP,
|
||||||
|
iface: opt.Interface,
|
||||||
|
rmark: opt.RoutingMark,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type conn struct {
|
||||||
|
net.Conn
|
||||||
|
chain C.Chain
|
||||||
|
}
|
||||||
|
|
||||||
|
// Chains implements C.Connection
|
||||||
|
func (c *conn) Chains() C.Chain {
|
||||||
|
return c.chain
|
||||||
|
}
|
||||||
|
|
||||||
|
// AppendToChains implements C.Connection
|
||||||
|
func (c *conn) AppendToChains(a C.ProxyAdapter) {
|
||||||
|
c.chain = append(c.chain, a.Name())
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewConn(c net.Conn, a C.ProxyAdapter) C.Conn {
|
||||||
|
return &conn{c, []string{a.Name()}}
|
||||||
|
}
|
||||||
|
|
||||||
|
type packetConn struct {
|
||||||
|
net.PacketConn
|
||||||
|
chain C.Chain
|
||||||
|
}
|
||||||
|
|
||||||
|
// Chains implements C.Connection
|
||||||
|
func (c *packetConn) Chains() C.Chain {
|
||||||
|
return c.chain
|
||||||
|
}
|
||||||
|
|
||||||
|
// AppendToChains implements C.Connection
|
||||||
|
func (c *packetConn) AppendToChains(a C.ProxyAdapter) {
|
||||||
|
c.chain = append(c.chain, a.Name())
|
||||||
|
}
|
||||||
|
|
||||||
|
func newPacketConn(pc net.PacketConn, a C.ProxyAdapter) C.PacketConn {
|
||||||
|
return &packetConn{pc, []string{a.Name()}}
|
||||||
|
}
|
@ -13,10 +13,8 @@ type Direct struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// DialContext implements C.ProxyAdapter
|
// DialContext implements C.ProxyAdapter
|
||||||
func (d *Direct) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
|
func (d *Direct) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.Conn, error) {
|
||||||
address := net.JoinHostPort(metadata.String(), metadata.DstPort)
|
c, err := dialer.DialContext(ctx, "tcp", metadata.RemoteAddress(), d.Base.DialOptions(opts...)...)
|
||||||
|
|
||||||
c, err := dialer.DialContext(ctx, "tcp", address)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -24,9 +22,9 @@ func (d *Direct) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn,
|
|||||||
return NewConn(c, d), nil
|
return NewConn(c, d), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// DialUDP implements C.ProxyAdapter
|
// ListenPacketContext implements C.ProxyAdapter
|
||||||
func (d *Direct) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
|
func (d *Direct) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) {
|
||||||
pc, err := dialer.ListenPacket("udp", "")
|
pc, err := dialer.ListenPacket(ctx, "udp", "", d.Base.DialOptions(opts...)...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
@ -25,6 +25,7 @@ type Http struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type HttpOption struct {
|
type HttpOption struct {
|
||||||
|
BasicOption
|
||||||
Name string `proxy:"name"`
|
Name string `proxy:"name"`
|
||||||
Server string `proxy:"server"`
|
Server string `proxy:"server"`
|
||||||
Port int `proxy:"port"`
|
Port int `proxy:"port"`
|
||||||
@ -53,8 +54,8 @@ func (h *Http) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// DialContext implements C.ProxyAdapter
|
// DialContext implements C.ProxyAdapter
|
||||||
func (h *Http) DialContext(ctx context.Context, metadata *C.Metadata) (_ C.Conn, err error) {
|
func (h *Http) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) {
|
||||||
c, err := dialer.DialContext(ctx, "tcp", h.addr)
|
c, err := dialer.DialContext(ctx, "tcp", h.addr, h.Base.DialOptions(opts...)...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("%s connect error: %w", h.addr, err)
|
return nil, fmt.Errorf("%s connect error: %w", h.addr, err)
|
||||||
}
|
}
|
||||||
@ -125,16 +126,16 @@ func NewHttp(option HttpOption) *Http {
|
|||||||
}
|
}
|
||||||
tlsConfig = &tls.Config{
|
tlsConfig = &tls.Config{
|
||||||
InsecureSkipVerify: option.SkipCertVerify,
|
InsecureSkipVerify: option.SkipCertVerify,
|
||||||
ClientSessionCache: getClientSessionCache(),
|
|
||||||
ServerName: sni,
|
ServerName: sni,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return &Http{
|
return &Http{
|
||||||
Base: &Base{
|
Base: &Base{
|
||||||
name: option.Name,
|
name: option.Name,
|
||||||
addr: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)),
|
addr: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)),
|
||||||
tp: C.Http,
|
tp: C.Http,
|
||||||
|
iface: option.Interface,
|
||||||
},
|
},
|
||||||
user: option.UserName,
|
user: option.UserName,
|
||||||
pass: option.Password,
|
pass: option.Password,
|
@ -7,6 +7,7 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/Dreamacro/clash/component/dialer"
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -15,12 +16,12 @@ type Reject struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// DialContext implements C.ProxyAdapter
|
// DialContext implements C.ProxyAdapter
|
||||||
func (r *Reject) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
|
func (r *Reject) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.Conn, error) {
|
||||||
return NewConn(&NopConn{}, r), nil
|
return NewConn(&NopConn{}, r), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// DialUDP implements C.ProxyAdapter
|
// ListenPacketContext implements C.ProxyAdapter
|
||||||
func (r *Reject) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
|
func (r *Reject) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) {
|
||||||
return nil, errors.New("match reject rule")
|
return nil, errors.New("match reject rule")
|
||||||
}
|
}
|
||||||
|
|
@ -2,7 +2,6 @@ package outbound
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
@ -10,10 +9,10 @@ import (
|
|||||||
|
|
||||||
"github.com/Dreamacro/clash/common/structure"
|
"github.com/Dreamacro/clash/common/structure"
|
||||||
"github.com/Dreamacro/clash/component/dialer"
|
"github.com/Dreamacro/clash/component/dialer"
|
||||||
obfs "github.com/Dreamacro/clash/component/simple-obfs"
|
|
||||||
"github.com/Dreamacro/clash/component/socks5"
|
|
||||||
v2rayObfs "github.com/Dreamacro/clash/component/v2ray-plugin"
|
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
|
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"
|
"github.com/Dreamacro/go-shadowsocks2/core"
|
||||||
)
|
)
|
||||||
@ -29,6 +28,7 @@ type ShadowSocks struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type ShadowSocksOption struct {
|
type ShadowSocksOption struct {
|
||||||
|
BasicOption
|
||||||
Name string `proxy:"name"`
|
Name string `proxy:"name"`
|
||||||
Server string `proxy:"server"`
|
Server string `proxy:"server"`
|
||||||
Port int `proxy:"port"`
|
Port int `proxy:"port"`
|
||||||
@ -75,8 +75,8 @@ func (ss *ShadowSocks) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, e
|
|||||||
}
|
}
|
||||||
|
|
||||||
// DialContext implements C.ProxyAdapter
|
// DialContext implements C.ProxyAdapter
|
||||||
func (ss *ShadowSocks) DialContext(ctx context.Context, metadata *C.Metadata) (_ C.Conn, err error) {
|
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)
|
c, err := dialer.DialContext(ctx, "tcp", ss.addr, ss.Base.DialOptions(opts...)...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("%s connect error: %w", ss.addr, err)
|
return nil, fmt.Errorf("%s connect error: %w", ss.addr, err)
|
||||||
}
|
}
|
||||||
@ -88,9 +88,9 @@ func (ss *ShadowSocks) DialContext(ctx context.Context, metadata *C.Metadata) (_
|
|||||||
return NewConn(c, ss), err
|
return NewConn(c, ss), err
|
||||||
}
|
}
|
||||||
|
|
||||||
// DialUDP implements C.ProxyAdapter
|
// ListenPacketContext implements C.ProxyAdapter
|
||||||
func (ss *ShadowSocks) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
|
func (ss *ShadowSocks) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) {
|
||||||
pc, err := dialer.ListenPacket("udp", "")
|
pc, err := dialer.ListenPacket(ctx, "udp", "", ss.Base.DialOptions(opts...)...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -105,13 +105,6 @@ func (ss *ShadowSocks) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
|
|||||||
return newPacketConn(&ssPacketConn{PacketConn: pc, rAddr: addr}, ss), nil
|
return newPacketConn(&ssPacketConn{PacketConn: pc, rAddr: addr}, ss), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// MarshalJSON implements C.ProxyAdapter
|
|
||||||
func (ss *ShadowSocks) MarshalJSON() ([]byte, error) {
|
|
||||||
return json.Marshal(map[string]string{
|
|
||||||
"type": ss.Type().String(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) {
|
func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) {
|
||||||
addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port))
|
addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port))
|
||||||
cipher := option.Cipher
|
cipher := option.Cipher
|
||||||
@ -157,16 +150,16 @@ func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) {
|
|||||||
if opts.TLS {
|
if opts.TLS {
|
||||||
v2rayOption.TLS = true
|
v2rayOption.TLS = true
|
||||||
v2rayOption.SkipCertVerify = opts.SkipCertVerify
|
v2rayOption.SkipCertVerify = opts.SkipCertVerify
|
||||||
v2rayOption.SessionCache = getClientSessionCache()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return &ShadowSocks{
|
return &ShadowSocks{
|
||||||
Base: &Base{
|
Base: &Base{
|
||||||
name: option.Name,
|
name: option.Name,
|
||||||
addr: addr,
|
addr: addr,
|
||||||
tp: C.Shadowsocks,
|
tp: C.Shadowsocks,
|
||||||
udp: option.UDP,
|
udp: option.UDP,
|
||||||
|
iface: option.Interface,
|
||||||
},
|
},
|
||||||
cipher: ciph,
|
cipher: ciph,
|
||||||
|
|
@ -2,15 +2,14 @@ package outbound
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/component/dialer"
|
"github.com/Dreamacro/clash/component/dialer"
|
||||||
"github.com/Dreamacro/clash/component/ssr/obfs"
|
|
||||||
"github.com/Dreamacro/clash/component/ssr/protocol"
|
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
|
"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/core"
|
||||||
"github.com/Dreamacro/go-shadowsocks2/shadowaead"
|
"github.com/Dreamacro/go-shadowsocks2/shadowaead"
|
||||||
@ -25,6 +24,7 @@ type ShadowSocksR struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type ShadowSocksROption struct {
|
type ShadowSocksROption struct {
|
||||||
|
BasicOption
|
||||||
Name string `proxy:"name"`
|
Name string `proxy:"name"`
|
||||||
Server string `proxy:"server"`
|
Server string `proxy:"server"`
|
||||||
Port int `proxy:"port"`
|
Port int `proxy:"port"`
|
||||||
@ -60,8 +60,8 @@ func (ssr *ShadowSocksR) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// DialContext implements C.ProxyAdapter
|
// DialContext implements C.ProxyAdapter
|
||||||
func (ssr *ShadowSocksR) DialContext(ctx context.Context, metadata *C.Metadata) (_ C.Conn, err error) {
|
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)
|
c, err := dialer.DialContext(ctx, "tcp", ssr.addr, ssr.Base.DialOptions(opts...)...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("%s connect error: %w", ssr.addr, err)
|
return nil, fmt.Errorf("%s connect error: %w", ssr.addr, err)
|
||||||
}
|
}
|
||||||
@ -73,9 +73,9 @@ func (ssr *ShadowSocksR) DialContext(ctx context.Context, metadata *C.Metadata)
|
|||||||
return NewConn(c, ssr), err
|
return NewConn(c, ssr), err
|
||||||
}
|
}
|
||||||
|
|
||||||
// DialUDP implements C.ProxyAdapter
|
// ListenPacketContext implements C.ProxyAdapter
|
||||||
func (ssr *ShadowSocksR) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
|
func (ssr *ShadowSocksR) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) {
|
||||||
pc, err := dialer.ListenPacket("udp", "")
|
pc, err := dialer.ListenPacket(ctx, "udp", "", ssr.Base.DialOptions(opts...)...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -91,13 +91,6 @@ func (ssr *ShadowSocksR) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
|
|||||||
return newPacketConn(&ssPacketConn{PacketConn: pc, rAddr: addr}, ssr), nil
|
return newPacketConn(&ssPacketConn{PacketConn: pc, rAddr: addr}, ssr), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// MarshalJSON implements C.ProxyAdapter
|
|
||||||
func (ssr *ShadowSocksR) MarshalJSON() ([]byte, error) {
|
|
||||||
return json.Marshal(map[string]string{
|
|
||||||
"type": ssr.Type().String(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewShadowSocksR(option ShadowSocksROption) (*ShadowSocksR, error) {
|
func NewShadowSocksR(option ShadowSocksROption) (*ShadowSocksR, error) {
|
||||||
addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port))
|
addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port))
|
||||||
cipher := option.Cipher
|
cipher := option.Cipher
|
||||||
@ -144,10 +137,11 @@ func NewShadowSocksR(option ShadowSocksROption) (*ShadowSocksR, error) {
|
|||||||
|
|
||||||
return &ShadowSocksR{
|
return &ShadowSocksR{
|
||||||
Base: &Base{
|
Base: &Base{
|
||||||
name: option.Name,
|
name: option.Name,
|
||||||
addr: addr,
|
addr: addr,
|
||||||
tp: C.ShadowsocksR,
|
tp: C.ShadowsocksR,
|
||||||
udp: option.UDP,
|
udp: option.UDP,
|
||||||
|
iface: option.Interface,
|
||||||
},
|
},
|
||||||
cipher: coreCiph,
|
cipher: coreCiph,
|
||||||
obfs: obfs,
|
obfs: obfs,
|
@ -8,9 +8,9 @@ import (
|
|||||||
|
|
||||||
"github.com/Dreamacro/clash/common/structure"
|
"github.com/Dreamacro/clash/common/structure"
|
||||||
"github.com/Dreamacro/clash/component/dialer"
|
"github.com/Dreamacro/clash/component/dialer"
|
||||||
obfs "github.com/Dreamacro/clash/component/simple-obfs"
|
|
||||||
"github.com/Dreamacro/clash/component/snell"
|
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
|
obfs "github.com/Dreamacro/clash/transport/simple-obfs"
|
||||||
|
"github.com/Dreamacro/clash/transport/snell"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Snell struct {
|
type Snell struct {
|
||||||
@ -22,6 +22,7 @@ type Snell struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type SnellOption struct {
|
type SnellOption struct {
|
||||||
|
BasicOption
|
||||||
Name string `proxy:"name"`
|
Name string `proxy:"name"`
|
||||||
Server string `proxy:"server"`
|
Server string `proxy:"server"`
|
||||||
Port int `proxy:"port"`
|
Port int `proxy:"port"`
|
||||||
@ -51,20 +52,20 @@ func streamConn(c net.Conn, option streamOption) *snell.Snell {
|
|||||||
// StreamConn implements C.ProxyAdapter
|
// StreamConn implements C.ProxyAdapter
|
||||||
func (s *Snell) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
func (s *Snell) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
||||||
c = streamConn(c, streamOption{s.psk, s.version, s.addr, s.obfsOption})
|
c = streamConn(c, streamOption{s.psk, s.version, s.addr, s.obfsOption})
|
||||||
port, _ := strconv.Atoi(metadata.DstPort)
|
port, _ := strconv.ParseInt(metadata.DstPort, 10, 16)
|
||||||
err := snell.WriteHeader(c, metadata.String(), uint(port), s.version)
|
err := snell.WriteHeader(c, metadata.String(), uint(port), s.version)
|
||||||
return c, err
|
return c, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// DialContext implements C.ProxyAdapter
|
// DialContext implements C.ProxyAdapter
|
||||||
func (s *Snell) DialContext(ctx context.Context, metadata *C.Metadata) (_ C.Conn, err error) {
|
func (s *Snell) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) {
|
||||||
if s.version == snell.Version2 {
|
if s.version == snell.Version2 && len(opts) == 0 {
|
||||||
c, err := s.pool.Get()
|
c, err := s.pool.Get()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
port, _ := strconv.Atoi(metadata.DstPort)
|
port, _ := strconv.ParseUint(metadata.DstPort, 10, 16)
|
||||||
if err = snell.WriteHeader(c, metadata.String(), uint(port), s.version); err != nil {
|
if err = snell.WriteHeader(c, metadata.String(), uint(port), s.version); err != nil {
|
||||||
c.Close()
|
c.Close()
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -72,7 +73,7 @@ func (s *Snell) DialContext(ctx context.Context, metadata *C.Metadata) (_ C.Conn
|
|||||||
return NewConn(c, s), err
|
return NewConn(c, s), err
|
||||||
}
|
}
|
||||||
|
|
||||||
c, err := dialer.DialContext(ctx, "tcp", s.addr)
|
c, err := dialer.DialContext(ctx, "tcp", s.addr, s.Base.DialOptions(opts...)...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("%s connect error: %w", s.addr, err)
|
return nil, fmt.Errorf("%s connect error: %w", s.addr, err)
|
||||||
}
|
}
|
||||||
@ -111,9 +112,10 @@ func NewSnell(option SnellOption) (*Snell, error) {
|
|||||||
|
|
||||||
s := &Snell{
|
s := &Snell{
|
||||||
Base: &Base{
|
Base: &Base{
|
||||||
name: option.Name,
|
name: option.Name,
|
||||||
addr: addr,
|
addr: addr,
|
||||||
tp: C.Snell,
|
tp: C.Snell,
|
||||||
|
iface: option.Interface,
|
||||||
},
|
},
|
||||||
psk: psk,
|
psk: psk,
|
||||||
obfsOption: obfsOption,
|
obfsOption: obfsOption,
|
||||||
@ -122,7 +124,7 @@ func NewSnell(option SnellOption) (*Snell, error) {
|
|||||||
|
|
||||||
if option.Version == snell.Version2 {
|
if option.Version == snell.Version2 {
|
||||||
s.pool = snell.NewPool(func(ctx context.Context) (*snell.Snell, error) {
|
s.pool = snell.NewPool(func(ctx context.Context) (*snell.Snell, error) {
|
||||||
c, err := dialer.DialContext(ctx, "tcp", addr)
|
c, err := dialer.DialContext(ctx, "tcp", addr, s.Base.DialOptions()...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
@ -6,13 +6,12 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
|
||||||
"net"
|
"net"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/component/dialer"
|
"github.com/Dreamacro/clash/component/dialer"
|
||||||
"github.com/Dreamacro/clash/component/socks5"
|
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
|
"github.com/Dreamacro/clash/transport/socks5"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Socks5 struct {
|
type Socks5 struct {
|
||||||
@ -25,6 +24,7 @@ type Socks5 struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Socks5Option struct {
|
type Socks5Option struct {
|
||||||
|
BasicOption
|
||||||
Name string `proxy:"name"`
|
Name string `proxy:"name"`
|
||||||
Server string `proxy:"server"`
|
Server string `proxy:"server"`
|
||||||
Port int `proxy:"port"`
|
Port int `proxy:"port"`
|
||||||
@ -60,8 +60,8 @@ func (ss *Socks5) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// DialContext implements C.ProxyAdapter
|
// DialContext implements C.ProxyAdapter
|
||||||
func (ss *Socks5) DialContext(ctx context.Context, metadata *C.Metadata) (_ C.Conn, err error) {
|
func (ss *Socks5) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) {
|
||||||
c, err := dialer.DialContext(ctx, "tcp", ss.addr)
|
c, err := dialer.DialContext(ctx, "tcp", ss.addr, ss.Base.DialOptions(opts...)...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("%s connect error: %w", ss.addr, err)
|
return nil, fmt.Errorf("%s connect error: %w", ss.addr, err)
|
||||||
}
|
}
|
||||||
@ -77,11 +77,9 @@ func (ss *Socks5) DialContext(ctx context.Context, metadata *C.Metadata) (_ C.Co
|
|||||||
return NewConn(c, ss), nil
|
return NewConn(c, ss), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// DialUDP implements C.ProxyAdapter
|
// ListenPacketContext implements C.ProxyAdapter
|
||||||
func (ss *Socks5) DialUDP(metadata *C.Metadata) (_ C.PacketConn, err error) {
|
func (ss *Socks5) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.PacketConn, err error) {
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), tcpTimeout)
|
c, err := dialer.DialContext(ctx, "tcp", ss.addr, ss.Base.DialOptions(opts...)...)
|
||||||
defer cancel()
|
|
||||||
c, err := dialer.DialContext(ctx, "tcp", ss.addr)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = fmt.Errorf("%s connect error: %w", ss.addr, err)
|
err = fmt.Errorf("%s connect error: %w", ss.addr, err)
|
||||||
return
|
return
|
||||||
@ -110,13 +108,13 @@ func (ss *Socks5) DialUDP(metadata *C.Metadata) (_ C.PacketConn, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
pc, err := dialer.ListenPacket("udp", "")
|
pc, err := dialer.ListenPacket(ctx, "udp", "", ss.Base.DialOptions(opts...)...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
io.Copy(ioutil.Discard, c)
|
io.Copy(io.Discard, c)
|
||||||
c.Close()
|
c.Close()
|
||||||
// A UDP association terminates when the TCP connection that the UDP
|
// A UDP association terminates when the TCP connection that the UDP
|
||||||
// ASSOCIATE request arrived on terminates. RFC1928
|
// ASSOCIATE request arrived on terminates. RFC1928
|
||||||
@ -145,17 +143,17 @@ func NewSocks5(option Socks5Option) *Socks5 {
|
|||||||
if option.TLS {
|
if option.TLS {
|
||||||
tlsConfig = &tls.Config{
|
tlsConfig = &tls.Config{
|
||||||
InsecureSkipVerify: option.SkipCertVerify,
|
InsecureSkipVerify: option.SkipCertVerify,
|
||||||
ClientSessionCache: getClientSessionCache(),
|
|
||||||
ServerName: option.Server,
|
ServerName: option.Server,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return &Socks5{
|
return &Socks5{
|
||||||
Base: &Base{
|
Base: &Base{
|
||||||
name: option.Name,
|
name: option.Name,
|
||||||
addr: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)),
|
addr: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)),
|
||||||
tp: C.Socks5,
|
tp: C.Socks5,
|
||||||
udp: option.UDP,
|
udp: option.UDP,
|
||||||
|
iface: option.Interface,
|
||||||
},
|
},
|
||||||
user: option.UserName,
|
user: option.UserName,
|
||||||
pass: option.Password,
|
pass: option.Password,
|
@ -3,15 +3,15 @@ package outbound
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/component/dialer"
|
"github.com/Dreamacro/clash/component/dialer"
|
||||||
"github.com/Dreamacro/clash/component/gun"
|
|
||||||
"github.com/Dreamacro/clash/component/trojan"
|
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
|
"github.com/Dreamacro/clash/transport/gun"
|
||||||
|
"github.com/Dreamacro/clash/transport/trojan"
|
||||||
|
|
||||||
"golang.org/x/net/http2"
|
"golang.org/x/net/http2"
|
||||||
)
|
)
|
||||||
@ -19,6 +19,7 @@ import (
|
|||||||
type Trojan struct {
|
type Trojan struct {
|
||||||
*Base
|
*Base
|
||||||
instance *trojan.Trojan
|
instance *trojan.Trojan
|
||||||
|
option *TrojanOption
|
||||||
|
|
||||||
// for gun mux
|
// for gun mux
|
||||||
gunTLSConfig *tls.Config
|
gunTLSConfig *tls.Config
|
||||||
@ -27,6 +28,7 @@ type Trojan struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type TrojanOption struct {
|
type TrojanOption struct {
|
||||||
|
BasicOption
|
||||||
Name string `proxy:"name"`
|
Name string `proxy:"name"`
|
||||||
Server string `proxy:"server"`
|
Server string `proxy:"server"`
|
||||||
Port int `proxy:"port"`
|
Port int `proxy:"port"`
|
||||||
@ -37,6 +39,34 @@ type TrojanOption struct {
|
|||||||
UDP bool `proxy:"udp,omitempty"`
|
UDP bool `proxy:"udp,omitempty"`
|
||||||
Network string `proxy:"network,omitempty"`
|
Network string `proxy:"network,omitempty"`
|
||||||
GrpcOpts GrpcOptions `proxy:"grpc-opts,omitempty"`
|
GrpcOpts GrpcOptions `proxy:"grpc-opts,omitempty"`
|
||||||
|
WSOpts WSOptions `proxy:"ws-opts,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Trojan) plainStream(c net.Conn) (net.Conn, error) {
|
||||||
|
if t.option.Network == "ws" {
|
||||||
|
host, port, _ := net.SplitHostPort(t.addr)
|
||||||
|
wsOpts := &trojan.WebsocketOption{
|
||||||
|
Host: host,
|
||||||
|
Port: port,
|
||||||
|
Path: t.option.WSOpts.Path,
|
||||||
|
}
|
||||||
|
|
||||||
|
if t.option.SNI != "" {
|
||||||
|
wsOpts.Host = t.option.SNI
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(t.option.WSOpts.Headers) != 0 {
|
||||||
|
header := http.Header{}
|
||||||
|
for key, value := range t.option.WSOpts.Headers {
|
||||||
|
header.Add(key, value)
|
||||||
|
}
|
||||||
|
wsOpts.Headers = header
|
||||||
|
}
|
||||||
|
|
||||||
|
return t.instance.StreamWebsocketConn(c, wsOpts)
|
||||||
|
}
|
||||||
|
|
||||||
|
return t.instance.StreamConn(c)
|
||||||
}
|
}
|
||||||
|
|
||||||
// StreamConn implements C.ProxyAdapter
|
// StreamConn implements C.ProxyAdapter
|
||||||
@ -45,7 +75,7 @@ func (t *Trojan) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error)
|
|||||||
if t.transport != nil {
|
if t.transport != nil {
|
||||||
c, err = gun.StreamGunWithConn(c, t.gunTLSConfig, t.gunConfig)
|
c, err = gun.StreamGunWithConn(c, t.gunTLSConfig, t.gunConfig)
|
||||||
} else {
|
} else {
|
||||||
c, err = t.instance.StreamConn(c)
|
c, err = t.plainStream(c)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -57,9 +87,9 @@ func (t *Trojan) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// DialContext implements C.ProxyAdapter
|
// DialContext implements C.ProxyAdapter
|
||||||
func (t *Trojan) DialContext(ctx context.Context, metadata *C.Metadata) (_ C.Conn, err error) {
|
func (t *Trojan) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) {
|
||||||
// gun transport
|
// gun transport
|
||||||
if t.transport != nil {
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -73,7 +103,7 @@ func (t *Trojan) DialContext(ctx context.Context, metadata *C.Metadata) (_ C.Con
|
|||||||
return NewConn(c, t), nil
|
return NewConn(c, t), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
c, err := dialer.DialContext(ctx, "tcp", t.addr)
|
c, err := dialer.DialContext(ctx, "tcp", t.addr, t.Base.DialOptions(opts...)...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("%s connect error: %w", t.addr, err)
|
return nil, fmt.Errorf("%s connect error: %w", t.addr, err)
|
||||||
}
|
}
|
||||||
@ -89,27 +119,25 @@ func (t *Trojan) DialContext(ctx context.Context, metadata *C.Metadata) (_ C.Con
|
|||||||
return NewConn(c, t), err
|
return NewConn(c, t), err
|
||||||
}
|
}
|
||||||
|
|
||||||
// DialUDP implements C.ProxyAdapter
|
// ListenPacketContext implements C.ProxyAdapter
|
||||||
func (t *Trojan) DialUDP(metadata *C.Metadata) (_ C.PacketConn, err error) {
|
func (t *Trojan) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.PacketConn, err error) {
|
||||||
var c net.Conn
|
var c net.Conn
|
||||||
|
|
||||||
// grpc transport
|
// grpc transport
|
||||||
if t.transport != nil {
|
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 {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("%s connect error: %w", t.addr, err)
|
return nil, fmt.Errorf("%s connect error: %w", t.addr, err)
|
||||||
}
|
}
|
||||||
defer safeConnClose(c, err)
|
defer safeConnClose(c, err)
|
||||||
} else {
|
} else {
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), tcpTimeout)
|
c, err = dialer.DialContext(ctx, "tcp", t.addr, t.Base.DialOptions(opts...)...)
|
||||||
defer cancel()
|
|
||||||
c, err = dialer.DialContext(ctx, "tcp", t.addr)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("%s connect error: %w", t.addr, err)
|
return nil, fmt.Errorf("%s connect error: %w", t.addr, err)
|
||||||
}
|
}
|
||||||
defer safeConnClose(c, err)
|
defer safeConnClose(c, err)
|
||||||
tcpKeepAlive(c)
|
tcpKeepAlive(c)
|
||||||
c, err = t.instance.StreamConn(c)
|
c, err = t.plainStream(c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("%s connect error: %w", t.addr, err)
|
return nil, fmt.Errorf("%s connect error: %w", t.addr, err)
|
||||||
}
|
}
|
||||||
@ -124,21 +152,14 @@ func (t *Trojan) DialUDP(metadata *C.Metadata) (_ C.PacketConn, err error) {
|
|||||||
return newPacketConn(pc, t), err
|
return newPacketConn(pc, t), err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Trojan) MarshalJSON() ([]byte, error) {
|
|
||||||
return json.Marshal(map[string]string{
|
|
||||||
"type": t.Type().String(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewTrojan(option TrojanOption) (*Trojan, error) {
|
func NewTrojan(option TrojanOption) (*Trojan, error) {
|
||||||
addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port))
|
addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port))
|
||||||
|
|
||||||
tOption := &trojan.Option{
|
tOption := &trojan.Option{
|
||||||
Password: option.Password,
|
Password: option.Password,
|
||||||
ALPN: option.ALPN,
|
ALPN: option.ALPN,
|
||||||
ServerName: option.Server,
|
ServerName: option.Server,
|
||||||
SkipCertVerify: option.SkipCertVerify,
|
SkipCertVerify: option.SkipCertVerify,
|
||||||
ClientSessionCache: getClientSessionCache(),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if option.SNI != "" {
|
if option.SNI != "" {
|
||||||
@ -147,17 +168,19 @@ func NewTrojan(option TrojanOption) (*Trojan, error) {
|
|||||||
|
|
||||||
t := &Trojan{
|
t := &Trojan{
|
||||||
Base: &Base{
|
Base: &Base{
|
||||||
name: option.Name,
|
name: option.Name,
|
||||||
addr: addr,
|
addr: addr,
|
||||||
tp: C.Trojan,
|
tp: C.Trojan,
|
||||||
udp: option.UDP,
|
udp: option.UDP,
|
||||||
|
iface: option.Interface,
|
||||||
},
|
},
|
||||||
instance: trojan.New(tOption),
|
instance: trojan.New(tOption),
|
||||||
|
option: &option,
|
||||||
}
|
}
|
||||||
|
|
||||||
if option.Network == "grpc" {
|
if option.Network == "grpc" {
|
||||||
dialFn := func(network, addr string) (net.Conn, error) {
|
dialFn := func(network, addr string) (net.Conn, error) {
|
||||||
c, err := dialer.DialContext(context.Background(), "tcp", t.addr)
|
c, err := dialer.DialContext(context.Background(), "tcp", t.addr, t.Base.DialOptions()...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("%s connect error: %s", t.addr, err.Error())
|
return nil, fmt.Errorf("%s connect error: %s", t.addr, err.Error())
|
||||||
}
|
}
|
||||||
@ -170,7 +193,6 @@ func NewTrojan(option TrojanOption) (*Trojan, error) {
|
|||||||
MinVersion: tls.VersionTLS12,
|
MinVersion: tls.VersionTLS12,
|
||||||
InsecureSkipVerify: tOption.SkipCertVerify,
|
InsecureSkipVerify: tOption.SkipCertVerify,
|
||||||
ServerName: tOption.ServerName,
|
ServerName: tOption.ServerName,
|
||||||
ClientSessionCache: getClientSessionCache(),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
t.transport = gun.NewHTTP2Client(dialFn, tlsConfig)
|
t.transport = gun.NewHTTP2Client(dialFn, tlsConfig)
|
@ -2,56 +2,15 @@ package outbound
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"crypto/tls"
|
|
||||||
"fmt"
|
|
||||||
"net"
|
"net"
|
||||||
"net/url"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"sync"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/component/resolver"
|
"github.com/Dreamacro/clash/component/resolver"
|
||||||
"github.com/Dreamacro/clash/component/socks5"
|
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
|
"github.com/Dreamacro/clash/transport/socks5"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
|
||||||
tcpTimeout = 5 * time.Second
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
globalClientSessionCache tls.ClientSessionCache
|
|
||||||
once sync.Once
|
|
||||||
)
|
|
||||||
|
|
||||||
func urlToMetadata(rawURL string) (addr C.Metadata, err error) {
|
|
||||||
u, err := url.Parse(rawURL)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
port := u.Port()
|
|
||||||
if port == "" {
|
|
||||||
switch u.Scheme {
|
|
||||||
case "https":
|
|
||||||
port = "443"
|
|
||||||
case "http":
|
|
||||||
port = "80"
|
|
||||||
default:
|
|
||||||
err = fmt.Errorf("%s scheme not Support", rawURL)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
addr = C.Metadata{
|
|
||||||
AddrType: C.AtypDomainName,
|
|
||||||
Host: u.Hostname(),
|
|
||||||
DstIP: nil,
|
|
||||||
DstPort: port,
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func tcpKeepAlive(c net.Conn) {
|
func tcpKeepAlive(c net.Conn) {
|
||||||
if tcp, ok := c.(*net.TCPConn); ok {
|
if tcp, ok := c.(*net.TCPConn); ok {
|
||||||
tcp.SetKeepAlive(true)
|
tcp.SetKeepAlive(true)
|
||||||
@ -59,17 +18,10 @@ func tcpKeepAlive(c net.Conn) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func getClientSessionCache() tls.ClientSessionCache {
|
|
||||||
once.Do(func() {
|
|
||||||
globalClientSessionCache = tls.NewLRUClientSessionCache(128)
|
|
||||||
})
|
|
||||||
return globalClientSessionCache
|
|
||||||
}
|
|
||||||
|
|
||||||
func serializesSocksAddr(metadata *C.Metadata) []byte {
|
func serializesSocksAddr(metadata *C.Metadata) []byte {
|
||||||
var buf [][]byte
|
var buf [][]byte
|
||||||
aType := uint8(metadata.AddrType)
|
aType := uint8(metadata.AddrType)
|
||||||
p, _ := strconv.Atoi(metadata.DstPort)
|
p, _ := strconv.ParseUint(metadata.DstPort, 10, 16)
|
||||||
port := []byte{uint8(p >> 8), uint8(p & 0xff)}
|
port := []byte{uint8(p >> 8), uint8(p & 0xff)}
|
||||||
switch metadata.AddrType {
|
switch metadata.AddrType {
|
||||||
case socks5.AtypDomainName:
|
case socks5.AtypDomainName:
|
@ -11,10 +11,10 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/component/dialer"
|
"github.com/Dreamacro/clash/component/dialer"
|
||||||
"github.com/Dreamacro/clash/component/gun"
|
|
||||||
"github.com/Dreamacro/clash/component/resolver"
|
"github.com/Dreamacro/clash/component/resolver"
|
||||||
"github.com/Dreamacro/clash/component/vmess"
|
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
|
"github.com/Dreamacro/clash/transport/gun"
|
||||||
|
"github.com/Dreamacro/clash/transport/vmess"
|
||||||
|
|
||||||
"golang.org/x/net/http2"
|
"golang.org/x/net/http2"
|
||||||
)
|
)
|
||||||
@ -31,22 +31,26 @@ type Vmess struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type VmessOption struct {
|
type VmessOption struct {
|
||||||
Name string `proxy:"name"`
|
BasicOption
|
||||||
Server string `proxy:"server"`
|
Name string `proxy:"name"`
|
||||||
Port int `proxy:"port"`
|
Server string `proxy:"server"`
|
||||||
UUID string `proxy:"uuid"`
|
Port int `proxy:"port"`
|
||||||
AlterID int `proxy:"alterId"`
|
UUID string `proxy:"uuid"`
|
||||||
Cipher string `proxy:"cipher"`
|
AlterID int `proxy:"alterId"`
|
||||||
TLS bool `proxy:"tls,omitempty"`
|
Cipher string `proxy:"cipher"`
|
||||||
UDP bool `proxy:"udp,omitempty"`
|
UDP bool `proxy:"udp,omitempty"`
|
||||||
Network string `proxy:"network,omitempty"`
|
Network string `proxy:"network,omitempty"`
|
||||||
HTTPOpts HTTPOptions `proxy:"http-opts,omitempty"`
|
TLS bool `proxy:"tls,omitempty"`
|
||||||
HTTP2Opts HTTP2Options `proxy:"h2-opts,omitempty"`
|
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
|
||||||
GrpcOpts GrpcOptions `proxy:"grpc-opts,omitempty"`
|
ServerName string `proxy:"servername,omitempty"`
|
||||||
WSPath string `proxy:"ws-path,omitempty"`
|
HTTPOpts HTTPOptions `proxy:"http-opts,omitempty"`
|
||||||
WSHeaders map[string]string `proxy:"ws-headers,omitempty"`
|
HTTP2Opts HTTP2Options `proxy:"h2-opts,omitempty"`
|
||||||
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
|
GrpcOpts GrpcOptions `proxy:"grpc-opts,omitempty"`
|
||||||
ServerName string `proxy:"servername,omitempty"`
|
WSOpts WSOptions `proxy:"ws-opts,omitempty"`
|
||||||
|
|
||||||
|
// TODO: remove these until 2022
|
||||||
|
WSHeaders map[string]string `proxy:"ws-headers,omitempty"`
|
||||||
|
WSPath string `proxy:"ws-path,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type HTTPOptions struct {
|
type HTTPOptions struct {
|
||||||
@ -64,21 +68,37 @@ type GrpcOptions struct {
|
|||||||
GrpcServiceName string `proxy:"grpc-service-name,omitempty"`
|
GrpcServiceName string `proxy:"grpc-service-name,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type WSOptions struct {
|
||||||
|
Path string `proxy:"path,omitempty"`
|
||||||
|
Headers map[string]string `proxy:"headers,omitempty"`
|
||||||
|
MaxEarlyData int `proxy:"max-early-data,omitempty"`
|
||||||
|
EarlyDataHeaderName string `proxy:"early-data-header-name,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
// StreamConn implements C.ProxyAdapter
|
// StreamConn implements C.ProxyAdapter
|
||||||
func (v *Vmess) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
func (v *Vmess) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
||||||
var err error
|
var err error
|
||||||
switch v.option.Network {
|
switch v.option.Network {
|
||||||
case "ws":
|
case "ws":
|
||||||
host, port, _ := net.SplitHostPort(v.addr)
|
if v.option.WSOpts.Path == "" {
|
||||||
wsOpts := &vmess.WebsocketConfig{
|
v.option.WSOpts.Path = v.option.WSPath
|
||||||
Host: host,
|
}
|
||||||
Port: port,
|
if len(v.option.WSOpts.Headers) == 0 {
|
||||||
Path: v.option.WSPath,
|
v.option.WSOpts.Headers = v.option.WSHeaders
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(v.option.WSHeaders) != 0 {
|
host, port, _ := net.SplitHostPort(v.addr)
|
||||||
|
wsOpts := &vmess.WebsocketConfig{
|
||||||
|
Host: host,
|
||||||
|
Port: port,
|
||||||
|
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{}
|
header := http.Header{}
|
||||||
for key, value := range v.option.WSHeaders {
|
for key, value := range v.option.WSOpts.Headers {
|
||||||
header.Add(key, value)
|
header.Add(key, value)
|
||||||
}
|
}
|
||||||
wsOpts.Headers = header
|
wsOpts.Headers = header
|
||||||
@ -86,9 +106,16 @@ func (v *Vmess) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
|||||||
|
|
||||||
if v.option.TLS {
|
if v.option.TLS {
|
||||||
wsOpts.TLS = true
|
wsOpts.TLS = true
|
||||||
wsOpts.SessionCache = getClientSessionCache()
|
wsOpts.TLSConfig = &tls.Config{
|
||||||
wsOpts.SkipCertVerify = v.option.SkipCertVerify
|
ServerName: host,
|
||||||
wsOpts.ServerName = v.option.ServerName
|
InsecureSkipVerify: v.option.SkipCertVerify,
|
||||||
|
NextProtos: []string{"http/1.1"},
|
||||||
|
}
|
||||||
|
if v.option.ServerName != "" {
|
||||||
|
wsOpts.TLSConfig.ServerName = v.option.ServerName
|
||||||
|
} else if host := wsOpts.Headers.Get("Host"); host != "" {
|
||||||
|
wsOpts.TLSConfig.ServerName = host
|
||||||
|
}
|
||||||
}
|
}
|
||||||
c, err = vmess.StreamWebsocketConn(c, wsOpts)
|
c, err = vmess.StreamWebsocketConn(c, wsOpts)
|
||||||
case "http":
|
case "http":
|
||||||
@ -98,7 +125,6 @@ func (v *Vmess) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
|||||||
tlsOpts := &vmess.TLSConfig{
|
tlsOpts := &vmess.TLSConfig{
|
||||||
Host: host,
|
Host: host,
|
||||||
SkipCertVerify: v.option.SkipCertVerify,
|
SkipCertVerify: v.option.SkipCertVerify,
|
||||||
SessionCache: getClientSessionCache(),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if v.option.ServerName != "" {
|
if v.option.ServerName != "" {
|
||||||
@ -125,7 +151,6 @@ func (v *Vmess) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
|||||||
tlsOpts := vmess.TLSConfig{
|
tlsOpts := vmess.TLSConfig{
|
||||||
Host: host,
|
Host: host,
|
||||||
SkipCertVerify: v.option.SkipCertVerify,
|
SkipCertVerify: v.option.SkipCertVerify,
|
||||||
SessionCache: getClientSessionCache(),
|
|
||||||
NextProtos: []string{"h2"},
|
NextProtos: []string{"h2"},
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -153,7 +178,6 @@ func (v *Vmess) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
|||||||
tlsOpts := &vmess.TLSConfig{
|
tlsOpts := &vmess.TLSConfig{
|
||||||
Host: host,
|
Host: host,
|
||||||
SkipCertVerify: v.option.SkipCertVerify,
|
SkipCertVerify: v.option.SkipCertVerify,
|
||||||
SessionCache: getClientSessionCache(),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if v.option.ServerName != "" {
|
if v.option.ServerName != "" {
|
||||||
@ -172,9 +196,9 @@ func (v *Vmess) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// DialContext implements C.ProxyAdapter
|
// DialContext implements C.ProxyAdapter
|
||||||
func (v *Vmess) DialContext(ctx context.Context, metadata *C.Metadata) (_ C.Conn, err error) {
|
func (v *Vmess) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) {
|
||||||
// gun transport
|
// gun transport
|
||||||
if v.transport != nil {
|
if v.transport != nil && len(opts) == 0 {
|
||||||
c, err := gun.StreamGunWithTransport(v.transport, v.gunConfig)
|
c, err := gun.StreamGunWithTransport(v.transport, v.gunConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -189,7 +213,7 @@ func (v *Vmess) DialContext(ctx context.Context, metadata *C.Metadata) (_ C.Conn
|
|||||||
return NewConn(c, v), nil
|
return NewConn(c, v), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
c, err := dialer.DialContext(ctx, "tcp", v.addr)
|
c, err := dialer.DialContext(ctx, "tcp", v.addr, v.Base.DialOptions(opts...)...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error())
|
return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error())
|
||||||
}
|
}
|
||||||
@ -200,8 +224,8 @@ func (v *Vmess) DialContext(ctx context.Context, metadata *C.Metadata) (_ C.Conn
|
|||||||
return NewConn(c, v), err
|
return NewConn(c, v), err
|
||||||
}
|
}
|
||||||
|
|
||||||
// DialUDP implements C.ProxyAdapter
|
// ListenPacketContext implements C.ProxyAdapter
|
||||||
func (v *Vmess) DialUDP(metadata *C.Metadata) (_ C.PacketConn, err error) {
|
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
|
// vmess use stream-oriented udp with a special address, so we needs a net.UDPAddr
|
||||||
if !metadata.Resolved() {
|
if !metadata.Resolved() {
|
||||||
ip, err := resolver.ResolveIP(metadata.Host)
|
ip, err := resolver.ResolveIP(metadata.Host)
|
||||||
@ -213,7 +237,7 @@ func (v *Vmess) DialUDP(metadata *C.Metadata) (_ C.PacketConn, err error) {
|
|||||||
|
|
||||||
var c net.Conn
|
var c net.Conn
|
||||||
// gun transport
|
// gun transport
|
||||||
if v.transport != nil {
|
if v.transport != nil && len(opts) == 0 {
|
||||||
c, err = gun.StreamGunWithTransport(v.transport, v.gunConfig)
|
c, err = gun.StreamGunWithTransport(v.transport, v.gunConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -222,9 +246,7 @@ func (v *Vmess) DialUDP(metadata *C.Metadata) (_ C.PacketConn, err error) {
|
|||||||
|
|
||||||
c, err = v.client.StreamConn(c, parseVmessAddr(metadata))
|
c, err = v.client.StreamConn(c, parseVmessAddr(metadata))
|
||||||
} else {
|
} else {
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), tcpTimeout)
|
c, err = dialer.DialContext(ctx, "tcp", v.addr, v.Base.DialOptions(opts...)...)
|
||||||
defer cancel()
|
|
||||||
c, err = dialer.DialContext(ctx, "tcp", v.addr)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error())
|
return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error())
|
||||||
}
|
}
|
||||||
@ -264,10 +286,11 @@ func NewVmess(option VmessOption) (*Vmess, error) {
|
|||||||
|
|
||||||
v := &Vmess{
|
v := &Vmess{
|
||||||
Base: &Base{
|
Base: &Base{
|
||||||
name: option.Name,
|
name: option.Name,
|
||||||
addr: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)),
|
addr: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)),
|
||||||
tp: C.Vmess,
|
tp: C.Vmess,
|
||||||
udp: option.UDP,
|
udp: option.UDP,
|
||||||
|
iface: option.Interface,
|
||||||
},
|
},
|
||||||
client: client,
|
client: client,
|
||||||
option: &option,
|
option: &option,
|
||||||
@ -280,7 +303,7 @@ func NewVmess(option VmessOption) (*Vmess, error) {
|
|||||||
}
|
}
|
||||||
case "grpc":
|
case "grpc":
|
||||||
dialFn := func(network, addr string) (net.Conn, error) {
|
dialFn := func(network, addr string) (net.Conn, error) {
|
||||||
c, err := dialer.DialContext(context.Background(), "tcp", v.addr)
|
c, err := dialer.DialContext(context.Background(), "tcp", v.addr, v.Base.DialOptions()...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error())
|
return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error())
|
||||||
}
|
}
|
||||||
@ -330,7 +353,7 @@ func parseVmessAddr(metadata *C.Metadata) *vmess.DstAddr {
|
|||||||
copy(addr[1:], []byte(metadata.Host))
|
copy(addr[1:], []byte(metadata.Host))
|
||||||
}
|
}
|
||||||
|
|
||||||
port, _ := strconv.Atoi(metadata.DstPort)
|
port, _ := strconv.ParseUint(metadata.DstPort, 10, 16)
|
||||||
return &vmess.DstAddr{
|
return &vmess.DstAddr{
|
||||||
UDP: metadata.NetWork == C.UDP,
|
UDP: metadata.NetWork == C.UDP,
|
||||||
AddrType: addrType,
|
AddrType: addrType,
|
@ -3,8 +3,8 @@ package outboundgroup
|
|||||||
import (
|
import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/adapters/provider"
|
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
|
"github.com/Dreamacro/clash/constant/provider"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
@ -4,10 +4,11 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/adapters/outbound"
|
"github.com/Dreamacro/clash/adapter/outbound"
|
||||||
"github.com/Dreamacro/clash/adapters/provider"
|
|
||||||
"github.com/Dreamacro/clash/common/singledo"
|
"github.com/Dreamacro/clash/common/singledo"
|
||||||
|
"github.com/Dreamacro/clash/component/dialer"
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
|
"github.com/Dreamacro/clash/constant/provider"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Fallback struct {
|
type Fallback struct {
|
||||||
@ -23,19 +24,19 @@ func (f *Fallback) Now() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// DialContext implements C.ProxyAdapter
|
// DialContext implements C.ProxyAdapter
|
||||||
func (f *Fallback) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
|
func (f *Fallback) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.Conn, error) {
|
||||||
proxy := f.findAliveProxy(true)
|
proxy := f.findAliveProxy(true)
|
||||||
c, err := proxy.DialContext(ctx, metadata)
|
c, err := proxy.DialContext(ctx, metadata, f.Base.DialOptions(opts...)...)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
c.AppendToChains(f)
|
c.AppendToChains(f)
|
||||||
}
|
}
|
||||||
return c, err
|
return c, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// DialUDP implements C.ProxyAdapter
|
// ListenPacketContext implements C.ProxyAdapter
|
||||||
func (f *Fallback) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
|
func (f *Fallback) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) {
|
||||||
proxy := f.findAliveProxy(true)
|
proxy := f.findAliveProxy(true)
|
||||||
pc, err := proxy.DialUDP(metadata)
|
pc, err := proxy.ListenPacketContext(ctx, metadata, f.Base.DialOptions(opts...)...)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
pc.AppendToChains(f)
|
pc.AppendToChains(f)
|
||||||
}
|
}
|
||||||
@ -90,11 +91,16 @@ func (f *Fallback) findAliveProxy(touch bool) C.Proxy {
|
|||||||
return proxies[0]
|
return proxies[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewFallback(options *GroupCommonOption, providers []provider.ProxyProvider) *Fallback {
|
func NewFallback(option *GroupCommonOption, providers []provider.ProxyProvider) *Fallback {
|
||||||
return &Fallback{
|
return &Fallback{
|
||||||
Base: outbound.NewBase(options.Name, "", C.Fallback, false),
|
Base: outbound.NewBase(outbound.BaseOption{
|
||||||
|
Name: option.Name,
|
||||||
|
Type: C.Fallback,
|
||||||
|
Interface: option.Interface,
|
||||||
|
RoutingMark: option.RoutingMark,
|
||||||
|
}),
|
||||||
single: singledo.NewSingle(defaultGetProxiesDuration),
|
single: singledo.NewSingle(defaultGetProxiesDuration),
|
||||||
providers: providers,
|
providers: providers,
|
||||||
disableUDP: options.DisableUDP,
|
disableUDP: option.DisableUDP,
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -7,11 +7,12 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/adapters/outbound"
|
"github.com/Dreamacro/clash/adapter/outbound"
|
||||||
"github.com/Dreamacro/clash/adapters/provider"
|
|
||||||
"github.com/Dreamacro/clash/common/murmur3"
|
"github.com/Dreamacro/clash/common/murmur3"
|
||||||
"github.com/Dreamacro/clash/common/singledo"
|
"github.com/Dreamacro/clash/common/singledo"
|
||||||
|
"github.com/Dreamacro/clash/component/dialer"
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
|
"github.com/Dreamacro/clash/constant/provider"
|
||||||
|
|
||||||
"golang.org/x/net/publicsuffix"
|
"golang.org/x/net/publicsuffix"
|
||||||
)
|
)
|
||||||
@ -69,7 +70,7 @@ func jumpHash(key uint64, buckets int32) int32 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// DialContext implements C.ProxyAdapter
|
// DialContext implements C.ProxyAdapter
|
||||||
func (lb *LoadBalance) DialContext(ctx context.Context, metadata *C.Metadata) (c C.Conn, err error) {
|
func (lb *LoadBalance) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (c C.Conn, err error) {
|
||||||
defer func() {
|
defer func() {
|
||||||
if err == nil {
|
if err == nil {
|
||||||
c.AppendToChains(lb)
|
c.AppendToChains(lb)
|
||||||
@ -78,12 +79,12 @@ func (lb *LoadBalance) DialContext(ctx context.Context, metadata *C.Metadata) (c
|
|||||||
|
|
||||||
proxy := lb.Unwrap(metadata)
|
proxy := lb.Unwrap(metadata)
|
||||||
|
|
||||||
c, err = proxy.DialContext(ctx, metadata)
|
c, err = proxy.DialContext(ctx, metadata, lb.Base.DialOptions(opts...)...)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// DialUDP implements C.ProxyAdapter
|
// ListenPacketContext implements C.ProxyAdapter
|
||||||
func (lb *LoadBalance) DialUDP(metadata *C.Metadata) (pc C.PacketConn, err error) {
|
func (lb *LoadBalance) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (pc C.PacketConn, err error) {
|
||||||
defer func() {
|
defer func() {
|
||||||
if err == nil {
|
if err == nil {
|
||||||
pc.AppendToChains(lb)
|
pc.AppendToChains(lb)
|
||||||
@ -91,8 +92,7 @@ func (lb *LoadBalance) DialUDP(metadata *C.Metadata) (pc C.PacketConn, err error
|
|||||||
}()
|
}()
|
||||||
|
|
||||||
proxy := lb.Unwrap(metadata)
|
proxy := lb.Unwrap(metadata)
|
||||||
|
return proxy.ListenPacketContext(ctx, metadata, lb.Base.DialOptions(opts...)...)
|
||||||
return proxy.DialUDP(metadata)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// SupportUDP implements C.ProxyAdapter
|
// SupportUDP implements C.ProxyAdapter
|
||||||
@ -159,7 +159,7 @@ func (lb *LoadBalance) MarshalJSON() ([]byte, error) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewLoadBalance(options *GroupCommonOption, providers []provider.ProxyProvider, strategy string) (lb *LoadBalance, err error) {
|
func NewLoadBalance(option *GroupCommonOption, providers []provider.ProxyProvider, strategy string) (lb *LoadBalance, err error) {
|
||||||
var strategyFn strategyFn
|
var strategyFn strategyFn
|
||||||
switch strategy {
|
switch strategy {
|
||||||
case "consistent-hashing":
|
case "consistent-hashing":
|
||||||
@ -170,10 +170,15 @@ func NewLoadBalance(options *GroupCommonOption, providers []provider.ProxyProvid
|
|||||||
return nil, fmt.Errorf("%w: %s", errStrategy, strategy)
|
return nil, fmt.Errorf("%w: %s", errStrategy, strategy)
|
||||||
}
|
}
|
||||||
return &LoadBalance{
|
return &LoadBalance{
|
||||||
Base: outbound.NewBase(options.Name, "", C.LoadBalance, false),
|
Base: outbound.NewBase(outbound.BaseOption{
|
||||||
|
Name: option.Name,
|
||||||
|
Type: C.LoadBalance,
|
||||||
|
Interface: option.Interface,
|
||||||
|
RoutingMark: option.RoutingMark,
|
||||||
|
}),
|
||||||
single: singledo.NewSingle(defaultGetProxiesDuration),
|
single: singledo.NewSingle(defaultGetProxiesDuration),
|
||||||
providers: providers,
|
providers: providers,
|
||||||
strategyFn: strategyFn,
|
strategyFn: strategyFn,
|
||||||
disableUDP: options.DisableUDP,
|
disableUDP: option.DisableUDP,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
@ -4,9 +4,11 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/adapters/provider"
|
"github.com/Dreamacro/clash/adapter/outbound"
|
||||||
|
"github.com/Dreamacro/clash/adapter/provider"
|
||||||
"github.com/Dreamacro/clash/common/structure"
|
"github.com/Dreamacro/clash/common/structure"
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
|
types "github.com/Dreamacro/clash/constant/provider"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -18,6 +20,7 @@ var (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type GroupCommonOption struct {
|
type GroupCommonOption struct {
|
||||||
|
outbound.BasicOption
|
||||||
Name string `group:"name"`
|
Name string `group:"name"`
|
||||||
Type string `group:"type"`
|
Type string `group:"type"`
|
||||||
Proxies []string `group:"proxies,omitempty"`
|
Proxies []string `group:"proxies,omitempty"`
|
||||||
@ -28,7 +31,7 @@ type GroupCommonOption struct {
|
|||||||
DisableUDP bool `group:"disable-udp,omitempty"`
|
DisableUDP bool `group:"disable-udp,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func ParseProxyGroup(config map[string]interface{}, proxyMap map[string]C.Proxy, providersMap map[string]provider.ProxyProvider) (C.ProxyAdapter, error) {
|
func ParseProxyGroup(config map[string]interface{}, proxyMap map[string]C.Proxy, providersMap map[string]types.ProxyProvider) (C.ProxyAdapter, error) {
|
||||||
decoder := structure.NewDecoder(structure.Option{TagName: "group", WeaklyTypedInput: true})
|
decoder := structure.NewDecoder(structure.Option{TagName: "group", WeaklyTypedInput: true})
|
||||||
|
|
||||||
groupOption := &GroupCommonOption{
|
groupOption := &GroupCommonOption{
|
||||||
@ -44,7 +47,7 @@ func ParseProxyGroup(config map[string]interface{}, proxyMap map[string]C.Proxy,
|
|||||||
|
|
||||||
groupName := groupOption.Name
|
groupName := groupOption.Name
|
||||||
|
|
||||||
providers := []provider.ProxyProvider{}
|
providers := []types.ProxyProvider{}
|
||||||
|
|
||||||
if len(groupOption.Proxies) == 0 && len(groupOption.Use) == 0 {
|
if len(groupOption.Proxies) == 0 && len(groupOption.Use) == 0 {
|
||||||
return nil, errMissProxy
|
return nil, errMissProxy
|
||||||
@ -138,15 +141,15 @@ func getProxies(mapping map[string]C.Proxy, list []string) ([]C.Proxy, error) {
|
|||||||
return ps, nil
|
return ps, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getProviders(mapping map[string]provider.ProxyProvider, list []string) ([]provider.ProxyProvider, error) {
|
func getProviders(mapping map[string]types.ProxyProvider, list []string) ([]types.ProxyProvider, error) {
|
||||||
var ps []provider.ProxyProvider
|
var ps []types.ProxyProvider
|
||||||
for _, name := range list {
|
for _, name := range list {
|
||||||
p, ok := mapping[name]
|
p, ok := mapping[name]
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, fmt.Errorf("'%s' not found", name)
|
return nil, fmt.Errorf("'%s' not found", name)
|
||||||
}
|
}
|
||||||
|
|
||||||
if p.VehicleType() == provider.Compatible {
|
if p.VehicleType() == types.Compatible {
|
||||||
return nil, fmt.Errorf("proxy group %s can't contains in `use`", name)
|
return nil, fmt.Errorf("proxy group %s can't contains in `use`", name)
|
||||||
}
|
}
|
||||||
ps = append(ps, p)
|
ps = append(ps, p)
|
@ -3,14 +3,13 @@ package outboundgroup
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/adapters/outbound"
|
"github.com/Dreamacro/clash/adapter/outbound"
|
||||||
"github.com/Dreamacro/clash/adapters/provider"
|
|
||||||
"github.com/Dreamacro/clash/common/singledo"
|
"github.com/Dreamacro/clash/common/singledo"
|
||||||
"github.com/Dreamacro/clash/component/dialer"
|
"github.com/Dreamacro/clash/component/dialer"
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
|
"github.com/Dreamacro/clash/constant/provider"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Relay struct {
|
type Relay struct {
|
||||||
@ -20,15 +19,25 @@ type Relay struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// DialContext implements C.ProxyAdapter
|
// DialContext implements C.ProxyAdapter
|
||||||
func (r *Relay) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
|
func (r *Relay) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.Conn, error) {
|
||||||
proxies := r.proxies(metadata, true)
|
var proxies []C.Proxy
|
||||||
if len(proxies) == 0 {
|
for _, proxy := range r.proxies(metadata, true) {
|
||||||
return nil, errors.New("proxy does not exist")
|
if proxy.Type() != C.Direct {
|
||||||
|
proxies = append(proxies, proxy)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
switch len(proxies) {
|
||||||
|
case 0:
|
||||||
|
return outbound.NewDirect().DialContext(ctx, metadata, r.Base.DialOptions(opts...)...)
|
||||||
|
case 1:
|
||||||
|
return proxies[0].DialContext(ctx, metadata, r.Base.DialOptions(opts...)...)
|
||||||
|
}
|
||||||
|
|
||||||
first := proxies[0]
|
first := proxies[0]
|
||||||
last := proxies[len(proxies)-1]
|
last := proxies[len(proxies)-1]
|
||||||
|
|
||||||
c, err := dialer.DialContext(ctx, "tcp", first.Addr())
|
c, err := dialer.DialContext(ctx, "tcp", first.Addr(), r.Base.DialOptions(opts...)...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("%s connect error: %w", first.Addr(), err)
|
return nil, fmt.Errorf("%s connect error: %w", first.Addr(), err)
|
||||||
}
|
}
|
||||||
@ -91,9 +100,14 @@ func (r *Relay) proxies(metadata *C.Metadata, touch bool) []C.Proxy {
|
|||||||
return proxies
|
return proxies
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewRelay(options *GroupCommonOption, providers []provider.ProxyProvider) *Relay {
|
func NewRelay(option *GroupCommonOption, providers []provider.ProxyProvider) *Relay {
|
||||||
return &Relay{
|
return &Relay{
|
||||||
Base: outbound.NewBase(options.Name, "", C.Relay, false),
|
Base: outbound.NewBase(outbound.BaseOption{
|
||||||
|
Name: option.Name,
|
||||||
|
Type: C.Relay,
|
||||||
|
Interface: option.Interface,
|
||||||
|
RoutingMark: option.RoutingMark,
|
||||||
|
}),
|
||||||
single: singledo.NewSingle(defaultGetProxiesDuration),
|
single: singledo.NewSingle(defaultGetProxiesDuration),
|
||||||
providers: providers,
|
providers: providers,
|
||||||
}
|
}
|
@ -5,10 +5,11 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/adapters/outbound"
|
"github.com/Dreamacro/clash/adapter/outbound"
|
||||||
"github.com/Dreamacro/clash/adapters/provider"
|
|
||||||
"github.com/Dreamacro/clash/common/singledo"
|
"github.com/Dreamacro/clash/common/singledo"
|
||||||
|
"github.com/Dreamacro/clash/component/dialer"
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
|
"github.com/Dreamacro/clash/constant/provider"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Selector struct {
|
type Selector struct {
|
||||||
@ -20,17 +21,17 @@ type Selector struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// DialContext implements C.ProxyAdapter
|
// DialContext implements C.ProxyAdapter
|
||||||
func (s *Selector) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
|
func (s *Selector) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.Conn, error) {
|
||||||
c, err := s.selectedProxy(true).DialContext(ctx, metadata)
|
c, err := s.selectedProxy(true).DialContext(ctx, metadata, s.Base.DialOptions(opts...)...)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
c.AppendToChains(s)
|
c.AppendToChains(s)
|
||||||
}
|
}
|
||||||
return c, err
|
return c, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// DialUDP implements C.ProxyAdapter
|
// ListenPacketContext implements C.ProxyAdapter
|
||||||
func (s *Selector) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
|
func (s *Selector) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) {
|
||||||
pc, err := s.selectedProxy(true).DialUDP(metadata)
|
pc, err := s.selectedProxy(true).ListenPacketContext(ctx, metadata, s.Base.DialOptions(opts...)...)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
pc.AppendToChains(s)
|
pc.AppendToChains(s)
|
||||||
}
|
}
|
||||||
@ -96,13 +97,18 @@ func (s *Selector) selectedProxy(touch bool) C.Proxy {
|
|||||||
return elm.(C.Proxy)
|
return elm.(C.Proxy)
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewSelector(options *GroupCommonOption, providers []provider.ProxyProvider) *Selector {
|
func NewSelector(option *GroupCommonOption, providers []provider.ProxyProvider) *Selector {
|
||||||
selected := providers[0].Proxies()[0].Name()
|
selected := providers[0].Proxies()[0].Name()
|
||||||
return &Selector{
|
return &Selector{
|
||||||
Base: outbound.NewBase(options.Name, "", C.Selector, false),
|
Base: outbound.NewBase(outbound.BaseOption{
|
||||||
|
Name: option.Name,
|
||||||
|
Type: C.Selector,
|
||||||
|
Interface: option.Interface,
|
||||||
|
RoutingMark: option.RoutingMark,
|
||||||
|
}),
|
||||||
single: singledo.NewSingle(defaultGetProxiesDuration),
|
single: singledo.NewSingle(defaultGetProxiesDuration),
|
||||||
providers: providers,
|
providers: providers,
|
||||||
selected: selected,
|
selected: selected,
|
||||||
disableUDP: options.DisableUDP,
|
disableUDP: option.DisableUDP,
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -5,10 +5,11 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/adapters/outbound"
|
"github.com/Dreamacro/clash/adapter/outbound"
|
||||||
"github.com/Dreamacro/clash/adapters/provider"
|
|
||||||
"github.com/Dreamacro/clash/common/singledo"
|
"github.com/Dreamacro/clash/common/singledo"
|
||||||
|
"github.com/Dreamacro/clash/component/dialer"
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
|
"github.com/Dreamacro/clash/constant/provider"
|
||||||
)
|
)
|
||||||
|
|
||||||
type urlTestOption func(*URLTest)
|
type urlTestOption func(*URLTest)
|
||||||
@ -34,17 +35,17 @@ func (u *URLTest) Now() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// DialContext implements C.ProxyAdapter
|
// DialContext implements C.ProxyAdapter
|
||||||
func (u *URLTest) DialContext(ctx context.Context, metadata *C.Metadata) (c C.Conn, err error) {
|
func (u *URLTest) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (c C.Conn, err error) {
|
||||||
c, err = u.fast(true).DialContext(ctx, metadata)
|
c, err = u.fast(true).DialContext(ctx, metadata, u.Base.DialOptions(opts...)...)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
c.AppendToChains(u)
|
c.AppendToChains(u)
|
||||||
}
|
}
|
||||||
return c, err
|
return c, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// DialUDP implements C.ProxyAdapter
|
// ListenPacketContext implements C.ProxyAdapter
|
||||||
func (u *URLTest) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
|
func (u *URLTest) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) {
|
||||||
pc, err := u.fast(true).DialUDP(metadata)
|
pc, err := u.fast(true).ListenPacketContext(ctx, metadata, u.Base.DialOptions(opts...)...)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
pc.AppendToChains(u)
|
pc.AppendToChains(u)
|
||||||
}
|
}
|
||||||
@ -133,13 +134,18 @@ func parseURLTestOption(config map[string]interface{}) []urlTestOption {
|
|||||||
return opts
|
return opts
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewURLTest(commonOptions *GroupCommonOption, providers []provider.ProxyProvider, options ...urlTestOption) *URLTest {
|
func NewURLTest(option *GroupCommonOption, providers []provider.ProxyProvider, options ...urlTestOption) *URLTest {
|
||||||
urlTest := &URLTest{
|
urlTest := &URLTest{
|
||||||
Base: outbound.NewBase(commonOptions.Name, "", C.URLTest, false),
|
Base: outbound.NewBase(outbound.BaseOption{
|
||||||
|
Name: option.Name,
|
||||||
|
Type: C.URLTest,
|
||||||
|
Interface: option.Interface,
|
||||||
|
RoutingMark: option.RoutingMark,
|
||||||
|
}),
|
||||||
single: singledo.NewSingle(defaultGetProxiesDuration),
|
single: singledo.NewSingle(defaultGetProxiesDuration),
|
||||||
fastSingle: singledo.NewSingle(time.Second * 10),
|
fastSingle: singledo.NewSingle(time.Second * 10),
|
||||||
providers: providers,
|
providers: providers,
|
||||||
disableUDP: commonOptions.DisableUDP,
|
disableUDP: option.DisableUDP,
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, option := range options {
|
for _, option := range options {
|
@ -1,8 +1,9 @@
|
|||||||
package outbound
|
package adapter
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/Dreamacro/clash/adapter/outbound"
|
||||||
"github.com/Dreamacro/clash/common/structure"
|
"github.com/Dreamacro/clash/common/structure"
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
)
|
)
|
||||||
@ -20,36 +21,36 @@ func ParseProxy(mapping map[string]interface{}) (C.Proxy, error) {
|
|||||||
)
|
)
|
||||||
switch proxyType {
|
switch proxyType {
|
||||||
case "ss":
|
case "ss":
|
||||||
ssOption := &ShadowSocksOption{}
|
ssOption := &outbound.ShadowSocksOption{}
|
||||||
err = decoder.Decode(mapping, ssOption)
|
err = decoder.Decode(mapping, ssOption)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
proxy, err = NewShadowSocks(*ssOption)
|
proxy, err = outbound.NewShadowSocks(*ssOption)
|
||||||
case "ssr":
|
case "ssr":
|
||||||
ssrOption := &ShadowSocksROption{}
|
ssrOption := &outbound.ShadowSocksROption{}
|
||||||
err = decoder.Decode(mapping, ssrOption)
|
err = decoder.Decode(mapping, ssrOption)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
proxy, err = NewShadowSocksR(*ssrOption)
|
proxy, err = outbound.NewShadowSocksR(*ssrOption)
|
||||||
case "socks5":
|
case "socks5":
|
||||||
socksOption := &Socks5Option{}
|
socksOption := &outbound.Socks5Option{}
|
||||||
err = decoder.Decode(mapping, socksOption)
|
err = decoder.Decode(mapping, socksOption)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
proxy = NewSocks5(*socksOption)
|
proxy = outbound.NewSocks5(*socksOption)
|
||||||
case "http":
|
case "http":
|
||||||
httpOption := &HttpOption{}
|
httpOption := &outbound.HttpOption{}
|
||||||
err = decoder.Decode(mapping, httpOption)
|
err = decoder.Decode(mapping, httpOption)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
proxy = NewHttp(*httpOption)
|
proxy = outbound.NewHttp(*httpOption)
|
||||||
case "vmess":
|
case "vmess":
|
||||||
vmessOption := &VmessOption{
|
vmessOption := &outbound.VmessOption{
|
||||||
HTTPOpts: HTTPOptions{
|
HTTPOpts: outbound.HTTPOptions{
|
||||||
Method: "GET",
|
Method: "GET",
|
||||||
Path: []string{"/"},
|
Path: []string{"/"},
|
||||||
},
|
},
|
||||||
@ -58,21 +59,21 @@ func ParseProxy(mapping map[string]interface{}) (C.Proxy, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
proxy, err = NewVmess(*vmessOption)
|
proxy, err = outbound.NewVmess(*vmessOption)
|
||||||
case "snell":
|
case "snell":
|
||||||
snellOption := &SnellOption{}
|
snellOption := &outbound.SnellOption{}
|
||||||
err = decoder.Decode(mapping, snellOption)
|
err = decoder.Decode(mapping, snellOption)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
proxy, err = NewSnell(*snellOption)
|
proxy, err = outbound.NewSnell(*snellOption)
|
||||||
case "trojan":
|
case "trojan":
|
||||||
trojanOption := &TrojanOption{}
|
trojanOption := &outbound.TrojanOption{}
|
||||||
err = decoder.Decode(mapping, trojanOption)
|
err = decoder.Decode(mapping, trojanOption)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
proxy, err = NewTrojan(*trojanOption)
|
proxy, err = outbound.NewTrojan(*trojanOption)
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("unsupport proxy type: %s", proxyType)
|
return nil, fmt.Errorf("unsupport proxy type: %s", proxyType)
|
||||||
}
|
}
|
@ -3,24 +3,24 @@ package provider
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"crypto/md5"
|
"crypto/md5"
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
types "github.com/Dreamacro/clash/constant/provider"
|
||||||
"github.com/Dreamacro/clash/log"
|
"github.com/Dreamacro/clash/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
fileMode os.FileMode = 0666
|
fileMode os.FileMode = 0o666
|
||||||
dirMode os.FileMode = 0755
|
dirMode os.FileMode = 0o755
|
||||||
)
|
)
|
||||||
|
|
||||||
type parser = func([]byte) (interface{}, error)
|
type parser = func([]byte) (interface{}, error)
|
||||||
|
|
||||||
type fetcher struct {
|
type fetcher struct {
|
||||||
name string
|
name string
|
||||||
vehicle Vehicle
|
vehicle types.Vehicle
|
||||||
updatedAt *time.Time
|
updatedAt *time.Time
|
||||||
ticker *time.Ticker
|
ticker *time.Ticker
|
||||||
done chan struct{}
|
done chan struct{}
|
||||||
@ -33,7 +33,7 @@ func (f *fetcher) Name() string {
|
|||||||
return f.name
|
return f.name
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *fetcher) VehicleType() VehicleType {
|
func (f *fetcher) VehicleType() types.VehicleType {
|
||||||
return f.vehicle.Type()
|
return f.vehicle.Type()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -44,7 +44,7 @@ func (f *fetcher) Initial() (interface{}, error) {
|
|||||||
isLocal bool
|
isLocal bool
|
||||||
)
|
)
|
||||||
if stat, fErr := os.Stat(f.vehicle.Path()); fErr == nil {
|
if stat, fErr := os.Stat(f.vehicle.Path()); fErr == nil {
|
||||||
buf, err = ioutil.ReadFile(f.vehicle.Path())
|
buf, err = os.ReadFile(f.vehicle.Path())
|
||||||
modTime := stat.ModTime()
|
modTime := stat.ModTime()
|
||||||
f.updatedAt = &modTime
|
f.updatedAt = &modTime
|
||||||
isLocal = true
|
isLocal = true
|
||||||
@ -76,7 +76,7 @@ func (f *fetcher) Initial() (interface{}, error) {
|
|||||||
isLocal = false
|
isLocal = false
|
||||||
}
|
}
|
||||||
|
|
||||||
if f.vehicle.Type() != File && !isLocal {
|
if f.vehicle.Type() != types.File && !isLocal {
|
||||||
if err := safeWrite(f.vehicle.Path(), buf); err != nil {
|
if err := safeWrite(f.vehicle.Path(), buf); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -110,7 +110,7 @@ func (f *fetcher) Update() (interface{}, bool, error) {
|
|||||||
return nil, false, err
|
return nil, false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if f.vehicle.Type() != File {
|
if f.vehicle.Type() != types.File {
|
||||||
if err := safeWrite(f.vehicle.Path(), buf); err != nil {
|
if err := safeWrite(f.vehicle.Path(), buf); err != nil {
|
||||||
return nil, false, err
|
return nil, false, err
|
||||||
}
|
}
|
||||||
@ -164,10 +164,10 @@ func safeWrite(path string, buf []byte) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return ioutil.WriteFile(path, buf, fileMode)
|
return os.WriteFile(path, buf, fileMode)
|
||||||
}
|
}
|
||||||
|
|
||||||
func newFetcher(name string, interval time.Duration, vehicle Vehicle, parser parser, onUpdate func(interface{})) *fetcher {
|
func newFetcher(name string, interval time.Duration, vehicle types.Vehicle, parser parser, onUpdate func(interface{})) *fetcher {
|
||||||
var ticker *time.Ticker
|
var ticker *time.Ticker
|
||||||
if interval != 0 {
|
if interval != 0 {
|
||||||
ticker = time.NewTicker(interval)
|
ticker = time.NewTicker(interval)
|
@ -2,9 +2,9 @@ package provider
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"sync"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/Dreamacro/clash/common/batch"
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
|
|
||||||
"go.uber.org/atomic"
|
"go.uber.org/atomic"
|
||||||
@ -59,20 +59,17 @@ func (hc *HealthCheck) touch() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (hc *HealthCheck) check() {
|
func (hc *HealthCheck) check() {
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), defaultURLTestTimeout)
|
b, _ := batch.New(context.Background(), batch.WithConcurrencyNum(10))
|
||||||
wg := &sync.WaitGroup{}
|
|
||||||
|
|
||||||
for _, proxy := range hc.proxies {
|
for _, proxy := range hc.proxies {
|
||||||
wg.Add(1)
|
p := proxy
|
||||||
|
b.Go(p.Name(), func() (interface{}, error) {
|
||||||
go func(p C.Proxy) {
|
ctx, cancel := context.WithTimeout(context.Background(), defaultURLTestTimeout)
|
||||||
|
defer cancel()
|
||||||
p.URLTest(ctx, hc.url)
|
p.URLTest(ctx, hc.url)
|
||||||
wg.Done()
|
return nil, nil
|
||||||
}(proxy)
|
})
|
||||||
}
|
}
|
||||||
|
b.Wait()
|
||||||
wg.Wait()
|
|
||||||
cancel()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (hc *HealthCheck) close() {
|
func (hc *HealthCheck) close() {
|
@ -7,11 +7,10 @@ import (
|
|||||||
|
|
||||||
"github.com/Dreamacro/clash/common/structure"
|
"github.com/Dreamacro/clash/common/structure"
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
|
types "github.com/Dreamacro/clash/constant/provider"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var errVehicleType = errors.New("unsupport vehicle type")
|
||||||
errVehicleType = errors.New("unsupport vehicle type")
|
|
||||||
)
|
|
||||||
|
|
||||||
type healthCheckSchema struct {
|
type healthCheckSchema struct {
|
||||||
Enable bool `provider:"enable"`
|
Enable bool `provider:"enable"`
|
||||||
@ -28,7 +27,7 @@ type proxyProviderSchema struct {
|
|||||||
HealthCheck healthCheckSchema `provider:"health-check,omitempty"`
|
HealthCheck healthCheckSchema `provider:"health-check,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func ParseProxyProvider(name string, mapping map[string]interface{}) (ProxyProvider, error) {
|
func ParseProxyProvider(name string, mapping map[string]interface{}) (types.ProxyProvider, error) {
|
||||||
decoder := structure.NewDecoder(structure.Option{TagName: "provider", WeaklyTypedInput: true})
|
decoder := structure.NewDecoder(structure.Option{TagName: "provider", WeaklyTypedInput: true})
|
||||||
|
|
||||||
schema := &proxyProviderSchema{
|
schema := &proxyProviderSchema{
|
||||||
@ -48,7 +47,7 @@ func ParseProxyProvider(name string, mapping map[string]interface{}) (ProxyProvi
|
|||||||
|
|
||||||
path := C.Path.Resolve(schema.Path)
|
path := C.Path.Resolve(schema.Path)
|
||||||
|
|
||||||
var vehicle Vehicle
|
var vehicle types.Vehicle
|
||||||
switch schema.Type {
|
switch schema.Type {
|
||||||
case "file":
|
case "file":
|
||||||
vehicle = NewFileVehicle(path)
|
vehicle = NewFileVehicle(path)
|
@ -7,8 +7,9 @@ import (
|
|||||||
"runtime"
|
"runtime"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/adapters/outbound"
|
"github.com/Dreamacro/clash/adapter"
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
|
types "github.com/Dreamacro/clash/constant/provider"
|
||||||
|
|
||||||
"gopkg.in/yaml.v2"
|
"gopkg.in/yaml.v2"
|
||||||
)
|
)
|
||||||
@ -17,45 +18,6 @@ const (
|
|||||||
ReservedName = "default"
|
ReservedName = "default"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Provider Type
|
|
||||||
const (
|
|
||||||
Proxy ProviderType = iota
|
|
||||||
Rule
|
|
||||||
)
|
|
||||||
|
|
||||||
// ProviderType defined
|
|
||||||
type ProviderType int
|
|
||||||
|
|
||||||
func (pt ProviderType) String() string {
|
|
||||||
switch pt {
|
|
||||||
case Proxy:
|
|
||||||
return "Proxy"
|
|
||||||
case Rule:
|
|
||||||
return "Rule"
|
|
||||||
default:
|
|
||||||
return "Unknown"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Provider interface
|
|
||||||
type Provider interface {
|
|
||||||
Name() string
|
|
||||||
VehicleType() VehicleType
|
|
||||||
Type() ProviderType
|
|
||||||
Initial() error
|
|
||||||
Update() error
|
|
||||||
}
|
|
||||||
|
|
||||||
// ProxyProvider interface
|
|
||||||
type ProxyProvider interface {
|
|
||||||
Provider
|
|
||||||
Proxies() []C.Proxy
|
|
||||||
// ProxiesWithTouch is used to inform the provider that the proxy is actually being used while getting the list of proxies.
|
|
||||||
// Commonly used in Dial and DialUDP
|
|
||||||
ProxiesWithTouch() []C.Proxy
|
|
||||||
HealthCheck()
|
|
||||||
}
|
|
||||||
|
|
||||||
type ProxySchema struct {
|
type ProxySchema struct {
|
||||||
Proxies []map[string]interface{} `yaml:"proxies"`
|
Proxies []map[string]interface{} `yaml:"proxies"`
|
||||||
}
|
}
|
||||||
@ -107,8 +69,8 @@ func (pp *proxySetProvider) Initial() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pp *proxySetProvider) Type() ProviderType {
|
func (pp *proxySetProvider) Type() types.ProviderType {
|
||||||
return Proxy
|
return types.Proxy
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pp *proxySetProvider) Proxies() []C.Proxy {
|
func (pp *proxySetProvider) Proxies() []C.Proxy {
|
||||||
@ -133,7 +95,7 @@ func proxiesParse(buf []byte) (interface{}, error) {
|
|||||||
|
|
||||||
proxies := []C.Proxy{}
|
proxies := []C.Proxy{}
|
||||||
for idx, mapping := range schema.Proxies {
|
for idx, mapping := range schema.Proxies {
|
||||||
proxy, err := outbound.ParseProxy(mapping)
|
proxy, err := adapter.ParseProxy(mapping)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("proxy %d error: %w", idx, err)
|
return nil, fmt.Errorf("proxy %d error: %w", idx, err)
|
||||||
}
|
}
|
||||||
@ -160,7 +122,7 @@ func stopProxyProvider(pd *ProxySetProvider) {
|
|||||||
pd.fetcher.Destroy()
|
pd.fetcher.Destroy()
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewProxySetProvider(name string, interval time.Duration, vehicle Vehicle, hc *HealthCheck) *ProxySetProvider {
|
func NewProxySetProvider(name string, interval time.Duration, vehicle types.Vehicle, hc *HealthCheck) *ProxySetProvider {
|
||||||
if hc.auto() {
|
if hc.auto() {
|
||||||
go hc.process()
|
go hc.process()
|
||||||
}
|
}
|
||||||
@ -219,12 +181,12 @@ func (cp *compatibleProvider) Initial() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cp *compatibleProvider) VehicleType() VehicleType {
|
func (cp *compatibleProvider) VehicleType() types.VehicleType {
|
||||||
return Compatible
|
return types.Compatible
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cp *compatibleProvider) Type() ProviderType {
|
func (cp *compatibleProvider) Type() types.ProviderType {
|
||||||
return Proxy
|
return types.Proxy
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cp *compatibleProvider) Proxies() []C.Proxy {
|
func (cp *compatibleProvider) Proxies() []C.Proxy {
|
||||||
@ -242,7 +204,7 @@ func stopCompatibleProvider(pd *CompatibleProvider) {
|
|||||||
|
|
||||||
func NewCompatibleProvider(name string, proxies []C.Proxy, hc *HealthCheck) (*CompatibleProvider, error) {
|
func NewCompatibleProvider(name string, proxies []C.Proxy, hc *HealthCheck) (*CompatibleProvider, error) {
|
||||||
if len(proxies) == 0 {
|
if len(proxies) == 0 {
|
||||||
return nil, errors.New("Provider need one proxy at least")
|
return nil, errors.New("provider need one proxy at least")
|
||||||
}
|
}
|
||||||
|
|
||||||
if hc.auto() {
|
if hc.auto() {
|
@ -2,49 +2,23 @@ package provider
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"io/ioutil"
|
"io"
|
||||||
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"os"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/component/dialer"
|
"github.com/Dreamacro/clash/component/dialer"
|
||||||
|
types "github.com/Dreamacro/clash/constant/provider"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Vehicle Type
|
|
||||||
const (
|
|
||||||
File VehicleType = iota
|
|
||||||
HTTP
|
|
||||||
Compatible
|
|
||||||
)
|
|
||||||
|
|
||||||
// VehicleType defined
|
|
||||||
type VehicleType int
|
|
||||||
|
|
||||||
func (v VehicleType) String() string {
|
|
||||||
switch v {
|
|
||||||
case File:
|
|
||||||
return "File"
|
|
||||||
case HTTP:
|
|
||||||
return "HTTP"
|
|
||||||
case Compatible:
|
|
||||||
return "Compatible"
|
|
||||||
default:
|
|
||||||
return "Unknown"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type Vehicle interface {
|
|
||||||
Read() ([]byte, error)
|
|
||||||
Path() string
|
|
||||||
Type() VehicleType
|
|
||||||
}
|
|
||||||
|
|
||||||
type FileVehicle struct {
|
type FileVehicle struct {
|
||||||
path string
|
path string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *FileVehicle) Type() VehicleType {
|
func (f *FileVehicle) Type() types.VehicleType {
|
||||||
return File
|
return types.File
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *FileVehicle) Path() string {
|
func (f *FileVehicle) Path() string {
|
||||||
@ -52,7 +26,7 @@ func (f *FileVehicle) Path() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (f *FileVehicle) Read() ([]byte, error) {
|
func (f *FileVehicle) Read() ([]byte, error) {
|
||||||
return ioutil.ReadFile(f.path)
|
return os.ReadFile(f.path)
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewFileVehicle(path string) *FileVehicle {
|
func NewFileVehicle(path string) *FileVehicle {
|
||||||
@ -64,8 +38,8 @@ type HTTPVehicle struct {
|
|||||||
path string
|
path string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *HTTPVehicle) Type() VehicleType {
|
func (h *HTTPVehicle) Type() types.VehicleType {
|
||||||
return HTTP
|
return types.HTTP
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *HTTPVehicle) Path() string {
|
func (h *HTTPVehicle) Path() string {
|
||||||
@ -99,7 +73,9 @@ func (h *HTTPVehicle) Read() ([]byte, error) {
|
|||||||
IdleConnTimeout: 90 * time.Second,
|
IdleConnTimeout: 90 * time.Second,
|
||||||
TLSHandshakeTimeout: 10 * time.Second,
|
TLSHandshakeTimeout: 10 * time.Second,
|
||||||
ExpectContinueTimeout: 1 * time.Second,
|
ExpectContinueTimeout: 1 * time.Second,
|
||||||
DialContext: dialer.DialContext,
|
DialContext: func(ctx context.Context, network, address string) (net.Conn, error) {
|
||||||
|
return dialer.DialContext(ctx, network, address)
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
client := http.Client{Transport: transport}
|
client := http.Client{Transport: transport}
|
||||||
@ -109,7 +85,7 @@ func (h *HTTPVehicle) Read() ([]byte, error) {
|
|||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
|
||||||
buf, err := ioutil.ReadAll(resp.Body)
|
buf, err := io.ReadAll(resp.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
@ -1,61 +0,0 @@
|
|||||||
package inbound
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net"
|
|
||||||
"net/http"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
C "github.com/Dreamacro/clash/constant"
|
|
||||||
"github.com/Dreamacro/clash/context"
|
|
||||||
)
|
|
||||||
|
|
||||||
// NewHTTP receive normal http request and return HTTPContext
|
|
||||||
func NewHTTP(request *http.Request, conn net.Conn) *context.HTTPContext {
|
|
||||||
metadata := parseHTTPAddr(request)
|
|
||||||
metadata.Type = C.HTTP
|
|
||||||
if ip, port, err := parseAddr(conn.RemoteAddr().String()); err == nil {
|
|
||||||
metadata.SrcIP = ip
|
|
||||||
metadata.SrcPort = port
|
|
||||||
}
|
|
||||||
return context.NewHTTPContext(conn, request, metadata)
|
|
||||||
}
|
|
||||||
|
|
||||||
// RemoveHopByHopHeaders remove hop-by-hop header
|
|
||||||
func RemoveHopByHopHeaders(header http.Header) {
|
|
||||||
// Strip hop-by-hop header based on RFC:
|
|
||||||
// http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.5.1
|
|
||||||
// https://www.mnot.net/blog/2011/07/11/what_proxies_must_do
|
|
||||||
|
|
||||||
header.Del("Proxy-Connection")
|
|
||||||
header.Del("Proxy-Authenticate")
|
|
||||||
header.Del("Proxy-Authorization")
|
|
||||||
header.Del("TE")
|
|
||||||
header.Del("Trailers")
|
|
||||||
header.Del("Transfer-Encoding")
|
|
||||||
header.Del("Upgrade")
|
|
||||||
|
|
||||||
connections := header.Get("Connection")
|
|
||||||
header.Del("Connection")
|
|
||||||
if len(connections) == 0 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
for _, h := range strings.Split(connections, ",") {
|
|
||||||
header.Del(strings.TrimSpace(h))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// RemoveExtraHTTPHostPort remove extra host port (example.com:80 --> example.com)
|
|
||||||
// It resolves the behavior of some HTTP servers that do not handle host:80 (e.g. baidu.com)
|
|
||||||
func RemoveExtraHTTPHostPort(req *http.Request) {
|
|
||||||
host := req.Host
|
|
||||||
if host == "" {
|
|
||||||
host = req.URL.Host
|
|
||||||
}
|
|
||||||
|
|
||||||
if pHost, port, err := net.SplitHostPort(host); err == nil && port == "80" {
|
|
||||||
host = pHost
|
|
||||||
}
|
|
||||||
|
|
||||||
req.Host = host
|
|
||||||
req.URL.Host = host
|
|
||||||
}
|
|
105
common/batch/batch.go
Normal file
105
common/batch/batch.go
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
package batch
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Option = func(b *Batch)
|
||||||
|
|
||||||
|
type Result struct {
|
||||||
|
Value interface{}
|
||||||
|
Err error
|
||||||
|
}
|
||||||
|
|
||||||
|
type Error struct {
|
||||||
|
Key string
|
||||||
|
Err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithConcurrencyNum(n int) Option {
|
||||||
|
return func(b *Batch) {
|
||||||
|
q := make(chan struct{}, n)
|
||||||
|
for i := 0; i < n; i++ {
|
||||||
|
q <- struct{}{}
|
||||||
|
}
|
||||||
|
b.queue = q
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Batch similar to errgroup, but can control the maximum number of concurrent
|
||||||
|
type Batch struct {
|
||||||
|
result map[string]Result
|
||||||
|
queue chan struct{}
|
||||||
|
wg sync.WaitGroup
|
||||||
|
mux sync.Mutex
|
||||||
|
err *Error
|
||||||
|
once sync.Once
|
||||||
|
cancel func()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Batch) Go(key string, fn func() (interface{}, error)) {
|
||||||
|
b.wg.Add(1)
|
||||||
|
go func() {
|
||||||
|
defer b.wg.Done()
|
||||||
|
if b.queue != nil {
|
||||||
|
<-b.queue
|
||||||
|
defer func() {
|
||||||
|
b.queue <- struct{}{}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
value, err := fn()
|
||||||
|
if err != nil {
|
||||||
|
b.once.Do(func() {
|
||||||
|
b.err = &Error{key, err}
|
||||||
|
if b.cancel != nil {
|
||||||
|
b.cancel()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
ret := Result{value, err}
|
||||||
|
b.mux.Lock()
|
||||||
|
defer b.mux.Unlock()
|
||||||
|
b.result[key] = ret
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Batch) Wait() *Error {
|
||||||
|
b.wg.Wait()
|
||||||
|
if b.cancel != nil {
|
||||||
|
b.cancel()
|
||||||
|
}
|
||||||
|
return b.err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Batch) WaitAndGetResult() (map[string]Result, *Error) {
|
||||||
|
err := b.Wait()
|
||||||
|
return b.Result(), err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Batch) Result() map[string]Result {
|
||||||
|
b.mux.Lock()
|
||||||
|
defer b.mux.Unlock()
|
||||||
|
copy := map[string]Result{}
|
||||||
|
for k, v := range b.result {
|
||||||
|
copy[k] = v
|
||||||
|
}
|
||||||
|
return copy
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(ctx context.Context, opts ...Option) (*Batch, context.Context) {
|
||||||
|
ctx, cancel := context.WithCancel(ctx)
|
||||||
|
|
||||||
|
b := &Batch{
|
||||||
|
result: map[string]Result{},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, o := range opts {
|
||||||
|
o(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
b.cancel = cancel
|
||||||
|
return b, ctx
|
||||||
|
}
|
83
common/batch/batch_test.go
Normal file
83
common/batch/batch_test.go
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
package batch
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"strconv"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestBatch(t *testing.T) {
|
||||||
|
b, _ := New(context.Background())
|
||||||
|
|
||||||
|
now := time.Now()
|
||||||
|
b.Go("foo", func() (interface{}, error) {
|
||||||
|
time.Sleep(time.Millisecond * 100)
|
||||||
|
return "foo", nil
|
||||||
|
})
|
||||||
|
b.Go("bar", func() (interface{}, error) {
|
||||||
|
time.Sleep(time.Millisecond * 150)
|
||||||
|
return "bar", nil
|
||||||
|
})
|
||||||
|
result, err := b.WaitAndGetResult()
|
||||||
|
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
duration := time.Since(now)
|
||||||
|
assert.Less(t, duration, time.Millisecond*200)
|
||||||
|
assert.Equal(t, 2, len(result))
|
||||||
|
|
||||||
|
for k, v := range result {
|
||||||
|
assert.NoError(t, v.Err)
|
||||||
|
assert.Equal(t, k, v.Value.(string))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBatchWithConcurrencyNum(t *testing.T) {
|
||||||
|
b, _ := New(
|
||||||
|
context.Background(),
|
||||||
|
WithConcurrencyNum(3),
|
||||||
|
)
|
||||||
|
|
||||||
|
now := time.Now()
|
||||||
|
for i := 0; i < 7; i++ {
|
||||||
|
idx := i
|
||||||
|
b.Go(strconv.Itoa(idx), func() (interface{}, error) {
|
||||||
|
time.Sleep(time.Millisecond * 100)
|
||||||
|
return strconv.Itoa(idx), nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
result, _ := b.WaitAndGetResult()
|
||||||
|
duration := time.Since(now)
|
||||||
|
assert.Greater(t, duration, time.Millisecond*260)
|
||||||
|
assert.Equal(t, 7, len(result))
|
||||||
|
|
||||||
|
for k, v := range result {
|
||||||
|
assert.NoError(t, v.Err)
|
||||||
|
assert.Equal(t, k, v.Value.(string))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBatchContext(t *testing.T) {
|
||||||
|
b, ctx := New(context.Background())
|
||||||
|
|
||||||
|
b.Go("error", func() (interface{}, error) {
|
||||||
|
time.Sleep(time.Millisecond * 100)
|
||||||
|
return nil, errors.New("test error")
|
||||||
|
})
|
||||||
|
|
||||||
|
b.Go("ctx", func() (interface{}, error) {
|
||||||
|
<-ctx.Done()
|
||||||
|
return nil, ctx.Err()
|
||||||
|
})
|
||||||
|
|
||||||
|
result, err := b.WaitAndGetResult()
|
||||||
|
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
assert.Equal(t, "error", err.Key)
|
||||||
|
|
||||||
|
assert.Equal(t, ctx.Err(), result["ctx"].Err)
|
||||||
|
}
|
1
common/cache/lrucache_test.go
vendored
1
common/cache/lrucache_test.go
vendored
@ -149,7 +149,6 @@ func TestSetWithExpire(t *testing.T) {
|
|||||||
assert.Equal(t, nil, res)
|
assert.Equal(t, nil, res)
|
||||||
assert.Equal(t, time.Time{}, expires)
|
assert.Equal(t, time.Time{}, expires)
|
||||||
assert.Equal(t, false, exist)
|
assert.Equal(t, false, exist)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestStale(t *testing.T) {
|
func TestStale(t *testing.T) {
|
||||||
|
@ -67,7 +67,6 @@ func (d *digest32) bmix(p []byte) (tail []byte) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (d *digest32) Sum32() (h1 uint32) {
|
func (d *digest32) Sum32() (h1 uint32) {
|
||||||
|
|
||||||
h1 = d.h1
|
h1 = d.h1
|
||||||
|
|
||||||
var k1 uint32
|
var k1 uint32
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
package mixed
|
package net
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
@ -11,6 +11,9 @@ type BufferedConn struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func NewBufferedConn(c net.Conn) *BufferedConn {
|
func NewBufferedConn(c net.Conn) *BufferedConn {
|
||||||
|
if bc, ok := c.(*BufferedConn); ok {
|
||||||
|
return bc
|
||||||
|
}
|
||||||
return &BufferedConn{bufio.NewReader(c), c}
|
return &BufferedConn{bufio.NewReader(c), c}
|
||||||
}
|
}
|
||||||
|
|
@ -38,7 +38,7 @@ func TestObservable_MultiSubscribe(t *testing.T) {
|
|||||||
src := NewObservable(iter)
|
src := NewObservable(iter)
|
||||||
ch1, _ := src.Subscribe()
|
ch1, _ := src.Subscribe()
|
||||||
ch2, _ := src.Subscribe()
|
ch2, _ := src.Subscribe()
|
||||||
var count = atomic.NewInt32(0)
|
count := atomic.NewInt32(0)
|
||||||
|
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
wg.Add(2)
|
wg.Add(2)
|
||||||
|
@ -8,11 +8,7 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
)
|
)
|
||||||
|
|
||||||
var defaultAllocator *Allocator
|
var defaultAllocator = NewAllocator()
|
||||||
|
|
||||||
func init() {
|
|
||||||
defaultAllocator = NewAllocator()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Allocator for incoming frames, optimized to prevent overwriting after zeroing
|
// Allocator for incoming frames, optimized to prevent overwriting after zeroing
|
||||||
type Allocator struct {
|
type Allocator struct {
|
||||||
@ -57,6 +53,7 @@ func (alloc *Allocator) Put(buf []byte) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//lint:ignore SA6002 ignore temporarily
|
//lint:ignore SA6002 ignore temporarily
|
||||||
|
//nolint
|
||||||
alloc.buffers[bits].Put(buf)
|
alloc.buffers[bits].Put(buf)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
17
common/pool/buffer.go
Normal file
17
common/pool/buffer.go
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
package pool
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
var bufferPool = sync.Pool{New: func() interface{} { return &bytes.Buffer{} }}
|
||||||
|
|
||||||
|
func GetBuffer() *bytes.Buffer {
|
||||||
|
return bufferPool.Get().(*bytes.Buffer)
|
||||||
|
}
|
||||||
|
|
||||||
|
func PutBuffer(buf *bytes.Buffer) {
|
||||||
|
buf.Reset()
|
||||||
|
bufferPool.Put(buf)
|
||||||
|
}
|
@ -5,6 +5,11 @@ const (
|
|||||||
// but the maximum packet size of vmess/shadowsocks is about 16 KiB
|
// but the maximum packet size of vmess/shadowsocks is about 16 KiB
|
||||||
// so define a buffer of 20 KiB to reduce the memory of each TCP relay
|
// so define a buffer of 20 KiB to reduce the memory of each TCP relay
|
||||||
RelayBufferSize = 20 * 1024
|
RelayBufferSize = 20 * 1024
|
||||||
|
|
||||||
|
// RelayBufferSize uses 20KiB, but due to the allocator it will actually
|
||||||
|
// request 32Kib. Most UDPs are smaller than the MTU, and the TUN's MTU
|
||||||
|
// set to 9000, so the UDP Buffer size set to 16Kib
|
||||||
|
UDPBufferSize = 16 * 1024
|
||||||
)
|
)
|
||||||
|
|
||||||
func Get(size int) []byte {
|
func Get(size int) []byte {
|
||||||
|
@ -12,7 +12,7 @@ import (
|
|||||||
func TestBasic(t *testing.T) {
|
func TestBasic(t *testing.T) {
|
||||||
single := NewSingle(time.Millisecond * 30)
|
single := NewSingle(time.Millisecond * 30)
|
||||||
foo := 0
|
foo := 0
|
||||||
var shardCount = atomic.NewInt32(0)
|
shardCount := atomic.NewInt32(0)
|
||||||
call := func() (interface{}, error) {
|
call := func() (interface{}, error) {
|
||||||
foo++
|
foo++
|
||||||
time.Sleep(time.Millisecond * 5)
|
time.Sleep(time.Millisecond * 5)
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
//go:build !linux
|
||||||
// +build !linux
|
// +build !linux
|
||||||
|
|
||||||
package sockopt
|
package sockopt
|
||||||
|
@ -37,6 +37,12 @@ func (d *Decoder) Decode(src map[string]interface{}, dst interface{}) error {
|
|||||||
v := reflect.ValueOf(dst).Elem()
|
v := reflect.ValueOf(dst).Elem()
|
||||||
for idx := 0; idx < v.NumField(); idx++ {
|
for idx := 0; idx < v.NumField(); idx++ {
|
||||||
field := t.Field(idx)
|
field := t.Field(idx)
|
||||||
|
if field.Anonymous {
|
||||||
|
if err := d.decodeStruct(field.Name, src, v.Field(idx)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
tag := field.Tag.Get(d.option.TagName)
|
tag := field.Tag.Get(d.option.TagName)
|
||||||
str := strings.SplitN(tag, ",", 2)
|
str := strings.SplitN(tag, ",", 2)
|
||||||
|
@ -1,12 +1,15 @@
|
|||||||
package structure
|
package structure
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"reflect"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
var decoder = NewDecoder(Option{TagName: "test"})
|
var (
|
||||||
var weakTypeDecoder = NewDecoder(Option{TagName: "test", WeaklyTypedInput: true})
|
decoder = NewDecoder(Option{TagName: "test"})
|
||||||
|
weakTypeDecoder = NewDecoder(Option{TagName: "test", WeaklyTypedInput: true})
|
||||||
|
)
|
||||||
|
|
||||||
type Baz struct {
|
type Baz struct {
|
||||||
Foo int `test:"foo"`
|
Foo int `test:"foo"`
|
||||||
@ -37,12 +40,8 @@ func TestStructure_Basic(t *testing.T) {
|
|||||||
|
|
||||||
s := &Baz{}
|
s := &Baz{}
|
||||||
err := decoder.Decode(rawMap, s)
|
err := decoder.Decode(rawMap, s)
|
||||||
if err != nil {
|
assert.Nil(t, err)
|
||||||
t.Fatal(err.Error())
|
assert.Equal(t, goal, s)
|
||||||
}
|
|
||||||
if !reflect.DeepEqual(s, goal) {
|
|
||||||
t.Fatalf("bad: %#v", s)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestStructure_Slice(t *testing.T) {
|
func TestStructure_Slice(t *testing.T) {
|
||||||
@ -58,12 +57,8 @@ func TestStructure_Slice(t *testing.T) {
|
|||||||
|
|
||||||
s := &BazSlice{}
|
s := &BazSlice{}
|
||||||
err := decoder.Decode(rawMap, s)
|
err := decoder.Decode(rawMap, s)
|
||||||
if err != nil {
|
assert.Nil(t, err)
|
||||||
t.Fatal(err.Error())
|
assert.Equal(t, goal, s)
|
||||||
}
|
|
||||||
if !reflect.DeepEqual(s, goal) {
|
|
||||||
t.Fatalf("bad: %#v", s)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestStructure_Optional(t *testing.T) {
|
func TestStructure_Optional(t *testing.T) {
|
||||||
@ -77,12 +72,8 @@ func TestStructure_Optional(t *testing.T) {
|
|||||||
|
|
||||||
s := &BazOptional{}
|
s := &BazOptional{}
|
||||||
err := decoder.Decode(rawMap, s)
|
err := decoder.Decode(rawMap, s)
|
||||||
if err != nil {
|
assert.Nil(t, err)
|
||||||
t.Fatal(err.Error())
|
assert.Equal(t, goal, s)
|
||||||
}
|
|
||||||
if !reflect.DeepEqual(s, goal) {
|
|
||||||
t.Fatalf("bad: %#v", s)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestStructure_MissingKey(t *testing.T) {
|
func TestStructure_MissingKey(t *testing.T) {
|
||||||
@ -92,18 +83,14 @@ func TestStructure_MissingKey(t *testing.T) {
|
|||||||
|
|
||||||
s := &Baz{}
|
s := &Baz{}
|
||||||
err := decoder.Decode(rawMap, s)
|
err := decoder.Decode(rawMap, s)
|
||||||
if err == nil {
|
assert.NotNilf(t, err, "should throw error: %#v", s)
|
||||||
t.Fatalf("should throw error: %#v", s)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestStructure_ParamError(t *testing.T) {
|
func TestStructure_ParamError(t *testing.T) {
|
||||||
rawMap := map[string]interface{}{}
|
rawMap := map[string]interface{}{}
|
||||||
s := Baz{}
|
s := Baz{}
|
||||||
err := decoder.Decode(rawMap, s)
|
err := decoder.Decode(rawMap, s)
|
||||||
if err == nil {
|
assert.NotNilf(t, err, "should throw error: %#v", s)
|
||||||
t.Fatalf("should throw error: %#v", s)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestStructure_SliceTypeError(t *testing.T) {
|
func TestStructure_SliceTypeError(t *testing.T) {
|
||||||
@ -114,9 +101,7 @@ func TestStructure_SliceTypeError(t *testing.T) {
|
|||||||
|
|
||||||
s := &BazSlice{}
|
s := &BazSlice{}
|
||||||
err := decoder.Decode(rawMap, s)
|
err := decoder.Decode(rawMap, s)
|
||||||
if err == nil {
|
assert.NotNilf(t, err, "should throw error: %#v", s)
|
||||||
t.Fatalf("should throw error: %#v", s)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestStructure_WeakType(t *testing.T) {
|
func TestStructure_WeakType(t *testing.T) {
|
||||||
@ -132,10 +117,23 @@ func TestStructure_WeakType(t *testing.T) {
|
|||||||
|
|
||||||
s := &BazSlice{}
|
s := &BazSlice{}
|
||||||
err := weakTypeDecoder.Decode(rawMap, s)
|
err := weakTypeDecoder.Decode(rawMap, s)
|
||||||
if err != nil {
|
assert.Nil(t, err)
|
||||||
t.Fatal(err.Error())
|
assert.Equal(t, goal, s)
|
||||||
}
|
}
|
||||||
if !reflect.DeepEqual(s, goal) {
|
|
||||||
t.Fatalf("bad: %#v", s)
|
func TestStructure_Nest(t *testing.T) {
|
||||||
}
|
rawMap := map[string]interface{}{
|
||||||
|
"foo": 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
goal := BazOptional{
|
||||||
|
Foo: 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
s := &struct {
|
||||||
|
BazOptional
|
||||||
|
}{}
|
||||||
|
err := decoder.Decode(rawMap, s)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Equal(t, s.BazOptional, goal)
|
||||||
}
|
}
|
||||||
|
18
component/dhcp/conn.go
Normal file
18
component/dhcp/conn.go
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
package dhcp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net"
|
||||||
|
"runtime"
|
||||||
|
|
||||||
|
"github.com/Dreamacro/clash/component/dialer"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ListenDHCPClient(ctx context.Context, ifaceName string) (net.PacketConn, error) {
|
||||||
|
listenAddr := "0.0.0.0:68"
|
||||||
|
if runtime.GOOS == "linux" || runtime.GOOS == "android" {
|
||||||
|
listenAddr = "255.255.255.255:68"
|
||||||
|
}
|
||||||
|
|
||||||
|
return dialer.ListenPacket(ctx, "udp4", listenAddr, dialer.WithInterface(ifaceName), dialer.WithAddrReuse(true))
|
||||||
|
}
|
88
component/dhcp/dhcp.go
Normal file
88
component/dhcp/dhcp.go
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
package dhcp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"net"
|
||||||
|
|
||||||
|
"github.com/Dreamacro/clash/component/iface"
|
||||||
|
|
||||||
|
"github.com/insomniacslk/dhcp/dhcpv4"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrNotResponding = errors.New("DHCP not responding")
|
||||||
|
ErrNotFound = errors.New("DNS option not found")
|
||||||
|
)
|
||||||
|
|
||||||
|
func ResolveDNSFromDHCP(context context.Context, ifaceName string) ([]net.IP, error) {
|
||||||
|
conn, err := ListenDHCPClient(context, ifaceName)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer conn.Close()
|
||||||
|
|
||||||
|
result := make(chan []net.IP, 1)
|
||||||
|
|
||||||
|
ifaceObj, err := iface.ResolveInterface(ifaceName)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
discovery, err := dhcpv4.NewDiscovery(ifaceObj.HardwareAddr, dhcpv4.WithBroadcast(true), dhcpv4.WithRequestedOptions(dhcpv4.OptionDomainNameServer))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
go receiveOffer(conn, discovery.TransactionID, result)
|
||||||
|
|
||||||
|
_, err = conn.WriteTo(discovery.ToBytes(), &net.UDPAddr{IP: net.IPv4bcast, Port: 67})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
case r, ok := <-result:
|
||||||
|
if !ok {
|
||||||
|
return nil, ErrNotFound
|
||||||
|
}
|
||||||
|
return r, nil
|
||||||
|
case <-context.Done():
|
||||||
|
return nil, ErrNotResponding
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func receiveOffer(conn net.PacketConn, id dhcpv4.TransactionID, result chan<- []net.IP) {
|
||||||
|
defer close(result)
|
||||||
|
|
||||||
|
buf := make([]byte, dhcpv4.MaxMessageSize)
|
||||||
|
|
||||||
|
for {
|
||||||
|
n, _, err := conn.ReadFrom(buf)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
pkt, err := dhcpv4.FromBytes(buf[:n])
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if pkt.MessageType() != dhcpv4.MessageTypeOffer {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if pkt.TransactionID != id {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
dns := pkt.DNS()
|
||||||
|
if len(dns) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
result <- dns
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
@ -1,118 +0,0 @@
|
|||||||
package dialer
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"net"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/common/singledo"
|
|
||||||
)
|
|
||||||
|
|
||||||
// In some OS, such as Windows, it takes a little longer to get interface information
|
|
||||||
var ifaceSingle = singledo.NewSingle(time.Second * 20)
|
|
||||||
|
|
||||||
var (
|
|
||||||
errPlatformNotSupport = errors.New("unsupport platform")
|
|
||||||
)
|
|
||||||
|
|
||||||
func lookupTCPAddr(ip net.IP, addrs []net.Addr) (*net.TCPAddr, error) {
|
|
||||||
ipv4 := ip.To4() != nil
|
|
||||||
|
|
||||||
for _, elm := range addrs {
|
|
||||||
addr, ok := elm.(*net.IPNet)
|
|
||||||
if !ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
addrV4 := addr.IP.To4() != nil
|
|
||||||
|
|
||||||
if addrV4 && ipv4 {
|
|
||||||
return &net.TCPAddr{IP: addr.IP, Port: 0}, nil
|
|
||||||
} else if !addrV4 && !ipv4 {
|
|
||||||
return &net.TCPAddr{IP: addr.IP, Port: 0}, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, ErrAddrNotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
func lookupUDPAddr(ip net.IP, addrs []net.Addr) (*net.UDPAddr, error) {
|
|
||||||
ipv4 := ip.To4() != nil
|
|
||||||
|
|
||||||
for _, elm := range addrs {
|
|
||||||
addr, ok := elm.(*net.IPNet)
|
|
||||||
if !ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
addrV4 := addr.IP.To4() != nil
|
|
||||||
|
|
||||||
if addrV4 && ipv4 {
|
|
||||||
return &net.UDPAddr{IP: addr.IP, Port: 0}, nil
|
|
||||||
} else if !addrV4 && !ipv4 {
|
|
||||||
return &net.UDPAddr{IP: addr.IP, Port: 0}, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, ErrAddrNotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
func fallbackBindToDialer(dialer *net.Dialer, network string, ip net.IP, name string) error {
|
|
||||||
if !ip.IsGlobalUnicast() {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
iface, err, _ := ifaceSingle.Do(func() (interface{}, error) {
|
|
||||||
return net.InterfaceByName(name)
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
addrs, err := iface.(*net.Interface).Addrs()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
switch network {
|
|
||||||
case "tcp", "tcp4", "tcp6":
|
|
||||||
if addr, err := lookupTCPAddr(ip, addrs); err == nil {
|
|
||||||
dialer.LocalAddr = addr
|
|
||||||
} else {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
case "udp", "udp4", "udp6":
|
|
||||||
if addr, err := lookupUDPAddr(ip, addrs); err == nil {
|
|
||||||
dialer.LocalAddr = addr
|
|
||||||
} else {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func fallbackBindToListenConfig(name string) (string, error) {
|
|
||||||
iface, err, _ := ifaceSingle.Do(func() (interface{}, error) {
|
|
||||||
return net.InterfaceByName(name)
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
addrs, err := iface.(*net.Interface).Addrs()
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, elm := range addrs {
|
|
||||||
addr, ok := elm.(*net.IPNet)
|
|
||||||
if !ok || addr.IP.To4() == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
return net.JoinHostPort(addr.IP.String(), "0"), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return "", ErrAddrNotFound
|
|
||||||
}
|
|
@ -3,51 +3,57 @@ package dialer
|
|||||||
import (
|
import (
|
||||||
"net"
|
"net"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
|
||||||
|
"github.com/Dreamacro/clash/component/iface"
|
||||||
)
|
)
|
||||||
|
|
||||||
type controlFn = func(network, address string, c syscall.RawConn) error
|
type controlFn = func(network, address string, c syscall.RawConn) error
|
||||||
|
|
||||||
func bindControl(ifaceIdx int) controlFn {
|
func bindControl(ifaceIdx int, chain controlFn) controlFn {
|
||||||
return func(network, address string, c syscall.RawConn) error {
|
return func(network, address string, c syscall.RawConn) (err error) {
|
||||||
|
defer func() {
|
||||||
|
if err == nil && chain != nil {
|
||||||
|
err = chain(network, address, c)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
ipStr, _, err := net.SplitHostPort(address)
|
ipStr, _, err := net.SplitHostPort(address)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
ip := net.ParseIP(ipStr)
|
ip := net.ParseIP(ipStr)
|
||||||
if ip != nil && !ip.IsGlobalUnicast() {
|
if ip != nil && !ip.IsGlobalUnicast() {
|
||||||
return nil
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return c.Control(func(fd uintptr) {
|
return c.Control(func(fd uintptr) {
|
||||||
switch network {
|
switch network {
|
||||||
case "tcp4", "udp4":
|
case "tcp4", "udp4":
|
||||||
syscall.SetsockoptInt(int(fd), syscall.IPPROTO_IP, syscall.IP_BOUND_IF, ifaceIdx)
|
unix.SetsockoptInt(int(fd), unix.IPPROTO_IP, unix.IP_BOUND_IF, ifaceIdx)
|
||||||
case "tcp6", "udp6":
|
case "tcp6", "udp6":
|
||||||
syscall.SetsockoptInt(int(fd), syscall.IPPROTO_IPV6, syscall.IPV6_BOUND_IF, ifaceIdx)
|
unix.SetsockoptInt(int(fd), unix.IPPROTO_IPV6, unix.IPV6_BOUND_IF, ifaceIdx)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func bindIfaceToDialer(dialer *net.Dialer, ifaceName string) error {
|
func bindIfaceToDialer(ifaceName string, dialer *net.Dialer, _ string, _ net.IP) error {
|
||||||
iface, err, _ := ifaceSingle.Do(func() (interface{}, error) {
|
ifaceObj, err := iface.ResolveInterface(ifaceName)
|
||||||
return net.InterfaceByName(ifaceName)
|
|
||||||
})
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
dialer.Control = bindControl(iface.(*net.Interface).Index)
|
dialer.Control = bindControl(ifaceObj.Index, dialer.Control)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func bindIfaceToListenConfig(lc *net.ListenConfig, ifaceName string) error {
|
func bindIfaceToListenConfig(ifaceName string, lc *net.ListenConfig, _, address string) (string, error) {
|
||||||
iface, err, _ := ifaceSingle.Do(func() (interface{}, error) {
|
ifaceObj, err := iface.ResolveInterface(ifaceName)
|
||||||
return net.InterfaceByName(ifaceName)
|
|
||||||
})
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
lc.Control = bindControl(iface.(*net.Interface).Index)
|
lc.Control = bindControl(ifaceObj.Index, lc.Control)
|
||||||
return nil
|
return address, nil
|
||||||
}
|
}
|
||||||
|
@ -3,34 +3,42 @@ package dialer
|
|||||||
import (
|
import (
|
||||||
"net"
|
"net"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
)
|
)
|
||||||
|
|
||||||
type controlFn = func(network, address string, c syscall.RawConn) error
|
type controlFn = func(network, address string, c syscall.RawConn) error
|
||||||
|
|
||||||
func bindControl(ifaceName string) controlFn {
|
func bindControl(ifaceName string, chain controlFn) controlFn {
|
||||||
return func(network, address string, c syscall.RawConn) error {
|
return func(network, address string, c syscall.RawConn) (err error) {
|
||||||
|
defer func() {
|
||||||
|
if err == nil && chain != nil {
|
||||||
|
err = chain(network, address, c)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
ipStr, _, err := net.SplitHostPort(address)
|
ipStr, _, err := net.SplitHostPort(address)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
ip := net.ParseIP(ipStr)
|
ip := net.ParseIP(ipStr)
|
||||||
if ip != nil && !ip.IsGlobalUnicast() {
|
if ip != nil && !ip.IsGlobalUnicast() {
|
||||||
return nil
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return c.Control(func(fd uintptr) {
|
return c.Control(func(fd uintptr) {
|
||||||
syscall.BindToDevice(int(fd), ifaceName)
|
unix.BindToDevice(int(fd), ifaceName)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func bindIfaceToDialer(dialer *net.Dialer, ifaceName string) error {
|
func bindIfaceToDialer(ifaceName string, dialer *net.Dialer, _ string, _ net.IP) error {
|
||||||
dialer.Control = bindControl(ifaceName)
|
dialer.Control = bindControl(ifaceName, dialer.Control)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func bindIfaceToListenConfig(lc *net.ListenConfig, ifaceName string) error {
|
func bindIfaceToListenConfig(ifaceName string, lc *net.ListenConfig, _, address string) (string, error) {
|
||||||
lc.Control = bindControl(ifaceName)
|
lc.Control = bindControl(ifaceName, lc.Control)
|
||||||
|
|
||||||
return nil
|
return address, nil
|
||||||
}
|
}
|
||||||
|
@ -1,13 +1,93 @@
|
|||||||
|
//go:build !linux && !darwin
|
||||||
// +build !linux,!darwin
|
// +build !linux,!darwin
|
||||||
|
|
||||||
package dialer
|
package dialer
|
||||||
|
|
||||||
import "net"
|
import (
|
||||||
|
"net"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
func bindIfaceToDialer(dialer *net.Dialer, ifaceName string) error {
|
"github.com/Dreamacro/clash/component/iface"
|
||||||
return errPlatformNotSupport
|
)
|
||||||
|
|
||||||
|
func lookupLocalAddr(ifaceName string, network string, destination net.IP, port int) (net.Addr, error) {
|
||||||
|
ifaceObj, err := iface.ResolveInterface(ifaceName)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var addr *net.IPNet
|
||||||
|
switch network {
|
||||||
|
case "udp4", "tcp4":
|
||||||
|
addr, err = ifaceObj.PickIPv4Addr(destination)
|
||||||
|
case "tcp6", "udp6":
|
||||||
|
addr, err = ifaceObj.PickIPv6Addr(destination)
|
||||||
|
default:
|
||||||
|
if destination != nil {
|
||||||
|
if destination.To4() != nil {
|
||||||
|
addr, err = ifaceObj.PickIPv4Addr(destination)
|
||||||
|
} else {
|
||||||
|
addr, err = ifaceObj.PickIPv6Addr(destination)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
addr, err = ifaceObj.PickIPv4Addr(destination)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.HasPrefix(network, "tcp") {
|
||||||
|
return &net.TCPAddr{
|
||||||
|
IP: addr.IP,
|
||||||
|
Port: port,
|
||||||
|
}, nil
|
||||||
|
} else if strings.HasPrefix(network, "udp") {
|
||||||
|
return &net.UDPAddr{
|
||||||
|
IP: addr.IP,
|
||||||
|
Port: port,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, iface.ErrAddrNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
func bindIfaceToListenConfig(lc *net.ListenConfig, ifaceName string) error {
|
func bindIfaceToDialer(ifaceName string, dialer *net.Dialer, network string, destination net.IP) error {
|
||||||
return errPlatformNotSupport
|
if !destination.IsGlobalUnicast() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
local := int64(0)
|
||||||
|
if dialer.LocalAddr != nil {
|
||||||
|
_, port, err := net.SplitHostPort(dialer.LocalAddr.String())
|
||||||
|
if err == nil {
|
||||||
|
local, _ = strconv.ParseInt(port, 10, 16)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
addr, err := lookupLocalAddr(ifaceName, network, destination, int(local))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
dialer.LocalAddr = addr
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func bindIfaceToListenConfig(ifaceName string, _ *net.ListenConfig, network, address string) (string, error) {
|
||||||
|
_, port, err := net.SplitHostPort(address)
|
||||||
|
if err != nil {
|
||||||
|
port = "0"
|
||||||
|
}
|
||||||
|
|
||||||
|
local, _ := strconv.ParseInt(port, 10, 16)
|
||||||
|
|
||||||
|
addr, err := lookupLocalAddr(ifaceName, network, nil, int(local))
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return addr.String(), nil
|
||||||
}
|
}
|
||||||
|
@ -8,22 +8,7 @@ import (
|
|||||||
"github.com/Dreamacro/clash/component/resolver"
|
"github.com/Dreamacro/clash/component/resolver"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Dialer() (*net.Dialer, error) {
|
func DialContext(ctx context.Context, network, address string, options ...Option) (net.Conn, error) {
|
||||||
dialer := &net.Dialer{}
|
|
||||||
if DialerHook != nil {
|
|
||||||
if err := DialerHook(dialer); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return dialer, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func Dial(network, address string) (net.Conn, error) {
|
|
||||||
return DialContext(context.Background(), network, address)
|
|
||||||
}
|
|
||||||
|
|
||||||
func DialContext(ctx context.Context, network, address string) (net.Conn, error) {
|
|
||||||
switch network {
|
switch network {
|
||||||
case "tcp4", "tcp6", "udp4", "udp6":
|
case "tcp4", "tcp6", "udp4", "udp6":
|
||||||
host, port, err := net.SplitHostPort(address)
|
host, port, err := net.SplitHostPort(address)
|
||||||
@ -31,11 +16,6 @@ func DialContext(ctx context.Context, network, address string) (net.Conn, error)
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
dialer, err := Dialer()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var ip net.IP
|
var ip net.IP
|
||||||
switch network {
|
switch network {
|
||||||
case "tcp4", "udp4":
|
case "tcp4", "udp4":
|
||||||
@ -43,38 +23,76 @@ func DialContext(ctx context.Context, network, address string) (net.Conn, error)
|
|||||||
default:
|
default:
|
||||||
ip, err = resolver.ResolveIPv6(host)
|
ip, err = resolver.ResolveIPv6(host)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if DialHook != nil {
|
return dialContext(ctx, network, ip, port, options)
|
||||||
if err := DialHook(dialer, network, ip); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return dialer.DialContext(ctx, network, net.JoinHostPort(ip.String(), port))
|
|
||||||
case "tcp", "udp":
|
case "tcp", "udp":
|
||||||
return dualStackDialContext(ctx, network, address)
|
return dualStackDialContext(ctx, network, address, options)
|
||||||
default:
|
default:
|
||||||
return nil, errors.New("network invalid")
|
return nil, errors.New("network invalid")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func ListenPacket(network, address string) (net.PacketConn, error) {
|
func ListenPacket(ctx context.Context, network, address string, options ...Option) (net.PacketConn, error) {
|
||||||
cfg := &net.ListenConfig{}
|
cfg := &option{
|
||||||
if ListenPacketHook != nil {
|
interfaceName: DefaultInterface.Load(),
|
||||||
var err error
|
}
|
||||||
address, err = ListenPacketHook(cfg, address)
|
|
||||||
|
for _, o := range DefaultOptions {
|
||||||
|
o(cfg)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, o := range options {
|
||||||
|
o(cfg)
|
||||||
|
}
|
||||||
|
|
||||||
|
lc := &net.ListenConfig{}
|
||||||
|
if cfg.interfaceName != "" {
|
||||||
|
addr, err := bindIfaceToListenConfig(cfg.interfaceName, lc, network, address)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
address = addr
|
||||||
|
}
|
||||||
|
if cfg.addrReuse {
|
||||||
|
addrReuseToListenConfig(lc)
|
||||||
|
}
|
||||||
|
if cfg.routingMark != 0 {
|
||||||
|
bindMarkToListenConfig(cfg.routingMark, lc, network, address)
|
||||||
}
|
}
|
||||||
|
|
||||||
return cfg.ListenPacket(context.Background(), network, address)
|
return lc.ListenPacket(ctx, network, address)
|
||||||
}
|
}
|
||||||
|
|
||||||
func dualStackDialContext(ctx context.Context, network, address string) (net.Conn, error) {
|
func dialContext(ctx context.Context, network string, destination net.IP, port string, options []Option) (net.Conn, error) {
|
||||||
|
opt := &option{
|
||||||
|
interfaceName: DefaultInterface.Load(),
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, o := range DefaultOptions {
|
||||||
|
o(opt)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, o := range options {
|
||||||
|
o(opt)
|
||||||
|
}
|
||||||
|
|
||||||
|
dialer := &net.Dialer{}
|
||||||
|
if opt.interfaceName != "" {
|
||||||
|
if err := bindIfaceToDialer(opt.interfaceName, dialer, network, destination); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if opt.routingMark != 0 {
|
||||||
|
bindMarkToDialer(opt.routingMark, dialer, network, destination)
|
||||||
|
}
|
||||||
|
|
||||||
|
return dialer.DialContext(ctx, network, net.JoinHostPort(destination.String(), port))
|
||||||
|
}
|
||||||
|
|
||||||
|
func dualStackDialContext(ctx context.Context, network, address string, options []Option) (net.Conn, error) {
|
||||||
host, port, err := net.SplitHostPort(address)
|
host, port, err := net.SplitHostPort(address)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -105,12 +123,6 @@ func dualStackDialContext(ctx context.Context, network, address string) (net.Con
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
dialer, err := Dialer()
|
|
||||||
if err != nil {
|
|
||||||
result.error = err
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var ip net.IP
|
var ip net.IP
|
||||||
if ipv6 {
|
if ipv6 {
|
||||||
ip, result.error = resolver.ResolveIPv6(host)
|
ip, result.error = resolver.ResolveIPv6(host)
|
||||||
@ -122,12 +134,7 @@ func dualStackDialContext(ctx context.Context, network, address string) (net.Con
|
|||||||
}
|
}
|
||||||
result.resolved = true
|
result.resolved = true
|
||||||
|
|
||||||
if DialHook != nil {
|
result.Conn, result.error = dialContext(ctx, network, ip, port, options)
|
||||||
if result.error = DialHook(dialer, network, ip); result.error != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
result.Conn, result.error = dialer.DialContext(ctx, network, net.JoinHostPort(ip.String(), port))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
go startRacer(ctx, network+"4", host, false)
|
go startRacer(ctx, network+"4", host, false)
|
||||||
|
@ -1,43 +0,0 @@
|
|||||||
package dialer
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"net"
|
|
||||||
)
|
|
||||||
|
|
||||||
type DialerHookFunc = func(dialer *net.Dialer) error
|
|
||||||
type DialHookFunc = func(dialer *net.Dialer, network string, ip net.IP) error
|
|
||||||
type ListenPacketHookFunc = func(lc *net.ListenConfig, address string) (string, error)
|
|
||||||
|
|
||||||
var (
|
|
||||||
DialerHook DialerHookFunc
|
|
||||||
DialHook DialHookFunc
|
|
||||||
ListenPacketHook ListenPacketHookFunc
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
ErrAddrNotFound = errors.New("addr not found")
|
|
||||||
ErrNetworkNotSupport = errors.New("network not support")
|
|
||||||
)
|
|
||||||
|
|
||||||
func ListenPacketWithInterface(name string) ListenPacketHookFunc {
|
|
||||||
return func(lc *net.ListenConfig, address string) (string, error) {
|
|
||||||
err := bindIfaceToListenConfig(lc, name)
|
|
||||||
if err == errPlatformNotSupport {
|
|
||||||
address, err = fallbackBindToListenConfig(name)
|
|
||||||
}
|
|
||||||
|
|
||||||
return address, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func DialerWithInterface(name string) DialHookFunc {
|
|
||||||
return func(dialer *net.Dialer, network string, ip net.IP) error {
|
|
||||||
err := bindIfaceToDialer(dialer, name)
|
|
||||||
if err == errPlatformNotSupport {
|
|
||||||
err = fallbackBindToDialer(dialer, network, ip, name)
|
|
||||||
}
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
44
component/dialer/mark_linux.go
Normal file
44
component/dialer/mark_linux.go
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
//go:build linux
|
||||||
|
// +build linux
|
||||||
|
|
||||||
|
package dialer
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"syscall"
|
||||||
|
)
|
||||||
|
|
||||||
|
func bindMarkToDialer(mark int, dialer *net.Dialer, _ string, _ net.IP) {
|
||||||
|
dialer.Control = bindMarkToControl(mark, dialer.Control)
|
||||||
|
}
|
||||||
|
|
||||||
|
func bindMarkToListenConfig(mark int, lc *net.ListenConfig, _, address string) {
|
||||||
|
lc.Control = bindMarkToControl(mark, lc.Control)
|
||||||
|
}
|
||||||
|
|
||||||
|
func bindMarkToControl(mark int, chain controlFn) controlFn {
|
||||||
|
return func(network, address string, c syscall.RawConn) (err error) {
|
||||||
|
defer func() {
|
||||||
|
if err == nil && chain != nil {
|
||||||
|
err = chain(network, address, c)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
ipStr, _, err := net.SplitHostPort(address)
|
||||||
|
if err == nil {
|
||||||
|
ip := net.ParseIP(ipStr)
|
||||||
|
if ip != nil && !ip.IsGlobalUnicast() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.Control(func(fd uintptr) {
|
||||||
|
switch network {
|
||||||
|
case "tcp4", "udp4":
|
||||||
|
syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, syscall.SO_MARK, mark)
|
||||||
|
case "tcp6", "udp6":
|
||||||
|
syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, syscall.SO_MARK, mark)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
27
component/dialer/mark_nonlinux.go
Normal file
27
component/dialer/mark_nonlinux.go
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
//go:build !linux
|
||||||
|
// +build !linux
|
||||||
|
|
||||||
|
package dialer
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/Dreamacro/clash/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
var printMarkWarnOnce sync.Once
|
||||||
|
|
||||||
|
func printMarkWarn() {
|
||||||
|
printMarkWarnOnce.Do(func() {
|
||||||
|
log.Warnln("Routing mark on socket is not supported on current platform")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func bindMarkToDialer(mark int, dialer *net.Dialer, _ string, _ net.IP) {
|
||||||
|
printMarkWarn()
|
||||||
|
}
|
||||||
|
|
||||||
|
func bindMarkToListenConfig(mark int, lc *net.ListenConfig, _, address string) {
|
||||||
|
printMarkWarn()
|
||||||
|
}
|
34
component/dialer/options.go
Normal file
34
component/dialer/options.go
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
package dialer
|
||||||
|
|
||||||
|
import "go.uber.org/atomic"
|
||||||
|
|
||||||
|
var (
|
||||||
|
DefaultOptions []Option
|
||||||
|
DefaultInterface = atomic.NewString("")
|
||||||
|
)
|
||||||
|
|
||||||
|
type option struct {
|
||||||
|
interfaceName string
|
||||||
|
addrReuse bool
|
||||||
|
routingMark int
|
||||||
|
}
|
||||||
|
|
||||||
|
type Option func(opt *option)
|
||||||
|
|
||||||
|
func WithInterface(name string) Option {
|
||||||
|
return func(opt *option) {
|
||||||
|
opt.interfaceName = name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithAddrReuse(reuse bool) Option {
|
||||||
|
return func(opt *option) {
|
||||||
|
opt.addrReuse = reuse
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithRoutingMark(mark int) Option {
|
||||||
|
return func(opt *option) {
|
||||||
|
opt.routingMark = mark
|
||||||
|
}
|
||||||
|
}
|
10
component/dialer/reuse_others.go
Normal file
10
component/dialer/reuse_others.go
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
//go:build !darwin && !dragonfly && !freebsd && !linux && !netbsd && !openbsd && !solaris && !windows
|
||||||
|
// +build !darwin,!dragonfly,!freebsd,!linux,!netbsd,!openbsd,!solaris,!windows
|
||||||
|
|
||||||
|
package dialer
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
)
|
||||||
|
|
||||||
|
func addrReuseToListenConfig(*net.ListenConfig) {}
|
28
component/dialer/reuse_unix.go
Normal file
28
component/dialer/reuse_unix.go
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
//go:build darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris
|
||||||
|
// +build darwin dragonfly freebsd linux netbsd openbsd solaris
|
||||||
|
|
||||||
|
package dialer
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
func addrReuseToListenConfig(lc *net.ListenConfig) {
|
||||||
|
chain := lc.Control
|
||||||
|
|
||||||
|
lc.Control = func(network, address string, c syscall.RawConn) (err error) {
|
||||||
|
defer func() {
|
||||||
|
if err == nil && chain != nil {
|
||||||
|
err = chain(network, address, c)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
return c.Control(func(fd uintptr) {
|
||||||
|
unix.SetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_REUSEADDR, 1)
|
||||||
|
unix.SetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_REUSEPORT, 1)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
24
component/dialer/reuse_windows.go
Normal file
24
component/dialer/reuse_windows.go
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
package dialer
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
|
"golang.org/x/sys/windows"
|
||||||
|
)
|
||||||
|
|
||||||
|
func addrReuseToListenConfig(lc *net.ListenConfig) {
|
||||||
|
chain := lc.Control
|
||||||
|
|
||||||
|
lc.Control = func(network, address string, c syscall.RawConn) (err error) {
|
||||||
|
defer func() {
|
||||||
|
if err == nil && chain != nil {
|
||||||
|
err = chain(network, address, c)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
return c.Control(func(fd uintptr) {
|
||||||
|
windows.SetsockoptInt(windows.Handle(fd), windows.SOL_SOCKET, windows.SO_REUSEADDR, 1)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
49
component/fakeip/cachefile.go
Normal file
49
component/fakeip/cachefile.go
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
package fakeip
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
|
||||||
|
"github.com/Dreamacro/clash/component/profile/cachefile"
|
||||||
|
)
|
||||||
|
|
||||||
|
type cachefileStore struct {
|
||||||
|
cache *cachefile.CacheFile
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetByHost implements store.GetByHost
|
||||||
|
func (c *cachefileStore) GetByHost(host string) (net.IP, bool) {
|
||||||
|
elm := c.cache.GetFakeip([]byte(host))
|
||||||
|
if elm == nil {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
return net.IP(elm), true
|
||||||
|
}
|
||||||
|
|
||||||
|
// PutByHost implements store.PutByHost
|
||||||
|
func (c *cachefileStore) PutByHost(host string, ip net.IP) {
|
||||||
|
c.cache.PutFakeip([]byte(host), ip)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetByIP implements store.GetByIP
|
||||||
|
func (c *cachefileStore) GetByIP(ip net.IP) (string, bool) {
|
||||||
|
elm := c.cache.GetFakeip(ip.To4())
|
||||||
|
if elm == nil {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
return string(elm), true
|
||||||
|
}
|
||||||
|
|
||||||
|
// PutByIP implements store.PutByIP
|
||||||
|
func (c *cachefileStore) PutByIP(ip net.IP, host string) {
|
||||||
|
c.cache.PutFakeip(ip.To4(), []byte(host))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exist implements store.Exist
|
||||||
|
func (c *cachefileStore) Exist(ip net.IP) bool {
|
||||||
|
_, exist := c.GetByIP(ip)
|
||||||
|
return exist
|
||||||
|
}
|
||||||
|
|
||||||
|
// CloneTo implements store.CloneTo
|
||||||
|
// already persistence
|
||||||
|
func (c *cachefileStore) CloneTo(store store) {}
|
60
component/fakeip/memory.go
Normal file
60
component/fakeip/memory.go
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
package fakeip
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
|
||||||
|
"github.com/Dreamacro/clash/common/cache"
|
||||||
|
)
|
||||||
|
|
||||||
|
type memoryStore struct {
|
||||||
|
cache *cache.LruCache
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetByHost implements store.GetByHost
|
||||||
|
func (m *memoryStore) GetByHost(host string) (net.IP, bool) {
|
||||||
|
if elm, exist := m.cache.Get(host); exist {
|
||||||
|
ip := elm.(net.IP)
|
||||||
|
|
||||||
|
// ensure ip --> host on head of linked list
|
||||||
|
m.cache.Get(ipToUint(ip.To4()))
|
||||||
|
return ip, true
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
|
||||||
|
// PutByHost implements store.PutByHost
|
||||||
|
func (m *memoryStore) PutByHost(host string, ip net.IP) {
|
||||||
|
m.cache.Set(host, ip)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetByIP implements store.GetByIP
|
||||||
|
func (m *memoryStore) GetByIP(ip net.IP) (string, bool) {
|
||||||
|
if elm, exist := m.cache.Get(ipToUint(ip.To4())); exist {
|
||||||
|
host := elm.(string)
|
||||||
|
|
||||||
|
// ensure host --> ip on head of linked list
|
||||||
|
m.cache.Get(host)
|
||||||
|
return host, true
|
||||||
|
}
|
||||||
|
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
|
||||||
|
// PutByIP implements store.PutByIP
|
||||||
|
func (m *memoryStore) PutByIP(ip net.IP, host string) {
|
||||||
|
m.cache.Set(ipToUint(ip.To4()), host)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exist implements store.Exist
|
||||||
|
func (m *memoryStore) Exist(ip net.IP) bool {
|
||||||
|
return m.cache.Exist(ipToUint(ip.To4()))
|
||||||
|
}
|
||||||
|
|
||||||
|
// CloneTo implements store.CloneTo
|
||||||
|
// only for memoryStore to memoryStore
|
||||||
|
func (m *memoryStore) CloneTo(store store) {
|
||||||
|
if ms, ok := store.(*memoryStore); ok {
|
||||||
|
m.cache.CloneTo(ms.cache)
|
||||||
|
}
|
||||||
|
}
|
@ -6,9 +6,19 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/common/cache"
|
"github.com/Dreamacro/clash/common/cache"
|
||||||
|
"github.com/Dreamacro/clash/component/profile/cachefile"
|
||||||
"github.com/Dreamacro/clash/component/trie"
|
"github.com/Dreamacro/clash/component/trie"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type store interface {
|
||||||
|
GetByHost(host string) (net.IP, bool)
|
||||||
|
PutByHost(host string, ip net.IP)
|
||||||
|
GetByIP(ip net.IP) (string, bool)
|
||||||
|
PutByIP(ip net.IP, host string)
|
||||||
|
Exist(ip net.IP) bool
|
||||||
|
CloneTo(store)
|
||||||
|
}
|
||||||
|
|
||||||
// Pool is a implementation about fake ip generator without storage
|
// Pool is a implementation about fake ip generator without storage
|
||||||
type Pool struct {
|
type Pool struct {
|
||||||
max uint32
|
max uint32
|
||||||
@ -18,25 +28,19 @@ type Pool struct {
|
|||||||
mux sync.Mutex
|
mux sync.Mutex
|
||||||
host *trie.DomainTrie
|
host *trie.DomainTrie
|
||||||
ipnet *net.IPNet
|
ipnet *net.IPNet
|
||||||
cache *cache.LruCache
|
store store
|
||||||
}
|
}
|
||||||
|
|
||||||
// Lookup return a fake ip with host
|
// Lookup return a fake ip with host
|
||||||
func (p *Pool) Lookup(host string) net.IP {
|
func (p *Pool) Lookup(host string) net.IP {
|
||||||
p.mux.Lock()
|
p.mux.Lock()
|
||||||
defer p.mux.Unlock()
|
defer p.mux.Unlock()
|
||||||
if elm, exist := p.cache.Get(host); exist {
|
if ip, exist := p.store.GetByHost(host); exist {
|
||||||
ip := elm.(net.IP)
|
|
||||||
|
|
||||||
// ensure ip --> host on head of linked list
|
|
||||||
n := ipToUint(ip.To4())
|
|
||||||
offset := n - p.min + 1
|
|
||||||
p.cache.Get(offset)
|
|
||||||
return ip
|
return ip
|
||||||
}
|
}
|
||||||
|
|
||||||
ip := p.get(host)
|
ip := p.get(host)
|
||||||
p.cache.Set(host, ip)
|
p.store.PutByHost(host, ip)
|
||||||
return ip
|
return ip
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -49,22 +53,11 @@ func (p *Pool) LookBack(ip net.IP) (string, bool) {
|
|||||||
return "", false
|
return "", false
|
||||||
}
|
}
|
||||||
|
|
||||||
n := ipToUint(ip.To4())
|
return p.store.GetByIP(ip)
|
||||||
offset := n - p.min + 1
|
|
||||||
|
|
||||||
if elm, exist := p.cache.Get(offset); exist {
|
|
||||||
host := elm.(string)
|
|
||||||
|
|
||||||
// ensure host --> ip on head of linked list
|
|
||||||
p.cache.Get(host)
|
|
||||||
return host, true
|
|
||||||
}
|
|
||||||
|
|
||||||
return "", false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// LookupHost return if domain in host
|
// ShouldSkipped return if domain should be skipped
|
||||||
func (p *Pool) LookupHost(domain string) bool {
|
func (p *Pool) ShouldSkipped(domain string) bool {
|
||||||
if p.host == nil {
|
if p.host == nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -80,9 +73,7 @@ func (p *Pool) Exist(ip net.IP) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
n := ipToUint(ip.To4())
|
return p.store.Exist(ip)
|
||||||
offset := n - p.min + 1
|
|
||||||
return p.cache.Exist(offset)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Gateway return gateway ip
|
// Gateway return gateway ip
|
||||||
@ -95,9 +86,9 @@ func (p *Pool) IPNet() *net.IPNet {
|
|||||||
return p.ipnet
|
return p.ipnet
|
||||||
}
|
}
|
||||||
|
|
||||||
// PatchFrom clone cache from old pool
|
// CloneFrom clone cache from old pool
|
||||||
func (p *Pool) PatchFrom(o *Pool) {
|
func (p *Pool) CloneFrom(o *Pool) {
|
||||||
o.cache.CloneTo(p.cache)
|
o.store.CloneTo(p.store)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Pool) get(host string) net.IP {
|
func (p *Pool) get(host string) net.IP {
|
||||||
@ -109,12 +100,13 @@ func (p *Pool) get(host string) net.IP {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
if !p.cache.Exist(p.offset) {
|
ip := uintToIP(p.min + p.offset - 1)
|
||||||
|
if !p.store.Exist(ip) {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ip := uintToIP(p.min + p.offset - 1)
|
ip := uintToIP(p.min + p.offset - 1)
|
||||||
p.cache.Set(p.offset, host)
|
p.store.PutByIP(ip, host)
|
||||||
return ip
|
return ip
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -130,11 +122,24 @@ func uintToIP(v uint32) net.IP {
|
|||||||
return net.IP{byte(v >> 24), byte(v >> 16), byte(v >> 8), byte(v)}
|
return net.IP{byte(v >> 24), byte(v >> 16), byte(v >> 8), byte(v)}
|
||||||
}
|
}
|
||||||
|
|
||||||
// New return Pool instance
|
type Options struct {
|
||||||
func New(ipnet *net.IPNet, size int, host *trie.DomainTrie) (*Pool, error) {
|
IPNet *net.IPNet
|
||||||
min := ipToUint(ipnet.IP) + 2
|
Host *trie.DomainTrie
|
||||||
|
|
||||||
ones, bits := ipnet.Mask.Size()
|
// Size sets the maximum number of entries in memory
|
||||||
|
// and does not work if Persistence is true
|
||||||
|
Size int
|
||||||
|
|
||||||
|
// Persistence will save the data to disk.
|
||||||
|
// Size will not work and record will be fully stored.
|
||||||
|
Persistence bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// New return Pool instance
|
||||||
|
func New(options Options) (*Pool, error) {
|
||||||
|
min := ipToUint(options.IPNet.IP) + 2
|
||||||
|
|
||||||
|
ones, bits := options.IPNet.Mask.Size()
|
||||||
total := 1<<uint(bits-ones) - 2
|
total := 1<<uint(bits-ones) - 2
|
||||||
|
|
||||||
if total <= 0 {
|
if total <= 0 {
|
||||||
@ -142,12 +147,22 @@ func New(ipnet *net.IPNet, size int, host *trie.DomainTrie) (*Pool, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
max := min + uint32(total) - 1
|
max := min + uint32(total) - 1
|
||||||
return &Pool{
|
pool := &Pool{
|
||||||
min: min,
|
min: min,
|
||||||
max: max,
|
max: max,
|
||||||
gateway: min - 1,
|
gateway: min - 1,
|
||||||
host: host,
|
host: options.Host,
|
||||||
ipnet: ipnet,
|
ipnet: options.IPNet,
|
||||||
cache: cache.NewLRUCache(cache.WithSize(size * 2)),
|
}
|
||||||
}, nil
|
if options.Persistence {
|
||||||
|
pool.store = &cachefileStore{
|
||||||
|
cache: cachefile.Cache(),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
pool.store = &memoryStore{
|
||||||
|
cache: cache.NewLRUCache(cache.WithSize(options.Size * 2)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return pool, nil
|
||||||
}
|
}
|
||||||
|
@ -2,38 +2,118 @@ package fakeip
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"net"
|
"net"
|
||||||
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/Dreamacro/clash/component/profile/cachefile"
|
||||||
|
"github.com/Dreamacro/clash/component/trie"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"go.etcd.io/bbolt"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func createPools(options Options) ([]*Pool, string, error) {
|
||||||
|
pool, err := New(options)
|
||||||
|
if err != nil {
|
||||||
|
return nil, "", err
|
||||||
|
}
|
||||||
|
filePool, tempfile, err := createCachefileStore(options)
|
||||||
|
if err != nil {
|
||||||
|
return nil, "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return []*Pool{pool, filePool}, tempfile, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func createCachefileStore(options Options) (*Pool, string, error) {
|
||||||
|
pool, err := New(options)
|
||||||
|
if err != nil {
|
||||||
|
return nil, "", err
|
||||||
|
}
|
||||||
|
f, err := os.CreateTemp("", "clash")
|
||||||
|
if err != nil {
|
||||||
|
return nil, "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
db, err := bbolt.Open(f.Name(), 0o666, &bbolt.Options{Timeout: time.Second})
|
||||||
|
if err != nil {
|
||||||
|
return nil, "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
pool.store = &cachefileStore{
|
||||||
|
cache: &cachefile.CacheFile{DB: db},
|
||||||
|
}
|
||||||
|
return pool, f.Name(), nil
|
||||||
|
}
|
||||||
|
|
||||||
func TestPool_Basic(t *testing.T) {
|
func TestPool_Basic(t *testing.T) {
|
||||||
_, ipnet, _ := net.ParseCIDR("192.168.0.1/29")
|
_, ipnet, _ := net.ParseCIDR("192.168.0.1/29")
|
||||||
pool, _ := New(ipnet, 10, nil)
|
pools, tempfile, err := createPools(Options{
|
||||||
|
IPNet: ipnet,
|
||||||
|
Size: 10,
|
||||||
|
})
|
||||||
|
assert.Nil(t, err)
|
||||||
|
defer os.Remove(tempfile)
|
||||||
|
|
||||||
first := pool.Lookup("foo.com")
|
for _, pool := range pools {
|
||||||
last := pool.Lookup("bar.com")
|
first := pool.Lookup("foo.com")
|
||||||
bar, exist := pool.LookBack(last)
|
last := pool.Lookup("bar.com")
|
||||||
|
bar, exist := pool.LookBack(last)
|
||||||
|
|
||||||
assert.True(t, first.Equal(net.IP{192, 168, 0, 2}))
|
assert.True(t, first.Equal(net.IP{192, 168, 0, 2}))
|
||||||
assert.True(t, last.Equal(net.IP{192, 168, 0, 3}))
|
assert.Equal(t, pool.Lookup("foo.com"), net.IP{192, 168, 0, 2})
|
||||||
assert.True(t, exist)
|
assert.True(t, last.Equal(net.IP{192, 168, 0, 3}))
|
||||||
assert.Equal(t, bar, "bar.com")
|
assert.True(t, exist)
|
||||||
|
assert.Equal(t, bar, "bar.com")
|
||||||
|
assert.Equal(t, pool.Gateway(), net.IP{192, 168, 0, 1})
|
||||||
|
assert.Equal(t, pool.IPNet().String(), ipnet.String())
|
||||||
|
assert.True(t, pool.Exist(net.IP{192, 168, 0, 3}))
|
||||||
|
assert.False(t, pool.Exist(net.IP{192, 168, 0, 4}))
|
||||||
|
assert.False(t, pool.Exist(net.ParseIP("::1")))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPool_Cycle(t *testing.T) {
|
func TestPool_CycleUsed(t *testing.T) {
|
||||||
_, ipnet, _ := net.ParseCIDR("192.168.0.1/30")
|
_, ipnet, _ := net.ParseCIDR("192.168.0.1/30")
|
||||||
pool, _ := New(ipnet, 10, nil)
|
pools, tempfile, err := createPools(Options{
|
||||||
|
IPNet: ipnet,
|
||||||
|
Size: 10,
|
||||||
|
})
|
||||||
|
assert.Nil(t, err)
|
||||||
|
defer os.Remove(tempfile)
|
||||||
|
|
||||||
first := pool.Lookup("foo.com")
|
for _, pool := range pools {
|
||||||
same := pool.Lookup("baz.com")
|
first := pool.Lookup("foo.com")
|
||||||
|
same := pool.Lookup("baz.com")
|
||||||
|
assert.True(t, first.Equal(same))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
assert.True(t, first.Equal(same))
|
func TestPool_Skip(t *testing.T) {
|
||||||
|
_, ipnet, _ := net.ParseCIDR("192.168.0.1/30")
|
||||||
|
tree := trie.New()
|
||||||
|
tree.Insert("example.com", tree)
|
||||||
|
pools, tempfile, err := createPools(Options{
|
||||||
|
IPNet: ipnet,
|
||||||
|
Size: 10,
|
||||||
|
Host: tree,
|
||||||
|
})
|
||||||
|
assert.Nil(t, err)
|
||||||
|
defer os.Remove(tempfile)
|
||||||
|
|
||||||
|
for _, pool := range pools {
|
||||||
|
assert.True(t, pool.ShouldSkipped("example.com"))
|
||||||
|
assert.False(t, pool.ShouldSkipped("foo.com"))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPool_MaxCacheSize(t *testing.T) {
|
func TestPool_MaxCacheSize(t *testing.T) {
|
||||||
_, ipnet, _ := net.ParseCIDR("192.168.0.1/24")
|
_, ipnet, _ := net.ParseCIDR("192.168.0.1/24")
|
||||||
pool, _ := New(ipnet, 2, nil)
|
pool, _ := New(Options{
|
||||||
|
IPNet: ipnet,
|
||||||
|
Size: 2,
|
||||||
|
})
|
||||||
|
|
||||||
first := pool.Lookup("foo.com")
|
first := pool.Lookup("foo.com")
|
||||||
pool.Lookup("bar.com")
|
pool.Lookup("bar.com")
|
||||||
@ -45,7 +125,10 @@ func TestPool_MaxCacheSize(t *testing.T) {
|
|||||||
|
|
||||||
func TestPool_DoubleMapping(t *testing.T) {
|
func TestPool_DoubleMapping(t *testing.T) {
|
||||||
_, ipnet, _ := net.ParseCIDR("192.168.0.1/24")
|
_, ipnet, _ := net.ParseCIDR("192.168.0.1/24")
|
||||||
pool, _ := New(ipnet, 2, nil)
|
pool, _ := New(Options{
|
||||||
|
IPNet: ipnet,
|
||||||
|
Size: 2,
|
||||||
|
})
|
||||||
|
|
||||||
// fill cache
|
// fill cache
|
||||||
fooIP := pool.Lookup("foo.com")
|
fooIP := pool.Lookup("foo.com")
|
||||||
@ -70,9 +153,35 @@ func TestPool_DoubleMapping(t *testing.T) {
|
|||||||
assert.False(t, bazIP.Equal(newBazIP))
|
assert.False(t, bazIP.Equal(newBazIP))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestPool_Clone(t *testing.T) {
|
||||||
|
_, ipnet, _ := net.ParseCIDR("192.168.0.1/24")
|
||||||
|
pool, _ := New(Options{
|
||||||
|
IPNet: ipnet,
|
||||||
|
Size: 2,
|
||||||
|
})
|
||||||
|
|
||||||
|
first := pool.Lookup("foo.com")
|
||||||
|
last := pool.Lookup("bar.com")
|
||||||
|
assert.True(t, first.Equal(net.IP{192, 168, 0, 2}))
|
||||||
|
assert.True(t, last.Equal(net.IP{192, 168, 0, 3}))
|
||||||
|
|
||||||
|
newPool, _ := New(Options{
|
||||||
|
IPNet: ipnet,
|
||||||
|
Size: 2,
|
||||||
|
})
|
||||||
|
newPool.CloneFrom(pool)
|
||||||
|
_, firstExist := newPool.LookBack(first)
|
||||||
|
_, lastExist := newPool.LookBack(last)
|
||||||
|
assert.True(t, firstExist)
|
||||||
|
assert.True(t, lastExist)
|
||||||
|
}
|
||||||
|
|
||||||
func TestPool_Error(t *testing.T) {
|
func TestPool_Error(t *testing.T) {
|
||||||
_, ipnet, _ := net.ParseCIDR("192.168.0.1/31")
|
_, ipnet, _ := net.ParseCIDR("192.168.0.1/31")
|
||||||
_, err := New(ipnet, 10, nil)
|
_, err := New(Options{
|
||||||
|
IPNet: ipnet,
|
||||||
|
Size: 10,
|
||||||
|
})
|
||||||
|
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
}
|
}
|
||||||
|
115
component/iface/iface.go
Normal file
115
component/iface/iface.go
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
package iface
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"net"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/Dreamacro/clash/common/singledo"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Interface struct {
|
||||||
|
Index int
|
||||||
|
Name string
|
||||||
|
Addrs []*net.IPNet
|
||||||
|
HardwareAddr net.HardwareAddr
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrIfaceNotFound = errors.New("interface not found")
|
||||||
|
ErrAddrNotFound = errors.New("addr not found")
|
||||||
|
)
|
||||||
|
|
||||||
|
var interfaces = singledo.NewSingle(time.Second * 20)
|
||||||
|
|
||||||
|
func ResolveInterface(name string) (*Interface, error) {
|
||||||
|
value, err, _ := interfaces.Do(func() (interface{}, error) {
|
||||||
|
ifaces, err := net.Interfaces()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
r := map[string]*Interface{}
|
||||||
|
|
||||||
|
for _, iface := range ifaces {
|
||||||
|
addrs, err := iface.Addrs()
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
ipNets := make([]*net.IPNet, 0, len(addrs))
|
||||||
|
for _, addr := range addrs {
|
||||||
|
ipNet := addr.(*net.IPNet)
|
||||||
|
if v4 := ipNet.IP.To4(); v4 != nil {
|
||||||
|
ipNet.IP = v4
|
||||||
|
}
|
||||||
|
|
||||||
|
ipNets = append(ipNets, ipNet)
|
||||||
|
}
|
||||||
|
|
||||||
|
r[iface.Name] = &Interface{
|
||||||
|
Index: iface.Index,
|
||||||
|
Name: iface.Name,
|
||||||
|
Addrs: ipNets,
|
||||||
|
HardwareAddr: iface.HardwareAddr,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return r, nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
ifaces := value.(map[string]*Interface)
|
||||||
|
iface, ok := ifaces[name]
|
||||||
|
if !ok {
|
||||||
|
return nil, ErrIfaceNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
return iface, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func FlushCache() {
|
||||||
|
interfaces.Reset()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (iface *Interface) PickIPv4Addr(destination net.IP) (*net.IPNet, error) {
|
||||||
|
return iface.pickIPAddr(destination, func(addr *net.IPNet) bool {
|
||||||
|
return addr.IP.To4() != nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (iface *Interface) PickIPv6Addr(destination net.IP) (*net.IPNet, error) {
|
||||||
|
return iface.pickIPAddr(destination, func(addr *net.IPNet) bool {
|
||||||
|
return addr.IP.To4() == nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (iface *Interface) pickIPAddr(destination net.IP, accept func(addr *net.IPNet) bool) (*net.IPNet, error) {
|
||||||
|
var fallback *net.IPNet
|
||||||
|
|
||||||
|
for _, addr := range iface.Addrs {
|
||||||
|
if !accept(addr) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if fallback == nil && !addr.IP.IsLinkLocalUnicast() {
|
||||||
|
fallback = addr
|
||||||
|
|
||||||
|
if destination == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if destination != nil && addr.Contains(destination) {
|
||||||
|
return addr, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if fallback == nil {
|
||||||
|
return nil, ErrAddrNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
return fallback, nil
|
||||||
|
}
|
@ -9,8 +9,10 @@ import (
|
|||||||
"github.com/oschwald/geoip2-golang"
|
"github.com/oschwald/geoip2-golang"
|
||||||
)
|
)
|
||||||
|
|
||||||
var mmdb *geoip2.Reader
|
var (
|
||||||
var once sync.Once
|
mmdb *geoip2.Reader
|
||||||
|
once sync.Once
|
||||||
|
)
|
||||||
|
|
||||||
func LoadFromBytes(buffer []byte) {
|
func LoadFromBytes(buffer []byte) {
|
||||||
once.Do(func() {
|
once.Do(func() {
|
||||||
|
@ -1,12 +1,13 @@
|
|||||||
package process
|
package process
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"net"
|
"net"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"syscall"
|
"syscall"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -94,12 +95,8 @@ func getExecPathFromPID(pid uint32) (string, error) {
|
|||||||
if errno != 0 {
|
if errno != 0 {
|
||||||
return "", errno
|
return "", errno
|
||||||
}
|
}
|
||||||
firstZero := bytes.IndexByte(buf, 0)
|
|
||||||
if firstZero <= 0 {
|
|
||||||
return "", nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return filepath.Base(string(buf[:firstZero])), nil
|
return filepath.Base(unix.ByteSliceToString(buf)), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func readNativeUint32(b []byte) uint32 {
|
func readNativeUint32(b []byte) uint32 {
|
||||||
|
@ -5,8 +5,8 @@ import (
|
|||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
|
||||||
"net"
|
"net"
|
||||||
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"syscall"
|
"syscall"
|
||||||
@ -16,17 +16,19 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// from https://github.com/vishvananda/netlink/blob/bca67dfc8220b44ef582c9da4e9172bf1c9ec973/nl/nl_linux.go#L52-L62
|
// from https://github.com/vishvananda/netlink/blob/bca67dfc8220b44ef582c9da4e9172bf1c9ec973/nl/nl_linux.go#L52-L62
|
||||||
func init() {
|
var nativeEndian = func() binary.ByteOrder {
|
||||||
var x uint32 = 0x01020304
|
var x uint32 = 0x01020304
|
||||||
if *(*byte)(unsafe.Pointer(&x)) == 0x01 {
|
if *(*byte)(unsafe.Pointer(&x)) == 0x01 {
|
||||||
nativeEndian = binary.BigEndian
|
return binary.BigEndian
|
||||||
} else {
|
|
||||||
nativeEndian = binary.LittleEndian
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
type SocketResolver func(network string, ip net.IP, srcPort int) (inode, uid int, err error)
|
return binary.LittleEndian
|
||||||
type ProcessNameResolver func(inode, uid int) (name string, err error)
|
}()
|
||||||
|
|
||||||
|
type (
|
||||||
|
SocketResolver func(network string, ip net.IP, srcPort int) (inode, uid int, err error)
|
||||||
|
ProcessNameResolver func(inode, uid int) (name string, err error)
|
||||||
|
)
|
||||||
|
|
||||||
// export for android
|
// export for android
|
||||||
var (
|
var (
|
||||||
@ -40,8 +42,6 @@ const (
|
|||||||
pathProc = "/proc"
|
pathProc = "/proc"
|
||||||
)
|
)
|
||||||
|
|
||||||
var nativeEndian binary.ByteOrder = binary.LittleEndian
|
|
||||||
|
|
||||||
func findProcessName(network string, ip net.IP, srcPort int) (string, error) {
|
func findProcessName(network string, ip net.IP, srcPort int) (string, error) {
|
||||||
inode, uid, err := DefaultSocketResolver(network, ip, srcPort)
|
inode, uid, err := DefaultSocketResolver(network, ip, srcPort)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -169,7 +169,7 @@ func unpackSocketDiagResponse(msg *syscall.NetlinkMessage) (inode, uid uint32) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func resolveProcessNameByProcSearch(inode, uid int) (string, error) {
|
func resolveProcessNameByProcSearch(inode, uid int) (string, error) {
|
||||||
files, err := ioutil.ReadDir(pathProc)
|
files, err := os.ReadDir(pathProc)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
@ -182,14 +182,18 @@ func resolveProcessNameByProcSearch(inode, uid int) (string, error) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if f.Sys().(*syscall.Stat_t).Uid != uint32(uid) {
|
info, err := f.Info()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if info.Sys().(*syscall.Stat_t).Uid != uint32(uid) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
processPath := path.Join(pathProc, f.Name())
|
processPath := path.Join(pathProc, f.Name())
|
||||||
fdPath := path.Join(processPath, "fd")
|
fdPath := path.Join(processPath, "fd")
|
||||||
|
|
||||||
fds, err := ioutil.ReadDir(fdPath)
|
fds, err := os.ReadDir(fdPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@ -201,7 +205,7 @@ func resolveProcessNameByProcSearch(inode, uid int) (string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if bytes.Equal(buffer[:n], socket) {
|
if bytes.Equal(buffer[:n], socket) {
|
||||||
cmdline, err := ioutil.ReadFile(path.Join(processPath, "cmdline"))
|
cmdline, err := os.ReadFile(path.Join(processPath, "cmdline"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,7 @@
|
|||||||
// +build !darwin,!linux,!windows
|
//go:build !darwin && !linux && !windows && (!freebsd || !amd64)
|
||||||
|
// +build !darwin
|
||||||
|
// +build !linux
|
||||||
|
// +build !windows
|
||||||
// +build !freebsd !amd64
|
// +build !freebsd !amd64
|
||||||
|
|
||||||
package process
|
package process
|
||||||
|
@ -3,52 +3,47 @@ package cachefile
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/gob"
|
"encoding/gob"
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
"os"
|
||||||
"sync"
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/component/profile"
|
"github.com/Dreamacro/clash/component/profile"
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
"github.com/Dreamacro/clash/log"
|
"github.com/Dreamacro/clash/log"
|
||||||
|
|
||||||
|
"go.etcd.io/bbolt"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
initOnce sync.Once
|
initOnce sync.Once
|
||||||
fileMode os.FileMode = 0666
|
fileMode os.FileMode = 0o666
|
||||||
defaultCache *CacheFile
|
defaultCache *CacheFile
|
||||||
)
|
|
||||||
|
|
||||||
type cache struct {
|
bucketSelected = []byte("selected")
|
||||||
Selected map[string]string
|
bucketFakeip = []byte("fakeip")
|
||||||
}
|
)
|
||||||
|
|
||||||
// CacheFile store and update the cache file
|
// CacheFile store and update the cache file
|
||||||
type CacheFile struct {
|
type CacheFile struct {
|
||||||
path string
|
DB *bbolt.DB
|
||||||
model *cache
|
|
||||||
buf *bytes.Buffer
|
|
||||||
mux sync.Mutex
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *CacheFile) SetSelected(group, selected string) {
|
func (c *CacheFile) SetSelected(group, selected string) {
|
||||||
if !profile.StoreSelected.Load() {
|
if !profile.StoreSelected.Load() {
|
||||||
return
|
return
|
||||||
}
|
} else if c.DB == nil {
|
||||||
|
|
||||||
c.mux.Lock()
|
|
||||||
defer c.mux.Unlock()
|
|
||||||
|
|
||||||
model := c.element()
|
|
||||||
|
|
||||||
model.Selected[group] = selected
|
|
||||||
c.buf.Reset()
|
|
||||||
if err := gob.NewEncoder(c.buf).Encode(model); err != nil {
|
|
||||||
log.Warnln("[CacheFile] encode gob failed: %s", err.Error())
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := ioutil.WriteFile(c.path, c.buf.Bytes(), fileMode); err != nil {
|
err := c.DB.Batch(func(t *bbolt.Tx) error {
|
||||||
log.Warnln("[CacheFile] write cache to %s failed: %s", c.path, err.Error())
|
bucket, err := t.CreateBucketIfNotExists(bucketSelected)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return bucket.Put([]byte(group), []byte(selected))
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
log.Warnln("[CacheFile] write cache to %s failed: %s", c.DB.Path(), err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -56,46 +51,131 @@ func (c *CacheFile) SetSelected(group, selected string) {
|
|||||||
func (c *CacheFile) SelectedMap() map[string]string {
|
func (c *CacheFile) SelectedMap() map[string]string {
|
||||||
if !profile.StoreSelected.Load() {
|
if !profile.StoreSelected.Load() {
|
||||||
return nil
|
return nil
|
||||||
|
} else if c.DB == nil {
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
c.mux.Lock()
|
|
||||||
defer c.mux.Unlock()
|
|
||||||
|
|
||||||
model := c.element()
|
|
||||||
|
|
||||||
mapping := map[string]string{}
|
mapping := map[string]string{}
|
||||||
for k, v := range model.Selected {
|
c.DB.View(func(t *bbolt.Tx) error {
|
||||||
mapping[k] = v
|
bucket := t.Bucket(bucketSelected)
|
||||||
}
|
if bucket == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
c := bucket.Cursor()
|
||||||
|
for k, v := c.First(); k != nil; k, v = c.Next() {
|
||||||
|
mapping[string(k)] = string(v)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
return mapping
|
return mapping
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *CacheFile) element() *cache {
|
func (c *CacheFile) PutFakeip(key, value []byte) error {
|
||||||
if c.model != nil {
|
if c.DB == nil {
|
||||||
return c.model
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
err := c.DB.Batch(func(t *bbolt.Tx) error {
|
||||||
|
bucket, err := t.CreateBucketIfNotExists(bucketFakeip)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return bucket.Put(key, value)
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
log.Warnln("[CacheFile] write cache to %s failed: %s", c.DB.Path(), err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CacheFile) GetFakeip(key []byte) []byte {
|
||||||
|
if c.DB == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
tx, err := c.DB.Begin(false)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
defer tx.Rollback()
|
||||||
|
|
||||||
|
bucket := tx.Bucket(bucketFakeip)
|
||||||
|
if bucket == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return bucket.Get(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CacheFile) Close() error {
|
||||||
|
return c.DB.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: remove migrateCache until 2022
|
||||||
|
func migrateCache() {
|
||||||
|
defer func() {
|
||||||
|
options := bbolt.Options{Timeout: time.Second}
|
||||||
|
db, err := bbolt.Open(C.Path.Cache(), fileMode, &options)
|
||||||
|
switch err {
|
||||||
|
case bbolt.ErrInvalid, bbolt.ErrChecksum, bbolt.ErrVersionMismatch:
|
||||||
|
if err = os.Remove(C.Path.Cache()); err != nil {
|
||||||
|
log.Warnln("[CacheFile] remove invalid cache file error: %s", err.Error())
|
||||||
|
break
|
||||||
|
}
|
||||||
|
log.Infoln("[CacheFile] remove invalid cache file and create new one")
|
||||||
|
db, err = bbolt.Open(C.Path.Cache(), fileMode, &options)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
log.Warnln("[CacheFile] can't open cache file: %s", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
defaultCache = &CacheFile{
|
||||||
|
DB: db,
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
buf, err := os.ReadFile(C.Path.OldCache())
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer os.Remove(C.Path.OldCache())
|
||||||
|
|
||||||
|
// read old cache file
|
||||||
|
type cache struct {
|
||||||
|
Selected map[string]string
|
||||||
|
}
|
||||||
model := &cache{
|
model := &cache{
|
||||||
Selected: map[string]string{},
|
Selected: map[string]string{},
|
||||||
}
|
}
|
||||||
|
bufReader := bytes.NewBuffer(buf)
|
||||||
|
gob.NewDecoder(bufReader).Decode(model)
|
||||||
|
|
||||||
if buf, err := ioutil.ReadFile(c.path); err == nil {
|
// write to new cache file
|
||||||
bufReader := bytes.NewBuffer(buf)
|
db, err := bbolt.Open(C.Path.Cache(), fileMode, nil)
|
||||||
gob.NewDecoder(bufReader).Decode(model)
|
if err != nil {
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
c.model = model
|
db.Batch(func(t *bbolt.Tx) error {
|
||||||
return c.model
|
bucket, err := t.CreateBucketIfNotExists(bucketSelected)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for group, selected := range model.Selected {
|
||||||
|
if err := bucket.Put([]byte(group), []byte(selected)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cache return singleton of CacheFile
|
// Cache return singleton of CacheFile
|
||||||
func Cache() *CacheFile {
|
func Cache() *CacheFile {
|
||||||
initOnce.Do(func() {
|
initOnce.Do(migrateCache)
|
||||||
defaultCache = &CacheFile{
|
|
||||||
path: C.Path.Cache(),
|
|
||||||
buf: &bytes.Buffer{},
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
return defaultCache
|
return defaultCache
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,5 @@ import (
|
|||||||
"go.uber.org/atomic"
|
"go.uber.org/atomic"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
// StoreSelected is a global switch for storing selected proxy to cache
|
||||||
// StoreSelected is a global switch for storing selected proxy to cache
|
var StoreSelected = atomic.NewBool(true)
|
||||||
StoreSelected = atomic.NewBool(true)
|
|
||||||
)
|
|
||||||
|
@ -1,18 +0,0 @@
|
|||||||
package tools
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"math/rand"
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/common/pool"
|
|
||||||
)
|
|
||||||
|
|
||||||
var BufPool = sync.Pool{New: func() interface{} { return &bytes.Buffer{} }}
|
|
||||||
|
|
||||||
func AppendRandBytes(b *bytes.Buffer, length int) {
|
|
||||||
randBytes := pool.Get(length)
|
|
||||||
defer pool.Put(randBytes)
|
|
||||||
rand.Read(randBytes)
|
|
||||||
b.Write(randBytes)
|
|
||||||
}
|
|
@ -12,10 +12,8 @@ const (
|
|||||||
domainStep = "."
|
domainStep = "."
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
// ErrInvalidDomain means insert domain is invalid
|
||||||
// ErrInvalidDomain means insert domain is invalid
|
var ErrInvalidDomain = errors.New("invalid domain")
|
||||||
ErrInvalidDomain = errors.New("invalid domain")
|
|
||||||
)
|
|
||||||
|
|
||||||
// DomainTrie contains the main logic for adding and searching nodes for domain segments.
|
// DomainTrie contains the main logic for adding and searching nodes for domain segments.
|
||||||
// support wildcard domain (e.g *.google.com)
|
// support wildcard domain (e.g *.google.com)
|
||||||
@ -23,7 +21,7 @@ type DomainTrie struct {
|
|||||||
root *Node
|
root *Node
|
||||||
}
|
}
|
||||||
|
|
||||||
func validAndSplitDomain(domain string) ([]string, bool) {
|
func ValidAndSplitDomain(domain string) ([]string, bool) {
|
||||||
if domain != "" && domain[len(domain)-1] == '.' {
|
if domain != "" && domain[len(domain)-1] == '.' {
|
||||||
return nil, false
|
return nil, false
|
||||||
}
|
}
|
||||||
@ -54,7 +52,7 @@ func validAndSplitDomain(domain string) ([]string, bool) {
|
|||||||
// 4. .example.com
|
// 4. .example.com
|
||||||
// 5. +.example.com
|
// 5. +.example.com
|
||||||
func (t *DomainTrie) Insert(domain string, data interface{}) error {
|
func (t *DomainTrie) Insert(domain string, data interface{}) error {
|
||||||
parts, valid := validAndSplitDomain(domain)
|
parts, valid := ValidAndSplitDomain(domain)
|
||||||
if !valid {
|
if !valid {
|
||||||
return ErrInvalidDomain
|
return ErrInvalidDomain
|
||||||
}
|
}
|
||||||
@ -91,7 +89,7 @@ func (t *DomainTrie) insert(parts []string, data interface{}) {
|
|||||||
// 2. wildcard domain
|
// 2. wildcard domain
|
||||||
// 2. dot wildcard domain
|
// 2. dot wildcard domain
|
||||||
func (t *DomainTrie) Search(domain string) *Node {
|
func (t *DomainTrie) Search(domain string) *Node {
|
||||||
parts, valid := validAndSplitDomain(domain)
|
parts, valid := ValidAndSplitDomain(domain)
|
||||||
if !valid || parts[0] == "" {
|
if !valid || parts[0] == "" {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -1,169 +0,0 @@
|
|||||||
package vmess
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/tls"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"net"
|
|
||||||
"net/http"
|
|
||||||
"net/url"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/gorilla/websocket"
|
|
||||||
)
|
|
||||||
|
|
||||||
type websocketConn struct {
|
|
||||||
conn *websocket.Conn
|
|
||||||
reader io.Reader
|
|
||||||
remoteAddr net.Addr
|
|
||||||
|
|
||||||
// https://godoc.org/github.com/gorilla/websocket#hdr-Concurrency
|
|
||||||
rMux sync.Mutex
|
|
||||||
wMux sync.Mutex
|
|
||||||
}
|
|
||||||
|
|
||||||
type WebsocketConfig struct {
|
|
||||||
Host string
|
|
||||||
Port string
|
|
||||||
Path string
|
|
||||||
Headers http.Header
|
|
||||||
TLS bool
|
|
||||||
SkipCertVerify bool
|
|
||||||
ServerName string
|
|
||||||
SessionCache tls.ClientSessionCache
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read implements net.Conn.Read()
|
|
||||||
func (wsc *websocketConn) Read(b []byte) (int, error) {
|
|
||||||
wsc.rMux.Lock()
|
|
||||||
defer wsc.rMux.Unlock()
|
|
||||||
for {
|
|
||||||
reader, err := wsc.getReader()
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
nBytes, err := reader.Read(b)
|
|
||||||
if err == io.EOF {
|
|
||||||
wsc.reader = nil
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
return nBytes, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write implements io.Writer.
|
|
||||||
func (wsc *websocketConn) Write(b []byte) (int, error) {
|
|
||||||
wsc.wMux.Lock()
|
|
||||||
defer wsc.wMux.Unlock()
|
|
||||||
if err := wsc.conn.WriteMessage(websocket.BinaryMessage, b); err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
return len(b), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (wsc *websocketConn) Close() error {
|
|
||||||
var errors []string
|
|
||||||
if err := wsc.conn.WriteControl(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, ""), time.Now().Add(time.Second*5)); err != nil {
|
|
||||||
errors = append(errors, err.Error())
|
|
||||||
}
|
|
||||||
if err := wsc.conn.Close(); err != nil {
|
|
||||||
errors = append(errors, err.Error())
|
|
||||||
}
|
|
||||||
if len(errors) > 0 {
|
|
||||||
return fmt.Errorf("failed to close connection: %s", strings.Join(errors, ","))
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (wsc *websocketConn) getReader() (io.Reader, error) {
|
|
||||||
if wsc.reader != nil {
|
|
||||||
return wsc.reader, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
_, reader, err := wsc.conn.NextReader()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
wsc.reader = reader
|
|
||||||
return reader, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (wsc *websocketConn) LocalAddr() net.Addr {
|
|
||||||
return wsc.conn.LocalAddr()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (wsc *websocketConn) RemoteAddr() net.Addr {
|
|
||||||
return wsc.remoteAddr
|
|
||||||
}
|
|
||||||
|
|
||||||
func (wsc *websocketConn) SetDeadline(t time.Time) error {
|
|
||||||
if err := wsc.SetReadDeadline(t); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return wsc.SetWriteDeadline(t)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (wsc *websocketConn) SetReadDeadline(t time.Time) error {
|
|
||||||
return wsc.conn.SetReadDeadline(t)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (wsc *websocketConn) SetWriteDeadline(t time.Time) error {
|
|
||||||
return wsc.conn.SetWriteDeadline(t)
|
|
||||||
}
|
|
||||||
|
|
||||||
func StreamWebsocketConn(conn net.Conn, c *WebsocketConfig) (net.Conn, error) {
|
|
||||||
dialer := &websocket.Dialer{
|
|
||||||
NetDial: func(network, addr string) (net.Conn, error) {
|
|
||||||
return conn, nil
|
|
||||||
},
|
|
||||||
ReadBufferSize: 4 * 1024,
|
|
||||||
WriteBufferSize: 4 * 1024,
|
|
||||||
HandshakeTimeout: time.Second * 8,
|
|
||||||
}
|
|
||||||
|
|
||||||
scheme := "ws"
|
|
||||||
if c.TLS {
|
|
||||||
scheme = "wss"
|
|
||||||
dialer.TLSClientConfig = &tls.Config{
|
|
||||||
ServerName: c.Host,
|
|
||||||
InsecureSkipVerify: c.SkipCertVerify,
|
|
||||||
ClientSessionCache: c.SessionCache,
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.ServerName != "" {
|
|
||||||
dialer.TLSClientConfig.ServerName = c.ServerName
|
|
||||||
} else if host := c.Headers.Get("Host"); host != "" {
|
|
||||||
dialer.TLSClientConfig.ServerName = host
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
uri := url.URL{
|
|
||||||
Scheme: scheme,
|
|
||||||
Host: net.JoinHostPort(c.Host, c.Port),
|
|
||||||
Path: c.Path,
|
|
||||||
}
|
|
||||||
|
|
||||||
headers := http.Header{}
|
|
||||||
if c.Headers != nil {
|
|
||||||
for k := range c.Headers {
|
|
||||||
headers.Add(k, c.Headers.Get(k))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
wsConn, resp, err := dialer.Dial(uri.String(), headers)
|
|
||||||
if err != nil {
|
|
||||||
reason := err.Error()
|
|
||||||
if resp != nil {
|
|
||||||
reason = resp.Status
|
|
||||||
}
|
|
||||||
return nil, fmt.Errorf("dial %s error: %s", uri.Host, reason)
|
|
||||||
}
|
|
||||||
|
|
||||||
return &websocketConn{
|
|
||||||
conn: wsConn,
|
|
||||||
remoteAddr: conn.RemoteAddr(),
|
|
||||||
}, nil
|
|
||||||
}
|
|
@ -8,19 +8,21 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/adapters/outbound"
|
"github.com/Dreamacro/clash/adapter"
|
||||||
"github.com/Dreamacro/clash/adapters/outboundgroup"
|
"github.com/Dreamacro/clash/adapter/outbound"
|
||||||
"github.com/Dreamacro/clash/adapters/provider"
|
"github.com/Dreamacro/clash/adapter/outboundgroup"
|
||||||
|
"github.com/Dreamacro/clash/adapter/provider"
|
||||||
"github.com/Dreamacro/clash/component/auth"
|
"github.com/Dreamacro/clash/component/auth"
|
||||||
"github.com/Dreamacro/clash/component/fakeip"
|
"github.com/Dreamacro/clash/component/fakeip"
|
||||||
"github.com/Dreamacro/clash/component/trie"
|
"github.com/Dreamacro/clash/component/trie"
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
|
providerTypes "github.com/Dreamacro/clash/constant/provider"
|
||||||
"github.com/Dreamacro/clash/dns"
|
"github.com/Dreamacro/clash/dns"
|
||||||
"github.com/Dreamacro/clash/log"
|
"github.com/Dreamacro/clash/log"
|
||||||
R "github.com/Dreamacro/clash/rules"
|
R "github.com/Dreamacro/clash/rule"
|
||||||
T "github.com/Dreamacro/clash/tunnel"
|
T "github.com/Dreamacro/clash/tunnel"
|
||||||
|
|
||||||
yaml "gopkg.in/yaml.v2"
|
"gopkg.in/yaml.v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
// General config
|
// General config
|
||||||
@ -60,22 +62,25 @@ type DNS struct {
|
|||||||
Fallback []dns.NameServer `yaml:"fallback"`
|
Fallback []dns.NameServer `yaml:"fallback"`
|
||||||
FallbackFilter FallbackFilter `yaml:"fallback-filter"`
|
FallbackFilter FallbackFilter `yaml:"fallback-filter"`
|
||||||
Listen string `yaml:"listen"`
|
Listen string `yaml:"listen"`
|
||||||
EnhancedMode dns.EnhancedMode `yaml:"enhanced-mode"`
|
EnhancedMode C.DNSMode `yaml:"enhanced-mode"`
|
||||||
DefaultNameserver []dns.NameServer `yaml:"default-nameserver"`
|
DefaultNameserver []dns.NameServer `yaml:"default-nameserver"`
|
||||||
FakeIPRange *fakeip.Pool
|
FakeIPRange *fakeip.Pool
|
||||||
Hosts *trie.DomainTrie
|
Hosts *trie.DomainTrie
|
||||||
|
NameServerPolicy map[string]dns.NameServer
|
||||||
}
|
}
|
||||||
|
|
||||||
// FallbackFilter config
|
// FallbackFilter config
|
||||||
type FallbackFilter struct {
|
type FallbackFilter struct {
|
||||||
GeoIP bool `yaml:"geoip"`
|
GeoIP bool `yaml:"geoip"`
|
||||||
IPCIDR []*net.IPNet `yaml:"ipcidr"`
|
GeoIPCode string `yaml:"geoip-code"`
|
||||||
Domain []string `yaml:"domain"`
|
IPCIDR []*net.IPNet `yaml:"ipcidr"`
|
||||||
|
Domain []string `yaml:"domain"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Profile config
|
// Profile config
|
||||||
type Profile struct {
|
type Profile struct {
|
||||||
StoreSelected bool `yaml:"store-selected"`
|
StoreSelected bool `yaml:"store-selected"`
|
||||||
|
StoreFakeIP bool `yaml:"store-fake-ip"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Experimental config
|
// Experimental config
|
||||||
@ -91,7 +96,7 @@ type Config struct {
|
|||||||
Rules []C.Rule
|
Rules []C.Rule
|
||||||
Users []auth.AuthUser
|
Users []auth.AuthUser
|
||||||
Proxies map[string]C.Proxy
|
Proxies map[string]C.Proxy
|
||||||
Providers map[string]provider.ProxyProvider
|
Providers map[string]providerTypes.ProxyProvider
|
||||||
}
|
}
|
||||||
|
|
||||||
type RawDNS struct {
|
type RawDNS struct {
|
||||||
@ -102,16 +107,18 @@ type RawDNS struct {
|
|||||||
Fallback []string `yaml:"fallback"`
|
Fallback []string `yaml:"fallback"`
|
||||||
FallbackFilter RawFallbackFilter `yaml:"fallback-filter"`
|
FallbackFilter RawFallbackFilter `yaml:"fallback-filter"`
|
||||||
Listen string `yaml:"listen"`
|
Listen string `yaml:"listen"`
|
||||||
EnhancedMode dns.EnhancedMode `yaml:"enhanced-mode"`
|
EnhancedMode C.DNSMode `yaml:"enhanced-mode"`
|
||||||
FakeIPRange string `yaml:"fake-ip-range"`
|
FakeIPRange string `yaml:"fake-ip-range"`
|
||||||
FakeIPFilter []string `yaml:"fake-ip-filter"`
|
FakeIPFilter []string `yaml:"fake-ip-filter"`
|
||||||
DefaultNameserver []string `yaml:"default-nameserver"`
|
DefaultNameserver []string `yaml:"default-nameserver"`
|
||||||
|
NameServerPolicy map[string]string `yaml:"nameserver-policy"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type RawFallbackFilter struct {
|
type RawFallbackFilter struct {
|
||||||
GeoIP bool `yaml:"geoip"`
|
GeoIP bool `yaml:"geoip"`
|
||||||
IPCIDR []string `yaml:"ipcidr"`
|
GeoIPCode string `yaml:"geoip-code"`
|
||||||
Domain []string `yaml:"domain"`
|
IPCIDR []string `yaml:"ipcidr"`
|
||||||
|
Domain []string `yaml:"domain"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type RawConfig struct {
|
type RawConfig struct {
|
||||||
@ -168,8 +175,9 @@ func UnmarshalRawConfig(buf []byte) (*RawConfig, error) {
|
|||||||
UseHosts: true,
|
UseHosts: true,
|
||||||
FakeIPRange: "198.18.0.1/16",
|
FakeIPRange: "198.18.0.1/16",
|
||||||
FallbackFilter: RawFallbackFilter{
|
FallbackFilter: RawFallbackFilter{
|
||||||
GeoIP: true,
|
GeoIP: true,
|
||||||
IPCIDR: []string{},
|
GeoIPCode: "CN",
|
||||||
|
IPCIDR: []string{},
|
||||||
},
|
},
|
||||||
DefaultNameserver: []string{
|
DefaultNameserver: []string{
|
||||||
"114.114.114.114",
|
"114.114.114.114",
|
||||||
@ -181,7 +189,7 @@ func UnmarshalRawConfig(buf []byte) (*RawConfig, error) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := yaml.Unmarshal(buf, &rawCfg); err != nil {
|
if err := yaml.Unmarshal(buf, rawCfg); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -219,7 +227,7 @@ func ParseRawConfig(rawCfg *RawConfig) (*Config, error) {
|
|||||||
}
|
}
|
||||||
config.Hosts = hosts
|
config.Hosts = hosts
|
||||||
|
|
||||||
dnsCfg, err := parseDNS(rawCfg.DNS, hosts)
|
dnsCfg, err := parseDNS(rawCfg, hosts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -264,21 +272,21 @@ func parseGeneral(cfg *RawConfig) (*General, error) {
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseProxies(cfg *RawConfig) (proxies map[string]C.Proxy, providersMap map[string]provider.ProxyProvider, err error) {
|
func parseProxies(cfg *RawConfig) (proxies map[string]C.Proxy, providersMap map[string]providerTypes.ProxyProvider, err error) {
|
||||||
proxies = make(map[string]C.Proxy)
|
proxies = make(map[string]C.Proxy)
|
||||||
providersMap = make(map[string]provider.ProxyProvider)
|
providersMap = make(map[string]providerTypes.ProxyProvider)
|
||||||
proxyList := []string{}
|
proxyList := []string{}
|
||||||
proxiesConfig := cfg.Proxy
|
proxiesConfig := cfg.Proxy
|
||||||
groupsConfig := cfg.ProxyGroup
|
groupsConfig := cfg.ProxyGroup
|
||||||
providersConfig := cfg.ProxyProvider
|
providersConfig := cfg.ProxyProvider
|
||||||
|
|
||||||
proxies["DIRECT"] = outbound.NewProxy(outbound.NewDirect())
|
proxies["DIRECT"] = adapter.NewProxy(outbound.NewDirect())
|
||||||
proxies["REJECT"] = outbound.NewProxy(outbound.NewReject())
|
proxies["REJECT"] = adapter.NewProxy(outbound.NewReject())
|
||||||
proxyList = append(proxyList, "DIRECT", "REJECT")
|
proxyList = append(proxyList, "DIRECT", "REJECT")
|
||||||
|
|
||||||
// parse proxy
|
// parse proxy
|
||||||
for idx, mapping := range proxiesConfig {
|
for idx, mapping := range proxiesConfig {
|
||||||
proxy, err := outbound.ParseProxy(mapping)
|
proxy, err := adapter.ParseProxy(mapping)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, fmt.Errorf("proxy %d: %w", idx, err)
|
return nil, nil, fmt.Errorf("proxy %d: %w", idx, err)
|
||||||
}
|
}
|
||||||
@ -337,12 +345,12 @@ func parseProxies(cfg *RawConfig) (proxies map[string]C.Proxy, providersMap map[
|
|||||||
return nil, nil, fmt.Errorf("proxy group %s: the duplicate name", groupName)
|
return nil, nil, fmt.Errorf("proxy group %s: the duplicate name", groupName)
|
||||||
}
|
}
|
||||||
|
|
||||||
proxies[groupName] = outbound.NewProxy(group)
|
proxies[groupName] = adapter.NewProxy(group)
|
||||||
}
|
}
|
||||||
|
|
||||||
// initial compatible provider
|
// initial compatible provider
|
||||||
for _, pd := range providersMap {
|
for _, pd := range providersMap {
|
||||||
if pd.VehicleType() != provider.Compatible {
|
if pd.VehicleType() != providerTypes.Compatible {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -364,9 +372,9 @@ func parseProxies(cfg *RawConfig) (proxies map[string]C.Proxy, providersMap map[
|
|||||||
&outboundgroup.GroupCommonOption{
|
&outboundgroup.GroupCommonOption{
|
||||||
Name: "GLOBAL",
|
Name: "GLOBAL",
|
||||||
},
|
},
|
||||||
[]provider.ProxyProvider{pd},
|
[]providerTypes.ProxyProvider{pd},
|
||||||
)
|
)
|
||||||
proxies["GLOBAL"] = outbound.NewProxy(global)
|
proxies["GLOBAL"] = adapter.NewProxy(global)
|
||||||
return proxies, providersMap, nil
|
return proxies, providersMap, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -481,6 +489,9 @@ func parseNameServer(servers []string) ([]dns.NameServer, error) {
|
|||||||
clearURL := url.URL{Scheme: "https", Host: u.Host, Path: u.Path}
|
clearURL := url.URL{Scheme: "https", Host: u.Host, Path: u.Path}
|
||||||
addr = clearURL.String()
|
addr = clearURL.String()
|
||||||
dnsNetType = "https" // DNS over HTTPS
|
dnsNetType = "https" // DNS over HTTPS
|
||||||
|
case "dhcp":
|
||||||
|
addr = u.Host
|
||||||
|
dnsNetType = "dhcp" // UDP from DHCP
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("DNS NameServer[%d] unsupport scheme: %s", idx, u.Scheme)
|
return nil, fmt.Errorf("DNS NameServer[%d] unsupport scheme: %s", idx, u.Scheme)
|
||||||
}
|
}
|
||||||
@ -500,6 +511,23 @@ func parseNameServer(servers []string) ([]dns.NameServer, error) {
|
|||||||
return nameservers, nil
|
return nameservers, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func parseNameServerPolicy(nsPolicy map[string]string) (map[string]dns.NameServer, error) {
|
||||||
|
policy := map[string]dns.NameServer{}
|
||||||
|
|
||||||
|
for domain, server := range nsPolicy {
|
||||||
|
nameservers, err := parseNameServer([]string{server})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if _, valid := trie.ValidAndSplitDomain(domain); !valid {
|
||||||
|
return nil, fmt.Errorf("DNS ResoverRule invalid domain: %s", domain)
|
||||||
|
}
|
||||||
|
policy[domain] = nameservers[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
return policy, nil
|
||||||
|
}
|
||||||
|
|
||||||
func parseFallbackIPCIDR(ips []string) ([]*net.IPNet, error) {
|
func parseFallbackIPCIDR(ips []string) ([]*net.IPNet, error) {
|
||||||
ipNets := []*net.IPNet{}
|
ipNets := []*net.IPNet{}
|
||||||
|
|
||||||
@ -514,7 +542,8 @@ func parseFallbackIPCIDR(ips []string) ([]*net.IPNet, error) {
|
|||||||
return ipNets, nil
|
return ipNets, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseDNS(cfg RawDNS, hosts *trie.DomainTrie) (*DNS, error) {
|
func parseDNS(rawCfg *RawConfig, hosts *trie.DomainTrie) (*DNS, error) {
|
||||||
|
cfg := rawCfg.DNS
|
||||||
if cfg.Enable && len(cfg.NameServer) == 0 {
|
if cfg.Enable && len(cfg.NameServer) == 0 {
|
||||||
return nil, fmt.Errorf("if DNS configuration is turned on, NameServer cannot be empty")
|
return nil, fmt.Errorf("if DNS configuration is turned on, NameServer cannot be empty")
|
||||||
}
|
}
|
||||||
@ -537,6 +566,10 @@ func parseDNS(cfg RawDNS, hosts *trie.DomainTrie) (*DNS, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if dnsCfg.NameServerPolicy, err = parseNameServerPolicy(cfg.NameServerPolicy); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
if len(cfg.DefaultNameserver) == 0 {
|
if len(cfg.DefaultNameserver) == 0 {
|
||||||
return nil, errors.New("default nameserver should have at least one nameserver")
|
return nil, errors.New("default nameserver should have at least one nameserver")
|
||||||
}
|
}
|
||||||
@ -551,7 +584,7 @@ func parseDNS(cfg RawDNS, hosts *trie.DomainTrie) (*DNS, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if cfg.EnhancedMode == dns.FAKEIP {
|
if cfg.EnhancedMode == C.DNSFakeIP {
|
||||||
_, ipnet, err := net.ParseCIDR(cfg.FakeIPRange)
|
_, ipnet, err := net.ParseCIDR(cfg.FakeIPRange)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -566,7 +599,12 @@ func parseDNS(cfg RawDNS, hosts *trie.DomainTrie) (*DNS, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pool, err := fakeip.New(ipnet, 1000, host)
|
pool, err := fakeip.New(fakeip.Options{
|
||||||
|
IPNet: ipnet,
|
||||||
|
Size: 1000,
|
||||||
|
Host: host,
|
||||||
|
Persistence: rawCfg.Profile.StoreFakeIP,
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -575,6 +613,7 @@ func parseDNS(cfg RawDNS, hosts *trie.DomainTrie) (*DNS, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dnsCfg.FallbackFilter.GeoIP = cfg.FallbackFilter.GeoIP
|
dnsCfg.FallbackFilter.GeoIP = cfg.FallbackFilter.GeoIP
|
||||||
|
dnsCfg.FallbackFilter.GeoIPCode = cfg.FallbackFilter.GeoIPCode
|
||||||
if fallbackip, err := parseFallbackIPCIDR(cfg.FallbackFilter.IPCIDR); err == nil {
|
if fallbackip, err := parseFallbackIPCIDR(cfg.FallbackFilter.IPCIDR); err == nil {
|
||||||
dnsCfg.FallbackFilter.IPCIDR = fallbackip
|
dnsCfg.FallbackFilter.IPCIDR = fallbackip
|
||||||
}
|
}
|
||||||
|
@ -18,7 +18,7 @@ func downloadMMDB(path string) (err error) {
|
|||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
|
||||||
f, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY, 0644)
|
f, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY, 0o644)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -54,7 +54,7 @@ func initMMDB() error {
|
|||||||
func Init(dir string) error {
|
func Init(dir string) error {
|
||||||
// initial homedir
|
// initial homedir
|
||||||
if _, err := os.Stat(dir); os.IsNotExist(err) {
|
if _, err := os.Stat(dir); os.IsNotExist(err) {
|
||||||
if err := os.MkdirAll(dir, 0777); err != nil {
|
if err := os.MkdirAll(dir, 0o777); err != nil {
|
||||||
return fmt.Errorf("can't create config directory %s: %s", dir, err.Error())
|
return fmt.Errorf("can't create config directory %s: %s", dir, err.Error())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -62,7 +62,7 @@ func Init(dir string) error {
|
|||||||
// initial config.yaml
|
// initial config.yaml
|
||||||
if _, err := os.Stat(C.Path.Config()); os.IsNotExist(err) {
|
if _, err := os.Stat(C.Path.Config()); os.IsNotExist(err) {
|
||||||
log.Infoln("Can't find config, create a initial config file")
|
log.Infoln("Can't find config, create a initial config file")
|
||||||
f, err := os.OpenFile(C.Path.Config(), os.O_CREATE|os.O_WRONLY, 0644)
|
f, err := os.OpenFile(C.Path.Config(), os.O_CREATE|os.O_WRONLY, 0o644)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("can't create file %s: %s", C.Path.Config(), err.Error())
|
return fmt.Errorf("can't create file %s: %s", C.Path.Config(), err.Error())
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/adapters/outboundgroup"
|
"github.com/Dreamacro/clash/adapter/outboundgroup"
|
||||||
"github.com/Dreamacro/clash/common/structure"
|
"github.com/Dreamacro/clash/common/structure"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -5,6 +5,8 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/Dreamacro/clash/component/dialer"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Adapter Type
|
// Adapter Type
|
||||||
@ -27,6 +29,11 @@ const (
|
|||||||
LoadBalance
|
LoadBalance
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
DefaultTCPTimeout = 5 * time.Second
|
||||||
|
DefaultUDPTimeout = DefaultTCPTimeout
|
||||||
|
)
|
||||||
|
|
||||||
type Connection interface {
|
type Connection interface {
|
||||||
Chains() Chain
|
Chains() Chain
|
||||||
AppendToChains(adapter ProxyAdapter)
|
AppendToChains(adapter ProxyAdapter)
|
||||||
@ -69,11 +76,14 @@ type PacketConn interface {
|
|||||||
type ProxyAdapter interface {
|
type ProxyAdapter interface {
|
||||||
Name() string
|
Name() string
|
||||||
Type() AdapterType
|
Type() AdapterType
|
||||||
|
Addr() string
|
||||||
|
SupportUDP() bool
|
||||||
|
MarshalJSON() ([]byte, error)
|
||||||
|
|
||||||
// StreamConn wraps a protocol around net.Conn with Metadata.
|
// StreamConn wraps a protocol around net.Conn with Metadata.
|
||||||
//
|
//
|
||||||
// Examples:
|
// Examples:
|
||||||
// conn, _ := net.Dial("tcp", "host:port")
|
// conn, _ := net.DialContext(context.Background(), "tcp", "host:port")
|
||||||
// conn, _ = adapter.StreamConn(conn, metadata)
|
// conn, _ = adapter.StreamConn(conn, metadata)
|
||||||
//
|
//
|
||||||
// It returns a C.Conn with protocol which start with
|
// It returns a C.Conn with protocol which start with
|
||||||
@ -82,12 +92,10 @@ type ProxyAdapter interface {
|
|||||||
|
|
||||||
// DialContext return a C.Conn with protocol which
|
// DialContext return a C.Conn with protocol which
|
||||||
// contains multiplexing-related reuse logic (if any)
|
// contains multiplexing-related reuse logic (if any)
|
||||||
DialContext(ctx context.Context, metadata *Metadata) (Conn, error)
|
DialContext(ctx context.Context, metadata *Metadata, opts ...dialer.Option) (Conn, error)
|
||||||
|
|
||||||
|
ListenPacketContext(ctx context.Context, metadata *Metadata, opts ...dialer.Option) (PacketConn, error)
|
||||||
|
|
||||||
DialUDP(metadata *Metadata) (PacketConn, error)
|
|
||||||
SupportUDP() bool
|
|
||||||
MarshalJSON() ([]byte, error)
|
|
||||||
Addr() string
|
|
||||||
// Unwrap extracts the proxy from a proxy-group. It returns nil when nothing to extract.
|
// Unwrap extracts the proxy from a proxy-group. It returns nil when nothing to extract.
|
||||||
Unwrap(metadata *Metadata) Proxy
|
Unwrap(metadata *Metadata) Proxy
|
||||||
}
|
}
|
||||||
@ -101,9 +109,14 @@ type Proxy interface {
|
|||||||
ProxyAdapter
|
ProxyAdapter
|
||||||
Alive() bool
|
Alive() bool
|
||||||
DelayHistory() []DelayHistory
|
DelayHistory() []DelayHistory
|
||||||
Dial(metadata *Metadata) (Conn, error)
|
|
||||||
LastDelay() uint16
|
LastDelay() uint16
|
||||||
URLTest(ctx context.Context, url string) (uint16, error)
|
URLTest(ctx context.Context, url string) (uint16, error)
|
||||||
|
|
||||||
|
// Deprecated: use DialContext instead.
|
||||||
|
Dial(metadata *Metadata) (Conn, error)
|
||||||
|
|
||||||
|
// Deprecated: use DialPacketConn instead.
|
||||||
|
DialUDP(metadata *Metadata) (PacketConn, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// AdapterType is enum of adapter type
|
// AdapterType is enum of adapter type
|
||||||
|
70
constant/dns.go
Normal file
70
constant/dns.go
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
package constant
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DNSModeMapping is a mapping for EnhancedMode enum
|
||||||
|
var DNSModeMapping = map[string]DNSMode{
|
||||||
|
DNSNormal.String(): DNSNormal,
|
||||||
|
DNSFakeIP.String(): DNSFakeIP,
|
||||||
|
DNSMapping.String(): DNSMapping,
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
DNSNormal DNSMode = iota
|
||||||
|
DNSFakeIP
|
||||||
|
DNSMapping
|
||||||
|
)
|
||||||
|
|
||||||
|
type DNSMode int
|
||||||
|
|
||||||
|
// UnmarshalYAML unserialize EnhancedMode with yaml
|
||||||
|
func (e *DNSMode) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
||||||
|
var tp string
|
||||||
|
if err := unmarshal(&tp); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
mode, exist := DNSModeMapping[tp]
|
||||||
|
if !exist {
|
||||||
|
return errors.New("invalid mode")
|
||||||
|
}
|
||||||
|
*e = mode
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalYAML serialize EnhancedMode with yaml
|
||||||
|
func (e DNSMode) MarshalYAML() (interface{}, error) {
|
||||||
|
return e.String(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalJSON unserialize EnhancedMode with json
|
||||||
|
func (e *DNSMode) UnmarshalJSON(data []byte) error {
|
||||||
|
var tp string
|
||||||
|
json.Unmarshal(data, &tp)
|
||||||
|
mode, exist := DNSModeMapping[tp]
|
||||||
|
if !exist {
|
||||||
|
return errors.New("invalid mode")
|
||||||
|
}
|
||||||
|
*e = mode
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalJSON serialize EnhancedMode with json
|
||||||
|
func (e DNSMode) MarshalJSON() ([]byte, error) {
|
||||||
|
return json.Marshal(e.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e DNSMode) String() string {
|
||||||
|
switch e {
|
||||||
|
case DNSNormal:
|
||||||
|
return "normal"
|
||||||
|
case DNSFakeIP:
|
||||||
|
return "fake-ip"
|
||||||
|
case DNSMapping:
|
||||||
|
return "redir-host"
|
||||||
|
default:
|
||||||
|
return "unknown"
|
||||||
|
}
|
||||||
|
}
|
7
constant/listener.go
Normal file
7
constant/listener.go
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
package constant
|
||||||
|
|
||||||
|
type Listener interface {
|
||||||
|
RawAddress() string
|
||||||
|
Address() string
|
||||||
|
Close() error
|
||||||
|
}
|
@ -17,7 +17,8 @@ const (
|
|||||||
|
|
||||||
HTTP Type = iota
|
HTTP Type = iota
|
||||||
HTTPCONNECT
|
HTTPCONNECT
|
||||||
SOCKS
|
SOCKS4
|
||||||
|
SOCKS5
|
||||||
REDIR
|
REDIR
|
||||||
TPROXY
|
TPROXY
|
||||||
)
|
)
|
||||||
@ -43,7 +44,9 @@ func (t Type) String() string {
|
|||||||
return "HTTP"
|
return "HTTP"
|
||||||
case HTTPCONNECT:
|
case HTTPCONNECT:
|
||||||
return "HTTP Connect"
|
return "HTTP Connect"
|
||||||
case SOCKS:
|
case SOCKS4:
|
||||||
|
return "Socks4"
|
||||||
|
case SOCKS5:
|
||||||
return "Socks5"
|
return "Socks5"
|
||||||
case REDIR:
|
case REDIR:
|
||||||
return "Redir"
|
return "Redir"
|
||||||
@ -68,6 +71,7 @@ type Metadata struct {
|
|||||||
DstPort string `json:"destinationPort"`
|
DstPort string `json:"destinationPort"`
|
||||||
AddrType int `json:"-"`
|
AddrType int `json:"-"`
|
||||||
Host string `json:"host"`
|
Host string `json:"host"`
|
||||||
|
DNSMode DNSMode `json:"dnsMode"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Metadata) RemoteAddress() string {
|
func (m *Metadata) RemoteAddress() string {
|
||||||
@ -82,14 +86,31 @@ func (m *Metadata) Resolved() bool {
|
|||||||
return m.DstIP != nil
|
return m.DstIP != nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Pure is used to solve unexpected behavior
|
||||||
|
// when dialing proxy connection in DNSMapping mode.
|
||||||
|
func (m *Metadata) Pure() *Metadata {
|
||||||
|
if m.DNSMode == DNSMapping && m.DstIP != nil {
|
||||||
|
copy := *m
|
||||||
|
copy.Host = ""
|
||||||
|
if copy.DstIP.To4() != nil {
|
||||||
|
copy.AddrType = AtypIPv4
|
||||||
|
} else {
|
||||||
|
copy.AddrType = AtypIPv6
|
||||||
|
}
|
||||||
|
return ©
|
||||||
|
}
|
||||||
|
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
func (m *Metadata) UDPAddr() *net.UDPAddr {
|
func (m *Metadata) UDPAddr() *net.UDPAddr {
|
||||||
if m.NetWork != UDP || m.DstIP == nil {
|
if m.NetWork != UDP || m.DstIP == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
port, _ := strconv.Atoi(m.DstPort)
|
port, _ := strconv.ParseInt(m.DstPort, 10, 16)
|
||||||
return &net.UDPAddr{
|
return &net.UDPAddr{
|
||||||
IP: m.DstIP,
|
IP: m.DstIP,
|
||||||
Port: port,
|
Port: int(port),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -9,21 +9,19 @@ import (
|
|||||||
const Name = "clash"
|
const Name = "clash"
|
||||||
|
|
||||||
// Path is used to get the configuration path
|
// Path is used to get the configuration path
|
||||||
var Path *path
|
var Path = func() *path {
|
||||||
|
|
||||||
type path struct {
|
|
||||||
homeDir string
|
|
||||||
configFile string
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
homeDir, err := os.UserHomeDir()
|
homeDir, err := os.UserHomeDir()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
homeDir, _ = os.Getwd()
|
homeDir, _ = os.Getwd()
|
||||||
}
|
}
|
||||||
|
|
||||||
homeDir = P.Join(homeDir, ".config", Name)
|
homeDir = P.Join(homeDir, ".config", Name)
|
||||||
Path = &path{homeDir: homeDir, configFile: "config.yaml"}
|
return &path{homeDir: homeDir, configFile: "config.yaml"}
|
||||||
|
}()
|
||||||
|
|
||||||
|
type path struct {
|
||||||
|
homeDir string
|
||||||
|
configFile string
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetHomeDir is used to set the configuration path
|
// SetHomeDir is used to set the configuration path
|
||||||
@ -57,6 +55,10 @@ func (p *path) MMDB() string {
|
|||||||
return P.Join(p.homeDir, "Country.mmdb")
|
return P.Join(p.homeDir, "Country.mmdb")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *path) Cache() string {
|
func (p *path) OldCache() string {
|
||||||
return P.Join(p.homeDir, ".cache")
|
return P.Join(p.homeDir, ".cache")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *path) Cache() string {
|
||||||
|
return P.Join(p.homeDir, "cache.db")
|
||||||
|
}
|
||||||
|
105
constant/provider/interface.go
Normal file
105
constant/provider/interface.go
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
package provider
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/Dreamacro/clash/constant"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Vehicle Type
|
||||||
|
const (
|
||||||
|
File VehicleType = iota
|
||||||
|
HTTP
|
||||||
|
Compatible
|
||||||
|
)
|
||||||
|
|
||||||
|
// VehicleType defined
|
||||||
|
type VehicleType int
|
||||||
|
|
||||||
|
func (v VehicleType) String() string {
|
||||||
|
switch v {
|
||||||
|
case File:
|
||||||
|
return "File"
|
||||||
|
case HTTP:
|
||||||
|
return "HTTP"
|
||||||
|
case Compatible:
|
||||||
|
return "Compatible"
|
||||||
|
default:
|
||||||
|
return "Unknown"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Vehicle interface {
|
||||||
|
Read() ([]byte, error)
|
||||||
|
Path() string
|
||||||
|
Type() VehicleType
|
||||||
|
}
|
||||||
|
|
||||||
|
// Provider Type
|
||||||
|
const (
|
||||||
|
Proxy ProviderType = iota
|
||||||
|
Rule
|
||||||
|
)
|
||||||
|
|
||||||
|
// ProviderType defined
|
||||||
|
type ProviderType int
|
||||||
|
|
||||||
|
func (pt ProviderType) String() string {
|
||||||
|
switch pt {
|
||||||
|
case Proxy:
|
||||||
|
return "Proxy"
|
||||||
|
case Rule:
|
||||||
|
return "Rule"
|
||||||
|
default:
|
||||||
|
return "Unknown"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Provider interface
|
||||||
|
type Provider interface {
|
||||||
|
Name() string
|
||||||
|
VehicleType() VehicleType
|
||||||
|
Type() ProviderType
|
||||||
|
Initial() error
|
||||||
|
Update() error
|
||||||
|
}
|
||||||
|
|
||||||
|
// ProxyProvider interface
|
||||||
|
type ProxyProvider interface {
|
||||||
|
Provider
|
||||||
|
Proxies() []constant.Proxy
|
||||||
|
// ProxiesWithTouch is used to inform the provider that the proxy is actually being used while getting the list of proxies.
|
||||||
|
// Commonly used in DialContext and DialPacketConn
|
||||||
|
ProxiesWithTouch() []constant.Proxy
|
||||||
|
HealthCheck()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rule Type
|
||||||
|
const (
|
||||||
|
Domain RuleType = iota
|
||||||
|
IPCIDR
|
||||||
|
Classical
|
||||||
|
)
|
||||||
|
|
||||||
|
// RuleType defined
|
||||||
|
type RuleType int
|
||||||
|
|
||||||
|
func (rt RuleType) String() string {
|
||||||
|
switch rt {
|
||||||
|
case Domain:
|
||||||
|
return "Domain"
|
||||||
|
case IPCIDR:
|
||||||
|
return "IPCIDR"
|
||||||
|
case Classical:
|
||||||
|
return "Classical"
|
||||||
|
default:
|
||||||
|
return "Unknown"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// RuleProvider interface
|
||||||
|
type RuleProvider interface {
|
||||||
|
Provider
|
||||||
|
Behavior() RuleType
|
||||||
|
Match(*constant.Metadata) bool
|
||||||
|
ShouldResolveIP() bool
|
||||||
|
AsRule(adaptor string) constant.Rule
|
||||||
|
}
|
@ -1,47 +0,0 @@
|
|||||||
package context
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net"
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
C "github.com/Dreamacro/clash/constant"
|
|
||||||
|
|
||||||
"github.com/gofrs/uuid"
|
|
||||||
)
|
|
||||||
|
|
||||||
type HTTPContext struct {
|
|
||||||
id uuid.UUID
|
|
||||||
metadata *C.Metadata
|
|
||||||
conn net.Conn
|
|
||||||
req *http.Request
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewHTTPContext(conn net.Conn, req *http.Request, metadata *C.Metadata) *HTTPContext {
|
|
||||||
id, _ := uuid.NewV4()
|
|
||||||
return &HTTPContext{
|
|
||||||
id: id,
|
|
||||||
metadata: metadata,
|
|
||||||
conn: conn,
|
|
||||||
req: req,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ID implement C.ConnContext ID
|
|
||||||
func (hc *HTTPContext) ID() uuid.UUID {
|
|
||||||
return hc.id
|
|
||||||
}
|
|
||||||
|
|
||||||
// Metadata implement C.ConnContext Metadata
|
|
||||||
func (hc *HTTPContext) Metadata() *C.Metadata {
|
|
||||||
return hc.metadata
|
|
||||||
}
|
|
||||||
|
|
||||||
// Conn implement C.ConnContext Conn
|
|
||||||
func (hc *HTTPContext) Conn() net.Conn {
|
|
||||||
return hc.conn
|
|
||||||
}
|
|
||||||
|
|
||||||
// Request return the http request struct
|
|
||||||
func (hc *HTTPContext) Request() *http.Request {
|
|
||||||
return hc.req
|
|
||||||
}
|
|
@ -2,6 +2,7 @@ package dns
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"crypto/tls"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"strings"
|
"strings"
|
||||||
@ -14,42 +15,46 @@ import (
|
|||||||
|
|
||||||
type client struct {
|
type client struct {
|
||||||
*D.Client
|
*D.Client
|
||||||
r *Resolver
|
r *Resolver
|
||||||
port string
|
port string
|
||||||
host string
|
host string
|
||||||
|
iface string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *client) Exchange(m *D.Msg) (msg *D.Msg, err error) {
|
func (c *client) Exchange(m *D.Msg) (*D.Msg, error) {
|
||||||
return c.ExchangeContext(context.Background(), m)
|
return c.ExchangeContext(context.Background(), m)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *client) ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg, err error) {
|
func (c *client) ExchangeContext(ctx context.Context, m *D.Msg) (*D.Msg, error) {
|
||||||
var ip net.IP
|
var (
|
||||||
|
ip net.IP
|
||||||
|
err error
|
||||||
|
)
|
||||||
if c.r == nil {
|
if c.r == nil {
|
||||||
// a default ip dns
|
// a default ip dns
|
||||||
ip = net.ParseIP(c.host)
|
if ip = net.ParseIP(c.host); ip == nil {
|
||||||
|
return nil, fmt.Errorf("dns %s not a valid ip", c.host)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
if ip, err = resolver.ResolveIPWithResolver(c.host, c.r); err != nil {
|
if ip, err = resolver.ResolveIPWithResolver(c.host, c.r); err != nil {
|
||||||
return nil, fmt.Errorf("use default dns resolve failed: %w", err)
|
return nil, fmt.Errorf("use default dns resolve failed: %w", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
d, err := dialer.Dialer()
|
network := "udp"
|
||||||
|
if strings.HasPrefix(c.Client.Net, "tcp") {
|
||||||
|
network = "tcp"
|
||||||
|
}
|
||||||
|
|
||||||
|
options := []dialer.Option{}
|
||||||
|
if c.iface != "" {
|
||||||
|
options = append(options, dialer.WithInterface(c.iface))
|
||||||
|
}
|
||||||
|
conn, err := dialer.DialContext(ctx, network, net.JoinHostPort(ip.String(), c.port), options...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
defer conn.Close()
|
||||||
if ip != nil && ip.IsGlobalUnicast() && dialer.DialHook != nil {
|
|
||||||
network := "udp"
|
|
||||||
if strings.HasPrefix(c.Client.Net, "tcp") {
|
|
||||||
network = "tcp"
|
|
||||||
}
|
|
||||||
if err := dialer.DialHook(d, network, ip); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
c.Client.Dialer = d
|
|
||||||
|
|
||||||
// miekg/dns ExchangeContext doesn't respond to context cancel.
|
// miekg/dns ExchangeContext doesn't respond to context cancel.
|
||||||
// this is a workaround
|
// this is a workaround
|
||||||
@ -59,7 +64,17 @@ func (c *client) ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg, err
|
|||||||
}
|
}
|
||||||
ch := make(chan result, 1)
|
ch := make(chan result, 1)
|
||||||
go func() {
|
go func() {
|
||||||
msg, _, err := c.Client.Exchange(m, net.JoinHostPort(ip.String(), c.port))
|
if strings.HasSuffix(c.Client.Net, "tls") {
|
||||||
|
conn = tls.Client(conn, c.Client.TLSConfig)
|
||||||
|
}
|
||||||
|
|
||||||
|
msg, _, err := c.Client.ExchangeWithConn(m, &D.Conn{
|
||||||
|
Conn: conn,
|
||||||
|
UDPSize: c.Client.UDPSize,
|
||||||
|
TsigSecret: c.Client.TsigSecret,
|
||||||
|
TsigProvider: c.Client.TsigProvider,
|
||||||
|
})
|
||||||
|
|
||||||
ch <- result{msg, err}
|
ch <- result{msg, err}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
147
dns/dhcp.go
Normal file
147
dns/dhcp.go
Normal file
@ -0,0 +1,147 @@
|
|||||||
|
package dns
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"net"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/Dreamacro/clash/component/dhcp"
|
||||||
|
"github.com/Dreamacro/clash/component/iface"
|
||||||
|
"github.com/Dreamacro/clash/component/resolver"
|
||||||
|
|
||||||
|
D "github.com/miekg/dns"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
IfaceTTL = time.Second * 20
|
||||||
|
DHCPTTL = time.Hour
|
||||||
|
DHCPTimeout = time.Minute
|
||||||
|
)
|
||||||
|
|
||||||
|
type dhcpClient struct {
|
||||||
|
ifaceName string
|
||||||
|
|
||||||
|
lock sync.Mutex
|
||||||
|
ifaceInvalidate time.Time
|
||||||
|
dnsInvalidate time.Time
|
||||||
|
|
||||||
|
ifaceAddr *net.IPNet
|
||||||
|
done chan struct{}
|
||||||
|
resolver *Resolver
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *dhcpClient) Exchange(m *D.Msg) (msg *D.Msg, err error) {
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), resolver.DefaultDNSTimeout)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
return d.ExchangeContext(ctx, m)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *dhcpClient) ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg, err error) {
|
||||||
|
res, err := d.resolve(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.ExchangeContext(ctx, m)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *dhcpClient) resolve(ctx context.Context) (*Resolver, error) {
|
||||||
|
d.lock.Lock()
|
||||||
|
|
||||||
|
invalidated, err := d.invalidate()
|
||||||
|
if err != nil {
|
||||||
|
d.err = err
|
||||||
|
} else if invalidated {
|
||||||
|
done := make(chan struct{})
|
||||||
|
|
||||||
|
d.done = done
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), DHCPTimeout)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
var res *Resolver
|
||||||
|
dns, err := dhcp.ResolveDNSFromDHCP(ctx, d.ifaceName)
|
||||||
|
if err == nil {
|
||||||
|
nameserver := make([]NameServer, 0, len(dns))
|
||||||
|
for _, item := range dns {
|
||||||
|
nameserver = append(nameserver, NameServer{
|
||||||
|
Addr: net.JoinHostPort(item.String(), "53"),
|
||||||
|
Interface: d.ifaceName,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
res = NewResolver(Config{
|
||||||
|
Main: nameserver,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
d.lock.Lock()
|
||||||
|
defer d.lock.Unlock()
|
||||||
|
|
||||||
|
close(done)
|
||||||
|
|
||||||
|
d.done = nil
|
||||||
|
d.resolver = res
|
||||||
|
d.err = err
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
d.lock.Unlock()
|
||||||
|
|
||||||
|
for {
|
||||||
|
d.lock.Lock()
|
||||||
|
|
||||||
|
res, err, done := d.resolver, d.err, d.done
|
||||||
|
|
||||||
|
d.lock.Unlock()
|
||||||
|
|
||||||
|
// initializing
|
||||||
|
if res == nil && err == nil {
|
||||||
|
select {
|
||||||
|
case <-done:
|
||||||
|
continue
|
||||||
|
case <-ctx.Done():
|
||||||
|
return nil, ctx.Err()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// dirty return
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *dhcpClient) invalidate() (bool, error) {
|
||||||
|
if time.Now().Before(d.ifaceInvalidate) {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
d.ifaceInvalidate = time.Now().Add(IfaceTTL)
|
||||||
|
|
||||||
|
ifaceObj, err := iface.ResolveInterface(d.ifaceName)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
addr, err := ifaceObj.PickIPv4Addr(nil)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if time.Now().Before(d.dnsInvalidate) && d.ifaceAddr.IP.Equal(addr.IP) && bytes.Equal(d.ifaceAddr.Mask, addr.Mask) {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
d.dnsInvalidate = time.Now().Add(DHCPTTL)
|
||||||
|
d.ifaceAddr = addr
|
||||||
|
|
||||||
|
return d.done == nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func newDHCPClient(ifaceName string) *dhcpClient {
|
||||||
|
return &dhcpClient{ifaceName: ifaceName}
|
||||||
|
}
|
18
dns/doh.go
18
dns/doh.go
@ -3,8 +3,7 @@ package dns
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"crypto/tls"
|
"io"
|
||||||
"io/ioutil"
|
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
@ -29,13 +28,21 @@ func (dc *dohClient) Exchange(m *D.Msg) (msg *D.Msg, err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (dc *dohClient) ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg, err error) {
|
func (dc *dohClient) ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg, err error) {
|
||||||
req, err := dc.newRequest(m)
|
// https://datatracker.ietf.org/doc/html/rfc8484#section-4.1
|
||||||
|
// In order to maximize cache friendliness, SHOULD use a DNS ID of 0 in every DNS request.
|
||||||
|
newM := *m
|
||||||
|
newM.Id = 0
|
||||||
|
req, err := dc.newRequest(&newM)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
req = req.WithContext(ctx)
|
req = req.WithContext(ctx)
|
||||||
return dc.doRequest(req)
|
msg, err = dc.doRequest(req)
|
||||||
|
if err == nil {
|
||||||
|
msg.Id = m.Id
|
||||||
|
}
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// newRequest returns a new DoH request given a dns.Msg.
|
// newRequest returns a new DoH request given a dns.Msg.
|
||||||
@ -63,7 +70,7 @@ func (dc *dohClient) doRequest(req *http.Request) (msg *D.Msg, err error) {
|
|||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
|
||||||
buf, err := ioutil.ReadAll(resp.Body)
|
buf, err := io.ReadAll(resp.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -76,7 +83,6 @@ func newDoHClient(url string, r *Resolver) *dohClient {
|
|||||||
return &dohClient{
|
return &dohClient{
|
||||||
url: url,
|
url: url,
|
||||||
transport: &http.Transport{
|
transport: &http.Transport{
|
||||||
TLSClientConfig: &tls.Config{ClientSessionCache: globalSessionCache},
|
|
||||||
ForceAttemptHTTP2: true,
|
ForceAttemptHTTP2: true,
|
||||||
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||||
host, port, err := net.SplitHostPort(addr)
|
host, port, err := net.SplitHostPort(addr)
|
||||||
|
@ -5,20 +5,21 @@ import (
|
|||||||
|
|
||||||
"github.com/Dreamacro/clash/common/cache"
|
"github.com/Dreamacro/clash/common/cache"
|
||||||
"github.com/Dreamacro/clash/component/fakeip"
|
"github.com/Dreamacro/clash/component/fakeip"
|
||||||
|
C "github.com/Dreamacro/clash/constant"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ResolverEnhancer struct {
|
type ResolverEnhancer struct {
|
||||||
mode EnhancedMode
|
mode C.DNSMode
|
||||||
fakePool *fakeip.Pool
|
fakePool *fakeip.Pool
|
||||||
mapping *cache.LruCache
|
mapping *cache.LruCache
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *ResolverEnhancer) FakeIPEnabled() bool {
|
func (h *ResolverEnhancer) FakeIPEnabled() bool {
|
||||||
return h.mode == FAKEIP
|
return h.mode == C.DNSFakeIP
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *ResolverEnhancer) MappingEnabled() bool {
|
func (h *ResolverEnhancer) MappingEnabled() bool {
|
||||||
return h.mode == FAKEIP || h.mode == MAPPING
|
return h.mode == C.DNSFakeIP || h.mode == C.DNSMapping
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *ResolverEnhancer) IsExistFakeIP(ip net.IP) bool {
|
func (h *ResolverEnhancer) IsExistFakeIP(ip net.IP) bool {
|
||||||
@ -67,7 +68,7 @@ func (h *ResolverEnhancer) PatchFrom(o *ResolverEnhancer) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if h.fakePool != nil && o.fakePool != nil {
|
if h.fakePool != nil && o.fakePool != nil {
|
||||||
h.fakePool.PatchFrom(o.fakePool)
|
h.fakePool.CloneFrom(o.fakePool)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -75,7 +76,7 @@ func NewEnhancer(cfg Config) *ResolverEnhancer {
|
|||||||
var fakePool *fakeip.Pool
|
var fakePool *fakeip.Pool
|
||||||
var mapping *cache.LruCache
|
var mapping *cache.LruCache
|
||||||
|
|
||||||
if cfg.EnhancedMode != NORMAL {
|
if cfg.EnhancedMode != C.DNSNormal {
|
||||||
fakePool = cfg.Pool
|
fakePool = cfg.Pool
|
||||||
mapping = cache.NewLRUCache(cache.WithSize(4096), cache.WithStale(true))
|
mapping = cache.NewLRUCache(cache.WithSize(4096), cache.WithStale(true))
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@ package dns
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"net"
|
"net"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/component/mmdb"
|
"github.com/Dreamacro/clash/component/mmdb"
|
||||||
"github.com/Dreamacro/clash/component/trie"
|
"github.com/Dreamacro/clash/component/trie"
|
||||||
@ -11,11 +12,13 @@ type fallbackIPFilter interface {
|
|||||||
Match(net.IP) bool
|
Match(net.IP) bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type geoipFilter struct{}
|
type geoipFilter struct {
|
||||||
|
code string
|
||||||
|
}
|
||||||
|
|
||||||
func (gf *geoipFilter) Match(ip net.IP) bool {
|
func (gf *geoipFilter) Match(ip net.IP) bool {
|
||||||
record, _ := mmdb.Instance().Country(ip)
|
record, _ := mmdb.Instance().Country(ip)
|
||||||
return record.Country.IsoCode != "CN" && record.Country.IsoCode != ""
|
return !strings.EqualFold(record.Country.IsoCode, gf.code) && !ip.IsPrivate()
|
||||||
}
|
}
|
||||||
|
|
||||||
type ipnetFilter struct {
|
type ipnetFilter struct {
|
||||||
@ -29,6 +32,7 @@ func (inf *ipnetFilter) Match(ip net.IP) bool {
|
|||||||
type fallbackDomainFilter interface {
|
type fallbackDomainFilter interface {
|
||||||
Match(domain string) bool
|
Match(domain string) bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type domainFilter struct {
|
type domainFilter struct {
|
||||||
tree *trie.DomainTrie
|
tree *trie.DomainTrie
|
||||||
}
|
}
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user