Compare commits
130 Commits
Author | SHA1 | Date | |
---|---|---|---|
5b7f0de48b | |||
a5b950a779 | |||
a2d59d6ef5 | |||
8ef5cdb8be | |||
c7b718f651 | |||
ff56e5c5de | |||
661c417fce | |||
7d20097465 | |||
a20b9a3960 | |||
e0d3f926b7 | |||
121bc910f6 | |||
4522cdc551 | |||
410772e81c | |||
0267b2efad | |||
c6d375eda2 | |||
847f41952e | |||
47044ec0d8 | |||
426ca36118 | |||
571d2a0075 | |||
1be09f5751 | |||
2663cb2e6e | |||
9b0bbb90ff | |||
588645a2c3 | |||
1bfebd0d03 | |||
3705996974 | |||
09697b7679 | |||
4578b2c826 | |||
b3a293ab07 | |||
507ba16065 | |||
aa9f8a39a3 | |||
8d37220566 | |||
53e17a916b | |||
247dd84970 | |||
c2f3111922 | |||
44872300e9 | |||
91ed0118f6 | |||
a461c2306a | |||
46f4f84442 | |||
250a9f4f84 | |||
b4292d0972 | |||
d755383e39 | |||
dff1e8f1ce | |||
995aa7a8fc | |||
3ca5d17c40 | |||
244cb370a4 | |||
c35cb24bda | |||
b6ff08074c | |||
70d53fd45a | |||
f231a63e93 | |||
6091fcdfec | |||
bcfc15e398 | |||
045edc188c | |||
0778591524 | |||
d5e52bed43 | |||
06fdd3abe0 | |||
4e5898197a | |||
f96ebab99f | |||
3c54f99fea | |||
824f5bd731 | |||
3f3db8476e | |||
f375f080da | |||
e19e9ef5a4 | |||
682e65cb54 | |||
16a6d409d9 | |||
4186bcf1b2 | |||
df5112175f | |||
d9341a49ea | |||
4e9e4b6cde | |||
936b7012ba | |||
a9cbd9ec98 | |||
c9943fb857 | |||
a40274e2a2 | |||
b59d45c660 | |||
7b01e103c2 | |||
93a8acecce | |||
586bb91c0c | |||
baf03b81e3 | |||
9807e1189c | |||
3d5a0d9f73 | |||
cc96187f58 | |||
3aefa1d924 | |||
42e21b3733 | |||
0a35237915 | |||
a1f3a5ea26 | |||
e63f995258 | |||
d0c829c578 | |||
4ad9761b32 | |||
1f593d37fb | |||
109bfcb0f9 | |||
7ee49f5171 | |||
d759d16944 | |||
807d53c1e7 | |||
1355196b7c | |||
573316bcde | |||
784c28266c | |||
5da1b2a8aa | |||
0976d27cb1 | |||
6c83ff3496 | |||
f7f97ef625 | |||
5acdd72a1d | |||
f53686103d | |||
f63c9eb22f | |||
a37243cf30 | |||
b3c1b4a840 | |||
14bbf6eedc | |||
aa81193d5b | |||
9eb98e399d | |||
d48cfecf60 | |||
6036fb63ba | |||
cd48f69b1f | |||
fcc594ae26 | |||
f4de055aa1 | |||
35925cb3da | |||
ff430df845 | |||
e4cdea2111 | |||
b6ee47a541 | |||
b25009cde7 | |||
6fedd7ec84 | |||
9619c3fb20 | |||
02d029dd2d | |||
09c28e0355 | |||
3600077f3b | |||
de7656a787 | |||
5dfe7f8561 | |||
ed27898a33 | |||
532396d25c | |||
4b1b494164 | |||
0d33dc3eb9 | |||
994cbff215 | |||
bea2ee8bf2 |
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
|
||||||
|
"
|
30
.github/workflows/codeql-analysis.yml
vendored
Normal file
30
.github/workflows/codeql-analysis.yml
vendored
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
name: "CodeQL"
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ master, dev ]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
analyze:
|
||||||
|
name: Analyze
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
language: [ 'go' ]
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
|
- name: Initialize CodeQL
|
||||||
|
uses: github/codeql-action/init@v1
|
||||||
|
with:
|
||||||
|
languages: ${{ matrix.language }}
|
||||||
|
|
||||||
|
- name: Autobuild
|
||||||
|
uses: github/codeql-action/autobuild@v1
|
||||||
|
|
||||||
|
- name: Perform CodeQL Analysis
|
||||||
|
uses: github/codeql-action/analyze@v1
|
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: |
|
||||||
|
7
.github/workflows/go.yml
vendored
7
.github/workflows/go.yml
vendored
@ -9,7 +9,7 @@ jobs:
|
|||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@v2
|
uses: actions/setup-go@v2
|
||||||
with:
|
with:
|
||||||
go-version: 1.15.x
|
go-version: 1.17
|
||||||
|
|
||||||
- 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
|
||||||
@ -26,7 +26,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
go test ./...
|
go test ./...
|
||||||
go vet ./...
|
go vet ./...
|
||||||
go get -u honnef.co/go/tools/cmd/staticcheck
|
go install honnef.co/go/tools/cmd/staticcheck@latest
|
||||||
staticcheck -- $(go list ./...)
|
staticcheck -- $(go list ./...)
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
@ -39,9 +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
|
||||||
prerelease: 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
|
||||||
|
@ -10,7 +10,7 @@ RUN go mod download && \
|
|||||||
mv ./bin/clash-docker /clash
|
mv ./bin/clash-docker /clash
|
||||||
|
|
||||||
FROM alpine:latest
|
FROM alpine:latest
|
||||||
LABEL org.opencontainers.image.source https://github.com/Dreamacro/clash
|
LABEL org.opencontainers.image.source="https://github.com/Dreamacro/clash"
|
||||||
|
|
||||||
RUN apk add --no-cache ca-certificates
|
RUN apk add --no-cache ca-certificates
|
||||||
COPY --from=builder /Country.mmdb /root/.config/clash/
|
COPY --from=builder /Country.mmdb /root/.config/clash/
|
||||||
|
16
Makefile
16
Makefile
@ -8,6 +8,7 @@ GOBUILD=CGO_ENABLED=0 go build -trimpath -ldflags '-X "github.com/Dreamacro/clas
|
|||||||
|
|
||||||
PLATFORM_LIST = \
|
PLATFORM_LIST = \
|
||||||
darwin-amd64 \
|
darwin-amd64 \
|
||||||
|
darwin-arm64 \
|
||||||
linux-386 \
|
linux-386 \
|
||||||
linux-amd64 \
|
linux-amd64 \
|
||||||
linux-armv5 \
|
linux-armv5 \
|
||||||
@ -21,11 +22,13 @@ PLATFORM_LIST = \
|
|||||||
linux-mips64 \
|
linux-mips64 \
|
||||||
linux-mips64le \
|
linux-mips64le \
|
||||||
freebsd-386 \
|
freebsd-386 \
|
||||||
freebsd-amd64
|
freebsd-amd64 \
|
||||||
|
freebsd-arm64
|
||||||
|
|
||||||
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
|
||||||
@ -36,6 +39,9 @@ docker:
|
|||||||
darwin-amd64:
|
darwin-amd64:
|
||||||
GOARCH=amd64 GOOS=darwin $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
GOARCH=amd64 GOOS=darwin $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
||||||
|
|
||||||
|
darwin-arm64:
|
||||||
|
GOARCH=arm64 GOOS=darwin $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
||||||
|
|
||||||
linux-386:
|
linux-386:
|
||||||
GOARCH=386 GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
GOARCH=386 GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
||||||
|
|
||||||
@ -78,12 +84,18 @@ freebsd-386:
|
|||||||
freebsd-amd64:
|
freebsd-amd64:
|
||||||
GOARCH=amd64 GOOS=freebsd $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
GOARCH=amd64 GOOS=freebsd $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
||||||
|
|
||||||
|
freebsd-arm64:
|
||||||
|
GOARCH=arm64 GOOS=freebsd $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
||||||
|
|
||||||
windows-386:
|
windows-386:
|
||||||
GOARCH=386 GOOS=windows $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.exe
|
GOARCH=386 GOOS=windows $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.exe
|
||||||
|
|
||||||
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
|
||||||
|
|
||||||
|
13
README.md
13
README.md
@ -28,9 +28,21 @@
|
|||||||
- Netfilter TCP redirecting. Deploy Clash on your Internet gateway with `iptables`.
|
- Netfilter TCP redirecting. Deploy Clash on your Internet gateway with `iptables`.
|
||||||
- Comprehensive HTTP RESTful API controller
|
- Comprehensive HTTP RESTful API controller
|
||||||
|
|
||||||
|
## Premium Features
|
||||||
|
|
||||||
|
- TUN mode on macOS, Linux and Windows. [Doc](https://github.com/Dreamacro/clash/wiki/premium-core-features#tun-device)
|
||||||
|
- Match your tunnel by [Script](https://github.com/Dreamacro/clash/wiki/premium-core-features#script)
|
||||||
|
- [Rule Provider](https://github.com/Dreamacro/clash/wiki/premium-core-features#rule-providers)
|
||||||
|
|
||||||
## Getting Started
|
## Getting Started
|
||||||
Documentations are now moved to [GitHub Wiki](https://github.com/Dreamacro/clash/wiki).
|
Documentations are now moved to [GitHub Wiki](https://github.com/Dreamacro/clash/wiki).
|
||||||
|
|
||||||
|
## Premium Release
|
||||||
|
[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)
|
||||||
@ -48,4 +60,3 @@ This software is released under the GPL-3.0 license.
|
|||||||
- [x] Redir proxy
|
- [x] Redir proxy
|
||||||
- [x] UDP support
|
- [x] UDP support
|
||||||
- [x] Connection manager
|
- [x] Connection manager
|
||||||
- [ ] ~~Event API~~
|
|
||||||
|
@ -1,11 +1,12 @@
|
|||||||
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"
|
||||||
@ -14,101 +15,25 @@ import (
|
|||||||
"go.uber.org/atomic"
|
"go.uber.org/atomic"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Base struct {
|
|
||||||
name string
|
|
||||||
addr string
|
|
||||||
tp C.AdapterType
|
|
||||||
udp bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *Base) Name() string {
|
|
||||||
return b.name
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *Base) Type() C.AdapterType {
|
|
||||||
return b.tp
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *Base) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
|
||||||
return c, errors.New("no support")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *Base) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
|
|
||||||
return nil, errors.New("no support")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *Base) SupportUDP() bool {
|
|
||||||
return b.udp
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *Base) MarshalJSON() ([]byte, error) {
|
|
||||||
return json.Marshal(map[string]string{
|
|
||||||
"type": b.Type().String(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *Base) Addr() string {
|
|
||||||
return b.addr
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *conn) Chains() C.Chain {
|
|
||||||
return c.chain
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *packetConn) Chains() C.Chain {
|
|
||||||
return c.chain
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
||||||
alive *atomic.Bool
|
alive *atomic.Bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Alive implements C.Proxy
|
||||||
func (p *Proxy) Alive() bool {
|
func (p *Proxy) Alive() bool {
|
||||||
return p.alive.Load()
|
return p.alive.Load()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
func (p *Proxy) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
|
func (p *Proxy) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
|
||||||
conn, err := p.ProxyAdapter.DialContext(ctx, metadata)
|
conn, err := p.ProxyAdapter.DialContext(ctx, metadata)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -117,6 +42,7 @@ func (p *Proxy) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn,
|
|||||||
return conn, err
|
return conn, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DelayHistory implements C.Proxy
|
||||||
func (p *Proxy) DelayHistory() []C.DelayHistory {
|
func (p *Proxy) DelayHistory() []C.DelayHistory {
|
||||||
queue := p.history.Copy()
|
queue := p.history.Copy()
|
||||||
histories := []C.DelayHistory{}
|
histories := []C.DelayHistory{}
|
||||||
@ -127,6 +53,7 @@ func (p *Proxy) DelayHistory() []C.DelayHistory {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// LastDelay return last history record. if proxy is not alive, return the max value of uint16.
|
// LastDelay return last history record. if proxy is not alive, return the max value of uint16.
|
||||||
|
// implements C.Proxy
|
||||||
func (p *Proxy) LastDelay() (delay uint16) {
|
func (p *Proxy) LastDelay() (delay uint16) {
|
||||||
var max uint16 = 0xffff
|
var max uint16 = 0xffff
|
||||||
if !p.alive.Load() {
|
if !p.alive.Load() {
|
||||||
@ -144,6 +71,7 @@ func (p *Proxy) LastDelay() (delay uint16) {
|
|||||||
return history.Delay
|
return history.Delay
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MarshalJSON implements C.ProxyAdapter
|
||||||
func (p *Proxy) MarshalJSON() ([]byte, error) {
|
func (p *Proxy) MarshalJSON() ([]byte, error) {
|
||||||
inner, err := p.ProxyAdapter.MarshalJSON()
|
inner, err := p.ProxyAdapter.MarshalJSON()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -154,10 +82,12 @@ 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)
|
||||||
}
|
}
|
||||||
|
|
||||||
// URLTest get the delay for the specified URL
|
// URLTest get the delay for the specified URL
|
||||||
|
// implements C.Proxy
|
||||||
func (p *Proxy) URLTest(ctx context.Context, url string) (t uint16, err error) {
|
func (p *Proxy) URLTest(ctx context.Context, url string) (t uint16, err error) {
|
||||||
defer func() {
|
defer func() {
|
||||||
p.alive.Store(err == nil)
|
p.alive.Store(err == nil)
|
||||||
@ -206,6 +136,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
|
||||||
@ -218,3 +150,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 string, source net.Addr, conn net.Conn) *context.ConnContext {
|
||||||
|
metadata := parseSocksAddr(socks5.ParseAddr(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)
|
||||||
|
}
|
@ -5,18 +5,16 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
|
"github.com/Dreamacro/clash/context"
|
||||||
)
|
)
|
||||||
|
|
||||||
// NewHTTPS is HTTPAdapter generator
|
// NewHTTPS receive CONNECT request and return ConnContext
|
||||||
func NewHTTPS(request *http.Request, conn net.Conn) *SocketAdapter {
|
func NewHTTPS(request *http.Request, conn net.Conn) *context.ConnContext {
|
||||||
metadata := parseHTTPAddr(request)
|
metadata := parseHTTPAddr(request)
|
||||||
metadata.Type = C.HTTPCONNECT
|
metadata.Type = C.HTTPCONNECT
|
||||||
if ip, port, err := parseAddr(conn.RemoteAddr().String()); err == nil {
|
if ip, port, err := parseAddr(conn.RemoteAddr().String()); err == nil {
|
||||||
metadata.SrcIP = ip
|
metadata.SrcIP = ip
|
||||||
metadata.SrcPort = port
|
metadata.SrcPort = port
|
||||||
}
|
}
|
||||||
return &SocketAdapter{
|
return context.NewConnContext(conn, metadata)
|
||||||
metadata: metadata,
|
|
||||||
Conn: conn,
|
|
||||||
}
|
|
||||||
}
|
}
|
@ -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
|
22
adapter/inbound/socket.go
Normal file
22
adapter/inbound/socket.go
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
package inbound
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
|
||||||
|
C "github.com/Dreamacro/clash/constant"
|
||||||
|
"github.com/Dreamacro/clash/context"
|
||||||
|
"github.com/Dreamacro/clash/transport/socks5"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewSocket receive TCP inbound and return ConnContext
|
||||||
|
func NewSocket(target socks5.Addr, conn net.Conn, source C.Type) *context.ConnContext {
|
||||||
|
metadata := parseSocksAddr(target)
|
||||||
|
metadata.NetWork = C.TCP
|
||||||
|
metadata.Type = source
|
||||||
|
if ip, port, err := parseAddr(conn.RemoteAddr().String()); err == nil {
|
||||||
|
metadata.SrcIP = ip
|
||||||
|
metadata.SrcPort = port
|
||||||
|
}
|
||||||
|
|
||||||
|
return context.NewConnContext(conn, metadata)
|
||||||
|
}
|
@ -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 {
|
100
adapter/outbound/base.go
Normal file
100
adapter/outbound/base.go
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
package outbound
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"net"
|
||||||
|
|
||||||
|
C "github.com/Dreamacro/clash/constant"
|
||||||
|
)
|
||||||
|
|
||||||
|
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()}}
|
||||||
|
}
|
@ -12,10 +12,9 @@ type Direct struct {
|
|||||||
*Base
|
*Base
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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) (C.Conn, error) {
|
||||||
address := net.JoinHostPort(metadata.String(), metadata.DstPort)
|
c, err := dialer.DialContext(ctx, "tcp", metadata.RemoteAddress())
|
||||||
|
|
||||||
c, err := dialer.DialContext(ctx, "tcp", address)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -23,8 +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
|
||||||
func (d *Direct) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
|
func (d *Direct) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
|
||||||
pc, err := dialer.ListenPacket("udp", "")
|
pc, err := dialer.ListenPacket(context.Background(), "udp", "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
@ -35,6 +35,7 @@ type HttpOption struct {
|
|||||||
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
|
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// StreamConn implements C.ProxyAdapter
|
||||||
func (h *Http) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
func (h *Http) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
||||||
if h.tlsConfig != nil {
|
if h.tlsConfig != nil {
|
||||||
cc := tls.Client(c, h.tlsConfig)
|
cc := tls.Client(c, h.tlsConfig)
|
||||||
@ -51,13 +52,16 @@ func (h *Http) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
|||||||
return c, nil
|
return c, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Http) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
|
// DialContext implements C.ProxyAdapter
|
||||||
|
func (h *Http) DialContext(ctx context.Context, metadata *C.Metadata) (_ C.Conn, err error) {
|
||||||
c, err := dialer.DialContext(ctx, "tcp", h.addr)
|
c, err := dialer.DialContext(ctx, "tcp", h.addr)
|
||||||
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)
|
||||||
}
|
}
|
||||||
tcpKeepAlive(c)
|
tcpKeepAlive(c)
|
||||||
|
|
||||||
|
defer safeConnClose(c, err)
|
||||||
|
|
||||||
c, err = h.StreamConn(c, metadata)
|
c, err = h.StreamConn(c, metadata)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -121,7 +125,6 @@ func NewHttp(option HttpOption) *Http {
|
|||||||
}
|
}
|
||||||
tlsConfig = &tls.Config{
|
tlsConfig = &tls.Config{
|
||||||
InsecureSkipVerify: option.SkipCertVerify,
|
InsecureSkipVerify: option.SkipCertVerify,
|
||||||
ClientSessionCache: getClientSessionCache(),
|
|
||||||
ServerName: sni,
|
ServerName: sni,
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -14,10 +14,12 @@ type Reject struct {
|
|||||||
*Base
|
*Base
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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) (C.Conn, error) {
|
||||||
return NewConn(&NopConn{}, r), nil
|
return NewConn(&NopConn{}, r), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DialUDP implements C.ProxyAdapter
|
||||||
func (r *Reject) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
|
func (r *Reject) DialUDP(metadata *C.Metadata) (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"
|
||||||
)
|
)
|
||||||
@ -40,7 +39,7 @@ type ShadowSocksOption struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type simpleObfsOption struct {
|
type simpleObfsOption struct {
|
||||||
Mode string `obfs:"mode"`
|
Mode string `obfs:"mode,omitempty"`
|
||||||
Host string `obfs:"host,omitempty"`
|
Host string `obfs:"host,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -54,6 +53,7 @@ type v2rayObfsOption struct {
|
|||||||
Mux bool `obfs:"mux,omitempty"`
|
Mux bool `obfs:"mux,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// StreamConn implements C.ProxyAdapter
|
||||||
func (ss *ShadowSocks) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
func (ss *ShadowSocks) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
||||||
switch ss.obfsMode {
|
switch ss.obfsMode {
|
||||||
case "tls":
|
case "tls":
|
||||||
@ -73,25 +73,30 @@ func (ss *ShadowSocks) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, e
|
|||||||
return c, err
|
return c, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ss *ShadowSocks) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
|
// DialContext implements C.ProxyAdapter
|
||||||
|
func (ss *ShadowSocks) DialContext(ctx context.Context, metadata *C.Metadata) (_ C.Conn, err error) {
|
||||||
c, err := dialer.DialContext(ctx, "tcp", ss.addr)
|
c, err := dialer.DialContext(ctx, "tcp", ss.addr)
|
||||||
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)
|
||||||
}
|
}
|
||||||
tcpKeepAlive(c)
|
tcpKeepAlive(c)
|
||||||
|
|
||||||
|
defer safeConnClose(c, err)
|
||||||
|
|
||||||
c, err = ss.StreamConn(c, metadata)
|
c, err = ss.StreamConn(c, metadata)
|
||||||
return NewConn(c, ss), err
|
return NewConn(c, ss), err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DialUDP implements C.ProxyAdapter
|
||||||
func (ss *ShadowSocks) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
|
func (ss *ShadowSocks) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
|
||||||
pc, err := dialer.ListenPacket("udp", "")
|
pc, err := dialer.ListenPacket(context.Background(), "udp", "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
addr, err := resolveUDPAddr("udp", ss.addr)
|
addr, err := resolveUDPAddr("udp", ss.addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
pc.Close()
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -99,12 +104,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
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
||||||
@ -150,7 +149,6 @@ 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()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -2,22 +2,23 @@ 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/shadowstream"
|
"github.com/Dreamacro/go-shadowsocks2/shadowstream"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ShadowSocksR struct {
|
type ShadowSocksR struct {
|
||||||
*Base
|
*Base
|
||||||
cipher *core.StreamCipher
|
cipher core.Cipher
|
||||||
obfs obfs.Obfs
|
obfs obfs.Obfs
|
||||||
protocol protocol.Protocol
|
protocol protocol.Protocol
|
||||||
}
|
}
|
||||||
@ -35,90 +36,103 @@ type ShadowSocksROption struct {
|
|||||||
UDP bool `proxy:"udp,omitempty"`
|
UDP bool `proxy:"udp,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// StreamConn implements C.ProxyAdapter
|
||||||
func (ssr *ShadowSocksR) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
func (ssr *ShadowSocksR) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
||||||
c = obfs.NewConn(c, ssr.obfs)
|
c = ssr.obfs.StreamConn(c)
|
||||||
c = ssr.cipher.StreamConn(c)
|
c = ssr.cipher.StreamConn(c)
|
||||||
conn, ok := c.(*shadowstream.Conn)
|
var (
|
||||||
if !ok {
|
iv []byte
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
switch conn := c.(type) {
|
||||||
|
case *shadowstream.Conn:
|
||||||
|
iv, err = conn.ObtainWriteIV()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
case *shadowaead.Conn:
|
||||||
return nil, fmt.Errorf("invalid connection type")
|
return nil, fmt.Errorf("invalid connection type")
|
||||||
}
|
}
|
||||||
iv, err := conn.ObtainWriteIV()
|
c = ssr.protocol.StreamConn(c, iv)
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
c = protocol.NewConn(c, ssr.protocol, iv)
|
|
||||||
_, err = c.Write(serializesSocksAddr(metadata))
|
_, err = c.Write(serializesSocksAddr(metadata))
|
||||||
return c, err
|
return c, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ssr *ShadowSocksR) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
|
// DialContext implements C.ProxyAdapter
|
||||||
|
func (ssr *ShadowSocksR) DialContext(ctx context.Context, metadata *C.Metadata) (_ C.Conn, err error) {
|
||||||
c, err := dialer.DialContext(ctx, "tcp", ssr.addr)
|
c, err := dialer.DialContext(ctx, "tcp", ssr.addr)
|
||||||
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)
|
||||||
}
|
}
|
||||||
tcpKeepAlive(c)
|
tcpKeepAlive(c)
|
||||||
|
|
||||||
|
defer safeConnClose(c, err)
|
||||||
|
|
||||||
c, err = ssr.StreamConn(c, metadata)
|
c, err = ssr.StreamConn(c, metadata)
|
||||||
return NewConn(c, ssr), err
|
return NewConn(c, ssr), err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DialUDP implements C.ProxyAdapter
|
||||||
func (ssr *ShadowSocksR) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
|
func (ssr *ShadowSocksR) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
|
||||||
pc, err := dialer.ListenPacket("udp", "")
|
pc, err := dialer.ListenPacket(context.Background(), "udp", "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
addr, err := resolveUDPAddr("udp", ssr.addr)
|
addr, err := resolveUDPAddr("udp", ssr.addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
pc.Close()
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
pc = ssr.cipher.PacketConn(pc)
|
pc = ssr.cipher.PacketConn(pc)
|
||||||
pc = protocol.NewPacketConn(pc, ssr.protocol)
|
pc = ssr.protocol.PacketConn(pc)
|
||||||
return newPacketConn(&ssPacketConn{PacketConn: pc, rAddr: addr}, ssr), nil
|
return newPacketConn(&ssPacketConn{PacketConn: pc, rAddr: addr}, ssr), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
||||||
password := option.Password
|
password := option.Password
|
||||||
coreCiph, err := core.PickCipher(cipher, nil, password)
|
coreCiph, err := core.PickCipher(cipher, nil, password)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("ssr %s initialize cipher error: %w", addr, err)
|
return nil, fmt.Errorf("ssr %s initialize error: %w", addr, err)
|
||||||
}
|
}
|
||||||
ciph, ok := coreCiph.(*core.StreamCipher)
|
var (
|
||||||
if !ok {
|
ivSize int
|
||||||
return nil, fmt.Errorf("%s is not a supported stream cipher in ssr", cipher)
|
key []byte
|
||||||
|
)
|
||||||
|
if option.Cipher == "dummy" {
|
||||||
|
ivSize = 0
|
||||||
|
key = core.Kdf(option.Password, 16)
|
||||||
|
} else {
|
||||||
|
ciph, ok := coreCiph.(*core.StreamCipher)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("%s is not dummy or a supported stream cipher in ssr", cipher)
|
||||||
|
}
|
||||||
|
ivSize = ciph.IVSize()
|
||||||
|
key = ciph.Key
|
||||||
}
|
}
|
||||||
|
|
||||||
obfs, err := obfs.PickObfs(option.Obfs, &obfs.Base{
|
obfs, obfsOverhead, err := obfs.PickObfs(option.Obfs, &obfs.Base{
|
||||||
IVSize: ciph.IVSize(),
|
Host: option.Server,
|
||||||
Key: ciph.Key,
|
Port: option.Port,
|
||||||
HeadLen: 30,
|
Key: key,
|
||||||
Host: option.Server,
|
IVSize: ivSize,
|
||||||
Port: option.Port,
|
Param: option.ObfsParam,
|
||||||
Param: option.ObfsParam,
|
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("ssr %s initialize obfs error: %w", addr, err)
|
return nil, fmt.Errorf("ssr %s initialize obfs error: %w", addr, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
protocol, err := protocol.PickProtocol(option.Protocol, &protocol.Base{
|
protocol, err := protocol.PickProtocol(option.Protocol, &protocol.Base{
|
||||||
IV: nil,
|
Key: key,
|
||||||
Key: ciph.Key,
|
Overhead: obfsOverhead,
|
||||||
TCPMss: 1460,
|
Param: option.ProtocolParam,
|
||||||
Param: option.ProtocolParam,
|
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("ssr %s initialize protocol error: %w", addr, err)
|
return nil, fmt.Errorf("ssr %s initialize protocol error: %w", addr, err)
|
||||||
}
|
}
|
||||||
protocol.SetOverhead(obfs.GetObfsOverhead() + protocol.GetProtocolOverhead())
|
|
||||||
|
|
||||||
return &ShadowSocksR{
|
return &ShadowSocksR{
|
||||||
Base: &Base{
|
Base: &Base{
|
||||||
@ -127,7 +141,7 @@ func NewShadowSocksR(option ShadowSocksROption) (*ShadowSocksR, error) {
|
|||||||
tp: C.ShadowsocksR,
|
tp: C.ShadowsocksR,
|
||||||
udp: option.UDP,
|
udp: option.UDP,
|
||||||
},
|
},
|
||||||
cipher: ciph,
|
cipher: coreCiph,
|
||||||
obfs: obfs,
|
obfs: obfs,
|
||||||
protocol: protocol,
|
protocol: protocol,
|
||||||
}, nil
|
}, nil
|
@ -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 {
|
||||||
@ -48,6 +48,7 @@ func streamConn(c net.Conn, option streamOption) *snell.Snell {
|
|||||||
return snell.StreamConn(c, option.psk, option.version)
|
return snell.StreamConn(c, option.psk, option.version)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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.Atoi(metadata.DstPort)
|
||||||
@ -55,7 +56,8 @@ func (s *Snell) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
|||||||
return c, err
|
return c, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Snell) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
|
// DialContext implements C.ProxyAdapter
|
||||||
|
func (s *Snell) DialContext(ctx context.Context, metadata *C.Metadata) (_ C.Conn, err error) {
|
||||||
if s.version == snell.Version2 {
|
if s.version == snell.Version2 {
|
||||||
c, err := s.pool.Get()
|
c, err := s.pool.Get()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -63,7 +65,10 @@ func (s *Snell) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn,
|
|||||||
}
|
}
|
||||||
|
|
||||||
port, _ := strconv.Atoi(metadata.DstPort)
|
port, _ := strconv.Atoi(metadata.DstPort)
|
||||||
err = snell.WriteHeader(c, metadata.String(), uint(port), s.version)
|
if err = snell.WriteHeader(c, metadata.String(), uint(port), s.version); err != nil {
|
||||||
|
c.Close()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
return NewConn(c, s), err
|
return NewConn(c, s), err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -73,6 +78,8 @@ func (s *Snell) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn,
|
|||||||
}
|
}
|
||||||
tcpKeepAlive(c)
|
tcpKeepAlive(c)
|
||||||
|
|
||||||
|
defer safeConnClose(c, err)
|
||||||
|
|
||||||
c, err = s.StreamConn(c, metadata)
|
c, err = s.StreamConn(c, metadata)
|
||||||
return NewConn(c, s), err
|
return NewConn(c, s), err
|
||||||
}
|
}
|
||||||
@ -87,7 +94,10 @@ func NewSnell(option SnellOption) (*Snell, error) {
|
|||||||
return nil, fmt.Errorf("snell %s initialize obfs error: %w", addr, err)
|
return nil, fmt.Errorf("snell %s initialize obfs error: %w", addr, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if obfsOption.Mode != "tls" && obfsOption.Mode != "http" {
|
switch obfsOption.Mode {
|
||||||
|
case "tls", "http", "":
|
||||||
|
break
|
||||||
|
default:
|
||||||
return nil, fmt.Errorf("snell %s obfs mode error: %s", addr, obfsOption.Mode)
|
return nil, fmt.Errorf("snell %s obfs mode error: %s", addr, obfsOption.Mode)
|
||||||
}
|
}
|
||||||
|
|
@ -11,8 +11,8 @@ import (
|
|||||||
"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 {
|
||||||
@ -35,6 +35,7 @@ type Socks5Option struct {
|
|||||||
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
|
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// StreamConn implements C.ProxyAdapter
|
||||||
func (ss *Socks5) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
func (ss *Socks5) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
||||||
if ss.tls {
|
if ss.tls {
|
||||||
cc := tls.Client(c, ss.tlsConfig)
|
cc := tls.Client(c, ss.tlsConfig)
|
||||||
@ -58,13 +59,16 @@ func (ss *Socks5) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error)
|
|||||||
return c, nil
|
return c, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ss *Socks5) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
|
// DialContext implements C.ProxyAdapter
|
||||||
|
func (ss *Socks5) DialContext(ctx context.Context, metadata *C.Metadata) (_ C.Conn, err error) {
|
||||||
c, err := dialer.DialContext(ctx, "tcp", ss.addr)
|
c, err := dialer.DialContext(ctx, "tcp", ss.addr)
|
||||||
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)
|
||||||
}
|
}
|
||||||
tcpKeepAlive(c)
|
tcpKeepAlive(c)
|
||||||
|
|
||||||
|
defer safeConnClose(c, err)
|
||||||
|
|
||||||
c, err = ss.StreamConn(c, metadata)
|
c, err = ss.StreamConn(c, metadata)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -73,8 +77,9 @@ func (ss *Socks5) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn
|
|||||||
return NewConn(c, ss), nil
|
return NewConn(c, ss), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DialUDP implements C.ProxyAdapter
|
||||||
func (ss *Socks5) DialUDP(metadata *C.Metadata) (_ C.PacketConn, err error) {
|
func (ss *Socks5) DialUDP(metadata *C.Metadata) (_ C.PacketConn, err error) {
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), tcpTimeout)
|
ctx, cancel := context.WithTimeout(context.Background(), C.DefaultTCPTimeout)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
c, err := dialer.DialContext(ctx, "tcp", ss.addr)
|
c, err := dialer.DialContext(ctx, "tcp", ss.addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -88,11 +93,7 @@ func (ss *Socks5) DialUDP(metadata *C.Metadata) (_ C.PacketConn, err error) {
|
|||||||
c = cc
|
c = cc
|
||||||
}
|
}
|
||||||
|
|
||||||
defer func() {
|
defer safeConnClose(c, err)
|
||||||
if err != nil {
|
|
||||||
c.Close()
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
tcpKeepAlive(c)
|
tcpKeepAlive(c)
|
||||||
var user *socks5.User
|
var user *socks5.User
|
||||||
@ -109,7 +110,7 @@ func (ss *Socks5) DialUDP(metadata *C.Metadata) (_ C.PacketConn, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
pc, err := dialer.ListenPacket("udp", "")
|
pc, err := dialer.ListenPacket(context.Background(), "udp", "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -122,7 +123,21 @@ func (ss *Socks5) DialUDP(metadata *C.Metadata) (_ C.PacketConn, err error) {
|
|||||||
pc.Close()
|
pc.Close()
|
||||||
}()
|
}()
|
||||||
|
|
||||||
return newPacketConn(&socksPacketConn{PacketConn: pc, rAddr: bindAddr.UDPAddr(), tcpConn: c}, ss), nil
|
// Support unspecified UDP bind address.
|
||||||
|
bindUDPAddr := bindAddr.UDPAddr()
|
||||||
|
if bindUDPAddr == nil {
|
||||||
|
err = errors.New("invalid UDP bind address")
|
||||||
|
return
|
||||||
|
} else if bindUDPAddr.IP.IsUnspecified() {
|
||||||
|
serverAddr, err := resolveUDPAddr("udp", ss.Addr())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
bindUDPAddr.IP = serverAddr.IP
|
||||||
|
}
|
||||||
|
|
||||||
|
return newPacketConn(&socksPacketConn{PacketConn: pc, rAddr: bindUDPAddr, tcpConn: c}, ss), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewSocks5(option Socks5Option) *Socks5 {
|
func NewSocks5(option Socks5Option) *Socks5 {
|
||||||
@ -130,7 +145,6 @@ 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,
|
||||||
}
|
}
|
||||||
}
|
}
|
176
adapter/outbound/trojan.go
Normal file
176
adapter/outbound/trojan.go
Normal file
@ -0,0 +1,176 @@
|
|||||||
|
package outbound
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/tls"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/Dreamacro/clash/component/dialer"
|
||||||
|
C "github.com/Dreamacro/clash/constant"
|
||||||
|
"github.com/Dreamacro/clash/transport/gun"
|
||||||
|
"github.com/Dreamacro/clash/transport/trojan"
|
||||||
|
|
||||||
|
"golang.org/x/net/http2"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Trojan struct {
|
||||||
|
*Base
|
||||||
|
instance *trojan.Trojan
|
||||||
|
|
||||||
|
// for gun mux
|
||||||
|
gunTLSConfig *tls.Config
|
||||||
|
gunConfig *gun.Config
|
||||||
|
transport *http2.Transport
|
||||||
|
}
|
||||||
|
|
||||||
|
type TrojanOption struct {
|
||||||
|
Name string `proxy:"name"`
|
||||||
|
Server string `proxy:"server"`
|
||||||
|
Port int `proxy:"port"`
|
||||||
|
Password string `proxy:"password"`
|
||||||
|
ALPN []string `proxy:"alpn,omitempty"`
|
||||||
|
SNI string `proxy:"sni,omitempty"`
|
||||||
|
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
|
||||||
|
UDP bool `proxy:"udp,omitempty"`
|
||||||
|
Network string `proxy:"network,omitempty"`
|
||||||
|
GrpcOpts GrpcOptions `proxy:"grpc-opts,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// StreamConn implements C.ProxyAdapter
|
||||||
|
func (t *Trojan) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
||||||
|
var err error
|
||||||
|
if t.transport != nil {
|
||||||
|
c, err = gun.StreamGunWithConn(c, t.gunTLSConfig, t.gunConfig)
|
||||||
|
} else {
|
||||||
|
c, err = t.instance.StreamConn(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("%s connect error: %w", t.addr, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = t.instance.WriteHeader(c, trojan.CommandTCP, serializesSocksAddr(metadata))
|
||||||
|
return c, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// DialContext implements C.ProxyAdapter
|
||||||
|
func (t *Trojan) DialContext(ctx context.Context, metadata *C.Metadata) (_ C.Conn, err error) {
|
||||||
|
// gun transport
|
||||||
|
if t.transport != nil {
|
||||||
|
c, err := gun.StreamGunWithTransport(t.transport, t.gunConfig)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = t.instance.WriteHeader(c, trojan.CommandTCP, serializesSocksAddr(metadata)); err != nil {
|
||||||
|
c.Close()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return NewConn(c, t), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
c, err := dialer.DialContext(ctx, "tcp", t.addr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("%s connect error: %w", t.addr, err)
|
||||||
|
}
|
||||||
|
tcpKeepAlive(c)
|
||||||
|
|
||||||
|
defer safeConnClose(c, err)
|
||||||
|
|
||||||
|
c, err = t.StreamConn(c, metadata)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return NewConn(c, t), err
|
||||||
|
}
|
||||||
|
|
||||||
|
// DialUDP implements C.ProxyAdapter
|
||||||
|
func (t *Trojan) DialUDP(metadata *C.Metadata) (_ C.PacketConn, err error) {
|
||||||
|
var c net.Conn
|
||||||
|
|
||||||
|
// grpc transport
|
||||||
|
if t.transport != nil {
|
||||||
|
c, err = gun.StreamGunWithTransport(t.transport, t.gunConfig)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("%s connect error: %w", t.addr, err)
|
||||||
|
}
|
||||||
|
defer safeConnClose(c, err)
|
||||||
|
} else {
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), C.DefaultTCPTimeout)
|
||||||
|
defer cancel()
|
||||||
|
c, err = dialer.DialContext(ctx, "tcp", t.addr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("%s connect error: %w", t.addr, err)
|
||||||
|
}
|
||||||
|
defer safeConnClose(c, err)
|
||||||
|
tcpKeepAlive(c)
|
||||||
|
c, err = t.instance.StreamConn(c)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("%s connect error: %w", t.addr, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err = t.instance.WriteHeader(c, trojan.CommandUDP, serializesSocksAddr(metadata))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
pc := t.instance.PacketConn(c)
|
||||||
|
return newPacketConn(pc, t), err
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewTrojan(option TrojanOption) (*Trojan, error) {
|
||||||
|
addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port))
|
||||||
|
|
||||||
|
tOption := &trojan.Option{
|
||||||
|
Password: option.Password,
|
||||||
|
ALPN: option.ALPN,
|
||||||
|
ServerName: option.Server,
|
||||||
|
SkipCertVerify: option.SkipCertVerify,
|
||||||
|
}
|
||||||
|
|
||||||
|
if option.SNI != "" {
|
||||||
|
tOption.ServerName = option.SNI
|
||||||
|
}
|
||||||
|
|
||||||
|
t := &Trojan{
|
||||||
|
Base: &Base{
|
||||||
|
name: option.Name,
|
||||||
|
addr: addr,
|
||||||
|
tp: C.Trojan,
|
||||||
|
udp: option.UDP,
|
||||||
|
},
|
||||||
|
instance: trojan.New(tOption),
|
||||||
|
}
|
||||||
|
|
||||||
|
if option.Network == "grpc" {
|
||||||
|
dialFn := func(network, addr string) (net.Conn, error) {
|
||||||
|
c, err := dialer.DialContext(context.Background(), "tcp", t.addr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("%s connect error: %s", t.addr, err.Error())
|
||||||
|
}
|
||||||
|
tcpKeepAlive(c)
|
||||||
|
return c, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
tlsConfig := &tls.Config{
|
||||||
|
NextProtos: option.ALPN,
|
||||||
|
MinVersion: tls.VersionTLS12,
|
||||||
|
InsecureSkipVerify: tOption.SkipCertVerify,
|
||||||
|
ServerName: tOption.ServerName,
|
||||||
|
}
|
||||||
|
|
||||||
|
t.transport = gun.NewHTTP2Client(dialFn, tlsConfig)
|
||||||
|
t.gunTLSConfig = tlsConfig
|
||||||
|
t.gunConfig = &gun.Config{
|
||||||
|
ServiceName: option.GrpcOpts.GrpcServiceName,
|
||||||
|
Host: tOption.ServerName,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return t, nil
|
||||||
|
}
|
@ -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,13 +18,6 @@ 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)
|
||||||
@ -98,3 +50,9 @@ func resolveUDPAddr(network, address string) (*net.UDPAddr, error) {
|
|||||||
}
|
}
|
||||||
return net.ResolveUDPAddr(network, net.JoinHostPort(ip.String(), port))
|
return net.ResolveUDPAddr(network, net.JoinHostPort(ip.String(), port))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func safeConnClose(c net.Conn, err error) {
|
||||||
|
if err != nil {
|
||||||
|
c.Close()
|
||||||
|
}
|
||||||
|
}
|
@ -2,6 +2,7 @@ package outbound
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"crypto/tls"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
@ -11,14 +12,22 @@ import (
|
|||||||
|
|
||||||
"github.com/Dreamacro/clash/component/dialer"
|
"github.com/Dreamacro/clash/component/dialer"
|
||||||
"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"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Vmess struct {
|
type Vmess struct {
|
||||||
*Base
|
*Base
|
||||||
client *vmess.Client
|
client *vmess.Client
|
||||||
option *VmessOption
|
option *VmessOption
|
||||||
|
|
||||||
|
// for gun mux
|
||||||
|
gunTLSConfig *tls.Config
|
||||||
|
gunConfig *gun.Config
|
||||||
|
transport *http2.Transport
|
||||||
}
|
}
|
||||||
|
|
||||||
type VmessOption struct {
|
type VmessOption struct {
|
||||||
@ -33,6 +42,8 @@ type VmessOption struct {
|
|||||||
Network string `proxy:"network,omitempty"`
|
Network string `proxy:"network,omitempty"`
|
||||||
HTTPOpts HTTPOptions `proxy:"http-opts,omitempty"`
|
HTTPOpts HTTPOptions `proxy:"http-opts,omitempty"`
|
||||||
HTTP2Opts HTTP2Options `proxy:"h2-opts,omitempty"`
|
HTTP2Opts HTTP2Options `proxy:"h2-opts,omitempty"`
|
||||||
|
GrpcOpts GrpcOptions `proxy:"grpc-opts,omitempty"`
|
||||||
|
WSOpts WSOptions `proxy:"ws-opts,omitempty"`
|
||||||
WSPath string `proxy:"ws-path,omitempty"`
|
WSPath string `proxy:"ws-path,omitempty"`
|
||||||
WSHeaders map[string]string `proxy:"ws-headers,omitempty"`
|
WSHeaders map[string]string `proxy:"ws-headers,omitempty"`
|
||||||
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
|
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
|
||||||
@ -50,18 +61,39 @@ type HTTP2Options struct {
|
|||||||
Path string `proxy:"path,omitempty"`
|
Path string `proxy:"path,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type GrpcOptions struct {
|
||||||
|
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
|
||||||
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.WSHeaders {
|
||||||
header.Add(key, value)
|
header.Add(key, value)
|
||||||
@ -71,7 +103,6 @@ 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.SkipCertVerify = v.option.SkipCertVerify
|
wsOpts.SkipCertVerify = v.option.SkipCertVerify
|
||||||
wsOpts.ServerName = v.option.ServerName
|
wsOpts.ServerName = v.option.ServerName
|
||||||
}
|
}
|
||||||
@ -83,7 +114,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 != "" {
|
||||||
@ -110,7 +140,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"},
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -129,6 +158,8 @@ func (v *Vmess) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
c, err = vmess.StreamH2Conn(c, h2Opts)
|
c, err = vmess.StreamH2Conn(c, h2Opts)
|
||||||
|
case "grpc":
|
||||||
|
c, err = gun.StreamGunWithConn(c, v.gunTLSConfig, v.gunConfig)
|
||||||
default:
|
default:
|
||||||
// handle TLS
|
// handle TLS
|
||||||
if v.option.TLS {
|
if v.option.TLS {
|
||||||
@ -136,7 +167,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 != "" {
|
||||||
@ -154,19 +184,38 @@ func (v *Vmess) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
|||||||
return v.client.StreamConn(c, parseVmessAddr(metadata))
|
return v.client.StreamConn(c, parseVmessAddr(metadata))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *Vmess) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
|
// DialContext implements C.ProxyAdapter
|
||||||
|
func (v *Vmess) DialContext(ctx context.Context, metadata *C.Metadata) (_ C.Conn, err error) {
|
||||||
|
// gun transport
|
||||||
|
if v.transport != nil {
|
||||||
|
c, err := gun.StreamGunWithTransport(v.transport, v.gunConfig)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer safeConnClose(c, err)
|
||||||
|
|
||||||
|
c, err = v.client.StreamConn(c, parseVmessAddr(metadata))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return NewConn(c, v), nil
|
||||||
|
}
|
||||||
|
|
||||||
c, err := dialer.DialContext(ctx, "tcp", v.addr)
|
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())
|
||||||
}
|
}
|
||||||
tcpKeepAlive(c)
|
tcpKeepAlive(c)
|
||||||
|
defer safeConnClose(c, err)
|
||||||
|
|
||||||
c, err = v.StreamConn(c, metadata)
|
c, err = v.StreamConn(c, metadata)
|
||||||
return NewConn(c, v), err
|
return NewConn(c, v), err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *Vmess) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
|
// DialUDP implements C.ProxyAdapter
|
||||||
// vmess use stream-oriented udp, so clash needs a net.UDPAddr
|
func (v *Vmess) DialUDP(metadata *C.Metadata) (_ C.PacketConn, err error) {
|
||||||
|
// 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)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -175,17 +224,33 @@ func (v *Vmess) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
|
|||||||
metadata.DstIP = ip
|
metadata.DstIP = ip
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), tcpTimeout)
|
var c net.Conn
|
||||||
defer cancel()
|
// gun transport
|
||||||
c, err := dialer.DialContext(ctx, "tcp", v.addr)
|
if v.transport != nil {
|
||||||
if err != nil {
|
c, err = gun.StreamGunWithTransport(v.transport, v.gunConfig)
|
||||||
return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error())
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer safeConnClose(c, err)
|
||||||
|
|
||||||
|
c, err = v.client.StreamConn(c, parseVmessAddr(metadata))
|
||||||
|
} else {
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), C.DefaultTCPTimeout)
|
||||||
|
defer cancel()
|
||||||
|
c, err = dialer.DialContext(ctx, "tcp", v.addr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error())
|
||||||
|
}
|
||||||
|
tcpKeepAlive(c)
|
||||||
|
defer safeConnClose(c, err)
|
||||||
|
|
||||||
|
c, err = v.StreamConn(c, metadata)
|
||||||
}
|
}
|
||||||
tcpKeepAlive(c)
|
|
||||||
c, err = v.StreamConn(c, metadata)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("new vmess client error: %v", err)
|
return nil, fmt.Errorf("new vmess client error: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return newPacketConn(&vmessPacketConn{Conn: c, rAddr: metadata.UDPAddr()}, v), nil
|
return newPacketConn(&vmessPacketConn{Conn: c, rAddr: metadata.UDPAddr()}, v), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -197,15 +262,20 @@ func NewVmess(option VmessOption) (*Vmess, error) {
|
|||||||
Security: security,
|
Security: security,
|
||||||
HostName: option.Server,
|
HostName: option.Server,
|
||||||
Port: strconv.Itoa(option.Port),
|
Port: strconv.Itoa(option.Port),
|
||||||
|
IsAead: option.AlterID == 0,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if option.Network == "h2" && !option.TLS {
|
|
||||||
return nil, fmt.Errorf("TLS must be true with h2 network")
|
switch option.Network {
|
||||||
|
case "h2", "grpc":
|
||||||
|
if !option.TLS {
|
||||||
|
return nil, fmt.Errorf("TLS must be true with h2/grpc network")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return &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)),
|
||||||
@ -214,7 +284,44 @@ func NewVmess(option VmessOption) (*Vmess, error) {
|
|||||||
},
|
},
|
||||||
client: client,
|
client: client,
|
||||||
option: &option,
|
option: &option,
|
||||||
}, nil
|
}
|
||||||
|
|
||||||
|
switch option.Network {
|
||||||
|
case "h2":
|
||||||
|
if len(option.HTTP2Opts.Host) == 0 {
|
||||||
|
option.HTTP2Opts.Host = append(option.HTTP2Opts.Host, "www.example.com")
|
||||||
|
}
|
||||||
|
case "grpc":
|
||||||
|
dialFn := func(network, addr string) (net.Conn, error) {
|
||||||
|
c, err := dialer.DialContext(context.Background(), "tcp", v.addr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error())
|
||||||
|
}
|
||||||
|
tcpKeepAlive(c)
|
||||||
|
return c, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
gunConfig := &gun.Config{
|
||||||
|
ServiceName: v.option.GrpcOpts.GrpcServiceName,
|
||||||
|
Host: v.option.ServerName,
|
||||||
|
}
|
||||||
|
tlsConfig := &tls.Config{
|
||||||
|
InsecureSkipVerify: v.option.SkipCertVerify,
|
||||||
|
ServerName: v.option.ServerName,
|
||||||
|
}
|
||||||
|
|
||||||
|
if v.option.ServerName == "" {
|
||||||
|
host, _, _ := net.SplitHostPort(v.addr)
|
||||||
|
tlsConfig.ServerName = host
|
||||||
|
gunConfig.Host = host
|
||||||
|
}
|
||||||
|
|
||||||
|
v.gunTLSConfig = tlsConfig
|
||||||
|
v.gunConfig = gunConfig
|
||||||
|
v.transport = gun.NewHTTP2Client(dialFn, tlsConfig)
|
||||||
|
}
|
||||||
|
|
||||||
|
return v, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseVmessAddr(metadata *C.Metadata) *vmess.DstAddr {
|
func parseVmessAddr(metadata *C.Metadata) *vmess.DstAddr {
|
@ -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,10 @@ 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"
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
|
"github.com/Dreamacro/clash/constant/provider"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Fallback struct {
|
type Fallback struct {
|
||||||
@ -22,6 +22,7 @@ func (f *Fallback) Now() string {
|
|||||||
return proxy.Name()
|
return proxy.Name()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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) (C.Conn, error) {
|
||||||
proxy := f.findAliveProxy(true)
|
proxy := f.findAliveProxy(true)
|
||||||
c, err := proxy.DialContext(ctx, metadata)
|
c, err := proxy.DialContext(ctx, metadata)
|
||||||
@ -31,6 +32,7 @@ func (f *Fallback) DialContext(ctx context.Context, metadata *C.Metadata) (C.Con
|
|||||||
return c, err
|
return c, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DialUDP implements C.ProxyAdapter
|
||||||
func (f *Fallback) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
|
func (f *Fallback) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
|
||||||
proxy := f.findAliveProxy(true)
|
proxy := f.findAliveProxy(true)
|
||||||
pc, err := proxy.DialUDP(metadata)
|
pc, err := proxy.DialUDP(metadata)
|
||||||
@ -40,6 +42,7 @@ func (f *Fallback) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
|
|||||||
return pc, err
|
return pc, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SupportUDP implements C.ProxyAdapter
|
||||||
func (f *Fallback) SupportUDP() bool {
|
func (f *Fallback) SupportUDP() bool {
|
||||||
if f.disableUDP {
|
if f.disableUDP {
|
||||||
return false
|
return false
|
||||||
@ -49,6 +52,7 @@ func (f *Fallback) SupportUDP() bool {
|
|||||||
return proxy.SupportUDP()
|
return proxy.SupportUDP()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MarshalJSON implements C.ProxyAdapter
|
||||||
func (f *Fallback) MarshalJSON() ([]byte, error) {
|
func (f *Fallback) MarshalJSON() ([]byte, error) {
|
||||||
var all []string
|
var all []string
|
||||||
for _, proxy := range f.proxies(false) {
|
for _, proxy := range f.proxies(false) {
|
||||||
@ -61,6 +65,7 @@ func (f *Fallback) MarshalJSON() ([]byte, error) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Unwrap implements C.ProxyAdapter
|
||||||
func (f *Fallback) Unwrap(metadata *C.Metadata) C.Proxy {
|
func (f *Fallback) Unwrap(metadata *C.Metadata) C.Proxy {
|
||||||
proxy := f.findAliveProxy(true)
|
proxy := f.findAliveProxy(true)
|
||||||
return proxy
|
return proxy
|
@ -7,11 +7,11 @@ 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"
|
||||||
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"
|
||||||
)
|
)
|
||||||
@ -68,6 +68,7 @@ func jumpHash(key uint64, buckets int32) int32 {
|
|||||||
return int32(b)
|
return int32(b)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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) (c C.Conn, err error) {
|
||||||
defer func() {
|
defer func() {
|
||||||
if err == nil {
|
if err == nil {
|
||||||
@ -81,6 +82,7 @@ func (lb *LoadBalance) DialContext(ctx context.Context, metadata *C.Metadata) (c
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DialUDP implements C.ProxyAdapter
|
||||||
func (lb *LoadBalance) DialUDP(metadata *C.Metadata) (pc C.PacketConn, err error) {
|
func (lb *LoadBalance) DialUDP(metadata *C.Metadata) (pc C.PacketConn, err error) {
|
||||||
defer func() {
|
defer func() {
|
||||||
if err == nil {
|
if err == nil {
|
||||||
@ -93,6 +95,7 @@ func (lb *LoadBalance) DialUDP(metadata *C.Metadata) (pc C.PacketConn, err error
|
|||||||
return proxy.DialUDP(metadata)
|
return proxy.DialUDP(metadata)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SupportUDP implements C.ProxyAdapter
|
||||||
func (lb *LoadBalance) SupportUDP() bool {
|
func (lb *LoadBalance) SupportUDP() bool {
|
||||||
return !lb.disableUDP
|
return !lb.disableUDP
|
||||||
}
|
}
|
||||||
@ -130,6 +133,7 @@ func strategyConsistentHashing() strategyFn {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Unwrap implements C.ProxyAdapter
|
||||||
func (lb *LoadBalance) Unwrap(metadata *C.Metadata) C.Proxy {
|
func (lb *LoadBalance) Unwrap(metadata *C.Metadata) C.Proxy {
|
||||||
proxies := lb.proxies(true)
|
proxies := lb.proxies(true)
|
||||||
return lb.strategyFn(proxies, metadata)
|
return lb.strategyFn(proxies, metadata)
|
||||||
@ -143,6 +147,7 @@ func (lb *LoadBalance) proxies(touch bool) []C.Proxy {
|
|||||||
return elm.([]C.Proxy)
|
return elm.([]C.Proxy)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MarshalJSON implements C.ProxyAdapter
|
||||||
func (lb *LoadBalance) MarshalJSON() ([]byte, error) {
|
func (lb *LoadBalance) MarshalJSON() ([]byte, error) {
|
||||||
var all []string
|
var all []string
|
||||||
for _, proxy := range lb.proxies(false) {
|
for _, proxy := range lb.proxies(false) {
|
@ -4,9 +4,10 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/adapters/provider"
|
"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 (
|
||||||
@ -28,7 +29,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 +45,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 +139,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 {
|
||||||
@ -19,11 +18,22 @@ type Relay struct {
|
|||||||
providers []provider.ProxyProvider
|
providers []provider.ProxyProvider
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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) (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)
|
||||||
|
case 1:
|
||||||
|
return proxies[0].DialContext(ctx, metadata)
|
||||||
|
}
|
||||||
|
|
||||||
first := proxies[0]
|
first := proxies[0]
|
||||||
last := proxies[len(proxies)-1]
|
last := proxies[len(proxies)-1]
|
||||||
|
|
||||||
@ -56,6 +66,7 @@ func (r *Relay) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn,
|
|||||||
return outbound.NewConn(c, r), nil
|
return outbound.NewConn(c, r), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MarshalJSON implements C.ProxyAdapter
|
||||||
func (r *Relay) MarshalJSON() ([]byte, error) {
|
func (r *Relay) MarshalJSON() ([]byte, error) {
|
||||||
var all []string
|
var all []string
|
||||||
for _, proxy := range r.rawProxies(false) {
|
for _, proxy := range r.rawProxies(false) {
|
@ -5,10 +5,10 @@ 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"
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
|
"github.com/Dreamacro/clash/constant/provider"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Selector struct {
|
type Selector struct {
|
||||||
@ -19,6 +19,7 @@ type Selector struct {
|
|||||||
providers []provider.ProxyProvider
|
providers []provider.ProxyProvider
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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) (C.Conn, error) {
|
||||||
c, err := s.selectedProxy(true).DialContext(ctx, metadata)
|
c, err := s.selectedProxy(true).DialContext(ctx, metadata)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
@ -27,6 +28,7 @@ func (s *Selector) DialContext(ctx context.Context, metadata *C.Metadata) (C.Con
|
|||||||
return c, err
|
return c, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DialUDP implements C.ProxyAdapter
|
||||||
func (s *Selector) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
|
func (s *Selector) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
|
||||||
pc, err := s.selectedProxy(true).DialUDP(metadata)
|
pc, err := s.selectedProxy(true).DialUDP(metadata)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
@ -35,6 +37,7 @@ func (s *Selector) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
|
|||||||
return pc, err
|
return pc, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SupportUDP implements C.ProxyAdapter
|
||||||
func (s *Selector) SupportUDP() bool {
|
func (s *Selector) SupportUDP() bool {
|
||||||
if s.disableUDP {
|
if s.disableUDP {
|
||||||
return false
|
return false
|
||||||
@ -43,6 +46,7 @@ func (s *Selector) SupportUDP() bool {
|
|||||||
return s.selectedProxy(false).SupportUDP()
|
return s.selectedProxy(false).SupportUDP()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MarshalJSON implements C.ProxyAdapter
|
||||||
func (s *Selector) MarshalJSON() ([]byte, error) {
|
func (s *Selector) MarshalJSON() ([]byte, error) {
|
||||||
var all []string
|
var all []string
|
||||||
for _, proxy := range getProvidersProxies(s.providers, false) {
|
for _, proxy := range getProvidersProxies(s.providers, false) {
|
||||||
@ -72,6 +76,7 @@ func (s *Selector) Set(name string) error {
|
|||||||
return errors.New("proxy not exist")
|
return errors.New("proxy not exist")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Unwrap implements C.ProxyAdapter
|
||||||
func (s *Selector) Unwrap(metadata *C.Metadata) C.Proxy {
|
func (s *Selector) Unwrap(metadata *C.Metadata) C.Proxy {
|
||||||
return s.selectedProxy(true)
|
return s.selectedProxy(true)
|
||||||
}
|
}
|
@ -5,10 +5,10 @@ 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"
|
||||||
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)
|
||||||
@ -33,6 +33,7 @@ func (u *URLTest) Now() string {
|
|||||||
return u.fast(false).Name()
|
return u.fast(false).Name()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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) (c C.Conn, err error) {
|
||||||
c, err = u.fast(true).DialContext(ctx, metadata)
|
c, err = u.fast(true).DialContext(ctx, metadata)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
@ -41,6 +42,7 @@ func (u *URLTest) DialContext(ctx context.Context, metadata *C.Metadata) (c C.Co
|
|||||||
return c, err
|
return c, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DialUDP implements C.ProxyAdapter
|
||||||
func (u *URLTest) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
|
func (u *URLTest) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
|
||||||
pc, err := u.fast(true).DialUDP(metadata)
|
pc, err := u.fast(true).DialUDP(metadata)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
@ -49,6 +51,7 @@ func (u *URLTest) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
|
|||||||
return pc, err
|
return pc, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Unwrap implements C.ProxyAdapter
|
||||||
func (u *URLTest) Unwrap(metadata *C.Metadata) C.Proxy {
|
func (u *URLTest) Unwrap(metadata *C.Metadata) C.Proxy {
|
||||||
return u.fast(true)
|
return u.fast(true)
|
||||||
}
|
}
|
||||||
@ -66,7 +69,13 @@ func (u *URLTest) fast(touch bool) C.Proxy {
|
|||||||
proxies := u.proxies(touch)
|
proxies := u.proxies(touch)
|
||||||
fast := proxies[0]
|
fast := proxies[0]
|
||||||
min := fast.LastDelay()
|
min := fast.LastDelay()
|
||||||
|
fastNotExist := true
|
||||||
|
|
||||||
for _, proxy := range proxies[1:] {
|
for _, proxy := range proxies[1:] {
|
||||||
|
if u.fastNode != nil && proxy.Name() == u.fastNode.Name() {
|
||||||
|
fastNotExist = false
|
||||||
|
}
|
||||||
|
|
||||||
if !proxy.Alive() {
|
if !proxy.Alive() {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@ -79,7 +88,7 @@ func (u *URLTest) fast(touch bool) C.Proxy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// tolerance
|
// tolerance
|
||||||
if u.fastNode == nil || u.fastNode.LastDelay() > fast.LastDelay()+u.tolerance {
|
if u.fastNode == nil || fastNotExist || !u.fastNode.Alive() || u.fastNode.LastDelay() > fast.LastDelay()+u.tolerance {
|
||||||
u.fastNode = fast
|
u.fastNode = fast
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -89,6 +98,7 @@ func (u *URLTest) fast(touch bool) C.Proxy {
|
|||||||
return elm.(C.Proxy)
|
return elm.(C.Proxy)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SupportUDP implements C.ProxyAdapter
|
||||||
func (u *URLTest) SupportUDP() bool {
|
func (u *URLTest) SupportUDP() bool {
|
||||||
if u.disableUDP {
|
if u.disableUDP {
|
||||||
return false
|
return false
|
||||||
@ -97,6 +107,7 @@ func (u *URLTest) SupportUDP() bool {
|
|||||||
return u.fast(false).SupportUDP()
|
return u.fast(false).SupportUDP()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MarshalJSON implements C.ProxyAdapter
|
||||||
func (u *URLTest) MarshalJSON() ([]byte, error) {
|
func (u *URLTest) MarshalJSON() ([]byte, error) {
|
||||||
var all []string
|
var all []string
|
||||||
for _, proxy := range u.proxies(false) {
|
for _, proxy := range u.proxies(false) {
|
@ -16,25 +16,7 @@ func addrToMetadata(rawAddress string) (addr *C.Metadata, err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ip := net.ParseIP(host)
|
ip := net.ParseIP(host)
|
||||||
if ip != nil {
|
if ip == nil {
|
||||||
if ip.To4() != nil {
|
|
||||||
addr = &C.Metadata{
|
|
||||||
AddrType: C.AtypIPv4,
|
|
||||||
Host: "",
|
|
||||||
DstIP: ip,
|
|
||||||
DstPort: port,
|
|
||||||
}
|
|
||||||
return
|
|
||||||
} else {
|
|
||||||
addr = &C.Metadata{
|
|
||||||
AddrType: C.AtypIPv6,
|
|
||||||
Host: "",
|
|
||||||
DstIP: ip,
|
|
||||||
DstPort: port,
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
addr = &C.Metadata{
|
addr = &C.Metadata{
|
||||||
AddrType: C.AtypDomainName,
|
AddrType: C.AtypDomainName,
|
||||||
Host: host,
|
Host: host,
|
||||||
@ -42,7 +24,23 @@ func addrToMetadata(rawAddress string) (addr *C.Metadata, err error) {
|
|||||||
DstPort: port,
|
DstPort: port,
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
|
} else if ip4 := ip.To4(); ip4 != nil {
|
||||||
|
addr = &C.Metadata{
|
||||||
|
AddrType: C.AtypIPv4,
|
||||||
|
Host: "",
|
||||||
|
DstIP: ip4,
|
||||||
|
DstPort: port,
|
||||||
|
}
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
addr = &C.Metadata{
|
||||||
|
AddrType: C.AtypIPv6,
|
||||||
|
Host: "",
|
||||||
|
DstIP: ip,
|
||||||
|
DstPort: port,
|
||||||
|
}
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func tcpKeepAlive(c net.Conn) {
|
func tcpKeepAlive(c net.Conn) {
|
@ -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)
|
||||||
}
|
}
|
@ -8,6 +8,7 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
types "github.com/Dreamacro/clash/constant/provider"
|
||||||
"github.com/Dreamacro/clash/log"
|
"github.com/Dreamacro/clash/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -20,7 +21,7 @@ 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 +34,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()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -72,9 +73,11 @@ func (f *fetcher) Initial() (interface{}, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isLocal = false
|
||||||
}
|
}
|
||||||
|
|
||||||
if f.vehicle.Type() != File {
|
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
|
||||||
}
|
}
|
||||||
@ -108,8 +111,10 @@ func (f *fetcher) Update() (interface{}, bool, error) {
|
|||||||
return nil, false, err
|
return nil, false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := safeWrite(f.vehicle.Path(), buf); err != nil {
|
if f.vehicle.Type() != types.File {
|
||||||
return nil, false, err
|
if err := safeWrite(f.vehicle.Path(), buf); err != nil {
|
||||||
|
return nil, false, err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
f.updatedAt = &now
|
f.updatedAt = &now
|
||||||
@ -163,7 +168,7 @@ func safeWrite(path string, buf []byte) error {
|
|||||||
return ioutil.WriteFile(path, buf, fileMode)
|
return ioutil.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)
|
@ -4,6 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"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"
|
||||||
@ -58,13 +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))
|
||||||
for _, proxy := range hc.proxies {
|
for _, proxy := range hc.proxies {
|
||||||
go proxy.URLTest(ctx, hc.url)
|
p := proxy
|
||||||
|
b.Go(p.Name(), func() (interface{}, error) {
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), defaultURLTestTimeout)
|
||||||
|
defer cancel()
|
||||||
|
p.URLTest(ctx, hc.url)
|
||||||
|
return nil, nil
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
b.Wait()
|
||||||
<-ctx.Done()
|
|
||||||
cancel()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (hc *HealthCheck) close() {
|
func (hc *HealthCheck) close() {
|
@ -7,6 +7,7 @@ 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 (
|
||||||
@ -28,7 +29,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{
|
||||||
@ -40,7 +41,7 @@ func ParseProxyProvider(name string, mapping map[string]interface{}) (ProxyProvi
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var hcInterval uint = 0
|
var hcInterval uint
|
||||||
if schema.HealthCheck.Enable {
|
if schema.HealthCheck.Enable {
|
||||||
hcInterval = uint(schema.HealthCheck.Interval)
|
hcInterval = uint(schema.HealthCheck.Interval)
|
||||||
}
|
}
|
||||||
@ -48,7 +49,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() {
|
@ -3,48 +3,21 @@ package provider
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"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 {
|
||||||
@ -64,8 +37,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 +72,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}
|
||||||
@ -107,6 +82,7 @@ func (h *HTTPVehicle) Read() ([]byte, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
buf, err := ioutil.ReadAll(resp.Body)
|
buf, err := ioutil.ReadAll(resp.Body)
|
||||||
if err != nil {
|
if err != nil {
|
@ -1,60 +0,0 @@
|
|||||||
package inbound
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net"
|
|
||||||
"net/http"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
C "github.com/Dreamacro/clash/constant"
|
|
||||||
)
|
|
||||||
|
|
||||||
// HTTPAdapter is a adapter for HTTP connection
|
|
||||||
type HTTPAdapter struct {
|
|
||||||
net.Conn
|
|
||||||
metadata *C.Metadata
|
|
||||||
R *http.Request
|
|
||||||
}
|
|
||||||
|
|
||||||
// Metadata return destination metadata
|
|
||||||
func (h *HTTPAdapter) Metadata() *C.Metadata {
|
|
||||||
return h.metadata
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewHTTP is HTTPAdapter generator
|
|
||||||
func NewHTTP(request *http.Request, conn net.Conn) *HTTPAdapter {
|
|
||||||
metadata := parseHTTPAddr(request)
|
|
||||||
metadata.Type = C.HTTP
|
|
||||||
if ip, port, err := parseAddr(conn.RemoteAddr().String()); err == nil {
|
|
||||||
metadata.SrcIP = ip
|
|
||||||
metadata.SrcPort = port
|
|
||||||
}
|
|
||||||
return &HTTPAdapter{
|
|
||||||
metadata: metadata,
|
|
||||||
R: request,
|
|
||||||
Conn: conn,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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))
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,35 +0,0 @@
|
|||||||
package inbound
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net"
|
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/component/socks5"
|
|
||||||
C "github.com/Dreamacro/clash/constant"
|
|
||||||
)
|
|
||||||
|
|
||||||
// SocketAdapter is a adapter for socks and redir connection
|
|
||||||
type SocketAdapter struct {
|
|
||||||
net.Conn
|
|
||||||
metadata *C.Metadata
|
|
||||||
}
|
|
||||||
|
|
||||||
// Metadata return destination metadata
|
|
||||||
func (s *SocketAdapter) Metadata() *C.Metadata {
|
|
||||||
return s.metadata
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewSocket is SocketAdapter generator
|
|
||||||
func NewSocket(target socks5.Addr, conn net.Conn, source C.Type) *SocketAdapter {
|
|
||||||
metadata := parseSocksAddr(target)
|
|
||||||
metadata.NetWork = C.TCP
|
|
||||||
metadata.Type = source
|
|
||||||
if ip, port, err := parseAddr(conn.RemoteAddr().String()); err == nil {
|
|
||||||
metadata.SrcIP = ip
|
|
||||||
metadata.SrcPort = port
|
|
||||||
}
|
|
||||||
|
|
||||||
return &SocketAdapter{
|
|
||||||
Conn: conn,
|
|
||||||
metadata: metadata,
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,107 +0,0 @@
|
|||||||
package outbound
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"net"
|
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/component/dialer"
|
|
||||||
"github.com/Dreamacro/clash/component/trojan"
|
|
||||||
C "github.com/Dreamacro/clash/constant"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Trojan struct {
|
|
||||||
*Base
|
|
||||||
instance *trojan.Trojan
|
|
||||||
}
|
|
||||||
|
|
||||||
type TrojanOption struct {
|
|
||||||
Name string `proxy:"name"`
|
|
||||||
Server string `proxy:"server"`
|
|
||||||
Port int `proxy:"port"`
|
|
||||||
Password string `proxy:"password"`
|
|
||||||
ALPN []string `proxy:"alpn,omitempty"`
|
|
||||||
SNI string `proxy:"sni,omitempty"`
|
|
||||||
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
|
|
||||||
UDP bool `proxy:"udp,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *Trojan) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
|
||||||
c, err := t.instance.StreamConn(c)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("%s connect error: %w", t.addr, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = t.instance.WriteHeader(c, trojan.CommandTCP, serializesSocksAddr(metadata))
|
|
||||||
return c, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *Trojan) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
|
|
||||||
c, err := dialer.DialContext(ctx, "tcp", t.addr)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("%s connect error: %w", t.addr, err)
|
|
||||||
}
|
|
||||||
tcpKeepAlive(c)
|
|
||||||
c, err = t.StreamConn(c, metadata)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return NewConn(c, t), err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *Trojan) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), tcpTimeout)
|
|
||||||
defer cancel()
|
|
||||||
c, err := dialer.DialContext(ctx, "tcp", t.addr)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("%s connect error: %w", t.addr, err)
|
|
||||||
}
|
|
||||||
tcpKeepAlive(c)
|
|
||||||
c, err = t.instance.StreamConn(c)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("%s connect error: %w", t.addr, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = t.instance.WriteHeader(c, trojan.CommandUDP, serializesSocksAddr(metadata))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
pc := t.instance.PacketConn(c)
|
|
||||||
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) {
|
|
||||||
addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port))
|
|
||||||
|
|
||||||
tOption := &trojan.Option{
|
|
||||||
Password: option.Password,
|
|
||||||
ALPN: option.ALPN,
|
|
||||||
ServerName: option.Server,
|
|
||||||
SkipCertVerify: option.SkipCertVerify,
|
|
||||||
ClientSessionCache: getClientSessionCache(),
|
|
||||||
}
|
|
||||||
|
|
||||||
if option.SNI != "" {
|
|
||||||
tOption.ServerName = option.SNI
|
|
||||||
}
|
|
||||||
|
|
||||||
return &Trojan{
|
|
||||||
Base: &Base{
|
|
||||||
name: option.Name,
|
|
||||||
addr: addr,
|
|
||||||
tp: C.Trojan,
|
|
||||||
udp: option.UDP,
|
|
||||||
},
|
|
||||||
instance: trojan.New(tOption),
|
|
||||||
}, nil
|
|
||||||
}
|
|
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,4 +1,4 @@
|
|||||||
package mixed
|
package net
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
11
common/net/io.go
Normal file
11
common/net/io.go
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
package net
|
||||||
|
|
||||||
|
import "io"
|
||||||
|
|
||||||
|
type ReadOnlyReader struct {
|
||||||
|
io.Reader
|
||||||
|
}
|
||||||
|
|
||||||
|
type WriteOnlyWriter struct {
|
||||||
|
io.Writer
|
||||||
|
}
|
@ -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 {
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
//go:build !linux
|
||||||
// +build !linux
|
// +build !linux
|
||||||
|
|
||||||
package sockopt
|
package sockopt
|
||||||
|
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))
|
||||||
|
}
|
94
component/dhcp/dhcp.go
Normal file
94
component/dhcp/dhcp.go
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
package dhcp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"math/rand"
|
||||||
|
"net"
|
||||||
|
|
||||||
|
"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)
|
||||||
|
|
||||||
|
discovery, err := dhcpv4.NewDiscovery(randomHardware(), 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func randomHardware() net.HardwareAddr {
|
||||||
|
addr := make(net.HardwareAddr, 6)
|
||||||
|
|
||||||
|
addr[0] = 0xff
|
||||||
|
|
||||||
|
for i := 1; i < len(addr); i++ {
|
||||||
|
addr[i] = byte(rand.Intn(254) + 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
return addr
|
||||||
|
}
|
@ -1,104 +0,0 @@
|
|||||||
package dialer
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"net"
|
|
||||||
)
|
|
||||||
|
|
||||||
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 {
|
|
||||||
iface, err := net.InterfaceByName(name)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
addrs, err := iface.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 := net.InterfaceByName(name)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
addrs, err := iface.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,49 +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 := net.InterfaceByName(ifaceName)
|
ifaceObj, err := iface.ResolveInterface(ifaceName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
dialer.Control = bindControl(iface.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 := net.InterfaceByName(ifaceName)
|
ifaceObj, err := iface.ResolveInterface(ifaceName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
lc.Control = bindControl(iface.Index)
|
lc.Control = bindControl(ifaceObj.Index, lc.Control)
|
||||||
|
return address, nil
|
||||||
return 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 := 0
|
||||||
|
if dialer.LocalAddr != nil {
|
||||||
|
_, port, err := net.SplitHostPort(dialer.LocalAddr.String())
|
||||||
|
if err == nil {
|
||||||
|
local, _ = strconv.Atoi(port)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
addr, err := lookupLocalAddr(ifaceName, network, destination, 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.Atoi(port)
|
||||||
|
|
||||||
|
addr, err := lookupLocalAddr(ifaceName, network, nil, 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,70 @@ 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 := &config{}
|
||||||
if ListenPacketHook != nil {
|
|
||||||
var err error
|
if !cfg.skipDefault {
|
||||||
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
return lc.ListenPacket(ctx, network, address)
|
||||||
|
}
|
||||||
|
|
||||||
|
func dialContext(ctx context.Context, network string, destination net.IP, port string, options []Option) (net.Conn, error) {
|
||||||
|
opt := &config{}
|
||||||
|
|
||||||
|
if !opt.skipDefault {
|
||||||
|
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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return cfg.ListenPacket(context.Background(), network, address)
|
return dialer.DialContext(ctx, network, net.JoinHostPort(destination.String(), port))
|
||||||
}
|
}
|
||||||
|
|
||||||
func dualStackDialContext(ctx context.Context, network, address string) (net.Conn, error) {
|
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 +117,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 +128,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
|
|
||||||
}
|
|
||||||
}
|
|
31
component/dialer/options.go
Normal file
31
component/dialer/options.go
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
package dialer
|
||||||
|
|
||||||
|
var (
|
||||||
|
DefaultOptions []Option
|
||||||
|
)
|
||||||
|
|
||||||
|
type config struct {
|
||||||
|
skipDefault bool
|
||||||
|
interfaceName string
|
||||||
|
addrReuse bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type Option func(opt *config)
|
||||||
|
|
||||||
|
func WithInterface(name string) Option {
|
||||||
|
return func(opt *config) {
|
||||||
|
opt.interfaceName = name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithAddrReuse(reuse bool) Option {
|
||||||
|
return func(opt *config) {
|
||||||
|
opt.addrReuse = reuse
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithSkipDefault(skip bool) Option {
|
||||||
|
return func(opt *config) {
|
||||||
|
opt.skipDefault = skip
|
||||||
|
}
|
||||||
|
}
|
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)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
113
component/iface/iface.go
Normal file
113
component/iface/iface.go
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
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")
|
||||||
|
var 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
|
||||||
|
}
|
21
component/process/process.go
Normal file
21
component/process/process.go
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
package process
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"net"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrInvalidNetwork = errors.New("invalid network")
|
||||||
|
ErrPlatformNotSupport = errors.New("not support on this platform")
|
||||||
|
ErrNotFound = errors.New("process not found")
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
TCP = "tcp"
|
||||||
|
UDP = "udp"
|
||||||
|
)
|
||||||
|
|
||||||
|
func FindProcessName(network string, srcIP net.IP, srcPort int) (string, error) {
|
||||||
|
return findProcessName(network, srcIP, srcPort)
|
||||||
|
}
|
@ -1,109 +1,27 @@
|
|||||||
package rules
|
package process
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"net"
|
"net"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"syscall"
|
"syscall"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/common/cache"
|
"golang.org/x/sys/unix"
|
||||||
C "github.com/Dreamacro/clash/constant"
|
|
||||||
"github.com/Dreamacro/clash/log"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// store process name for when dealing with multiple PROCESS-NAME rules
|
|
||||||
var processCache = cache.NewLRUCache(cache.WithAge(2), cache.WithSize(64))
|
|
||||||
|
|
||||||
type Process struct {
|
|
||||||
adapter string
|
|
||||||
process string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ps *Process) RuleType() C.RuleType {
|
|
||||||
return C.Process
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ps *Process) Match(metadata *C.Metadata) bool {
|
|
||||||
key := fmt.Sprintf("%s:%s:%s", metadata.NetWork.String(), metadata.SrcIP.String(), metadata.SrcPort)
|
|
||||||
cached, hit := processCache.Get(key)
|
|
||||||
if !hit {
|
|
||||||
name, err := getExecPathFromAddress(metadata)
|
|
||||||
if err != nil {
|
|
||||||
log.Debugln("[%s] getExecPathFromAddress error: %s", C.Process.String(), err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
processCache.Set(key, name)
|
|
||||||
|
|
||||||
cached = name
|
|
||||||
}
|
|
||||||
|
|
||||||
return strings.EqualFold(cached.(string), ps.process)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Process) Adapter() string {
|
|
||||||
return p.adapter
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Process) Payload() string {
|
|
||||||
return p.process
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Process) ShouldResolveIP() bool {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewProcess(process string, adapter string) (*Process, error) {
|
|
||||||
return &Process{
|
|
||||||
adapter: adapter,
|
|
||||||
process: process,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
const (
|
||||||
procpidpathinfo = 0xb
|
procpidpathinfo = 0xb
|
||||||
procpidpathinfosize = 1024
|
procpidpathinfosize = 1024
|
||||||
proccallnumpidinfo = 0x2
|
proccallnumpidinfo = 0x2
|
||||||
)
|
)
|
||||||
|
|
||||||
func getExecPathFromPID(pid uint32) (string, error) {
|
func findProcessName(network string, ip net.IP, port int) (string, error) {
|
||||||
buf := make([]byte, procpidpathinfosize)
|
|
||||||
_, _, errno := syscall.Syscall6(
|
|
||||||
syscall.SYS_PROC_INFO,
|
|
||||||
proccallnumpidinfo,
|
|
||||||
uintptr(pid),
|
|
||||||
procpidpathinfo,
|
|
||||||
0,
|
|
||||||
uintptr(unsafe.Pointer(&buf[0])),
|
|
||||||
procpidpathinfosize)
|
|
||||||
if errno != 0 {
|
|
||||||
return "", errno
|
|
||||||
}
|
|
||||||
firstZero := bytes.IndexByte(buf, 0)
|
|
||||||
if firstZero <= 0 {
|
|
||||||
return "", nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return filepath.Base(string(buf[:firstZero])), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func getExecPathFromAddress(metadata *C.Metadata) (string, error) {
|
|
||||||
ip := metadata.SrcIP
|
|
||||||
port, err := strconv.Atoi(metadata.SrcPort)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
var spath string
|
var spath string
|
||||||
switch metadata.NetWork {
|
switch network {
|
||||||
case C.TCP:
|
case TCP:
|
||||||
spath = "net.inet.tcp.pcblist_n"
|
spath = "net.inet.tcp.pcblist_n"
|
||||||
case C.UDP:
|
case UDP:
|
||||||
spath = "net.inet.udp.pcblist_n"
|
spath = "net.inet.udp.pcblist_n"
|
||||||
default:
|
default:
|
||||||
return "", ErrInvalidNetwork
|
return "", ErrInvalidNetwork
|
||||||
@ -123,7 +41,7 @@ func getExecPathFromAddress(metadata *C.Metadata) (string, error) {
|
|||||||
// rup8(sizeof(xinpcb_n)) + rup8(sizeof(xsocket_n)) +
|
// rup8(sizeof(xinpcb_n)) + rup8(sizeof(xsocket_n)) +
|
||||||
// 2 * rup8(sizeof(xsockbuf_n)) + rup8(sizeof(xsockstat_n))
|
// 2 * rup8(sizeof(xsockbuf_n)) + rup8(sizeof(xsockstat_n))
|
||||||
itemSize := 384
|
itemSize := 384
|
||||||
if metadata.NetWork == C.TCP {
|
if network == TCP {
|
||||||
// rup8(sizeof(xtcpcb_n))
|
// rup8(sizeof(xtcpcb_n))
|
||||||
itemSize += 208
|
itemSize += 208
|
||||||
}
|
}
|
||||||
@ -161,7 +79,24 @@ func getExecPathFromAddress(metadata *C.Metadata) (string, error) {
|
|||||||
return getExecPathFromPID(pid)
|
return getExecPathFromPID(pid)
|
||||||
}
|
}
|
||||||
|
|
||||||
return "", errors.New("process not found")
|
return "", ErrNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
func getExecPathFromPID(pid uint32) (string, error) {
|
||||||
|
buf := make([]byte, procpidpathinfosize)
|
||||||
|
_, _, errno := syscall.Syscall6(
|
||||||
|
syscall.SYS_PROC_INFO,
|
||||||
|
proccallnumpidinfo,
|
||||||
|
uintptr(pid),
|
||||||
|
procpidpathinfo,
|
||||||
|
0,
|
||||||
|
uintptr(unsafe.Pointer(&buf[0])),
|
||||||
|
procpidpathinfosize)
|
||||||
|
if errno != 0 {
|
||||||
|
return "", errno
|
||||||
|
}
|
||||||
|
|
||||||
|
return filepath.Base(unix.ByteSliceToString(buf)), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func readNativeUint32(b []byte) uint32 {
|
func readNativeUint32(b []byte) uint32 {
|
@ -1,8 +1,7 @@
|
|||||||
package rules
|
package process
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
@ -12,78 +11,52 @@ import (
|
|||||||
"syscall"
|
"syscall"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/common/cache"
|
|
||||||
C "github.com/Dreamacro/clash/constant"
|
|
||||||
"github.com/Dreamacro/clash/log"
|
"github.com/Dreamacro/clash/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
// store process name for when dealing with multiple PROCESS-NAME rules
|
// store process name for when dealing with multiple PROCESS-NAME rules
|
||||||
var (
|
var (
|
||||||
processCache = cache.NewLRUCache(cache.WithAge(2), cache.WithSize(64))
|
|
||||||
errNotFound = errors.New("process not found")
|
|
||||||
matchMeta = func(p *Process, m *C.Metadata) bool { return false }
|
|
||||||
|
|
||||||
defaultSearcher *searcher
|
defaultSearcher *searcher
|
||||||
|
|
||||||
once sync.Once
|
once sync.Once
|
||||||
)
|
)
|
||||||
|
|
||||||
type Process struct {
|
func findProcessName(network string, ip net.IP, srcPort int) (string, error) {
|
||||||
adapter string
|
|
||||||
process string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ps *Process) RuleType() C.RuleType {
|
|
||||||
return C.Process
|
|
||||||
}
|
|
||||||
|
|
||||||
func match(ps *Process, metadata *C.Metadata) bool {
|
|
||||||
key := fmt.Sprintf("%s:%s:%s", metadata.NetWork.String(), metadata.SrcIP.String(), metadata.SrcPort)
|
|
||||||
cached, hit := processCache.Get(key)
|
|
||||||
if !hit {
|
|
||||||
name, err := getExecPathFromAddress(metadata)
|
|
||||||
if err != nil {
|
|
||||||
log.Debugln("[%s] getExecPathFromAddress error: %s", C.Process.String(), err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
processCache.Set(key, name)
|
|
||||||
|
|
||||||
cached = name
|
|
||||||
}
|
|
||||||
|
|
||||||
return strings.EqualFold(cached.(string), ps.process)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ps *Process) Match(metadata *C.Metadata) bool {
|
|
||||||
return matchMeta(ps, metadata)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Process) Adapter() string {
|
|
||||||
return p.adapter
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Process) Payload() string {
|
|
||||||
return p.process
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Process) ShouldResolveIP() bool {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewProcess(process string, adapter string) (*Process, error) {
|
|
||||||
once.Do(func() {
|
once.Do(func() {
|
||||||
err := initSearcher()
|
if err := initSearcher(); err != nil {
|
||||||
if err != nil {
|
|
||||||
log.Errorln("Initialize PROCESS-NAME failed: %s", err.Error())
|
log.Errorln("Initialize PROCESS-NAME failed: %s", err.Error())
|
||||||
log.Warnln("All PROCESS-NAME rules will be skipped")
|
log.Warnln("All PROCESS-NAME rules will be skipped")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
matchMeta = match
|
|
||||||
})
|
})
|
||||||
return &Process{
|
|
||||||
adapter: adapter,
|
if defaultSearcher == nil {
|
||||||
process: process,
|
return "", ErrPlatformNotSupport
|
||||||
}, nil
|
}
|
||||||
|
|
||||||
|
var spath string
|
||||||
|
isTCP := network == TCP
|
||||||
|
switch network {
|
||||||
|
case TCP:
|
||||||
|
spath = "net.inet.tcp.pcblist"
|
||||||
|
case UDP:
|
||||||
|
spath = "net.inet.udp.pcblist"
|
||||||
|
default:
|
||||||
|
return "", ErrInvalidNetwork
|
||||||
|
}
|
||||||
|
|
||||||
|
value, err := syscall.Sysctl(spath)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
buf := []byte(value)
|
||||||
|
pid, err := defaultSearcher.Search(buf, ip, uint16(srcPort), isTCP)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return getExecPathFromPID(pid)
|
||||||
}
|
}
|
||||||
|
|
||||||
func getExecPathFromPID(pid uint32) (string, error) {
|
func getExecPathFromPID(pid uint32) (string, error) {
|
||||||
@ -107,41 +80,6 @@ func getExecPathFromPID(pid uint32) (string, error) {
|
|||||||
return filepath.Base(string(buf[:size-1])), nil
|
return filepath.Base(string(buf[:size-1])), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getExecPathFromAddress(metadata *C.Metadata) (string, error) {
|
|
||||||
ip := metadata.SrcIP
|
|
||||||
port, err := strconv.Atoi(metadata.SrcPort)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
var spath string
|
|
||||||
var isTCP bool
|
|
||||||
switch metadata.NetWork {
|
|
||||||
case C.TCP:
|
|
||||||
spath = "net.inet.tcp.pcblist"
|
|
||||||
isTCP = true
|
|
||||||
case C.UDP:
|
|
||||||
spath = "net.inet.udp.pcblist"
|
|
||||||
isTCP = false
|
|
||||||
default:
|
|
||||||
return "", ErrInvalidNetwork
|
|
||||||
}
|
|
||||||
|
|
||||||
value, err := syscall.Sysctl(spath)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
buf := []byte(value)
|
|
||||||
|
|
||||||
pid, err := defaultSearcher.Search(buf, ip, uint16(port), isTCP)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
return getExecPathFromPID(pid)
|
|
||||||
}
|
|
||||||
|
|
||||||
func readNativeUint32(b []byte) uint32 {
|
func readNativeUint32(b []byte) uint32 {
|
||||||
return *(*uint32)(unsafe.Pointer(&b[0]))
|
return *(*uint32)(unsafe.Pointer(&b[0]))
|
||||||
}
|
}
|
||||||
@ -213,7 +151,7 @@ func (s *searcher) Search(buf []byte, ip net.IP, port uint16, isTCP bool) (uint3
|
|||||||
socket := binary.BigEndian.Uint64(buf[inp+s.socket : inp+s.socket+8])
|
socket := binary.BigEndian.Uint64(buf[inp+s.socket : inp+s.socket+8])
|
||||||
return s.searchSocketPid(socket)
|
return s.searchSocketPid(socket)
|
||||||
}
|
}
|
||||||
return 0, errNotFound
|
return 0, ErrNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *searcher) searchSocketPid(socket uint64) (uint32, error) {
|
func (s *searcher) searchSocketPid(socket uint64) (uint32, error) {
|
||||||
@ -235,11 +173,11 @@ func (s *searcher) searchSocketPid(socket uint64) (uint32, error) {
|
|||||||
return pid, nil
|
return pid, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return 0, errNotFound
|
return 0, ErrNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
func newSearcher(major int) *searcher {
|
func newSearcher(major int) *searcher {
|
||||||
var s *searcher = nil
|
var s *searcher
|
||||||
switch major {
|
switch major {
|
||||||
case 11:
|
case 11:
|
||||||
s = &searcher{
|
s = &searcher{
|
||||||
@ -256,6 +194,8 @@ func newSearcher(major int) *searcher {
|
|||||||
udpInpOffset: 8,
|
udpInpOffset: 8,
|
||||||
}
|
}
|
||||||
case 12:
|
case 12:
|
||||||
|
fallthrough
|
||||||
|
case 13:
|
||||||
s = &searcher{
|
s = &searcher{
|
||||||
headSize: 64,
|
headSize: 64,
|
||||||
tcpItemSize: 744,
|
tcpItemSize: 744,
|
@ -1,4 +1,4 @@
|
|||||||
package rules
|
package process
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
@ -9,28 +9,23 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"path"
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"syscall"
|
"syscall"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/common/cache"
|
|
||||||
"github.com/Dreamacro/clash/common/pool"
|
"github.com/Dreamacro/clash/common/pool"
|
||||||
C "github.com/Dreamacro/clash/constant"
|
|
||||||
"github.com/Dreamacro/clash/log"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// 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(metadata *C.Metadata) (inode, uid int, err error)
|
return binary.LittleEndian
|
||||||
|
}()
|
||||||
|
|
||||||
|
type SocketResolver func(network string, ip net.IP, srcPort int) (inode, uid int, err error)
|
||||||
type ProcessNameResolver func(inode, uid int) (name string, err error)
|
type ProcessNameResolver func(inode, uid int) (name string, err error)
|
||||||
|
|
||||||
// export for android
|
// export for android
|
||||||
@ -39,63 +34,14 @@ var (
|
|||||||
DefaultProcessNameResolver ProcessNameResolver = resolveProcessNameByProcSearch
|
DefaultProcessNameResolver ProcessNameResolver = resolveProcessNameByProcSearch
|
||||||
)
|
)
|
||||||
|
|
||||||
type Process struct {
|
|
||||||
adapter string
|
|
||||||
process string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Process) RuleType() C.RuleType {
|
|
||||||
return C.Process
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Process) Match(metadata *C.Metadata) bool {
|
|
||||||
key := fmt.Sprintf("%s:%s:%s", metadata.NetWork.String(), metadata.SrcIP.String(), metadata.SrcPort)
|
|
||||||
cached, hit := processCache.Get(key)
|
|
||||||
if !hit {
|
|
||||||
processName, err := resolveProcessName(metadata)
|
|
||||||
if err != nil {
|
|
||||||
log.Debugln("[%s] Resolve process of %s failure: %s", C.Process.String(), key, err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
processCache.Set(key, processName)
|
|
||||||
|
|
||||||
cached = processName
|
|
||||||
}
|
|
||||||
|
|
||||||
return strings.EqualFold(cached.(string), p.process)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Process) Adapter() string {
|
|
||||||
return p.adapter
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Process) Payload() string {
|
|
||||||
return p.process
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Process) ShouldResolveIP() bool {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewProcess(process string, adapter string) (*Process, error) {
|
|
||||||
return &Process{
|
|
||||||
adapter: adapter,
|
|
||||||
process: process,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
const (
|
||||||
sizeOfSocketDiagRequest = syscall.SizeofNlMsghdr + 8 + 48
|
sizeOfSocketDiagRequest = syscall.SizeofNlMsghdr + 8 + 48
|
||||||
socketDiagByFamily = 20
|
socketDiagByFamily = 20
|
||||||
pathProc = "/proc"
|
pathProc = "/proc"
|
||||||
)
|
)
|
||||||
|
|
||||||
var nativeEndian binary.ByteOrder = binary.LittleEndian
|
func findProcessName(network string, ip net.IP, srcPort int) (string, error) {
|
||||||
|
inode, uid, err := DefaultSocketResolver(network, ip, srcPort)
|
||||||
var processCache = cache.NewLRUCache(cache.WithAge(2), cache.WithSize(64))
|
|
||||||
|
|
||||||
func resolveProcessName(metadata *C.Metadata) (string, error) {
|
|
||||||
inode, uid, err := DefaultSocketResolver(metadata)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
@ -103,31 +49,26 @@ func resolveProcessName(metadata *C.Metadata) (string, error) {
|
|||||||
return DefaultProcessNameResolver(inode, uid)
|
return DefaultProcessNameResolver(inode, uid)
|
||||||
}
|
}
|
||||||
|
|
||||||
func resolveSocketByNetlink(metadata *C.Metadata) (int, int, error) {
|
func resolveSocketByNetlink(network string, ip net.IP, srcPort int) (int, int, error) {
|
||||||
var family byte
|
var family byte
|
||||||
var protocol byte
|
var protocol byte
|
||||||
|
|
||||||
switch metadata.NetWork {
|
switch network {
|
||||||
case C.TCP:
|
case TCP:
|
||||||
protocol = syscall.IPPROTO_TCP
|
protocol = syscall.IPPROTO_TCP
|
||||||
case C.UDP:
|
case UDP:
|
||||||
protocol = syscall.IPPROTO_UDP
|
protocol = syscall.IPPROTO_UDP
|
||||||
default:
|
default:
|
||||||
return 0, 0, ErrInvalidNetwork
|
return 0, 0, ErrInvalidNetwork
|
||||||
}
|
}
|
||||||
|
|
||||||
if metadata.SrcIP.To4() != nil {
|
if ip.To4() != nil {
|
||||||
family = syscall.AF_INET
|
family = syscall.AF_INET
|
||||||
} else {
|
} else {
|
||||||
family = syscall.AF_INET6
|
family = syscall.AF_INET6
|
||||||
}
|
}
|
||||||
|
|
||||||
srcPort, err := strconv.Atoi(metadata.SrcPort)
|
req := packSocketDiagRequest(family, protocol, ip, uint16(srcPort))
|
||||||
if err != nil {
|
|
||||||
return 0, 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
req := packSocketDiagRequest(family, protocol, metadata.SrcIP, uint16(srcPort))
|
|
||||||
|
|
||||||
socket, err := syscall.Socket(syscall.AF_NETLINK, syscall.SOCK_DGRAM, syscall.NETLINK_INET_DIAG)
|
socket, err := syscall.Socket(syscall.AF_NETLINK, syscall.SOCK_DGRAM, syscall.NETLINK_INET_DIAG)
|
||||||
if err != nil {
|
if err != nil {
|
13
component/process/process_other.go
Normal file
13
component/process/process_other.go
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
//go:build !darwin && !linux && !windows && (!freebsd || !amd64)
|
||||||
|
// +build !darwin
|
||||||
|
// +build !linux
|
||||||
|
// +build !windows
|
||||||
|
// +build !freebsd !amd64
|
||||||
|
|
||||||
|
package process
|
||||||
|
|
||||||
|
import "net"
|
||||||
|
|
||||||
|
func findProcessName(network string, ip net.IP, srcPort int) (string, error) {
|
||||||
|
return "", ErrPlatformNotSupport
|
||||||
|
}
|
@ -1,18 +1,13 @@
|
|||||||
package rules
|
package process
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
"sync"
|
||||||
"syscall"
|
"syscall"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/common/cache"
|
|
||||||
C "github.com/Dreamacro/clash/constant"
|
|
||||||
"github.com/Dreamacro/clash/log"
|
"github.com/Dreamacro/clash/log"
|
||||||
|
|
||||||
"golang.org/x/sys/windows"
|
"golang.org/x/sys/windows"
|
||||||
@ -27,12 +22,8 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
processCache = cache.NewLRUCache(cache.WithAge(2), cache.WithSize(64))
|
getExTCPTable uintptr
|
||||||
errNotFound = errors.New("process not found")
|
getExUDPTable uintptr
|
||||||
matchMeta = func(p *Process, m *C.Metadata) bool { return false }
|
|
||||||
|
|
||||||
getExTcpTable uintptr
|
|
||||||
getExUdpTable uintptr
|
|
||||||
queryProcName uintptr
|
queryProcName uintptr
|
||||||
|
|
||||||
once sync.Once
|
once sync.Once
|
||||||
@ -44,12 +35,12 @@ func initWin32API() error {
|
|||||||
return fmt.Errorf("LoadLibrary iphlpapi.dll failed: %s", err.Error())
|
return fmt.Errorf("LoadLibrary iphlpapi.dll failed: %s", err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
getExTcpTable, err = windows.GetProcAddress(h, tcpTableFunc)
|
getExTCPTable, err = windows.GetProcAddress(h, tcpTableFunc)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("GetProcAddress of %s failed: %s", tcpTableFunc, err.Error())
|
return fmt.Errorf("GetProcAddress of %s failed: %s", tcpTableFunc, err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
getExUdpTable, err = windows.GetProcAddress(h, udpTableFunc)
|
getExUDPTable, err = windows.GetProcAddress(h, udpTableFunc)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("GetProcAddress of %s failed: %s", udpTableFunc, err.Error())
|
return fmt.Errorf("GetProcAddress of %s failed: %s", udpTableFunc, err.Error())
|
||||||
}
|
}
|
||||||
@ -67,47 +58,7 @@ func initWin32API() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type Process struct {
|
func findProcessName(network string, ip net.IP, srcPort int) (string, error) {
|
||||||
adapter string
|
|
||||||
process string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Process) RuleType() C.RuleType {
|
|
||||||
return C.Process
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Process) Adapter() string {
|
|
||||||
return p.adapter
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Process) Payload() string {
|
|
||||||
return p.process
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Process) ShouldResolveIP() bool {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func match(p *Process, metadata *C.Metadata) bool {
|
|
||||||
key := fmt.Sprintf("%s:%s:%s", metadata.NetWork.String(), metadata.SrcIP.String(), metadata.SrcPort)
|
|
||||||
cached, hit := processCache.Get(key)
|
|
||||||
if !hit {
|
|
||||||
processName, err := resolveProcessName(metadata)
|
|
||||||
if err != nil {
|
|
||||||
log.Debugln("[%s] Resolve process of %s failed: %s", C.Process.String(), key, err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
processCache.Set(key, processName)
|
|
||||||
cached = processName
|
|
||||||
}
|
|
||||||
return strings.EqualFold(cached.(string), p.process)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Process) Match(metadata *C.Metadata) bool {
|
|
||||||
return matchMeta(p, metadata)
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewProcess(process string, adapter string) (*Process, error) {
|
|
||||||
once.Do(func() {
|
once.Do(func() {
|
||||||
err := initWin32API()
|
err := initWin32API()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -115,16 +66,7 @@ func NewProcess(process string, adapter string) (*Process, error) {
|
|||||||
log.Warnln("All PROCESS-NAMES rules will be skiped")
|
log.Warnln("All PROCESS-NAMES rules will be skiped")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
matchMeta = match
|
|
||||||
})
|
})
|
||||||
return &Process{
|
|
||||||
adapter: adapter,
|
|
||||||
process: process,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func resolveProcessName(metadata *C.Metadata) (string, error) {
|
|
||||||
ip := metadata.SrcIP
|
|
||||||
family := windows.AF_INET
|
family := windows.AF_INET
|
||||||
if ip.To4() == nil {
|
if ip.To4() == nil {
|
||||||
family = windows.AF_INET6
|
family = windows.AF_INET6
|
||||||
@ -132,28 +74,23 @@ func resolveProcessName(metadata *C.Metadata) (string, error) {
|
|||||||
|
|
||||||
var class int
|
var class int
|
||||||
var fn uintptr
|
var fn uintptr
|
||||||
switch metadata.NetWork {
|
switch network {
|
||||||
case C.TCP:
|
case TCP:
|
||||||
fn = getExTcpTable
|
fn = getExTCPTable
|
||||||
class = tcpTablePidConn
|
class = tcpTablePidConn
|
||||||
case C.UDP:
|
case UDP:
|
||||||
fn = getExUdpTable
|
fn = getExUDPTable
|
||||||
class = udpTablePid
|
class = udpTablePid
|
||||||
default:
|
default:
|
||||||
return "", ErrInvalidNetwork
|
return "", ErrInvalidNetwork
|
||||||
}
|
}
|
||||||
|
|
||||||
srcPort, err := strconv.Atoi(metadata.SrcPort)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
buf, err := getTransportTable(fn, family, class)
|
buf, err := getTransportTable(fn, family, class)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
s := newSearcher(family == windows.AF_INET, metadata.NetWork == C.TCP)
|
s := newSearcher(family == windows.AF_INET, network == TCP)
|
||||||
|
|
||||||
pid, err := s.Search(buf, ip, uint16(srcPort))
|
pid, err := s.Search(buf, ip, uint16(srcPort))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -196,14 +133,15 @@ func (s *searcher) Search(b []byte, ip net.IP, port uint16) (uint32, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
srcIP := net.IP(row[s.ip : s.ip+s.ipSize])
|
srcIP := net.IP(row[s.ip : s.ip+s.ipSize])
|
||||||
if !ip.Equal(srcIP) {
|
// windows binds an unbound udp socket to 0.0.0.0/[::] while first sendto
|
||||||
|
if !ip.Equal(srcIP) && (!srcIP.IsUnspecified() || s.tcpState != -1) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
pid := readNativeUint32(row[s.pid : s.pid+4])
|
pid := readNativeUint32(row[s.pid : s.pid+4])
|
||||||
return pid, nil
|
return pid, nil
|
||||||
}
|
}
|
||||||
return 0, errNotFound
|
return 0, ErrNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
func newSearcher(isV4, isTCP bool) *searcher {
|
func newSearcher(isV4, isTCP bool) *searcher {
|
101
component/profile/cachefile/cache.go
Normal file
101
component/profile/cachefile/cache.go
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
package cachefile
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/gob"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/Dreamacro/clash/component/profile"
|
||||||
|
C "github.com/Dreamacro/clash/constant"
|
||||||
|
"github.com/Dreamacro/clash/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
initOnce sync.Once
|
||||||
|
fileMode os.FileMode = 0666
|
||||||
|
defaultCache *CacheFile
|
||||||
|
)
|
||||||
|
|
||||||
|
type cache struct {
|
||||||
|
Selected map[string]string
|
||||||
|
}
|
||||||
|
|
||||||
|
// CacheFile store and update the cache file
|
||||||
|
type CacheFile struct {
|
||||||
|
path string
|
||||||
|
model *cache
|
||||||
|
buf *bytes.Buffer
|
||||||
|
mux sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CacheFile) SetSelected(group, selected string) {
|
||||||
|
if !profile.StoreSelected.Load() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := ioutil.WriteFile(c.path, c.buf.Bytes(), fileMode); err != nil {
|
||||||
|
log.Warnln("[CacheFile] write cache to %s failed: %s", c.path, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CacheFile) SelectedMap() map[string]string {
|
||||||
|
if !profile.StoreSelected.Load() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
c.mux.Lock()
|
||||||
|
defer c.mux.Unlock()
|
||||||
|
|
||||||
|
model := c.element()
|
||||||
|
|
||||||
|
mapping := map[string]string{}
|
||||||
|
for k, v := range model.Selected {
|
||||||
|
mapping[k] = v
|
||||||
|
}
|
||||||
|
return mapping
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CacheFile) element() *cache {
|
||||||
|
if c.model != nil {
|
||||||
|
return c.model
|
||||||
|
}
|
||||||
|
|
||||||
|
model := &cache{
|
||||||
|
Selected: map[string]string{},
|
||||||
|
}
|
||||||
|
|
||||||
|
if buf, err := ioutil.ReadFile(c.path); err == nil {
|
||||||
|
bufReader := bytes.NewBuffer(buf)
|
||||||
|
gob.NewDecoder(bufReader).Decode(model)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.model = model
|
||||||
|
return c.model
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cache return singleton of CacheFile
|
||||||
|
func Cache() *CacheFile {
|
||||||
|
initOnce.Do(func() {
|
||||||
|
defaultCache = &CacheFile{
|
||||||
|
path: C.Path.Cache(),
|
||||||
|
buf: &bytes.Buffer{},
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return defaultCache
|
||||||
|
}
|
10
component/profile/profile.go
Normal file
10
component/profile/profile.go
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
package profile
|
||||||
|
|
||||||
|
import (
|
||||||
|
"go.uber.org/atomic"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// StoreSelected is a global switch for storing selected proxy to cache
|
||||||
|
StoreSelected = atomic.NewBool(true)
|
||||||
|
)
|
@ -1,9 +1,12 @@
|
|||||||
package resolver
|
package resolver
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
|
"math/rand"
|
||||||
"net"
|
"net"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/component/trie"
|
"github.com/Dreamacro/clash/component/trie"
|
||||||
)
|
)
|
||||||
@ -18,6 +21,9 @@ var (
|
|||||||
|
|
||||||
// DefaultHosts aim to resolve hosts
|
// DefaultHosts aim to resolve hosts
|
||||||
DefaultHosts = trie.New()
|
DefaultHosts = trie.New()
|
||||||
|
|
||||||
|
// DefaultDNSTimeout defined the default dns request timeout
|
||||||
|
DefaultDNSTimeout = time.Second * 5
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -52,18 +58,16 @@ func ResolveIPv4(host string) (net.IP, error) {
|
|||||||
return DefaultResolver.ResolveIPv4(host)
|
return DefaultResolver.ResolveIPv4(host)
|
||||||
}
|
}
|
||||||
|
|
||||||
ipAddrs, err := net.LookupIP(host)
|
ctx, cancel := context.WithTimeout(context.Background(), DefaultDNSTimeout)
|
||||||
|
defer cancel()
|
||||||
|
ipAddrs, err := net.DefaultResolver.LookupIP(ctx, "ip4", host)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
} else if len(ipAddrs) == 0 {
|
||||||
|
return nil, ErrIPNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, ip := range ipAddrs {
|
return ipAddrs[rand.Intn(len(ipAddrs))], nil
|
||||||
if ip4 := ip.To4(); ip4 != nil {
|
|
||||||
return ip4, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, ErrIPNotFound
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ResolveIPv6 with a host, return ipv6
|
// ResolveIPv6 with a host, return ipv6
|
||||||
@ -90,31 +94,29 @@ func ResolveIPv6(host string) (net.IP, error) {
|
|||||||
return DefaultResolver.ResolveIPv6(host)
|
return DefaultResolver.ResolveIPv6(host)
|
||||||
}
|
}
|
||||||
|
|
||||||
ipAddrs, err := net.LookupIP(host)
|
ctx, cancel := context.WithTimeout(context.Background(), DefaultDNSTimeout)
|
||||||
|
defer cancel()
|
||||||
|
ipAddrs, err := net.DefaultResolver.LookupIP(ctx, "ip6", host)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
} else if len(ipAddrs) == 0 {
|
||||||
|
return nil, ErrIPNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, ip := range ipAddrs {
|
return ipAddrs[rand.Intn(len(ipAddrs))], nil
|
||||||
if ip.To4() == nil {
|
|
||||||
return ip, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, ErrIPNotFound
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ResolveIP with a host, return ip
|
// ResolveIPWithResolver same as ResolveIP, but with a resolver
|
||||||
func ResolveIP(host string) (net.IP, error) {
|
func ResolveIPWithResolver(host string, r Resolver) (net.IP, error) {
|
||||||
if node := DefaultHosts.Search(host); node != nil {
|
if node := DefaultHosts.Search(host); node != nil {
|
||||||
return node.Data.(net.IP), nil
|
return node.Data.(net.IP), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if DefaultResolver != nil {
|
if r != nil {
|
||||||
if DisableIPv6 {
|
if DisableIPv6 {
|
||||||
return DefaultResolver.ResolveIPv4(host)
|
return r.ResolveIPv4(host)
|
||||||
}
|
}
|
||||||
return DefaultResolver.ResolveIP(host)
|
return r.ResolveIP(host)
|
||||||
} else if DisableIPv6 {
|
} else if DisableIPv6 {
|
||||||
return ResolveIPv4(host)
|
return ResolveIPv4(host)
|
||||||
}
|
}
|
||||||
@ -131,3 +133,8 @@ func ResolveIP(host string) (net.IP, error) {
|
|||||||
|
|
||||||
return ipAddr.IP, nil
|
return ipAddr.IP, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ResolveIP with a host, return ip
|
||||||
|
func ResolveIP(host string) (net.IP, error) {
|
||||||
|
return ResolveIPWithResolver(host, DefaultResolver)
|
||||||
|
}
|
||||||
|
@ -1,11 +0,0 @@
|
|||||||
package obfs
|
|
||||||
|
|
||||||
// Base information for obfs
|
|
||||||
type Base struct {
|
|
||||||
IVSize int
|
|
||||||
Key []byte
|
|
||||||
HeadLen int
|
|
||||||
Host string
|
|
||||||
Port int
|
|
||||||
Param string
|
|
||||||
}
|
|
@ -1,402 +0,0 @@
|
|||||||
package obfs
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding/hex"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"math/rand"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
type httpObfs struct {
|
|
||||||
*Base
|
|
||||||
firstRequest bool
|
|
||||||
firstResponse bool
|
|
||||||
post bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
register("http_simple", newHTTPSimple)
|
|
||||||
}
|
|
||||||
|
|
||||||
func newHTTPSimple(b *Base) Obfs {
|
|
||||||
return &httpObfs{Base: b}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *httpObfs) initForConn() Obfs {
|
|
||||||
return &httpObfs{
|
|
||||||
Base: h.Base,
|
|
||||||
firstRequest: true,
|
|
||||||
firstResponse: true,
|
|
||||||
post: h.post,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *httpObfs) GetObfsOverhead() int {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *httpObfs) Decode(b []byte) ([]byte, bool, error) {
|
|
||||||
if h.firstResponse {
|
|
||||||
idx := bytes.Index(b, []byte("\r\n\r\n"))
|
|
||||||
if idx == -1 {
|
|
||||||
return nil, false, io.EOF
|
|
||||||
}
|
|
||||||
h.firstResponse = false
|
|
||||||
return b[idx+4:], false, nil
|
|
||||||
}
|
|
||||||
return b, false, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *httpObfs) Encode(b []byte) ([]byte, error) {
|
|
||||||
if h.firstRequest {
|
|
||||||
bSize := len(b)
|
|
||||||
var headData []byte
|
|
||||||
|
|
||||||
if headSize := h.IVSize + h.HeadLen; bSize-headSize > 64 {
|
|
||||||
headData = make([]byte, headSize+rand.Intn(64))
|
|
||||||
} else {
|
|
||||||
headData = make([]byte, bSize)
|
|
||||||
}
|
|
||||||
copy(headData, b[:len(headData)])
|
|
||||||
host := h.Host
|
|
||||||
var customHead string
|
|
||||||
|
|
||||||
if len(h.Param) > 0 {
|
|
||||||
customHeads := strings.Split(h.Param, "#")
|
|
||||||
if len(customHeads) > 2 {
|
|
||||||
customHeads = customHeads[:2]
|
|
||||||
}
|
|
||||||
customHosts := h.Param
|
|
||||||
if len(customHeads) > 1 {
|
|
||||||
customHosts = customHeads[0]
|
|
||||||
customHead = customHeads[1]
|
|
||||||
}
|
|
||||||
hosts := strings.Split(customHosts, ",")
|
|
||||||
if len(hosts) > 0 {
|
|
||||||
host = strings.TrimSpace(hosts[rand.Intn(len(hosts))])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
method := "GET /"
|
|
||||||
if h.post {
|
|
||||||
method = "POST /"
|
|
||||||
}
|
|
||||||
requestPathIndex := rand.Intn(len(requestPath)/2) * 2
|
|
||||||
httpBuf := fmt.Sprintf("%s%s%s%s HTTP/1.1\r\nHost: %s:%d\r\n",
|
|
||||||
method,
|
|
||||||
requestPath[requestPathIndex],
|
|
||||||
data2URLEncode(headData),
|
|
||||||
requestPath[requestPathIndex+1],
|
|
||||||
host, h.Port)
|
|
||||||
if len(customHead) > 0 {
|
|
||||||
httpBuf = httpBuf + strings.Replace(customHead, "\\n", "\r\n", -1) + "\r\n\r\n"
|
|
||||||
} else {
|
|
||||||
var contentType string
|
|
||||||
if h.post {
|
|
||||||
contentType = "Content-Type: multipart/form-data; boundary=" + boundary() + "\r\n"
|
|
||||||
}
|
|
||||||
httpBuf = httpBuf + "User-agent: " + requestUserAgent[rand.Intn(len(requestUserAgent))] + "\r\n" +
|
|
||||||
"Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n" +
|
|
||||||
"Accept-Language: en-US,en;q=0.8\r\n" +
|
|
||||||
"Accept-Encoding: gzip, deflate\r\n" +
|
|
||||||
contentType +
|
|
||||||
"DNT: 1\r\n" +
|
|
||||||
"Connection: keep-alive\r\n" +
|
|
||||||
"\r\n"
|
|
||||||
}
|
|
||||||
|
|
||||||
var encoded []byte
|
|
||||||
if len(headData) < bSize {
|
|
||||||
encoded = make([]byte, len(httpBuf)+(bSize-len(headData)))
|
|
||||||
copy(encoded, []byte(httpBuf))
|
|
||||||
copy(encoded[len(httpBuf):], b[len(headData):])
|
|
||||||
} else {
|
|
||||||
encoded = []byte(httpBuf)
|
|
||||||
}
|
|
||||||
h.firstRequest = false
|
|
||||||
return encoded, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return b, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func data2URLEncode(data []byte) (ret string) {
|
|
||||||
for i := 0; i < len(data); i++ {
|
|
||||||
ret = fmt.Sprintf("%s%%%s", ret, hex.EncodeToString([]byte{data[i]}))
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func boundary() (ret string) {
|
|
||||||
set := "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
|
|
||||||
for i := 0; i < 32; i++ {
|
|
||||||
ret = fmt.Sprintf("%s%c", ret, set[rand.Intn(len(set))])
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
requestPath = []string{
|
|
||||||
"", "",
|
|
||||||
"login.php?redir=", "",
|
|
||||||
"register.php?code=", "",
|
|
||||||
"?keyword=", "",
|
|
||||||
"search?src=typd&q=", "&lang=en",
|
|
||||||
"s?ie=utf-8&f=8&rsv_bp=1&rsv_idx=1&ch=&bar=&wd=", "&rn=",
|
|
||||||
"post.php?id=", "&goto=view.php",
|
|
||||||
}
|
|
||||||
requestUserAgent = []string{
|
|
||||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.162 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.85 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Linux; Android 7.0; Moto C Build/NRD90M.059) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Linux; Android 6.0.1; SM-G532M Build/MMB29T; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/55.0.2883.91 Mobile Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.80 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.101 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Windows NT 6.2; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/40.0.2214.111 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.94 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Linux; Android 5.1.1; SM-J120M Build/LMY47X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Windows NT 6.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.80 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Linux; Android 7.0; Moto G (5) Build/NPPS25.137-93-14) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.106 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Linux; Android 7.0; SM-G570M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.80 Mobile Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Windows NT 5.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.80 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.112 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Linux; Android 6.0; CAM-L03 Build/HUAWEICAM-L03) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.76 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.117 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/534.3 (KHTML, like Gecko) Chrome/6.0.472.63 Safari/534.3",
|
|
||||||
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.106 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/534.7 (KHTML, like Gecko) Chrome/7.0.517.44 Safari/534.7",
|
|
||||||
"Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.75 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.71 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/534.3 (KHTML, like Gecko) Chrome/6.0.472.63 Safari/534.3",
|
|
||||||
"Mozilla/5.0 (Linux; Android 8.0.0; FIG-LX3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.80 Mobile Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/40.0.2214.115 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/534.10 (KHTML, like Gecko) Chrome/8.0.552.237 Safari/534.10",
|
|
||||||
"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (X11; U; Linux x86_64; en-US) AppleWebKit/533.2 (KHTML, like Gecko) Chrome/5.0.342.1 Safari/533.2",
|
|
||||||
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.110 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.89 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.81 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (X11; Datanyze; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.71 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.1916.153 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Linux; Android 5.1.1; SM-J111M Build/LMY47V) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Windows NT 6.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.106 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/37.0.2062.120 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.84 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.99 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/32.0.1700.107 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Linux; Android 6.0.1; SM-J700M Build/MMB29K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.63 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (X11; Linux i686) AppleWebKit/534.30 (KHTML, like Gecko) Slackware/Chrome/12.0.742.100 Safari/534.30",
|
|
||||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.86 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.167 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/48.0.2564.116 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.102 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.143 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (X11; Linux i686) AppleWebKit/534.30 (KHTML, like Gecko) Chrome/12.0.742.100 Safari/534.30",
|
|
||||||
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Linux; Android 8.0.0; WAS-LX3 Build/HUAWEIWAS-LX3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.99 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.87 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.102 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.57 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.101 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.1805 Safari/537.36 MVisionPlayer/1.0.0.0",
|
|
||||||
"Mozilla/5.0 (Linux; Android 7.0; TRT-LX3 Build/HUAWEITRT-LX3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.89 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Linux; Android 6.0; vivo 1610 Build/MMB29M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.124 Mobile Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Windows NT 6.2; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.87 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.111 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Linux; Android 4.4.2; de-de; SAMSUNG GT-I9195 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Version/1.5 Chrome/28.0.1500.94 Mobile Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.95 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.90 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.110 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Linux; Android 8.0.0; ANE-LX3 Build/HUAWEIANE-LX3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.112 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.87 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.143 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (X11; U; Linux i586; en-US) AppleWebKit/533.2 (KHTML, like Gecko) Chrome/5.0.342.1 Safari/533.2",
|
|
||||||
"Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/43.0.2357.65 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Linux; Android 7.0; SM-G610M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.80 Mobile Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Linux; Android 6.0.1; SM-J500M Build/MMB29M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/534.7 (KHTML, like Gecko) Chrome/7.0.517.44 Safari/534.7",
|
|
||||||
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.104 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Linux; Android 6.0; vivo 1606 Build/MMB29M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.124 Mobile Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.135 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Linux; Android 7.0; SM-G610M Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Linux; Android 7.1; vivo 1716 Build/N2G47H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.98 Mobile Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.93 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Linux; Android 7.0; SM-G570M Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Windows NT 6.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Linux; Android 6.0; MYA-L22 Build/HUAWEIMYA-L22) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.84 Mobile Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Linux; Android 5.1; A1601 Build/LMY47I) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.98 Mobile Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Linux; Android 7.0; TRT-LX2 Build/HUAWEITRT-LX2; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/59.0.3071.125 Mobile Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Windows NT 6.2; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Windows NT 6.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Windows NT 5.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/534.17 (KHTML, like Gecko) Chrome/10.0.649.0 Safari/534.17",
|
|
||||||
"Mozilla/5.0 (Linux; Android 6.0; CAM-L21 Build/HUAWEICAM-L21; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/62.0.3202.84 Mobile Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.94 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/534.24 (KHTML, like Gecko) Chrome/11.0.696.3 Safari/534.24",
|
|
||||||
"Mozilla/5.0 (Linux; Android 7.1.2; Redmi 4X Build/N2G47H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.111 Mobile Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.106 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.181 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Linux; Android 4.4.2; SM-G7102 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.84 Mobile Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/48.0.2564.109 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Linux; Android 5.1; HUAWEI CUN-L22 Build/HUAWEICUN-L22; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/62.0.3202.84 Mobile Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Linux; Android 5.1.1; A37fw Build/LMY47V) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.84 Mobile Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Linux; Android 7.0; SM-J730GM Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.111 Mobile Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Linux; Android 7.0; SM-G610F Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.111 Mobile Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.101 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Linux; Android 7.1.2; Redmi Note 5A Build/N2G47H; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/63.0.3239.111 Mobile Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Linux; Android 7.0; Redmi Note 4 Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.111 Mobile Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.106 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Unknown; Linux) AppleWebKit/538.1 (KHTML, like Gecko) Chrome/v1.0.0 Safari/538.1",
|
|
||||||
"Mozilla/5.0 (Linux; Android 7.0; BLL-L22 Build/HUAWEIBLL-L22) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.91 Mobile Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Linux; Android 7.0; SM-J710F Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.84 Mobile Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Linux; Android 6.0.1; SM-G532M Build/MMB29T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.91 Mobile Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Linux; Android 7.1.1; CPH1723 Build/N6F26Q) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.98 Mobile Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.118 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.79 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.101 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.94 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.139 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Linux; Android 8.0.0; FIG-LX3 Build/HUAWEIFIG-LX3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Windows; U; Windows NT 6.1; de-DE) AppleWebKit/534.17 (KHTML, like Gecko) Chrome/10.0.649.0 Safari/534.17",
|
|
||||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.63 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2228.0 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.67 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.94 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.99 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.67 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.67 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.67 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/43.0.2357.65 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Linux; Android 7.1; Mi A1 Build/N2G47H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.83 Mobile Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.117 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/533.4 (KHTML, like Gecko) Chrome/5.0.375.99 Safari/533.4",
|
|
||||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/36.0.1985.125 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.89 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.111 Safari/537.36 MVisionPlayer/1.0.0.0",
|
|
||||||
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.103 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Linux; Android 5.1; A37f Build/LMY47V) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/43.0.2357.93 Mobile Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Windows NT 6.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Windows NT 6.2; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.100 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.86 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.76 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Windows NT 5.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.80 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Linux; Android 6.0.1; CPH1607 Build/MMB29M; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/63.0.3239.111 Mobile Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.99 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.95 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.100 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Linux; Android 6.0.1; vivo 1603 Build/MMB29M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.83 Mobile Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.186 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Linux; Android 6.0.1; SM-G532M Build/MMB29T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.87 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Linux; Android 6.0.1; Redmi 4A Build/MMB29M; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/60.0.3112.116 Mobile Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.103 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Windows NT 6.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.112 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/44.0.2403.157 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.71 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Windows NT 5.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Windows NT 6.2; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.90 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.100 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.31 (KHTML, like Gecko) Chrome/26.0.1410.64 Safari/537.31",
|
|
||||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.84 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.186 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.80 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/36.0.1985.143 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.112 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.67 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.181 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.106 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Linux; Android 6.0.1; SM-G532G Build/MMB29T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.83 Mobile Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/48.0.2564.109 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.117 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.139 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.67 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/34.0.1847.131 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Linux; Android 6.0; vivo 1713 Build/MRA58K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.124 Mobile Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.89 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.80 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.101 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.67 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36",
|
|
||||||
}
|
|
||||||
)
|
|
@ -1,37 +0,0 @@
|
|||||||
package obfs
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
errTLS12TicketAuthIncorrectMagicNumber = errors.New("tls1.2_ticket_auth incorrect magic number")
|
|
||||||
errTLS12TicketAuthTooShortData = errors.New("tls1.2_ticket_auth too short data")
|
|
||||||
errTLS12TicketAuthHMACError = errors.New("tls1.2_ticket_auth hmac verifying failed")
|
|
||||||
)
|
|
||||||
|
|
||||||
// Obfs provides methods for decoding and encoding
|
|
||||||
type Obfs interface {
|
|
||||||
initForConn() Obfs
|
|
||||||
GetObfsOverhead() int
|
|
||||||
Decode(b []byte) ([]byte, bool, error)
|
|
||||||
Encode(b []byte) ([]byte, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
type obfsCreator func(b *Base) Obfs
|
|
||||||
|
|
||||||
var obfsList = make(map[string]obfsCreator)
|
|
||||||
|
|
||||||
func register(name string, c obfsCreator) {
|
|
||||||
obfsList[name] = c
|
|
||||||
}
|
|
||||||
|
|
||||||
// PickObfs returns an obfs of the given name
|
|
||||||
func PickObfs(name string, b *Base) (Obfs, error) {
|
|
||||||
if obfsCreator, ok := obfsList[strings.ToLower(name)]; ok {
|
|
||||||
return obfsCreator(b), nil
|
|
||||||
}
|
|
||||||
return nil, fmt.Errorf("Obfs %s not supported", name)
|
|
||||||
}
|
|
@ -1,25 +0,0 @@
|
|||||||
package obfs
|
|
||||||
|
|
||||||
type plain struct{}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
register("plain", newPlain)
|
|
||||||
}
|
|
||||||
|
|
||||||
func newPlain(b *Base) Obfs {
|
|
||||||
return &plain{}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *plain) initForConn() Obfs { return &plain{} }
|
|
||||||
|
|
||||||
func (p *plain) GetObfsOverhead() int {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *plain) Encode(b []byte) ([]byte, error) {
|
|
||||||
return b, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *plain) Decode(b []byte) ([]byte, bool, error) {
|
|
||||||
return b, false, nil
|
|
||||||
}
|
|
@ -1,75 +0,0 @@
|
|||||||
package obfs
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/binary"
|
|
||||||
"hash/crc32"
|
|
||||||
"math/rand"
|
|
||||||
)
|
|
||||||
|
|
||||||
type randomHead struct {
|
|
||||||
*Base
|
|
||||||
firstRequest bool
|
|
||||||
firstResponse bool
|
|
||||||
headerSent bool
|
|
||||||
buffer []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
register("random_head", newRandomHead)
|
|
||||||
}
|
|
||||||
|
|
||||||
func newRandomHead(b *Base) Obfs {
|
|
||||||
return &randomHead{Base: b}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *randomHead) initForConn() Obfs {
|
|
||||||
return &randomHead{
|
|
||||||
Base: r.Base,
|
|
||||||
firstRequest: true,
|
|
||||||
firstResponse: true,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *randomHead) GetObfsOverhead() int {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *randomHead) Encode(b []byte) (encoded []byte, err error) {
|
|
||||||
if !r.firstRequest {
|
|
||||||
return b, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
bSize := len(b)
|
|
||||||
if r.headerSent {
|
|
||||||
if bSize > 0 {
|
|
||||||
d := make([]byte, len(r.buffer)+bSize)
|
|
||||||
copy(d, r.buffer)
|
|
||||||
copy(d[len(r.buffer):], b)
|
|
||||||
r.buffer = d
|
|
||||||
} else {
|
|
||||||
encoded = r.buffer
|
|
||||||
r.buffer = nil
|
|
||||||
r.firstRequest = false
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
size := rand.Intn(96) + 8
|
|
||||||
encoded = make([]byte, size)
|
|
||||||
rand.Read(encoded)
|
|
||||||
crc := (0xFFFFFFFF - crc32.ChecksumIEEE(encoded[:size-4])) & 0xFFFFFFFF
|
|
||||||
binary.LittleEndian.PutUint32(encoded[size-4:], crc)
|
|
||||||
|
|
||||||
d := make([]byte, bSize)
|
|
||||||
copy(d, b)
|
|
||||||
r.buffer = d
|
|
||||||
}
|
|
||||||
r.headerSent = true
|
|
||||||
return encoded, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *randomHead) Decode(b []byte) ([]byte, bool, error) {
|
|
||||||
if r.firstResponse {
|
|
||||||
r.firstResponse = false
|
|
||||||
return b, true, nil
|
|
||||||
}
|
|
||||||
return b, false, nil
|
|
||||||
}
|
|
@ -1,72 +0,0 @@
|
|||||||
package obfs
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net"
|
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/common/pool"
|
|
||||||
)
|
|
||||||
|
|
||||||
// NewConn wraps a stream-oriented net.Conn with obfs decoding/encoding
|
|
||||||
func NewConn(c net.Conn, o Obfs) net.Conn {
|
|
||||||
return &Conn{Conn: c, Obfs: o.initForConn()}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Conn represents an obfs connection
|
|
||||||
type Conn struct {
|
|
||||||
net.Conn
|
|
||||||
Obfs
|
|
||||||
buf []byte
|
|
||||||
offset int
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Conn) Read(b []byte) (int, error) {
|
|
||||||
if c.buf != nil {
|
|
||||||
n := copy(b, c.buf[c.offset:])
|
|
||||||
c.offset += n
|
|
||||||
if c.offset == len(c.buf) {
|
|
||||||
pool.Put(c.buf)
|
|
||||||
c.buf = nil
|
|
||||||
}
|
|
||||||
return n, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
buf := pool.Get(pool.RelayBufferSize)
|
|
||||||
defer pool.Put(buf)
|
|
||||||
n, err := c.Conn.Read(buf)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
decoded, sendback, err := c.Decode(buf[:n])
|
|
||||||
// decoded may be part of buf
|
|
||||||
decodedData := pool.Get(len(decoded))
|
|
||||||
copy(decodedData, decoded)
|
|
||||||
if err != nil {
|
|
||||||
pool.Put(decodedData)
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
if sendback {
|
|
||||||
c.Write(nil)
|
|
||||||
pool.Put(decodedData)
|
|
||||||
return 0, nil
|
|
||||||
}
|
|
||||||
n = copy(b, decodedData)
|
|
||||||
if len(decodedData) > len(b) {
|
|
||||||
c.buf = decodedData
|
|
||||||
c.offset = n
|
|
||||||
} else {
|
|
||||||
pool.Put(decodedData)
|
|
||||||
}
|
|
||||||
return n, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Conn) Write(b []byte) (int, error) {
|
|
||||||
encoded, err := c.Encode(b)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
_, err = c.Conn.Write(encoded)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
return len(b), nil
|
|
||||||
}
|
|
@ -1,290 +0,0 @@
|
|||||||
package obfs
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"crypto/hmac"
|
|
||||||
"encoding/binary"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"math/rand"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/common/pool"
|
|
||||||
"github.com/Dreamacro/clash/component/ssr/tools"
|
|
||||||
"github.com/Dreamacro/clash/log"
|
|
||||||
)
|
|
||||||
|
|
||||||
type tlsAuthData struct {
|
|
||||||
localClientID [32]byte
|
|
||||||
}
|
|
||||||
|
|
||||||
type tls12Ticket struct {
|
|
||||||
*Base
|
|
||||||
*tlsAuthData
|
|
||||||
handshakeStatus int
|
|
||||||
sendSaver bytes.Buffer
|
|
||||||
recvBuffer bytes.Buffer
|
|
||||||
buffer bytes.Buffer
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
register("tls1.2_ticket_auth", newTLS12Ticket)
|
|
||||||
register("tls1.2_ticket_fastauth", newTLS12Ticket)
|
|
||||||
}
|
|
||||||
|
|
||||||
func newTLS12Ticket(b *Base) Obfs {
|
|
||||||
return &tls12Ticket{
|
|
||||||
Base: b,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *tls12Ticket) initForConn() Obfs {
|
|
||||||
r := &tls12Ticket{
|
|
||||||
Base: t.Base,
|
|
||||||
tlsAuthData: &tlsAuthData{},
|
|
||||||
}
|
|
||||||
rand.Read(r.localClientID[:])
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *tls12Ticket) GetObfsOverhead() int {
|
|
||||||
return 5
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *tls12Ticket) Decode(b []byte) ([]byte, bool, error) {
|
|
||||||
if t.handshakeStatus == -1 {
|
|
||||||
return b, false, nil
|
|
||||||
}
|
|
||||||
t.buffer.Reset()
|
|
||||||
if t.handshakeStatus == 8 {
|
|
||||||
t.recvBuffer.Write(b)
|
|
||||||
for t.recvBuffer.Len() > 5 {
|
|
||||||
var h [5]byte
|
|
||||||
t.recvBuffer.Read(h[:])
|
|
||||||
if !bytes.Equal(h[:3], []byte{0x17, 0x3, 0x3}) {
|
|
||||||
log.Warnln("incorrect magic number %x, 0x170303 is expected", h[:3])
|
|
||||||
return nil, false, errTLS12TicketAuthIncorrectMagicNumber
|
|
||||||
}
|
|
||||||
size := int(binary.BigEndian.Uint16(h[3:5]))
|
|
||||||
if t.recvBuffer.Len() < size {
|
|
||||||
// 不够读,下回再读吧
|
|
||||||
unread := t.recvBuffer.Bytes()
|
|
||||||
t.recvBuffer.Reset()
|
|
||||||
t.recvBuffer.Write(h[:])
|
|
||||||
t.recvBuffer.Write(unread)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
d := pool.Get(size)
|
|
||||||
t.recvBuffer.Read(d)
|
|
||||||
t.buffer.Write(d)
|
|
||||||
pool.Put(d)
|
|
||||||
}
|
|
||||||
return t.buffer.Bytes(), false, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(b) < 11+32+1+32 {
|
|
||||||
return nil, false, errTLS12TicketAuthTooShortData
|
|
||||||
}
|
|
||||||
|
|
||||||
hash := t.hmacSHA1(b[11 : 11+22])
|
|
||||||
|
|
||||||
if !hmac.Equal(b[33:33+tools.HmacSHA1Len], hash) {
|
|
||||||
return nil, false, errTLS12TicketAuthHMACError
|
|
||||||
}
|
|
||||||
return nil, true, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *tls12Ticket) Encode(b []byte) ([]byte, error) {
|
|
||||||
t.buffer.Reset()
|
|
||||||
switch t.handshakeStatus {
|
|
||||||
case 8:
|
|
||||||
if len(b) < 1024 {
|
|
||||||
d := []byte{0x17, 0x3, 0x3, 0, 0}
|
|
||||||
binary.BigEndian.PutUint16(d[3:5], uint16(len(b)&0xFFFF))
|
|
||||||
t.buffer.Write(d)
|
|
||||||
t.buffer.Write(b)
|
|
||||||
return t.buffer.Bytes(), nil
|
|
||||||
}
|
|
||||||
start := 0
|
|
||||||
var l int
|
|
||||||
for len(b)-start > 2048 {
|
|
||||||
l = rand.Intn(4096) + 100
|
|
||||||
if l > len(b)-start {
|
|
||||||
l = len(b) - start
|
|
||||||
}
|
|
||||||
packData(&t.buffer, b[start:start+l])
|
|
||||||
start += l
|
|
||||||
}
|
|
||||||
if len(b)-start > 0 {
|
|
||||||
l = len(b) - start
|
|
||||||
packData(&t.buffer, b[start:start+l])
|
|
||||||
}
|
|
||||||
return t.buffer.Bytes(), nil
|
|
||||||
case 1:
|
|
||||||
if len(b) > 0 {
|
|
||||||
if len(b) < 1024 {
|
|
||||||
packData(&t.sendSaver, b)
|
|
||||||
} else {
|
|
||||||
start := 0
|
|
||||||
var l int
|
|
||||||
for len(b)-start > 2048 {
|
|
||||||
l = rand.Intn(4096) + 100
|
|
||||||
if l > len(b)-start {
|
|
||||||
l = len(b) - start
|
|
||||||
}
|
|
||||||
packData(&t.buffer, b[start:start+l])
|
|
||||||
start += l
|
|
||||||
}
|
|
||||||
if len(b)-start > 0 {
|
|
||||||
l = len(b) - start
|
|
||||||
packData(&t.buffer, b[start:start+l])
|
|
||||||
}
|
|
||||||
io.Copy(&t.sendSaver, &t.buffer)
|
|
||||||
}
|
|
||||||
return []byte{}, nil
|
|
||||||
}
|
|
||||||
hmacData := make([]byte, 43)
|
|
||||||
handshakeFinish := []byte("\x14\x03\x03\x00\x01\x01\x16\x03\x03\x00\x20")
|
|
||||||
copy(hmacData, handshakeFinish)
|
|
||||||
rand.Read(hmacData[11:33])
|
|
||||||
h := t.hmacSHA1(hmacData[:33])
|
|
||||||
copy(hmacData[33:], h)
|
|
||||||
t.buffer.Write(hmacData)
|
|
||||||
io.Copy(&t.buffer, &t.sendSaver)
|
|
||||||
t.handshakeStatus = 8
|
|
||||||
return t.buffer.Bytes(), nil
|
|
||||||
case 0:
|
|
||||||
tlsData0 := []byte("\x00\x1c\xc0\x2b\xc0\x2f\xcc\xa9\xcc\xa8\xcc\x14\xcc\x13\xc0\x0a\xc0\x14\xc0\x09\xc0\x13\x00\x9c\x00\x35\x00\x2f\x00\x0a\x01\x00")
|
|
||||||
tlsData1 := []byte("\xff\x01\x00\x01\x00")
|
|
||||||
tlsData2 := []byte("\x00\x17\x00\x00\x00\x23\x00\xd0")
|
|
||||||
// tlsData3 := []byte("\x00\x0d\x00\x16\x00\x14\x06\x01\x06\x03\x05\x01\x05\x03\x04\x01\x04\x03\x03\x01\x03\x03\x02\x01\x02\x03\x00\x05\x00\x05\x01\x00\x00\x00\x00\x00\x12\x00\x00\x75\x50\x00\x00\x00\x0b\x00\x02\x01\x00\x00\x0a\x00\x06\x00\x04\x00\x17\x00\x18\x00\x15\x00\x66\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00")
|
|
||||||
tlsData3 := []byte("\x00\x0d\x00\x16\x00\x14\x06\x01\x06\x03\x05\x01\x05\x03\x04\x01\x04\x03\x03\x01\x03\x03\x02\x01\x02\x03\x00\x05\x00\x05\x01\x00\x00\x00\x00\x00\x12\x00\x00\x75\x50\x00\x00\x00\x0b\x00\x02\x01\x00\x00\x0a\x00\x06\x00\x04\x00\x17\x00\x18")
|
|
||||||
|
|
||||||
var tlsData [2048]byte
|
|
||||||
tlsDataLen := 0
|
|
||||||
copy(tlsData[0:], tlsData1)
|
|
||||||
tlsDataLen += len(tlsData1)
|
|
||||||
sni := t.sni(t.getHost())
|
|
||||||
copy(tlsData[tlsDataLen:], sni)
|
|
||||||
tlsDataLen += len(sni)
|
|
||||||
copy(tlsData[tlsDataLen:], tlsData2)
|
|
||||||
tlsDataLen += len(tlsData2)
|
|
||||||
ticketLen := rand.Intn(164)*2 + 64
|
|
||||||
tlsData[tlsDataLen-1] = uint8(ticketLen & 0xff)
|
|
||||||
tlsData[tlsDataLen-2] = uint8(ticketLen >> 8)
|
|
||||||
//ticketLen := 208
|
|
||||||
rand.Read(tlsData[tlsDataLen : tlsDataLen+ticketLen])
|
|
||||||
tlsDataLen += ticketLen
|
|
||||||
copy(tlsData[tlsDataLen:], tlsData3)
|
|
||||||
tlsDataLen += len(tlsData3)
|
|
||||||
|
|
||||||
length := 11 + 32 + 1 + 32 + len(tlsData0) + 2 + tlsDataLen
|
|
||||||
encodedData := make([]byte, length)
|
|
||||||
pdata := length - tlsDataLen
|
|
||||||
l := tlsDataLen
|
|
||||||
copy(encodedData[pdata:], tlsData[:tlsDataLen])
|
|
||||||
encodedData[pdata-1] = uint8(tlsDataLen)
|
|
||||||
encodedData[pdata-2] = uint8(tlsDataLen >> 8)
|
|
||||||
pdata -= 2
|
|
||||||
l += 2
|
|
||||||
copy(encodedData[pdata-len(tlsData0):], tlsData0)
|
|
||||||
pdata -= len(tlsData0)
|
|
||||||
l += len(tlsData0)
|
|
||||||
copy(encodedData[pdata-32:], t.localClientID[:])
|
|
||||||
pdata -= 32
|
|
||||||
l += 32
|
|
||||||
encodedData[pdata-1] = 0x20
|
|
||||||
pdata--
|
|
||||||
l++
|
|
||||||
copy(encodedData[pdata-32:], t.packAuthData())
|
|
||||||
pdata -= 32
|
|
||||||
l += 32
|
|
||||||
encodedData[pdata-1] = 0x3
|
|
||||||
encodedData[pdata-2] = 0x3 // tls version
|
|
||||||
pdata -= 2
|
|
||||||
l += 2
|
|
||||||
encodedData[pdata-1] = uint8(l)
|
|
||||||
encodedData[pdata-2] = uint8(l >> 8)
|
|
||||||
encodedData[pdata-3] = 0
|
|
||||||
encodedData[pdata-4] = 1
|
|
||||||
pdata -= 4
|
|
||||||
l += 4
|
|
||||||
encodedData[pdata-1] = uint8(l)
|
|
||||||
encodedData[pdata-2] = uint8(l >> 8)
|
|
||||||
pdata -= 2
|
|
||||||
l += 2
|
|
||||||
encodedData[pdata-1] = 0x1
|
|
||||||
encodedData[pdata-2] = 0x3 // tls version
|
|
||||||
pdata -= 2
|
|
||||||
l += 2
|
|
||||||
encodedData[pdata-1] = 0x16 // tls handshake
|
|
||||||
pdata--
|
|
||||||
l++
|
|
||||||
packData(&t.sendSaver, b)
|
|
||||||
t.handshakeStatus = 1
|
|
||||||
return encodedData, nil
|
|
||||||
default:
|
|
||||||
return nil, fmt.Errorf("unexpected handshake status: %d", t.handshakeStatus)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *tls12Ticket) hmacSHA1(data []byte) []byte {
|
|
||||||
key := make([]byte, len(t.Key)+32)
|
|
||||||
copy(key, t.Key)
|
|
||||||
copy(key[len(t.Key):], t.localClientID[:])
|
|
||||||
|
|
||||||
sha1Data := tools.HmacSHA1(key, data)
|
|
||||||
return sha1Data[:tools.HmacSHA1Len]
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *tls12Ticket) sni(u string) []byte {
|
|
||||||
bURL := []byte(u)
|
|
||||||
length := len(bURL)
|
|
||||||
ret := make([]byte, length+9)
|
|
||||||
copy(ret[9:9+length], bURL)
|
|
||||||
binary.BigEndian.PutUint16(ret[7:], uint16(length&0xFFFF))
|
|
||||||
length += 3
|
|
||||||
binary.BigEndian.PutUint16(ret[4:], uint16(length&0xFFFF))
|
|
||||||
length += 2
|
|
||||||
binary.BigEndian.PutUint16(ret[2:], uint16(length&0xFFFF))
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *tls12Ticket) getHost() string {
|
|
||||||
host := t.Host
|
|
||||||
if len(t.Param) > 0 {
|
|
||||||
hosts := strings.Split(t.Param, ",")
|
|
||||||
if len(hosts) > 0 {
|
|
||||||
|
|
||||||
host = hosts[rand.Intn(len(hosts))]
|
|
||||||
host = strings.TrimSpace(host)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(host) > 0 && host[len(host)-1] >= byte('0') && host[len(host)-1] <= byte('9') && len(t.Param) == 0 {
|
|
||||||
host = ""
|
|
||||||
}
|
|
||||||
return host
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *tls12Ticket) packAuthData() (ret []byte) {
|
|
||||||
retSize := 32
|
|
||||||
ret = make([]byte, retSize)
|
|
||||||
|
|
||||||
now := time.Now().Unix()
|
|
||||||
binary.BigEndian.PutUint32(ret[:4], uint32(now))
|
|
||||||
|
|
||||||
rand.Read(ret[4 : 4+18])
|
|
||||||
|
|
||||||
hash := t.hmacSHA1(ret[:retSize-tools.HmacSHA1Len])
|
|
||||||
copy(ret[retSize-tools.HmacSHA1Len:], hash)
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func packData(buffer *bytes.Buffer, suffix []byte) {
|
|
||||||
d := []byte{0x17, 0x3, 0x3, 0, 0}
|
|
||||||
binary.BigEndian.PutUint16(d[3:5], uint16(len(suffix)&0xFFFF))
|
|
||||||
buffer.Write(d)
|
|
||||||
buffer.Write(suffix)
|
|
||||||
}
|
|
@ -1,310 +0,0 @@
|
|||||||
package protocol
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"crypto/aes"
|
|
||||||
"crypto/cipher"
|
|
||||||
"encoding/base64"
|
|
||||||
"encoding/binary"
|
|
||||||
"math/rand"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/common/pool"
|
|
||||||
"github.com/Dreamacro/clash/component/ssr/tools"
|
|
||||||
"github.com/Dreamacro/go-shadowsocks2/core"
|
|
||||||
)
|
|
||||||
|
|
||||||
type authAES128 struct {
|
|
||||||
*Base
|
|
||||||
*recvInfo
|
|
||||||
*authData
|
|
||||||
hasSentHeader bool
|
|
||||||
packID uint32
|
|
||||||
userKey []byte
|
|
||||||
uid [4]byte
|
|
||||||
salt string
|
|
||||||
hmac hmacMethod
|
|
||||||
hashDigest hashDigestMethod
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
register("auth_aes128_md5", newAuthAES128MD5)
|
|
||||||
}
|
|
||||||
|
|
||||||
func newAuthAES128MD5(b *Base) Protocol {
|
|
||||||
return &authAES128{
|
|
||||||
Base: b,
|
|
||||||
authData: &authData{},
|
|
||||||
salt: "auth_aes128_md5",
|
|
||||||
hmac: tools.HmacMD5,
|
|
||||||
hashDigest: tools.MD5Sum,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *authAES128) initForConn(iv []byte) Protocol {
|
|
||||||
return &authAES128{
|
|
||||||
Base: &Base{
|
|
||||||
IV: iv,
|
|
||||||
Key: a.Key,
|
|
||||||
TCPMss: a.TCPMss,
|
|
||||||
Overhead: a.Overhead,
|
|
||||||
Param: a.Param,
|
|
||||||
},
|
|
||||||
recvInfo: &recvInfo{recvID: 1, buffer: new(bytes.Buffer)},
|
|
||||||
authData: a.authData,
|
|
||||||
packID: 1,
|
|
||||||
salt: a.salt,
|
|
||||||
hmac: a.hmac,
|
|
||||||
hashDigest: a.hashDigest,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *authAES128) GetProtocolOverhead() int {
|
|
||||||
return 9
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *authAES128) SetOverhead(overhead int) {
|
|
||||||
a.Overhead = overhead
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *authAES128) Decode(b []byte) ([]byte, int, error) {
|
|
||||||
a.buffer.Reset()
|
|
||||||
bSize := len(b)
|
|
||||||
readSize := 0
|
|
||||||
key := pool.Get(len(a.userKey) + 4)
|
|
||||||
defer pool.Put(key)
|
|
||||||
copy(key, a.userKey)
|
|
||||||
for bSize > 4 {
|
|
||||||
binary.LittleEndian.PutUint32(key[len(key)-4:], a.recvID)
|
|
||||||
|
|
||||||
h := a.hmac(key, b[:2])
|
|
||||||
if !bytes.Equal(h[:2], b[2:4]) {
|
|
||||||
return nil, 0, errAuthAES128IncorrectMAC
|
|
||||||
}
|
|
||||||
|
|
||||||
length := int(binary.LittleEndian.Uint16(b[:2]))
|
|
||||||
if length >= 8192 || length < 8 {
|
|
||||||
return nil, 0, errAuthAES128DataLengthError
|
|
||||||
}
|
|
||||||
if length > bSize {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
h = a.hmac(key, b[:length-4])
|
|
||||||
if !bytes.Equal(h[:4], b[length-4:length]) {
|
|
||||||
return nil, 0, errAuthAES128IncorrectChecksum
|
|
||||||
}
|
|
||||||
|
|
||||||
a.recvID++
|
|
||||||
pos := int(b[4])
|
|
||||||
if pos < 255 {
|
|
||||||
pos += 4
|
|
||||||
} else {
|
|
||||||
pos = int(binary.LittleEndian.Uint16(b[5:7])) + 4
|
|
||||||
}
|
|
||||||
|
|
||||||
if pos > length-4 {
|
|
||||||
return nil, 0, errAuthAES128PositionTooLarge
|
|
||||||
}
|
|
||||||
a.buffer.Write(b[pos : length-4])
|
|
||||||
b = b[length:]
|
|
||||||
bSize -= length
|
|
||||||
readSize += length
|
|
||||||
}
|
|
||||||
return a.buffer.Bytes(), readSize, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *authAES128) Encode(b []byte) ([]byte, error) {
|
|
||||||
a.buffer.Reset()
|
|
||||||
bSize := len(b)
|
|
||||||
offset := 0
|
|
||||||
if bSize > 0 && !a.hasSentHeader {
|
|
||||||
authSize := bSize
|
|
||||||
if authSize > 1200 {
|
|
||||||
authSize = 1200
|
|
||||||
}
|
|
||||||
a.hasSentHeader = true
|
|
||||||
a.buffer.Write(a.packAuthData(b[:authSize]))
|
|
||||||
bSize -= authSize
|
|
||||||
offset += authSize
|
|
||||||
}
|
|
||||||
const blockSize = 4096
|
|
||||||
for bSize > blockSize {
|
|
||||||
packSize, randSize := a.packedDataSize(b[offset : offset+blockSize])
|
|
||||||
pack := pool.Get(packSize)
|
|
||||||
a.packData(b[offset:offset+blockSize], pack, randSize)
|
|
||||||
a.buffer.Write(pack)
|
|
||||||
pool.Put(pack)
|
|
||||||
bSize -= blockSize
|
|
||||||
offset += blockSize
|
|
||||||
}
|
|
||||||
if bSize > 0 {
|
|
||||||
packSize, randSize := a.packedDataSize(b[offset:])
|
|
||||||
pack := pool.Get(packSize)
|
|
||||||
a.packData(b[offset:], pack, randSize)
|
|
||||||
a.buffer.Write(pack)
|
|
||||||
pool.Put(pack)
|
|
||||||
}
|
|
||||||
return a.buffer.Bytes(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *authAES128) DecodePacket(b []byte) ([]byte, int, error) {
|
|
||||||
bSize := len(b)
|
|
||||||
h := a.hmac(a.Key, b[:bSize-4])
|
|
||||||
if !bytes.Equal(h[:4], b[bSize-4:]) {
|
|
||||||
return nil, 0, errAuthAES128IncorrectMAC
|
|
||||||
}
|
|
||||||
return b[:bSize-4], bSize - 4, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *authAES128) EncodePacket(b []byte) ([]byte, error) {
|
|
||||||
a.initUserKeyAndID()
|
|
||||||
|
|
||||||
var buf bytes.Buffer
|
|
||||||
buf.Write(b)
|
|
||||||
buf.Write(a.uid[:])
|
|
||||||
h := a.hmac(a.userKey, buf.Bytes())
|
|
||||||
buf.Write(h[:4])
|
|
||||||
return buf.Bytes(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *authAES128) initUserKeyAndID() {
|
|
||||||
if a.userKey == nil {
|
|
||||||
params := strings.Split(a.Param, ":")
|
|
||||||
if len(params) >= 2 {
|
|
||||||
if userID, err := strconv.ParseUint(params[0], 10, 32); err == nil {
|
|
||||||
binary.LittleEndian.PutUint32(a.uid[:], uint32(userID))
|
|
||||||
a.userKey = a.hashDigest([]byte(params[1]))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if a.userKey == nil {
|
|
||||||
rand.Read(a.uid[:])
|
|
||||||
a.userKey = make([]byte, len(a.Key))
|
|
||||||
copy(a.userKey, a.Key)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *authAES128) packedDataSize(data []byte) (packSize, randSize int) {
|
|
||||||
dataSize := len(data)
|
|
||||||
randSize = 1
|
|
||||||
if dataSize <= 1200 {
|
|
||||||
if a.packID > 4 {
|
|
||||||
randSize += rand.Intn(32)
|
|
||||||
} else {
|
|
||||||
if dataSize > 900 {
|
|
||||||
randSize += rand.Intn(128)
|
|
||||||
} else {
|
|
||||||
randSize += rand.Intn(512)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
packSize = randSize + dataSize + 8
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *authAES128) packData(data, ret []byte, randSize int) {
|
|
||||||
dataSize := len(data)
|
|
||||||
retSize := len(ret)
|
|
||||||
// 0~1, ret_size
|
|
||||||
binary.LittleEndian.PutUint16(ret[0:], uint16(retSize&0xFFFF))
|
|
||||||
// 2~3, hmac
|
|
||||||
key := pool.Get(len(a.userKey) + 4)
|
|
||||||
defer pool.Put(key)
|
|
||||||
copy(key, a.userKey)
|
|
||||||
binary.LittleEndian.PutUint32(key[len(key)-4:], a.packID)
|
|
||||||
h := a.hmac(key, ret[:2])
|
|
||||||
copy(ret[2:4], h[:2])
|
|
||||||
// 4~rand_size+4, rand number
|
|
||||||
rand.Read(ret[4 : 4+randSize])
|
|
||||||
// 4, rand_size
|
|
||||||
if randSize < 128 {
|
|
||||||
ret[4] = byte(randSize & 0xFF)
|
|
||||||
} else {
|
|
||||||
// 4, magic number 0xFF
|
|
||||||
ret[4] = 0xFF
|
|
||||||
// 5~6, rand_size
|
|
||||||
binary.LittleEndian.PutUint16(ret[5:], uint16(randSize&0xFFFF))
|
|
||||||
}
|
|
||||||
// rand_size+4~ret_size-4, data
|
|
||||||
if dataSize > 0 {
|
|
||||||
copy(ret[randSize+4:], data)
|
|
||||||
}
|
|
||||||
a.packID++
|
|
||||||
h = a.hmac(key, ret[:retSize-4])
|
|
||||||
copy(ret[retSize-4:], h[:4])
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *authAES128) packAuthData(data []byte) (ret []byte) {
|
|
||||||
dataSize := len(data)
|
|
||||||
var randSize int
|
|
||||||
|
|
||||||
if dataSize > 400 {
|
|
||||||
randSize = rand.Intn(512)
|
|
||||||
} else {
|
|
||||||
randSize = rand.Intn(1024)
|
|
||||||
}
|
|
||||||
|
|
||||||
dataOffset := randSize + 16 + 4 + 4 + 7
|
|
||||||
retSize := dataOffset + dataSize + 4
|
|
||||||
ret = make([]byte, retSize)
|
|
||||||
encrypt := make([]byte, 24)
|
|
||||||
key := make([]byte, len(a.IV)+len(a.Key))
|
|
||||||
copy(key, a.IV)
|
|
||||||
copy(key[len(a.IV):], a.Key)
|
|
||||||
|
|
||||||
rand.Read(ret[dataOffset-randSize:])
|
|
||||||
a.mutex.Lock()
|
|
||||||
defer a.mutex.Unlock()
|
|
||||||
a.connectionID++
|
|
||||||
if a.connectionID > 0xFF000000 {
|
|
||||||
a.clientID = nil
|
|
||||||
}
|
|
||||||
if len(a.clientID) == 0 {
|
|
||||||
a.clientID = make([]byte, 8)
|
|
||||||
rand.Read(a.clientID)
|
|
||||||
b := make([]byte, 4)
|
|
||||||
rand.Read(b)
|
|
||||||
a.connectionID = binary.LittleEndian.Uint32(b) & 0xFFFFFF
|
|
||||||
}
|
|
||||||
copy(encrypt[4:], a.clientID)
|
|
||||||
binary.LittleEndian.PutUint32(encrypt[8:], a.connectionID)
|
|
||||||
|
|
||||||
now := time.Now().Unix()
|
|
||||||
binary.LittleEndian.PutUint32(encrypt[:4], uint32(now))
|
|
||||||
|
|
||||||
binary.LittleEndian.PutUint16(encrypt[12:], uint16(retSize&0xFFFF))
|
|
||||||
binary.LittleEndian.PutUint16(encrypt[14:], uint16(randSize&0xFFFF))
|
|
||||||
|
|
||||||
a.initUserKeyAndID()
|
|
||||||
|
|
||||||
aesCipherKey := core.Kdf(base64.StdEncoding.EncodeToString(a.userKey)+a.salt, 16)
|
|
||||||
block, err := aes.NewCipher(aesCipherKey)
|
|
||||||
if err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
encryptData := make([]byte, 16)
|
|
||||||
iv := make([]byte, aes.BlockSize)
|
|
||||||
cbc := cipher.NewCBCEncrypter(block, iv)
|
|
||||||
cbc.CryptBlocks(encryptData, encrypt[:16])
|
|
||||||
copy(encrypt[:4], a.uid[:])
|
|
||||||
copy(encrypt[4:4+16], encryptData)
|
|
||||||
|
|
||||||
h := a.hmac(key, encrypt[:20])
|
|
||||||
copy(encrypt[20:], h[:4])
|
|
||||||
|
|
||||||
rand.Read(ret[:1])
|
|
||||||
h = a.hmac(key, ret[:1])
|
|
||||||
copy(ret[1:], h[:7-1])
|
|
||||||
|
|
||||||
copy(ret[7:], encrypt)
|
|
||||||
copy(ret[dataOffset:], data)
|
|
||||||
|
|
||||||
h = a.hmac(a.userKey, ret[:retSize-4])
|
|
||||||
copy(ret[retSize-4:], h[:4])
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
@ -1,22 +0,0 @@
|
|||||||
package protocol
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/component/ssr/tools"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
register("auth_aes128_sha1", newAuthAES128SHA1)
|
|
||||||
}
|
|
||||||
|
|
||||||
func newAuthAES128SHA1(b *Base) Protocol {
|
|
||||||
return &authAES128{
|
|
||||||
Base: b,
|
|
||||||
recvInfo: &recvInfo{buffer: new(bytes.Buffer)},
|
|
||||||
authData: &authData{},
|
|
||||||
salt: "auth_aes128_sha1",
|
|
||||||
hmac: tools.HmacSHA1,
|
|
||||||
hashDigest: tools.SHA1Sum,
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,427 +0,0 @@
|
|||||||
package protocol
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"crypto/aes"
|
|
||||||
"crypto/cipher"
|
|
||||||
"crypto/rc4"
|
|
||||||
"encoding/base64"
|
|
||||||
"encoding/binary"
|
|
||||||
"math/rand"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/common/pool"
|
|
||||||
"github.com/Dreamacro/clash/component/ssr/tools"
|
|
||||||
"github.com/Dreamacro/go-shadowsocks2/core"
|
|
||||||
)
|
|
||||||
|
|
||||||
type authChain struct {
|
|
||||||
*Base
|
|
||||||
*recvInfo
|
|
||||||
*authData
|
|
||||||
randomClient shift128PlusContext
|
|
||||||
randomServer shift128PlusContext
|
|
||||||
enc cipher.Stream
|
|
||||||
dec cipher.Stream
|
|
||||||
headerSent bool
|
|
||||||
lastClientHash []byte
|
|
||||||
lastServerHash []byte
|
|
||||||
userKey []byte
|
|
||||||
uid [4]byte
|
|
||||||
salt string
|
|
||||||
hmac hmacMethod
|
|
||||||
hashDigest hashDigestMethod
|
|
||||||
rnd rndMethod
|
|
||||||
dataSizeList []int
|
|
||||||
dataSizeList2 []int
|
|
||||||
chunkID uint32
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
register("auth_chain_a", newAuthChainA)
|
|
||||||
}
|
|
||||||
|
|
||||||
func newAuthChainA(b *Base) Protocol {
|
|
||||||
return &authChain{
|
|
||||||
Base: b,
|
|
||||||
authData: &authData{},
|
|
||||||
salt: "auth_chain_a",
|
|
||||||
hmac: tools.HmacMD5,
|
|
||||||
hashDigest: tools.SHA1Sum,
|
|
||||||
rnd: authChainAGetRandLen,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *authChain) initForConn(iv []byte) Protocol {
|
|
||||||
r := &authChain{
|
|
||||||
Base: &Base{
|
|
||||||
IV: iv,
|
|
||||||
Key: a.Key,
|
|
||||||
TCPMss: a.TCPMss,
|
|
||||||
Overhead: a.Overhead,
|
|
||||||
Param: a.Param,
|
|
||||||
},
|
|
||||||
recvInfo: &recvInfo{recvID: 1, buffer: new(bytes.Buffer)},
|
|
||||||
authData: a.authData,
|
|
||||||
salt: a.salt,
|
|
||||||
hmac: a.hmac,
|
|
||||||
hashDigest: a.hashDigest,
|
|
||||||
rnd: a.rnd,
|
|
||||||
}
|
|
||||||
if r.salt == "auth_chain_b" {
|
|
||||||
initDataSize(r)
|
|
||||||
}
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *authChain) GetProtocolOverhead() int {
|
|
||||||
return 4
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *authChain) SetOverhead(overhead int) {
|
|
||||||
a.Overhead = overhead
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *authChain) Decode(b []byte) ([]byte, int, error) {
|
|
||||||
a.buffer.Reset()
|
|
||||||
key := pool.Get(len(a.userKey) + 4)
|
|
||||||
defer pool.Put(key)
|
|
||||||
readSize := 0
|
|
||||||
copy(key, a.userKey)
|
|
||||||
for len(b) > 4 {
|
|
||||||
binary.LittleEndian.PutUint32(key[len(a.userKey):], a.recvID)
|
|
||||||
dataLen := (int)((uint(b[1]^a.lastServerHash[15]) << 8) + uint(b[0]^a.lastServerHash[14]))
|
|
||||||
randLen := a.getServerRandLen(dataLen, a.Overhead)
|
|
||||||
length := randLen + dataLen
|
|
||||||
if length >= 4096 {
|
|
||||||
return nil, 0, errAuthChainDataLengthError
|
|
||||||
}
|
|
||||||
length += 4
|
|
||||||
if length > len(b) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
hash := a.hmac(key, b[:length-2])
|
|
||||||
if !bytes.Equal(hash[:2], b[length-2:length]) {
|
|
||||||
return nil, 0, errAuthChainHMACError
|
|
||||||
}
|
|
||||||
var dataPos int
|
|
||||||
if dataLen > 0 && randLen > 0 {
|
|
||||||
dataPos = 2 + getRandStartPos(&a.randomServer, randLen)
|
|
||||||
} else {
|
|
||||||
dataPos = 2
|
|
||||||
}
|
|
||||||
d := pool.Get(dataLen)
|
|
||||||
a.dec.XORKeyStream(d, b[dataPos:dataPos+dataLen])
|
|
||||||
a.buffer.Write(d)
|
|
||||||
pool.Put(d)
|
|
||||||
if a.recvID == 1 {
|
|
||||||
a.TCPMss = int(binary.LittleEndian.Uint16(a.buffer.Next(2)))
|
|
||||||
}
|
|
||||||
a.lastServerHash = hash
|
|
||||||
a.recvID++
|
|
||||||
b = b[length:]
|
|
||||||
readSize += length
|
|
||||||
}
|
|
||||||
return a.buffer.Bytes(), readSize, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *authChain) Encode(b []byte) ([]byte, error) {
|
|
||||||
a.buffer.Reset()
|
|
||||||
bSize := len(b)
|
|
||||||
offset := 0
|
|
||||||
if bSize > 0 && !a.headerSent {
|
|
||||||
headSize := 1200
|
|
||||||
if headSize > bSize {
|
|
||||||
headSize = bSize
|
|
||||||
}
|
|
||||||
a.buffer.Write(a.packAuthData(b[:headSize]))
|
|
||||||
offset += headSize
|
|
||||||
bSize -= headSize
|
|
||||||
a.headerSent = true
|
|
||||||
}
|
|
||||||
var unitSize = a.TCPMss - a.Overhead
|
|
||||||
for bSize > unitSize {
|
|
||||||
dataLen, randLength := a.packedDataLen(b[offset : offset+unitSize])
|
|
||||||
d := pool.Get(dataLen)
|
|
||||||
a.packData(d, b[offset:offset+unitSize], randLength)
|
|
||||||
a.buffer.Write(d)
|
|
||||||
pool.Put(d)
|
|
||||||
bSize -= unitSize
|
|
||||||
offset += unitSize
|
|
||||||
}
|
|
||||||
if bSize > 0 {
|
|
||||||
dataLen, randLength := a.packedDataLen(b[offset:])
|
|
||||||
d := pool.Get(dataLen)
|
|
||||||
a.packData(d, b[offset:], randLength)
|
|
||||||
a.buffer.Write(d)
|
|
||||||
pool.Put(d)
|
|
||||||
}
|
|
||||||
return a.buffer.Bytes(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *authChain) DecodePacket(b []byte) ([]byte, int, error) {
|
|
||||||
bSize := len(b)
|
|
||||||
if bSize < 9 {
|
|
||||||
return nil, 0, errAuthChainDataLengthError
|
|
||||||
}
|
|
||||||
h := a.hmac(a.userKey, b[:bSize-1])
|
|
||||||
if h[0] != b[bSize-1] {
|
|
||||||
return nil, 0, errAuthChainHMACError
|
|
||||||
}
|
|
||||||
hash := a.hmac(a.Key, b[bSize-8:bSize-1])
|
|
||||||
cipherKey := a.getRC4CipherKey(hash)
|
|
||||||
dec, _ := rc4.NewCipher(cipherKey)
|
|
||||||
randLength := udpGetRandLen(&a.randomServer, hash)
|
|
||||||
bSize -= 8 + randLength
|
|
||||||
dec.XORKeyStream(b, b[:bSize])
|
|
||||||
return b, bSize, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *authChain) EncodePacket(b []byte) ([]byte, error) {
|
|
||||||
a.initUserKeyAndID()
|
|
||||||
authData := pool.Get(3)
|
|
||||||
defer pool.Put(authData)
|
|
||||||
rand.Read(authData)
|
|
||||||
hash := a.hmac(a.Key, authData)
|
|
||||||
uid := pool.Get(4)
|
|
||||||
defer pool.Put(uid)
|
|
||||||
for i := 0; i < 4; i++ {
|
|
||||||
uid[i] = a.uid[i] ^ hash[i]
|
|
||||||
}
|
|
||||||
|
|
||||||
cipherKey := a.getRC4CipherKey(hash)
|
|
||||||
enc, _ := rc4.NewCipher(cipherKey)
|
|
||||||
var buf bytes.Buffer
|
|
||||||
enc.XORKeyStream(b, b)
|
|
||||||
buf.Write(b)
|
|
||||||
|
|
||||||
randLength := udpGetRandLen(&a.randomClient, hash)
|
|
||||||
randBytes := pool.Get(randLength)
|
|
||||||
defer pool.Put(randBytes)
|
|
||||||
buf.Write(randBytes)
|
|
||||||
|
|
||||||
buf.Write(authData)
|
|
||||||
buf.Write(uid)
|
|
||||||
|
|
||||||
h := a.hmac(a.userKey, buf.Bytes())
|
|
||||||
buf.Write(h[:1])
|
|
||||||
return buf.Bytes(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *authChain) getRC4CipherKey(hash []byte) []byte {
|
|
||||||
base64UserKey := base64.StdEncoding.EncodeToString(a.userKey)
|
|
||||||
return a.calcRC4CipherKey(hash, base64UserKey)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *authChain) calcRC4CipherKey(hash []byte, base64UserKey string) []byte {
|
|
||||||
password := pool.Get(len(base64UserKey) + base64.StdEncoding.EncodedLen(16))
|
|
||||||
defer pool.Put(password)
|
|
||||||
copy(password, base64UserKey)
|
|
||||||
base64.StdEncoding.Encode(password[len(base64UserKey):], hash[:16])
|
|
||||||
return core.Kdf(string(password), 16)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *authChain) initUserKeyAndID() {
|
|
||||||
if a.userKey == nil {
|
|
||||||
params := strings.Split(a.Param, ":")
|
|
||||||
if len(params) >= 2 {
|
|
||||||
if userID, err := strconv.ParseUint(params[0], 10, 32); err == nil {
|
|
||||||
binary.LittleEndian.PutUint32(a.uid[:], uint32(userID))
|
|
||||||
a.userKey = []byte(params[1])[:len(a.userKey)]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if a.userKey == nil {
|
|
||||||
rand.Read(a.uid[:])
|
|
||||||
a.userKey = make([]byte, len(a.Key))
|
|
||||||
copy(a.userKey, a.Key)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *authChain) getClientRandLen(dataLength int, overhead int) int {
|
|
||||||
return a.rnd(dataLength, &a.randomClient, a.lastClientHash, a.dataSizeList, a.dataSizeList2, overhead)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *authChain) getServerRandLen(dataLength int, overhead int) int {
|
|
||||||
return a.rnd(dataLength, &a.randomServer, a.lastServerHash, a.dataSizeList, a.dataSizeList2, overhead)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *authChain) packedDataLen(data []byte) (chunkLength, randLength int) {
|
|
||||||
dataLength := len(data)
|
|
||||||
randLength = a.getClientRandLen(dataLength, a.Overhead)
|
|
||||||
chunkLength = randLength + dataLength + 2 + 2
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *authChain) packData(outData []byte, data []byte, randLength int) {
|
|
||||||
dataLength := len(data)
|
|
||||||
outLength := randLength + dataLength + 2
|
|
||||||
outData[0] = byte(dataLength) ^ a.lastClientHash[14]
|
|
||||||
outData[1] = byte(dataLength>>8) ^ a.lastClientHash[15]
|
|
||||||
|
|
||||||
{
|
|
||||||
if dataLength > 0 {
|
|
||||||
randPart1Length := getRandStartPos(&a.randomClient, randLength)
|
|
||||||
rand.Read(outData[2 : 2+randPart1Length])
|
|
||||||
a.enc.XORKeyStream(outData[2+randPart1Length:], data)
|
|
||||||
rand.Read(outData[2+randPart1Length+dataLength : outLength])
|
|
||||||
} else {
|
|
||||||
rand.Read(outData[2 : 2+randLength])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
userKeyLen := uint8(len(a.userKey))
|
|
||||||
key := pool.Get(int(userKeyLen + 4))
|
|
||||||
defer pool.Put(key)
|
|
||||||
copy(key, a.userKey)
|
|
||||||
a.chunkID++
|
|
||||||
binary.LittleEndian.PutUint32(key[userKeyLen:], a.chunkID)
|
|
||||||
a.lastClientHash = a.hmac(key, outData[:outLength])
|
|
||||||
copy(outData[outLength:], a.lastClientHash[:2])
|
|
||||||
}
|
|
||||||
|
|
||||||
const authHeadLength = 4 + 8 + 4 + 16 + 4
|
|
||||||
|
|
||||||
func (a *authChain) packAuthData(data []byte) (outData []byte) {
|
|
||||||
outData = make([]byte, authHeadLength, authHeadLength+1500)
|
|
||||||
a.mutex.Lock()
|
|
||||||
defer a.mutex.Unlock()
|
|
||||||
a.connectionID++
|
|
||||||
if a.connectionID > 0xFF000000 {
|
|
||||||
rand.Read(a.clientID)
|
|
||||||
b := make([]byte, 4)
|
|
||||||
rand.Read(b)
|
|
||||||
a.connectionID = binary.LittleEndian.Uint32(b) & 0xFFFFFF
|
|
||||||
}
|
|
||||||
var key = make([]byte, len(a.IV)+len(a.Key))
|
|
||||||
copy(key, a.IV)
|
|
||||||
copy(key[len(a.IV):], a.Key)
|
|
||||||
|
|
||||||
encrypt := make([]byte, 20)
|
|
||||||
t := time.Now().Unix()
|
|
||||||
binary.LittleEndian.PutUint32(encrypt[:4], uint32(t))
|
|
||||||
copy(encrypt[4:8], a.clientID)
|
|
||||||
binary.LittleEndian.PutUint32(encrypt[8:], a.connectionID)
|
|
||||||
binary.LittleEndian.PutUint16(encrypt[12:], uint16(a.Overhead))
|
|
||||||
binary.LittleEndian.PutUint16(encrypt[14:], 0)
|
|
||||||
|
|
||||||
// first 12 bytes
|
|
||||||
{
|
|
||||||
rand.Read(outData[:4])
|
|
||||||
a.lastClientHash = a.hmac(key, outData[:4])
|
|
||||||
copy(outData[4:], a.lastClientHash[:8])
|
|
||||||
}
|
|
||||||
var base64UserKey string
|
|
||||||
// uid & 16 bytes auth data
|
|
||||||
{
|
|
||||||
a.initUserKeyAndID()
|
|
||||||
uid := make([]byte, 4)
|
|
||||||
for i := 0; i < 4; i++ {
|
|
||||||
uid[i] = a.uid[i] ^ a.lastClientHash[8+i]
|
|
||||||
}
|
|
||||||
base64UserKey = base64.StdEncoding.EncodeToString(a.userKey)
|
|
||||||
aesCipherKey := core.Kdf(base64UserKey+a.salt, 16)
|
|
||||||
block, err := aes.NewCipher(aesCipherKey)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
encryptData := make([]byte, 16)
|
|
||||||
iv := make([]byte, aes.BlockSize)
|
|
||||||
cbc := cipher.NewCBCEncrypter(block, iv)
|
|
||||||
cbc.CryptBlocks(encryptData, encrypt[:16])
|
|
||||||
copy(encrypt[:4], uid[:])
|
|
||||||
copy(encrypt[4:4+16], encryptData)
|
|
||||||
}
|
|
||||||
// final HMAC
|
|
||||||
{
|
|
||||||
a.lastServerHash = a.hmac(a.userKey, encrypt[:20])
|
|
||||||
|
|
||||||
copy(outData[12:], encrypt)
|
|
||||||
copy(outData[12+20:], a.lastServerHash[:4])
|
|
||||||
}
|
|
||||||
|
|
||||||
// init cipher
|
|
||||||
cipherKey := a.calcRC4CipherKey(a.lastClientHash, base64UserKey)
|
|
||||||
a.enc, _ = rc4.NewCipher(cipherKey)
|
|
||||||
a.dec, _ = rc4.NewCipher(cipherKey)
|
|
||||||
|
|
||||||
// data
|
|
||||||
chunkLength, randLength := a.packedDataLen(data)
|
|
||||||
if chunkLength <= 1500 {
|
|
||||||
outData = outData[:authHeadLength+chunkLength]
|
|
||||||
} else {
|
|
||||||
newOutData := make([]byte, authHeadLength+chunkLength)
|
|
||||||
copy(newOutData, outData[:authHeadLength])
|
|
||||||
outData = newOutData
|
|
||||||
}
|
|
||||||
a.packData(outData[authHeadLength:], data, randLength)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func getRandStartPos(random *shift128PlusContext, randLength int) int {
|
|
||||||
if randLength > 0 {
|
|
||||||
return int(random.Next() % 8589934609 % uint64(randLength))
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func authChainAGetRandLen(dataLength int, random *shift128PlusContext, lastHash []byte, dataSizeList, dataSizeList2 []int, overhead int) int {
|
|
||||||
if dataLength > 1440 {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
random.InitFromBinDatalen(lastHash[:16], dataLength)
|
|
||||||
if dataLength > 1300 {
|
|
||||||
return int(random.Next() % 31)
|
|
||||||
}
|
|
||||||
if dataLength > 900 {
|
|
||||||
return int(random.Next() % 127)
|
|
||||||
}
|
|
||||||
if dataLength > 400 {
|
|
||||||
return int(random.Next() % 521)
|
|
||||||
}
|
|
||||||
return int(random.Next() % 1021)
|
|
||||||
}
|
|
||||||
|
|
||||||
func udpGetRandLen(random *shift128PlusContext, lastHash []byte) int {
|
|
||||||
random.InitFromBin(lastHash[:16])
|
|
||||||
return int(random.Next() % 127)
|
|
||||||
}
|
|
||||||
|
|
||||||
type shift128PlusContext struct {
|
|
||||||
v [2]uint64
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ctx *shift128PlusContext) InitFromBin(bin []byte) {
|
|
||||||
var fillBin [16]byte
|
|
||||||
copy(fillBin[:], bin)
|
|
||||||
|
|
||||||
ctx.v[0] = binary.LittleEndian.Uint64(fillBin[:8])
|
|
||||||
ctx.v[1] = binary.LittleEndian.Uint64(fillBin[8:])
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ctx *shift128PlusContext) InitFromBinDatalen(bin []byte, datalen int) {
|
|
||||||
var fillBin [16]byte
|
|
||||||
copy(fillBin[:], bin)
|
|
||||||
binary.LittleEndian.PutUint16(fillBin[:2], uint16(datalen))
|
|
||||||
|
|
||||||
ctx.v[0] = binary.LittleEndian.Uint64(fillBin[:8])
|
|
||||||
ctx.v[1] = binary.LittleEndian.Uint64(fillBin[8:])
|
|
||||||
|
|
||||||
for i := 0; i < 4; i++ {
|
|
||||||
ctx.Next()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ctx *shift128PlusContext) Next() uint64 {
|
|
||||||
x := ctx.v[0]
|
|
||||||
y := ctx.v[1]
|
|
||||||
ctx.v[0] = y
|
|
||||||
x ^= x << 23
|
|
||||||
x ^= y ^ (x >> 17) ^ (y >> 26)
|
|
||||||
ctx.v[1] = x
|
|
||||||
return x + y
|
|
||||||
}
|
|
@ -1,72 +0,0 @@
|
|||||||
package protocol
|
|
||||||
|
|
||||||
import (
|
|
||||||
"sort"
|
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/component/ssr/tools"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
register("auth_chain_b", newAuthChainB)
|
|
||||||
}
|
|
||||||
|
|
||||||
func newAuthChainB(b *Base) Protocol {
|
|
||||||
return &authChain{
|
|
||||||
Base: b,
|
|
||||||
authData: &authData{},
|
|
||||||
salt: "auth_chain_b",
|
|
||||||
hmac: tools.HmacMD5,
|
|
||||||
hashDigest: tools.SHA1Sum,
|
|
||||||
rnd: authChainBGetRandLen,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func initDataSize(r *authChain) {
|
|
||||||
random := &r.randomServer
|
|
||||||
random.InitFromBin(r.Key)
|
|
||||||
len := random.Next()%8 + 4
|
|
||||||
r.dataSizeList = make([]int, len)
|
|
||||||
for i := 0; i < int(len); i++ {
|
|
||||||
r.dataSizeList[i] = int(random.Next() % 2340 % 2040 % 1440)
|
|
||||||
}
|
|
||||||
sort.Ints(r.dataSizeList)
|
|
||||||
|
|
||||||
len = random.Next()%16 + 8
|
|
||||||
r.dataSizeList2 = make([]int, len)
|
|
||||||
for i := 0; i < int(len); i++ {
|
|
||||||
r.dataSizeList2[i] = int(random.Next() % 2340 % 2040 % 1440)
|
|
||||||
}
|
|
||||||
sort.Ints(r.dataSizeList2)
|
|
||||||
}
|
|
||||||
|
|
||||||
func authChainBGetRandLen(dataLength int, random *shift128PlusContext, lastHash []byte, dataSizeList, dataSizeList2 []int, overhead int) int {
|
|
||||||
if dataLength > 1440 {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
random.InitFromBinDatalen(lastHash[:16], dataLength)
|
|
||||||
pos := sort.Search(len(dataSizeList), func(i int) bool { return dataSizeList[i] > dataLength+overhead })
|
|
||||||
finalPos := uint64(pos) + random.Next()%uint64(len(dataSizeList))
|
|
||||||
if finalPos < uint64(len(dataSizeList)) {
|
|
||||||
return dataSizeList[finalPos] - dataLength - overhead
|
|
||||||
}
|
|
||||||
|
|
||||||
pos = sort.Search(len(dataSizeList2), func(i int) bool { return dataSizeList2[i] > dataLength+overhead })
|
|
||||||
finalPos = uint64(pos) + random.Next()%uint64(len(dataSizeList2))
|
|
||||||
if finalPos < uint64(len(dataSizeList2)) {
|
|
||||||
return dataSizeList2[finalPos] - dataLength - overhead
|
|
||||||
}
|
|
||||||
if finalPos < uint64(pos+len(dataSizeList2)-1) {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
if dataLength > 1300 {
|
|
||||||
return int(random.Next() % 31)
|
|
||||||
}
|
|
||||||
if dataLength > 900 {
|
|
||||||
return int(random.Next() % 127)
|
|
||||||
}
|
|
||||||
if dataLength > 400 {
|
|
||||||
return int(random.Next() % 521)
|
|
||||||
}
|
|
||||||
return int(random.Next() % 1021)
|
|
||||||
}
|
|
@ -1,253 +0,0 @@
|
|||||||
package protocol
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding/binary"
|
|
||||||
"hash/adler32"
|
|
||||||
"hash/crc32"
|
|
||||||
"math/rand"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/common/pool"
|
|
||||||
"github.com/Dreamacro/clash/component/ssr/tools"
|
|
||||||
)
|
|
||||||
|
|
||||||
type authSHA1V4 struct {
|
|
||||||
*Base
|
|
||||||
*authData
|
|
||||||
headerSent bool
|
|
||||||
buffer bytes.Buffer
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
register("auth_sha1_v4", newAuthSHA1V4)
|
|
||||||
}
|
|
||||||
|
|
||||||
func newAuthSHA1V4(b *Base) Protocol {
|
|
||||||
return &authSHA1V4{Base: b, authData: &authData{}}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *authSHA1V4) initForConn(iv []byte) Protocol {
|
|
||||||
return &authSHA1V4{
|
|
||||||
Base: &Base{
|
|
||||||
IV: iv,
|
|
||||||
Key: a.Key,
|
|
||||||
TCPMss: a.TCPMss,
|
|
||||||
Overhead: a.Overhead,
|
|
||||||
Param: a.Param,
|
|
||||||
},
|
|
||||||
authData: a.authData,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *authSHA1V4) GetProtocolOverhead() int {
|
|
||||||
return 7
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *authSHA1V4) SetOverhead(overhead int) {
|
|
||||||
a.Overhead = overhead
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *authSHA1V4) Decode(b []byte) ([]byte, int, error) {
|
|
||||||
a.buffer.Reset()
|
|
||||||
bSize := len(b)
|
|
||||||
originalSize := bSize
|
|
||||||
for bSize > 4 {
|
|
||||||
crc := crc32.ChecksumIEEE(b[:2]) & 0xFFFF
|
|
||||||
if binary.LittleEndian.Uint16(b[2:4]) != uint16(crc) {
|
|
||||||
return nil, 0, errAuthSHA1v4CRC32Error
|
|
||||||
}
|
|
||||||
length := int(binary.BigEndian.Uint16(b[:2]))
|
|
||||||
if length >= 8192 || length < 8 {
|
|
||||||
return nil, 0, errAuthSHA1v4DataLengthError
|
|
||||||
}
|
|
||||||
if length > bSize {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
if adler32.Checksum(b[:length-4]) == binary.LittleEndian.Uint32(b[length-4:]) {
|
|
||||||
pos := int(b[4])
|
|
||||||
if pos != 0xFF {
|
|
||||||
pos += 4
|
|
||||||
} else {
|
|
||||||
pos = int(binary.BigEndian.Uint16(b[5:5+2])) + 4
|
|
||||||
}
|
|
||||||
retSize := length - pos - 4
|
|
||||||
a.buffer.Write(b[pos : pos+retSize])
|
|
||||||
bSize -= length
|
|
||||||
b = b[length:]
|
|
||||||
} else {
|
|
||||||
return nil, 0, errAuthSHA1v4IncorrectChecksum
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return a.buffer.Bytes(), originalSize - bSize, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *authSHA1V4) Encode(b []byte) ([]byte, error) {
|
|
||||||
a.buffer.Reset()
|
|
||||||
bSize := len(b)
|
|
||||||
offset := 0
|
|
||||||
if !a.headerSent && bSize > 0 {
|
|
||||||
headSize := getHeadSize(b, 30)
|
|
||||||
if headSize > bSize {
|
|
||||||
headSize = bSize
|
|
||||||
}
|
|
||||||
a.buffer.Write(a.packAuthData(b[:headSize]))
|
|
||||||
offset += headSize
|
|
||||||
bSize -= headSize
|
|
||||||
a.headerSent = true
|
|
||||||
}
|
|
||||||
const blockSize = 4096
|
|
||||||
for bSize > blockSize {
|
|
||||||
packSize, randSize := a.packedDataSize(b[offset : offset+blockSize])
|
|
||||||
pack := pool.Get(packSize)
|
|
||||||
a.packData(b[offset:offset+blockSize], pack, randSize)
|
|
||||||
a.buffer.Write(pack)
|
|
||||||
pool.Put(pack)
|
|
||||||
offset += blockSize
|
|
||||||
bSize -= blockSize
|
|
||||||
}
|
|
||||||
if bSize > 0 {
|
|
||||||
packSize, randSize := a.packedDataSize(b[offset:])
|
|
||||||
pack := pool.Get(packSize)
|
|
||||||
a.packData(b[offset:], pack, randSize)
|
|
||||||
a.buffer.Write(pack)
|
|
||||||
pool.Put(pack)
|
|
||||||
}
|
|
||||||
return a.buffer.Bytes(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *authSHA1V4) DecodePacket(b []byte) ([]byte, int, error) {
|
|
||||||
return b, len(b), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *authSHA1V4) EncodePacket(b []byte) ([]byte, error) {
|
|
||||||
return b, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *authSHA1V4) packedDataSize(data []byte) (packSize, randSize int) {
|
|
||||||
dataSize := len(data)
|
|
||||||
randSize = 1
|
|
||||||
if dataSize <= 1300 {
|
|
||||||
if dataSize > 400 {
|
|
||||||
randSize += rand.Intn(128)
|
|
||||||
} else {
|
|
||||||
randSize += rand.Intn(1024)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
packSize = randSize + dataSize + 8
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *authSHA1V4) packData(data, ret []byte, randSize int) {
|
|
||||||
dataSize := len(data)
|
|
||||||
retSize := len(ret)
|
|
||||||
// 0~1, ret size
|
|
||||||
binary.BigEndian.PutUint16(ret[:2], uint16(retSize&0xFFFF))
|
|
||||||
// 2~3, crc of ret size
|
|
||||||
crc := crc32.ChecksumIEEE(ret[:2]) & 0xFFFF
|
|
||||||
binary.LittleEndian.PutUint16(ret[2:4], uint16(crc))
|
|
||||||
// 4, rand size
|
|
||||||
if randSize < 128 {
|
|
||||||
ret[4] = uint8(randSize & 0xFF)
|
|
||||||
} else {
|
|
||||||
ret[4] = uint8(0xFF)
|
|
||||||
binary.BigEndian.PutUint16(ret[5:7], uint16(randSize&0xFFFF))
|
|
||||||
}
|
|
||||||
// (rand size+4)~(ret size-4), data
|
|
||||||
if dataSize > 0 {
|
|
||||||
copy(ret[randSize+4:], data)
|
|
||||||
}
|
|
||||||
// (ret size-4)~end, adler32 of full data
|
|
||||||
adler := adler32.Checksum(ret[:retSize-4])
|
|
||||||
binary.LittleEndian.PutUint32(ret[retSize-4:], adler)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *authSHA1V4) packAuthData(data []byte) (ret []byte) {
|
|
||||||
dataSize := len(data)
|
|
||||||
randSize := 1
|
|
||||||
if dataSize <= 1300 {
|
|
||||||
if dataSize > 400 {
|
|
||||||
randSize += rand.Intn(128)
|
|
||||||
} else {
|
|
||||||
randSize += rand.Intn(1024)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
dataOffset := randSize + 4 + 2
|
|
||||||
retSize := dataOffset + dataSize + 12 + tools.HmacSHA1Len
|
|
||||||
ret = make([]byte, retSize)
|
|
||||||
a.mutex.Lock()
|
|
||||||
defer a.mutex.Unlock()
|
|
||||||
a.connectionID++
|
|
||||||
if a.connectionID > 0xFF000000 {
|
|
||||||
a.clientID = nil
|
|
||||||
}
|
|
||||||
if len(a.clientID) == 0 {
|
|
||||||
a.clientID = make([]byte, 8)
|
|
||||||
rand.Read(a.clientID)
|
|
||||||
b := make([]byte, 4)
|
|
||||||
rand.Read(b)
|
|
||||||
a.connectionID = binary.LittleEndian.Uint32(b) & 0xFFFFFF
|
|
||||||
}
|
|
||||||
// 0~1, ret size
|
|
||||||
binary.BigEndian.PutUint16(ret[:2], uint16(retSize&0xFFFF))
|
|
||||||
|
|
||||||
// 2~6, crc of (ret size+salt+key)
|
|
||||||
salt := []byte("auth_sha1_v4")
|
|
||||||
crcData := make([]byte, len(salt)+len(a.Key)+2)
|
|
||||||
copy(crcData[:2], ret[:2])
|
|
||||||
copy(crcData[2:], salt)
|
|
||||||
copy(crcData[2+len(salt):], a.Key)
|
|
||||||
crc := crc32.ChecksumIEEE(crcData) & 0xFFFFFFFF
|
|
||||||
// 2~6, crc of (ret size+salt+key)
|
|
||||||
binary.LittleEndian.PutUint32(ret[2:], crc)
|
|
||||||
// 6~(rand size+6), rand numbers
|
|
||||||
rand.Read(ret[dataOffset-randSize : dataOffset])
|
|
||||||
// 6, rand size
|
|
||||||
if randSize < 128 {
|
|
||||||
ret[6] = byte(randSize & 0xFF)
|
|
||||||
} else {
|
|
||||||
// 6, magic number 0xFF
|
|
||||||
ret[6] = 0xFF
|
|
||||||
// 7~8, rand size
|
|
||||||
binary.BigEndian.PutUint16(ret[7:9], uint16(randSize&0xFFFF))
|
|
||||||
}
|
|
||||||
// rand size+6~(rand size+10), time stamp
|
|
||||||
now := time.Now().Unix()
|
|
||||||
binary.LittleEndian.PutUint32(ret[dataOffset:dataOffset+4], uint32(now))
|
|
||||||
// rand size+10~(rand size+14), client ID
|
|
||||||
copy(ret[dataOffset+4:dataOffset+4+4], a.clientID[:4])
|
|
||||||
// rand size+14~(rand size+18), connection ID
|
|
||||||
binary.LittleEndian.PutUint32(ret[dataOffset+8:dataOffset+8+4], a.connectionID)
|
|
||||||
// rand size+18~(rand size+18)+data length, data
|
|
||||||
copy(ret[dataOffset+12:], data)
|
|
||||||
|
|
||||||
key := make([]byte, len(a.IV)+len(a.Key))
|
|
||||||
copy(key, a.IV)
|
|
||||||
copy(key[len(a.IV):], a.Key)
|
|
||||||
|
|
||||||
h := tools.HmacSHA1(key, ret[:retSize-tools.HmacSHA1Len])
|
|
||||||
// (ret size-10)~(ret size)/(rand size)+18+data length~end, hmac
|
|
||||||
copy(ret[retSize-tools.HmacSHA1Len:], h[:tools.HmacSHA1Len])
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
|
|
||||||
func getHeadSize(data []byte, defaultValue int) int {
|
|
||||||
if data == nil || len(data) < 2 {
|
|
||||||
return defaultValue
|
|
||||||
}
|
|
||||||
headType := data[0] & 0x07
|
|
||||||
switch headType {
|
|
||||||
case 1:
|
|
||||||
// IPv4 1+4+2
|
|
||||||
return 7
|
|
||||||
case 4:
|
|
||||||
// IPv6 1+16+2
|
|
||||||
return 19
|
|
||||||
case 3:
|
|
||||||
// domain name, variant length
|
|
||||||
return 4 + int(data[1])
|
|
||||||
}
|
|
||||||
|
|
||||||
return defaultValue
|
|
||||||
}
|
|
@ -1,10 +0,0 @@
|
|||||||
package protocol
|
|
||||||
|
|
||||||
// Base information for protocol
|
|
||||||
type Base struct {
|
|
||||||
IV []byte
|
|
||||||
Key []byte
|
|
||||||
TCPMss int
|
|
||||||
Overhead int
|
|
||||||
Param string
|
|
||||||
}
|
|
@ -1,36 +0,0 @@
|
|||||||
package protocol
|
|
||||||
|
|
||||||
type origin struct{ *Base }
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
register("origin", newOrigin)
|
|
||||||
}
|
|
||||||
|
|
||||||
func newOrigin(b *Base) Protocol {
|
|
||||||
return &origin{}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *origin) initForConn(iv []byte) Protocol { return &origin{} }
|
|
||||||
|
|
||||||
func (o *origin) GetProtocolOverhead() int {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *origin) SetOverhead(overhead int) {
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *origin) Decode(b []byte) ([]byte, int, error) {
|
|
||||||
return b, len(b), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *origin) Encode(b []byte) ([]byte, error) {
|
|
||||||
return b, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *origin) DecodePacket(b []byte) ([]byte, int, error) {
|
|
||||||
return b, len(b), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *origin) EncodePacket(b []byte) ([]byte, error) {
|
|
||||||
return b, nil
|
|
||||||
}
|
|
@ -1,42 +0,0 @@
|
|||||||
package protocol
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net"
|
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/common/pool"
|
|
||||||
)
|
|
||||||
|
|
||||||
// NewPacketConn returns a net.NewPacketConn with protocol decoding/encoding
|
|
||||||
func NewPacketConn(pc net.PacketConn, p Protocol) net.PacketConn {
|
|
||||||
return &PacketConn{PacketConn: pc, Protocol: p.initForConn(nil)}
|
|
||||||
}
|
|
||||||
|
|
||||||
// PacketConn represents a protocol packet connection
|
|
||||||
type PacketConn struct {
|
|
||||||
net.PacketConn
|
|
||||||
Protocol
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *PacketConn) WriteTo(b []byte, addr net.Addr) (int, error) {
|
|
||||||
buf := pool.Get(pool.RelayBufferSize)
|
|
||||||
defer pool.Put(buf)
|
|
||||||
buf, err := c.EncodePacket(b)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
_, err = c.PacketConn.WriteTo(buf, addr)
|
|
||||||
return len(b), err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *PacketConn) ReadFrom(b []byte) (int, net.Addr, error) {
|
|
||||||
n, addr, err := c.PacketConn.ReadFrom(b)
|
|
||||||
if err != nil {
|
|
||||||
return n, addr, err
|
|
||||||
}
|
|
||||||
bb, length, err := c.DecodePacket(b[:n])
|
|
||||||
if err != nil {
|
|
||||||
return n, addr, err
|
|
||||||
}
|
|
||||||
copy(b, bb)
|
|
||||||
return length, addr, err
|
|
||||||
}
|
|
@ -1,63 +0,0 @@
|
|||||||
package protocol
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
errAuthAES128IncorrectMAC = errors.New("auth_aes128_* post decrypt incorrect mac")
|
|
||||||
errAuthAES128DataLengthError = errors.New("auth_aes128_* post decrypt length mismatch")
|
|
||||||
errAuthAES128IncorrectChecksum = errors.New("auth_aes128_* post decrypt incorrect checksum")
|
|
||||||
errAuthAES128PositionTooLarge = errors.New("auth_aes128_* post decrypt position is too large")
|
|
||||||
errAuthSHA1v4CRC32Error = errors.New("auth_sha1_v4 post decrypt data crc32 error")
|
|
||||||
errAuthSHA1v4DataLengthError = errors.New("auth_sha1_v4 post decrypt data length error")
|
|
||||||
errAuthSHA1v4IncorrectChecksum = errors.New("auth_sha1_v4 post decrypt incorrect checksum")
|
|
||||||
errAuthChainDataLengthError = errors.New("auth_chain_* post decrypt length mismatch")
|
|
||||||
errAuthChainHMACError = errors.New("auth_chain_* post decrypt hmac error")
|
|
||||||
)
|
|
||||||
|
|
||||||
type authData struct {
|
|
||||||
clientID []byte
|
|
||||||
connectionID uint32
|
|
||||||
mutex sync.Mutex
|
|
||||||
}
|
|
||||||
|
|
||||||
type recvInfo struct {
|
|
||||||
recvID uint32
|
|
||||||
buffer *bytes.Buffer
|
|
||||||
}
|
|
||||||
|
|
||||||
type hmacMethod func(key []byte, data []byte) []byte
|
|
||||||
type hashDigestMethod func(data []byte) []byte
|
|
||||||
type rndMethod func(dataSize int, random *shift128PlusContext, lastHash []byte, dataSizeList, dataSizeList2 []int, overhead int) int
|
|
||||||
|
|
||||||
// Protocol provides methods for decoding, encoding and iv setting
|
|
||||||
type Protocol interface {
|
|
||||||
initForConn(iv []byte) Protocol
|
|
||||||
GetProtocolOverhead() int
|
|
||||||
SetOverhead(int)
|
|
||||||
Decode([]byte) ([]byte, int, error)
|
|
||||||
Encode([]byte) ([]byte, error)
|
|
||||||
DecodePacket([]byte) ([]byte, int, error)
|
|
||||||
EncodePacket([]byte) ([]byte, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
type protocolCreator func(b *Base) Protocol
|
|
||||||
|
|
||||||
var protocolList = make(map[string]protocolCreator)
|
|
||||||
|
|
||||||
func register(name string, c protocolCreator) {
|
|
||||||
protocolList[name] = c
|
|
||||||
}
|
|
||||||
|
|
||||||
// PickProtocol returns a protocol of the given name
|
|
||||||
func PickProtocol(name string, b *Base) (Protocol, error) {
|
|
||||||
if protocolCreator, ok := protocolList[strings.ToLower(name)]; ok {
|
|
||||||
return protocolCreator(b), nil
|
|
||||||
}
|
|
||||||
return nil, fmt.Errorf("Protocol %s not supported", name)
|
|
||||||
}
|
|
@ -1,68 +0,0 @@
|
|||||||
package protocol
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"net"
|
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/common/pool"
|
|
||||||
)
|
|
||||||
|
|
||||||
// NewConn wraps a stream-oriented net.Conn with protocol decoding/encoding
|
|
||||||
func NewConn(c net.Conn, p Protocol, iv []byte) net.Conn {
|
|
||||||
return &Conn{Conn: c, Protocol: p.initForConn(iv)}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Conn represents a protocol connection
|
|
||||||
type Conn struct {
|
|
||||||
net.Conn
|
|
||||||
Protocol
|
|
||||||
buf []byte
|
|
||||||
offset int
|
|
||||||
underDecoded bytes.Buffer
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Conn) Read(b []byte) (int, error) {
|
|
||||||
if c.buf != nil {
|
|
||||||
n := copy(b, c.buf[c.offset:])
|
|
||||||
c.offset += n
|
|
||||||
if c.offset == len(c.buf) {
|
|
||||||
c.buf = nil
|
|
||||||
}
|
|
||||||
return n, nil
|
|
||||||
}
|
|
||||||
buf := pool.Get(pool.RelayBufferSize)
|
|
||||||
defer pool.Put(buf)
|
|
||||||
n, err := c.Conn.Read(buf)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
c.underDecoded.Write(buf[:n])
|
|
||||||
underDecoded := c.underDecoded.Bytes()
|
|
||||||
decoded, length, err := c.Decode(underDecoded)
|
|
||||||
if err != nil {
|
|
||||||
c.underDecoded.Reset()
|
|
||||||
return 0, nil
|
|
||||||
}
|
|
||||||
if length == 0 {
|
|
||||||
return 0, nil
|
|
||||||
}
|
|
||||||
c.underDecoded.Next(length)
|
|
||||||
n = copy(b, decoded)
|
|
||||||
if len(decoded) > len(b) {
|
|
||||||
c.buf = decoded
|
|
||||||
c.offset = n
|
|
||||||
}
|
|
||||||
return n, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Conn) Write(b []byte) (int, error) {
|
|
||||||
encoded, err := c.Encode(b)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
_, err = c.Conn.Write(encoded)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
return len(b), nil
|
|
||||||
}
|
|
@ -23,7 +23,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 +54,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 +91,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
|
||||||
}
|
}
|
||||||
@ -122,11 +122,7 @@ func (t *DomainTrie) search(node *Node, parts []string) *Node {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if c := node.getChild(dotWildcard); c != nil {
|
return node.getChild(dotWildcard)
|
||||||
return c
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// New returns a new, empty Trie.
|
// New returns a new, empty Trie.
|
||||||
|
@ -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,16 +8,18 @@ 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"
|
yaml "gopkg.in/yaml.v2"
|
||||||
@ -30,7 +32,7 @@ type General struct {
|
|||||||
Mode T.TunnelMode `json:"mode"`
|
Mode T.TunnelMode `json:"mode"`
|
||||||
LogLevel log.LogLevel `json:"log-level"`
|
LogLevel log.LogLevel `json:"log-level"`
|
||||||
IPv6 bool `json:"ipv6"`
|
IPv6 bool `json:"ipv6"`
|
||||||
Interface string `json:"interface-name"`
|
Interface string `json:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Inbound
|
// Inbound
|
||||||
@ -64,13 +66,20 @@ type DNS struct {
|
|||||||
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
|
||||||
|
type Profile struct {
|
||||||
|
StoreSelected bool `yaml:"store-selected"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Experimental config
|
// Experimental config
|
||||||
@ -82,10 +91,11 @@ type Config struct {
|
|||||||
DNS *DNS
|
DNS *DNS
|
||||||
Experimental *Experimental
|
Experimental *Experimental
|
||||||
Hosts *trie.DomainTrie
|
Hosts *trie.DomainTrie
|
||||||
|
Profile *Profile
|
||||||
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 {
|
||||||
@ -100,12 +110,14 @@ type RawDNS struct {
|
|||||||
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 {
|
||||||
@ -129,6 +141,7 @@ type RawConfig struct {
|
|||||||
Hosts map[string]string `yaml:"hosts"`
|
Hosts map[string]string `yaml:"hosts"`
|
||||||
DNS RawDNS `yaml:"dns"`
|
DNS RawDNS `yaml:"dns"`
|
||||||
Experimental Experimental `yaml:"experimental"`
|
Experimental Experimental `yaml:"experimental"`
|
||||||
|
Profile Profile `yaml:"profile"`
|
||||||
Proxy []map[string]interface{} `yaml:"proxies"`
|
Proxy []map[string]interface{} `yaml:"proxies"`
|
||||||
ProxyGroup []map[string]interface{} `yaml:"proxy-groups"`
|
ProxyGroup []map[string]interface{} `yaml:"proxy-groups"`
|
||||||
Rule []string `yaml:"rules"`
|
Rule []string `yaml:"rules"`
|
||||||
@ -145,7 +158,7 @@ func Parse(buf []byte) (*Config, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func UnmarshalRawConfig(buf []byte) (*RawConfig, error) {
|
func UnmarshalRawConfig(buf []byte) (*RawConfig, error) {
|
||||||
// config with some default value
|
// config with default value
|
||||||
rawCfg := &RawConfig{
|
rawCfg := &RawConfig{
|
||||||
AllowLan: false,
|
AllowLan: false,
|
||||||
BindAddress: "*",
|
BindAddress: "*",
|
||||||
@ -161,14 +174,18 @@ 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",
|
||||||
"8.8.8.8",
|
"8.8.8.8",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
Profile: Profile{
|
||||||
|
StoreSelected: true,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := yaml.Unmarshal(buf, &rawCfg); err != nil {
|
if err := yaml.Unmarshal(buf, &rawCfg); err != nil {
|
||||||
@ -182,6 +199,7 @@ func ParseRawConfig(rawCfg *RawConfig) (*Config, error) {
|
|||||||
config := &Config{}
|
config := &Config{}
|
||||||
|
|
||||||
config.Experimental = &rawCfg.Experimental
|
config.Experimental = &rawCfg.Experimental
|
||||||
|
config.Profile = &rawCfg.Profile
|
||||||
|
|
||||||
general, err := parseGeneral(rawCfg)
|
general, err := parseGeneral(rawCfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -253,21 +271,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)
|
||||||
}
|
}
|
||||||
@ -326,12 +344,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
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -353,9 +371,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
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -395,10 +413,6 @@ func parseRules(cfg *RawConfig, proxies map[string]C.Proxy) ([]C.Rule, error) {
|
|||||||
|
|
||||||
parsed, parseErr := R.ParseRule(rule[0], payload, target, params)
|
parsed, parseErr := R.ParseRule(rule[0], payload, target, params)
|
||||||
if parseErr != nil {
|
if parseErr != nil {
|
||||||
if parseErr == R.ErrPlatformNotSupport {
|
|
||||||
log.Warnln("Rules[%d] [%s] don't support current OS, skip", idx, line)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
return nil, fmt.Errorf("rules[%d] [%s] error: %s", idx, line, parseErr.Error())
|
return nil, fmt.Errorf("rules[%d] [%s] error: %s", idx, line, parseErr.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -474,6 +488,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)
|
||||||
}
|
}
|
||||||
@ -493,6 +510,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{}
|
||||||
|
|
||||||
@ -530,6 +564,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")
|
||||||
}
|
}
|
||||||
@ -568,6 +606,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
|
||||||
}
|
}
|
||||||
|
@ -12,7 +12,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func downloadMMDB(path string) (err error) {
|
func downloadMMDB(path string) (err error) {
|
||||||
resp, err := http.Get("https://github.com/Dreamacro/maxmind-geoip/releases/latest/download/Country.mmdb")
|
resp, err := http.Get("https://cdn.jsdelivr.net/gh/Dreamacro/maxmind-geoip@release/Country.mmdb")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -66,7 +66,7 @@ func Init(dir string) error {
|
|||||||
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())
|
||||||
}
|
}
|
||||||
f.Write([]byte(`port: 7890`))
|
f.Write([]byte(`mixed-port: 7890`))
|
||||||
f.Close()
|
f.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -27,10 +27,9 @@ const (
|
|||||||
LoadBalance
|
LoadBalance
|
||||||
)
|
)
|
||||||
|
|
||||||
type ServerAdapter interface {
|
const (
|
||||||
net.Conn
|
DefaultTCPTimeout = 5 * time.Second
|
||||||
Metadata() *Metadata
|
)
|
||||||
}
|
|
||||||
|
|
||||||
type Connection interface {
|
type Connection interface {
|
||||||
Chains() Chain
|
Chains() Chain
|
||||||
@ -50,6 +49,15 @@ func (c Chain) String() string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c Chain) Last() string {
|
||||||
|
switch len(c) {
|
||||||
|
case 0:
|
||||||
|
return ""
|
||||||
|
default:
|
||||||
|
return c[0]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type Conn interface {
|
type Conn interface {
|
||||||
net.Conn
|
net.Conn
|
||||||
Connection
|
Connection
|
||||||
@ -65,8 +73,21 @@ type PacketConn interface {
|
|||||||
type ProxyAdapter interface {
|
type ProxyAdapter interface {
|
||||||
Name() string
|
Name() string
|
||||||
Type() AdapterType
|
Type() AdapterType
|
||||||
|
|
||||||
|
// StreamConn wraps a protocol around net.Conn with Metadata.
|
||||||
|
//
|
||||||
|
// Examples:
|
||||||
|
// conn, _ := net.Dial("tcp", "host:port")
|
||||||
|
// conn, _ = adapter.StreamConn(conn, metadata)
|
||||||
|
//
|
||||||
|
// It returns a C.Conn with protocol which start with
|
||||||
|
// a new session (if any)
|
||||||
StreamConn(c net.Conn, metadata *Metadata) (net.Conn, error)
|
StreamConn(c net.Conn, metadata *Metadata) (net.Conn, error)
|
||||||
|
|
||||||
|
// DialContext return a C.Conn with protocol which
|
||||||
|
// contains multiplexing-related reuse logic (if any)
|
||||||
DialContext(ctx context.Context, metadata *Metadata) (Conn, error)
|
DialContext(ctx context.Context, metadata *Metadata) (Conn, error)
|
||||||
|
|
||||||
DialUDP(metadata *Metadata) (PacketConn, error)
|
DialUDP(metadata *Metadata) (PacketConn, error)
|
||||||
SupportUDP() bool
|
SupportUDP() bool
|
||||||
MarshalJSON() ([]byte, error)
|
MarshalJSON() ([]byte, error)
|
||||||
|
23
constant/context.go
Normal file
23
constant/context.go
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
package constant
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
|
||||||
|
"github.com/gofrs/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
type PlainContext interface {
|
||||||
|
ID() uuid.UUID
|
||||||
|
}
|
||||||
|
|
||||||
|
type ConnContext interface {
|
||||||
|
PlainContext
|
||||||
|
Metadata() *Metadata
|
||||||
|
Conn() net.Conn
|
||||||
|
}
|
||||||
|
|
||||||
|
type PacketConnContext interface {
|
||||||
|
PlainContext
|
||||||
|
Metadata() *Metadata
|
||||||
|
PacketConn() net.PacketConn
|
||||||
|
}
|
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"
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user