Compare commits
49 Commits
Author | SHA1 | Date | |
---|---|---|---|
b3cd4ebbd3 | |||
b0f83e401f | |||
f5806d9263 | |||
55600c49c9 | |||
beb88cc46f | |||
d49b38b00f | |||
0c79d1207e | |||
400dc923e0 | |||
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 |
100
.github/ISSUE_TEMPLATE/bug_report.md
vendored
100
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@ -1,100 +0,0 @@
|
|||||||
---
|
|
||||||
name: Bug report
|
|
||||||
about: Create a report to help us improve
|
|
||||||
title: "[Bug]"
|
|
||||||
labels: ''
|
|
||||||
assignees: ''
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
<!--
|
|
||||||
感谢你向 Clash Core 提交 issue!
|
|
||||||
在提交之前,请确认:
|
|
||||||
|
|
||||||
- [ ] 如果你可以自己 debug 并解决的话,提交 PR 吧!
|
|
||||||
- [ ] 我已经在 [Issue Tracker](……/) 中找过我要提出的问题
|
|
||||||
- [ ] 我已经使用 dev 分支版本测试过,问题依旧存在
|
|
||||||
- [ ] 我已经仔细看过 [Documentation](https://github.com/Dreamacro/clash/wiki/) 并无法自行解决问题
|
|
||||||
- [ ] 这是 Clash 核心的问题,并非我所使用的 Clash 衍生版本(如 OpenClash、KoolClash 等)的特定问题
|
|
||||||
|
|
||||||
请注意,如果你并没有遵照这个 issue template 填写内容,我们将直接关闭这个 issue。
|
|
||||||
|
|
||||||
Thanks for opening an issue towards the Clash core!
|
|
||||||
But before so, please do the following checklist:
|
|
||||||
|
|
||||||
- [ ] Is this something you can **debug and fix**? Send a pull request! Bug fixes and documentation fixes are welcome.
|
|
||||||
- [ ] I have searched on the [issue tracker](……/) for a related issue.
|
|
||||||
- [ ] I have tested using the dev branch, and the issue still exists.
|
|
||||||
- [ ] I have read the [documentation](https://github.com/Dreamacro/clash/wiki/) and was unable to solve the issue
|
|
||||||
- [ ] This is an issue of the Clash core *per se*, not to the derivatives of Clash, like OpenClash or KoolClash
|
|
||||||
|
|
||||||
Please understand that we close issues that fail to follow this issue template.
|
|
||||||
-->
|
|
||||||
|
|
||||||
------------------------------------------------------------------
|
|
||||||
|
|
||||||
<!--
|
|
||||||
请附上任何可以帮助我们解决这个问题的信息,如果我们收到的信息不足,我们将对这个 issue 加上 *Needs more information* 标记并在收到更多资讯之前关闭 issue。
|
|
||||||
Make sure to add **all the information needed to understand the bug** so that someone can help. If the info is missing we'll add the 'Needs more information' label and close the issue until there is enough information.
|
|
||||||
-->
|
|
||||||
|
|
||||||
### Clash config
|
|
||||||
<!--
|
|
||||||
在下方附上 Clash core 脱敏后配置文件的内容
|
|
||||||
Paste the Clash core configuration below.
|
|
||||||
-->
|
|
||||||
<details>
|
|
||||||
<summary>config.yaml</summary>
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
……
|
|
||||||
```
|
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
### Clash log
|
|
||||||
<!--
|
|
||||||
在下方附上 Clash Core 的日志,log level 使用 DEBUG
|
|
||||||
Paste the Clash core log below with the log level set to `DEBUG`.
|
|
||||||
-->
|
|
||||||
```
|
|
||||||
……
|
|
||||||
```
|
|
||||||
|
|
||||||
### 环境 Environment
|
|
||||||
|
|
||||||
* 操作系统 (the OS that the Clash core is running on)
|
|
||||||
……
|
|
||||||
* 网路环境或拓扑 (network conditions/topology)
|
|
||||||
……
|
|
||||||
* iptables,如果适用 (if applicable)
|
|
||||||
……
|
|
||||||
* ISP 有没有进行 DNS 污染 (is your ISP performing DNS pollution?)
|
|
||||||
……
|
|
||||||
* 其他 (any other information that would be useful)
|
|
||||||
……
|
|
||||||
|
|
||||||
### 说明 Description
|
|
||||||
|
|
||||||
<!--
|
|
||||||
请详细、清晰地表达你要提出的论述,例如这个问题如何影响到你?你想实现什么功能?
|
|
||||||
-->
|
|
||||||
|
|
||||||
### 重现问题的具体布骤 Steps to Reproduce
|
|
||||||
|
|
||||||
1. [First Step]
|
|
||||||
2. [Second Step]
|
|
||||||
3. ……
|
|
||||||
|
|
||||||
**我预期会发生……?**
|
|
||||||
<!-- **Expected behavior:** [What you expected to happen] -->
|
|
||||||
|
|
||||||
**实际上发生了什么?**
|
|
||||||
<!-- **Actual behavior:** [What actually happened] -->
|
|
||||||
|
|
||||||
### 可能的解决方案 Possible Solution
|
|
||||||
<!-- 此项非必须,但是如果你有想法的话欢迎提出。 -->
|
|
||||||
<!-- Not obligatory, but suggest a fix/reason for the bug, -->
|
|
||||||
<!-- or ideas how to implement the addition or change -->
|
|
||||||
|
|
||||||
### 更多信息 More Information
|
|
76
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
Normal file
76
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
name: Bug report
|
||||||
|
description: Create a report to help us improve
|
||||||
|
title: "[Bug] "
|
||||||
|
body:
|
||||||
|
- type: checkboxes
|
||||||
|
id: ensure
|
||||||
|
attributes:
|
||||||
|
label: Verify steps
|
||||||
|
description: "
|
||||||
|
在提交之前,请确认
|
||||||
|
Please verify that you've followed these steps
|
||||||
|
"
|
||||||
|
options:
|
||||||
|
- label: "
|
||||||
|
如果你可以自己 debug 并解决的话,提交 PR 吧
|
||||||
|
Is this something you can **debug and fix**? Send a pull request! Bug fixes and documentation fixes are welcome.
|
||||||
|
"
|
||||||
|
required: true
|
||||||
|
- label: "
|
||||||
|
我已经在 [Issue Tracker](……/) 中找过我要提出的问题
|
||||||
|
I have searched on the [issue tracker](……/) for a related issue.
|
||||||
|
"
|
||||||
|
required: true
|
||||||
|
- label: "
|
||||||
|
我已经使用 dev 分支版本测试过,问题依旧存在
|
||||||
|
I have tested using the dev branch, and the issue still exists.
|
||||||
|
"
|
||||||
|
required: true
|
||||||
|
- label: "
|
||||||
|
我已经仔细看过 [Documentation](https://github.com/Dreamacro/clash/wiki/) 并无法自行解决问题
|
||||||
|
I have read the [documentation](https://github.com/Dreamacro/clash/wiki/) and was unable to solve the issue.
|
||||||
|
"
|
||||||
|
required: true
|
||||||
|
- label: "
|
||||||
|
这是 Clash 核心的问题,并非我所使用的 Clash 衍生版本(如 OpenClash、KoolClash 等)的特定问题
|
||||||
|
This is an issue of the Clash core *per se*, not to the derivatives of Clash, like OpenClash or KoolClash.
|
||||||
|
"
|
||||||
|
required: true
|
||||||
|
- type: input
|
||||||
|
attributes:
|
||||||
|
label: Clash version
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: dropdown
|
||||||
|
id: os
|
||||||
|
attributes:
|
||||||
|
label: What OS are you seeing the problem on?
|
||||||
|
multiple: true
|
||||||
|
options:
|
||||||
|
- macOS
|
||||||
|
- Windows
|
||||||
|
- Linux
|
||||||
|
- OpenBSD/FreeBSD
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
render: yaml
|
||||||
|
label: "Clash config"
|
||||||
|
description: "
|
||||||
|
在下方附上 Clash core 脱敏后配置文件的内容
|
||||||
|
Paste the Clash core configuration below.
|
||||||
|
"
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
render: shell
|
||||||
|
label: Clash log
|
||||||
|
description: "
|
||||||
|
在下方附上 Clash Core 的日志,log level 使用 DEBUG
|
||||||
|
Paste the Clash core log below with the log level set to `DEBUG`.
|
||||||
|
"
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: Description
|
||||||
|
validations:
|
||||||
|
required: true
|
6
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
6
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
blank_issues_enabled: false
|
||||||
|
|
||||||
|
contact_links:
|
||||||
|
- name: Get help in GitHub Discussions
|
||||||
|
url: https://github.com/Dreamacro/clash/discussions
|
||||||
|
about: Have a question? Not sure if your issue affects everyone reproducibly? The quickest way to get help is on Clash's GitHub Discussions!
|
78
.github/ISSUE_TEMPLATE/feature_request.md
vendored
78
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@ -1,78 +0,0 @@
|
|||||||
---
|
|
||||||
name: Feature request
|
|
||||||
about: Suggest an idea for this project
|
|
||||||
title: "[Feature]"
|
|
||||||
labels: ''
|
|
||||||
assignees: ''
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
<!-- The English version is available. -->
|
|
||||||
感谢你向 Clash Core 提交 Feature Request!
|
|
||||||
在提交之前,请确认:
|
|
||||||
|
|
||||||
- [ ] 我已经在 [Issue Tracker](……/) 中找过我要提出的请求
|
|
||||||
|
|
||||||
请注意,如果你并没有遵照这个 issue template 填写内容,我们将直接关闭这个 issue。
|
|
||||||
|
|
||||||
<!--
|
|
||||||
Thanks for submitting a feature request towards the Clash core!
|
|
||||||
But before so, please do the following checklist:
|
|
||||||
|
|
||||||
- [ ] I have searched on the [issue tracker](……/) before creating the issue.
|
|
||||||
|
|
||||||
Please understand that we close issues that fail to follow the issue template.
|
|
||||||
-->
|
|
||||||
|
|
||||||
我都确认过了,我要继续提交。
|
|
||||||
<!-- None of the above, create a feature request -->
|
|
||||||
------------------------------------------------------------------
|
|
||||||
|
|
||||||
请附上任何可以帮助我们解决这个问题的信息,如果我们收到的信息不足,我们将对这个 issue 加上 *Needs more information* 标记并在收到更多资讯之前关闭 issue。
|
|
||||||
<!-- Make sure to add **all the information needed to understand the bug** so that someone can help. If the info is missing we'll add the 'Needs more information' label and close the issue until there is enough information. -->
|
|
||||||
|
|
||||||
### Clash core config
|
|
||||||
<!--
|
|
||||||
在下方附上 Clash Core 脱敏后的配置内容
|
|
||||||
Paste the Clash core configuration below.
|
|
||||||
-->
|
|
||||||
```
|
|
||||||
……
|
|
||||||
```
|
|
||||||
|
|
||||||
### Clash log
|
|
||||||
<!--
|
|
||||||
在下方附上 Clash Core 的日志,log level 请使用 DEBUG
|
|
||||||
Paste the Clash core log below with the log level set to `DEBUG`.
|
|
||||||
-->
|
|
||||||
```
|
|
||||||
……
|
|
||||||
```
|
|
||||||
|
|
||||||
### 环境 Environment
|
|
||||||
|
|
||||||
* Clash Core 的操作系统 (the OS that the Clash core is running on)
|
|
||||||
……
|
|
||||||
* 使用者的操作系统 (the OS running on the client)
|
|
||||||
……
|
|
||||||
* 网路环境或拓扑 (network conditions/topology)
|
|
||||||
……
|
|
||||||
* iptables,如果适用 (if applicable)
|
|
||||||
……
|
|
||||||
* ISP 有没有进行 DNS 污染 (is your ISP performing DNS pollution?)
|
|
||||||
……
|
|
||||||
* 其他
|
|
||||||
……
|
|
||||||
|
|
||||||
### 说明 Description
|
|
||||||
|
|
||||||
<!--
|
|
||||||
请详细、清晰地表达你要提出的论述,例如这个问题如何影响到你?你想实现什么功能?目前 Clash Core 的行为是什麽?
|
|
||||||
-->
|
|
||||||
|
|
||||||
### 可能的解决方案 Possible Solution
|
|
||||||
<!-- 此项非必须,但是如果你有想法的话欢迎提出。 -->
|
|
||||||
<!-- Not obligatory, but suggest a fix/reason for the bug, -->
|
|
||||||
<!-- or ideas how to implement the addition or change -->
|
|
||||||
|
|
||||||
### 更多信息 More Information
|
|
36
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
Normal file
36
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
name: Feature request
|
||||||
|
description: Suggest an idea for this project
|
||||||
|
title: "[Feature] "
|
||||||
|
body:
|
||||||
|
- type: checkboxes
|
||||||
|
id: ensure
|
||||||
|
attributes:
|
||||||
|
label: Verify steps
|
||||||
|
description: "
|
||||||
|
在提交之前,请确认
|
||||||
|
Please verify that you've followed these steps
|
||||||
|
"
|
||||||
|
options:
|
||||||
|
- label: "
|
||||||
|
我已经在 [Issue Tracker](……/) 中找过我要提出的请求
|
||||||
|
I have searched on the [issue tracker](……/) for a related feature request.
|
||||||
|
"
|
||||||
|
required: true
|
||||||
|
- label: "
|
||||||
|
我已经仔细看过 [Documentation](https://github.com/Dreamacro/clash/wiki/) 并无法自行解决问题
|
||||||
|
I have read the [documentation](https://github.com/Dreamacro/clash/wiki/) and was unable to solve the issue.
|
||||||
|
"
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: Description
|
||||||
|
description: 请详细、清晰地表达你要提出的论述,例如这个问题如何影响到你?你想实现什么功能?目前 Clash Core 的行为是什麽?
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: Possible Solution
|
||||||
|
description: "
|
||||||
|
此项非必须,但是如果你有想法的话欢迎提出。
|
||||||
|
Not obligatory, but suggest a fix/reason for the bug, or ideas how to implement the addition or change
|
||||||
|
"
|
2
.github/workflows/docker.yml
vendored
2
.github/workflows/docker.yml
vendored
@ -52,7 +52,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Get all docker tags
|
- name: Get all docker tags
|
||||||
if: startsWith(github.ref, 'refs/tags/')
|
if: startsWith(github.ref, 'refs/tags/')
|
||||||
uses: actions/github-script@v3
|
uses: actions/github-script@v4
|
||||||
id: tags
|
id: tags
|
||||||
with:
|
with:
|
||||||
script: |
|
script: |
|
||||||
|
4
.github/workflows/go.yml
vendored
4
.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.16
|
go-version: 1.17.x
|
||||||
|
|
||||||
- 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
|
||||||
@ -39,8 +39,6 @@ jobs:
|
|||||||
- name: Upload Release
|
- name: Upload Release
|
||||||
uses: softprops/action-gh-release@v1
|
uses: softprops/action-gh-release@v1
|
||||||
if: startsWith(github.ref, 'refs/tags/')
|
if: startsWith(github.ref, 'refs/tags/')
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
with:
|
with:
|
||||||
files: bin/*
|
files: bin/*
|
||||||
draft: true
|
draft: true
|
||||||
|
3
.github/workflows/stale.yml
vendored
3
.github/workflows/stale.yml
vendored
@ -11,9 +11,8 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/stale@v3
|
- uses: actions/stale@v4
|
||||||
with:
|
with:
|
||||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
stale-issue-message: 'This issue is stale because it has been open 60 days with no activity. Remove stale label or comment or this will be closed in 5 days'
|
stale-issue-message: 'This issue is stale because it has been open 60 days with no activity. Remove stale label or comment or this will be closed in 5 days'
|
||||||
days-before-stale: 60
|
days-before-stale: 60
|
||||||
days-before-close: 5
|
days-before-close: 5
|
||||||
|
6
Makefile
6
Makefile
@ -28,6 +28,7 @@ PLATFORM_LIST = \
|
|||||||
WINDOWS_ARCH_LIST = \
|
WINDOWS_ARCH_LIST = \
|
||||||
windows-386 \
|
windows-386 \
|
||||||
windows-amd64 \
|
windows-amd64 \
|
||||||
|
windows-arm64 \
|
||||||
windows-arm32v7
|
windows-arm32v7
|
||||||
|
|
||||||
all: linux-amd64 darwin-amd64 windows-amd64 # Most used
|
all: linux-amd64 darwin-amd64 windows-amd64 # Most used
|
||||||
@ -91,7 +92,10 @@ windows-386:
|
|||||||
|
|
||||||
windows-amd64:
|
windows-amd64:
|
||||||
GOARCH=amd64 GOOS=windows $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.exe
|
GOARCH=amd64 GOOS=windows $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.exe
|
||||||
|
|
||||||
|
windows-arm64:
|
||||||
|
GOARCH=arm64 GOOS=windows $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.exe
|
||||||
|
|
||||||
windows-arm32v7:
|
windows-arm32v7:
|
||||||
GOARCH=arm GOOS=windows GOARM=7 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.exe
|
GOARCH=arm GOOS=windows GOARM=7 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.exe
|
||||||
|
|
||||||
|
@ -136,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
|
||||||
|
@ -9,8 +9,8 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// NewHTTP receive normal http request and return HTTPContext
|
// NewHTTP receive normal http request and return HTTPContext
|
||||||
func NewHTTP(target string, source net.Addr, conn net.Conn) *context.ConnContext {
|
func NewHTTP(target socks5.Addr, source net.Addr, conn net.Conn) *context.ConnContext {
|
||||||
metadata := parseSocksAddr(socks5.ParseAddr(target))
|
metadata := parseSocksAddr(target)
|
||||||
metadata.NetWork = C.TCP
|
metadata.NetWork = C.TCP
|
||||||
metadata.Type = C.HTTP
|
metadata.Type = C.HTTP
|
||||||
if ip, port, err := parseAddr(source.String()); err == nil {
|
if ip, port, err := parseAddr(source.String()); err == nil {
|
||||||
|
@ -14,9 +14,7 @@ type Direct struct {
|
|||||||
|
|
||||||
// DialContext implements C.ProxyAdapter
|
// DialContext implements C.ProxyAdapter
|
||||||
func (d *Direct) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
|
func (d *Direct) DialContext(ctx context.Context, metadata *C.Metadata) (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
|
||||||
}
|
}
|
||||||
@ -26,7 +24,7 @@ func (d *Direct) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn,
|
|||||||
|
|
||||||
// DialUDP implements C.ProxyAdapter
|
// 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
|
||||||
}
|
}
|
||||||
|
@ -89,7 +89,7 @@ func (ss *ShadowSocks) DialContext(ctx context.Context, metadata *C.Metadata) (_
|
|||||||
|
|
||||||
// DialUDP implements C.ProxyAdapter
|
// 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
|
||||||
}
|
}
|
||||||
|
@ -74,7 +74,7 @@ func (ssr *ShadowSocksR) DialContext(ctx context.Context, metadata *C.Metadata)
|
|||||||
|
|
||||||
// DialUDP implements C.ProxyAdapter
|
// 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
|
||||||
}
|
}
|
||||||
|
@ -110,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
|
||||||
}
|
}
|
||||||
|
@ -43,6 +43,7 @@ type VmessOption struct {
|
|||||||
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"`
|
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"`
|
||||||
@ -64,21 +65,37 @@ type GrpcOptions struct {
|
|||||||
GrpcServiceName string `proxy:"grpc-service-name,omitempty"`
|
GrpcServiceName string `proxy:"grpc-service-name,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type WSOptions struct {
|
||||||
|
Path string `proxy:"path,omitempty"`
|
||||||
|
Headers map[string]string `proxy:"headers,omitempty"`
|
||||||
|
MaxEarlyData int `proxy:"max-early-data,omitempty"`
|
||||||
|
EarlyDataHeaderName string `proxy:"early-data-header-name,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
// StreamConn implements C.ProxyAdapter
|
// StreamConn implements C.ProxyAdapter
|
||||||
func (v *Vmess) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
func (v *Vmess) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
||||||
var err error
|
var err error
|
||||||
switch v.option.Network {
|
switch v.option.Network {
|
||||||
case "ws":
|
case "ws":
|
||||||
host, port, _ := net.SplitHostPort(v.addr)
|
if v.option.WSOpts.Path == "" {
|
||||||
wsOpts := &vmess.WebsocketConfig{
|
v.option.WSOpts.Path = v.option.WSPath
|
||||||
Host: host,
|
}
|
||||||
Port: port,
|
if len(v.option.WSOpts.Headers) == 0 {
|
||||||
Path: v.option.WSPath,
|
v.option.WSOpts.Headers = v.option.WSHeaders
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(v.option.WSHeaders) != 0 {
|
host, port, _ := net.SplitHostPort(v.addr)
|
||||||
|
wsOpts := &vmess.WebsocketConfig{
|
||||||
|
Host: host,
|
||||||
|
Port: port,
|
||||||
|
Path: v.option.WSOpts.Path,
|
||||||
|
MaxEarlyData: v.option.WSOpts.MaxEarlyData,
|
||||||
|
EarlyDataHeaderName: v.option.WSOpts.EarlyDataHeaderName,
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(v.option.WSOpts.Headers) != 0 {
|
||||||
header := http.Header{}
|
header := http.Header{}
|
||||||
for key, value := range v.option.WSHeaders {
|
for key, value := range v.option.WSOpts.Headers {
|
||||||
header.Add(key, value)
|
header.Add(key, value)
|
||||||
}
|
}
|
||||||
wsOpts.Headers = header
|
wsOpts.Headers = header
|
||||||
|
@ -3,8 +3,8 @@ package outboundgroup
|
|||||||
import (
|
import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/adapter/provider"
|
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
|
"github.com/Dreamacro/clash/constant/provider"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -5,9 +5,9 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/adapter/outbound"
|
"github.com/Dreamacro/clash/adapter/outbound"
|
||||||
"github.com/Dreamacro/clash/adapter/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 {
|
||||||
|
@ -8,10 +8,10 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/adapter/outbound"
|
"github.com/Dreamacro/clash/adapter/outbound"
|
||||||
"github.com/Dreamacro/clash/adapter/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"
|
||||||
)
|
)
|
||||||
|
@ -7,6 +7,7 @@ import (
|
|||||||
"github.com/Dreamacro/clash/adapter/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/adapter/outbound"
|
"github.com/Dreamacro/clash/adapter/outbound"
|
||||||
"github.com/Dreamacro/clash/adapter/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 {
|
||||||
@ -21,10 +20,20 @@ type Relay struct {
|
|||||||
|
|
||||||
// DialContext implements C.ProxyAdapter
|
// DialContext implements C.ProxyAdapter
|
||||||
func (r *Relay) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
|
func (r *Relay) DialContext(ctx context.Context, metadata *C.Metadata) (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]
|
||||||
|
|
||||||
|
@ -6,9 +6,9 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/adapter/outbound"
|
"github.com/Dreamacro/clash/adapter/outbound"
|
||||||
"github.com/Dreamacro/clash/adapter/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 {
|
||||||
|
@ -6,9 +6,9 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/adapter/outbound"
|
"github.com/Dreamacro/clash/adapter/outbound"
|
||||||
"github.com/Dreamacro/clash/adapter/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)
|
||||||
|
@ -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()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -76,7 +77,7 @@ func (f *fetcher) Initial() (interface{}, error) {
|
|||||||
isLocal = false
|
isLocal = false
|
||||||
}
|
}
|
||||||
|
|
||||||
if f.vehicle.Type() != File && !isLocal {
|
if f.vehicle.Type() != types.File && !isLocal {
|
||||||
if err := safeWrite(f.vehicle.Path(), buf); err != nil {
|
if err := safeWrite(f.vehicle.Path(), buf); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -110,7 +111,7 @@ func (f *fetcher) Update() (interface{}, bool, error) {
|
|||||||
return nil, false, err
|
return nil, false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if f.vehicle.Type() != File {
|
if f.vehicle.Type() != types.File {
|
||||||
if err := safeWrite(f.vehicle.Path(), buf); err != nil {
|
if err := safeWrite(f.vehicle.Path(), buf); err != nil {
|
||||||
return nil, false, err
|
return nil, false, err
|
||||||
}
|
}
|
||||||
@ -167,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)
|
||||||
|
@ -2,9 +2,9 @@ package provider
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"sync"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/Dreamacro/clash/common/batch"
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
|
|
||||||
"go.uber.org/atomic"
|
"go.uber.org/atomic"
|
||||||
@ -59,20 +59,17 @@ func (hc *HealthCheck) touch() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (hc *HealthCheck) check() {
|
func (hc *HealthCheck) check() {
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), defaultURLTestTimeout)
|
b, _ := batch.New(context.Background(), batch.WithConcurrencyNum(10))
|
||||||
wg := &sync.WaitGroup{}
|
|
||||||
|
|
||||||
for _, proxy := range hc.proxies {
|
for _, proxy := range hc.proxies {
|
||||||
wg.Add(1)
|
p := proxy
|
||||||
|
b.Go(p.Name(), func() (interface{}, error) {
|
||||||
go func(p C.Proxy) {
|
ctx, cancel := context.WithTimeout(context.Background(), defaultURLTestTimeout)
|
||||||
|
defer cancel()
|
||||||
p.URLTest(ctx, hc.url)
|
p.URLTest(ctx, hc.url)
|
||||||
wg.Done()
|
return nil, nil
|
||||||
}(proxy)
|
})
|
||||||
}
|
}
|
||||||
|
b.Wait()
|
||||||
wg.Wait()
|
|
||||||
cancel()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (hc *HealthCheck) close() {
|
func (hc *HealthCheck) close() {
|
||||||
|
@ -7,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{
|
||||||
@ -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)
|
||||||
|
@ -9,6 +9,7 @@ import (
|
|||||||
|
|
||||||
"github.com/Dreamacro/clash/adapter"
|
"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 {
|
||||||
@ -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}
|
||||||
|
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)
|
||||||
|
}
|
@ -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,118 +0,0 @@
|
|||||||
package dialer
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"net"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/common/singledo"
|
|
||||||
)
|
|
||||||
|
|
||||||
// In some OS, such as Windows, it takes a little longer to get interface information
|
|
||||||
var ifaceSingle = singledo.NewSingle(time.Second * 20)
|
|
||||||
|
|
||||||
var (
|
|
||||||
errPlatformNotSupport = errors.New("unsupport platform")
|
|
||||||
)
|
|
||||||
|
|
||||||
func lookupTCPAddr(ip net.IP, addrs []net.Addr) (*net.TCPAddr, error) {
|
|
||||||
ipv4 := ip.To4() != nil
|
|
||||||
|
|
||||||
for _, elm := range addrs {
|
|
||||||
addr, ok := elm.(*net.IPNet)
|
|
||||||
if !ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
addrV4 := addr.IP.To4() != nil
|
|
||||||
|
|
||||||
if addrV4 && ipv4 {
|
|
||||||
return &net.TCPAddr{IP: addr.IP, Port: 0}, nil
|
|
||||||
} else if !addrV4 && !ipv4 {
|
|
||||||
return &net.TCPAddr{IP: addr.IP, Port: 0}, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, ErrAddrNotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
func lookupUDPAddr(ip net.IP, addrs []net.Addr) (*net.UDPAddr, error) {
|
|
||||||
ipv4 := ip.To4() != nil
|
|
||||||
|
|
||||||
for _, elm := range addrs {
|
|
||||||
addr, ok := elm.(*net.IPNet)
|
|
||||||
if !ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
addrV4 := addr.IP.To4() != nil
|
|
||||||
|
|
||||||
if addrV4 && ipv4 {
|
|
||||||
return &net.UDPAddr{IP: addr.IP, Port: 0}, nil
|
|
||||||
} else if !addrV4 && !ipv4 {
|
|
||||||
return &net.UDPAddr{IP: addr.IP, Port: 0}, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, ErrAddrNotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
func fallbackBindToDialer(dialer *net.Dialer, network string, ip net.IP, name string) error {
|
|
||||||
if !ip.IsGlobalUnicast() {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
iface, err, _ := ifaceSingle.Do(func() (interface{}, error) {
|
|
||||||
return net.InterfaceByName(name)
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
addrs, err := iface.(*net.Interface).Addrs()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
switch network {
|
|
||||||
case "tcp", "tcp4", "tcp6":
|
|
||||||
if addr, err := lookupTCPAddr(ip, addrs); err == nil {
|
|
||||||
dialer.LocalAddr = addr
|
|
||||||
} else {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
case "udp", "udp4", "udp6":
|
|
||||||
if addr, err := lookupUDPAddr(ip, addrs); err == nil {
|
|
||||||
dialer.LocalAddr = addr
|
|
||||||
} else {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func fallbackBindToListenConfig(name string) (string, error) {
|
|
||||||
iface, err, _ := ifaceSingle.Do(func() (interface{}, error) {
|
|
||||||
return net.InterfaceByName(name)
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
addrs, err := iface.(*net.Interface).Addrs()
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, elm := range addrs {
|
|
||||||
addr, ok := elm.(*net.IPNet)
|
|
||||||
if !ok || addr.IP.To4() == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
return net.JoinHostPort(addr.IP.String(), "0"), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return "", ErrAddrNotFound
|
|
||||||
}
|
|
@ -3,51 +3,57 @@ package dialer
|
|||||||
import (
|
import (
|
||||||
"net"
|
"net"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
|
||||||
|
"github.com/Dreamacro/clash/component/iface"
|
||||||
)
|
)
|
||||||
|
|
||||||
type controlFn = func(network, address string, c syscall.RawConn) error
|
type controlFn = func(network, address string, c syscall.RawConn) error
|
||||||
|
|
||||||
func bindControl(ifaceIdx int) controlFn {
|
func bindControl(ifaceIdx int, chain controlFn) controlFn {
|
||||||
return func(network, address string, c syscall.RawConn) error {
|
return func(network, address string, c syscall.RawConn) (err error) {
|
||||||
|
defer func() {
|
||||||
|
if err == nil && chain != nil {
|
||||||
|
err = chain(network, address, c)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
ipStr, _, err := net.SplitHostPort(address)
|
ipStr, _, err := net.SplitHostPort(address)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
ip := net.ParseIP(ipStr)
|
ip := net.ParseIP(ipStr)
|
||||||
if ip != nil && !ip.IsGlobalUnicast() {
|
if ip != nil && !ip.IsGlobalUnicast() {
|
||||||
return nil
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return c.Control(func(fd uintptr) {
|
return c.Control(func(fd uintptr) {
|
||||||
switch network {
|
switch network {
|
||||||
case "tcp4", "udp4":
|
case "tcp4", "udp4":
|
||||||
syscall.SetsockoptInt(int(fd), syscall.IPPROTO_IP, syscall.IP_BOUND_IF, ifaceIdx)
|
unix.SetsockoptInt(int(fd), unix.IPPROTO_IP, unix.IP_BOUND_IF, ifaceIdx)
|
||||||
case "tcp6", "udp6":
|
case "tcp6", "udp6":
|
||||||
syscall.SetsockoptInt(int(fd), syscall.IPPROTO_IPV6, syscall.IPV6_BOUND_IF, ifaceIdx)
|
unix.SetsockoptInt(int(fd), unix.IPPROTO_IPV6, unix.IPV6_BOUND_IF, ifaceIdx)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func bindIfaceToDialer(dialer *net.Dialer, ifaceName string) error {
|
func bindIfaceToDialer(ifaceName string, dialer *net.Dialer, _ string, _ net.IP) error {
|
||||||
iface, err, _ := ifaceSingle.Do(func() (interface{}, error) {
|
ifaceObj, err := iface.ResolveInterface(ifaceName)
|
||||||
return net.InterfaceByName(ifaceName)
|
|
||||||
})
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
dialer.Control = bindControl(iface.(*net.Interface).Index)
|
dialer.Control = bindControl(ifaceObj.Index, dialer.Control)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func bindIfaceToListenConfig(lc *net.ListenConfig, ifaceName string) error {
|
func bindIfaceToListenConfig(ifaceName string, lc *net.ListenConfig, _, address string) (string, error) {
|
||||||
iface, err, _ := ifaceSingle.Do(func() (interface{}, error) {
|
ifaceObj, err := iface.ResolveInterface(ifaceName)
|
||||||
return net.InterfaceByName(ifaceName)
|
|
||||||
})
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
lc.Control = bindControl(iface.(*net.Interface).Index)
|
lc.Control = bindControl(ifaceObj.Index, lc.Control)
|
||||||
return nil
|
return address, nil
|
||||||
}
|
}
|
||||||
|
@ -3,34 +3,42 @@ package dialer
|
|||||||
import (
|
import (
|
||||||
"net"
|
"net"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
)
|
)
|
||||||
|
|
||||||
type controlFn = func(network, address string, c syscall.RawConn) error
|
type controlFn = func(network, address string, c syscall.RawConn) error
|
||||||
|
|
||||||
func bindControl(ifaceName string) controlFn {
|
func bindControl(ifaceName string, chain controlFn) controlFn {
|
||||||
return func(network, address string, c syscall.RawConn) error {
|
return func(network, address string, c syscall.RawConn) (err error) {
|
||||||
|
defer func() {
|
||||||
|
if err == nil && chain != nil {
|
||||||
|
err = chain(network, address, c)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
ipStr, _, err := net.SplitHostPort(address)
|
ipStr, _, err := net.SplitHostPort(address)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
ip := net.ParseIP(ipStr)
|
ip := net.ParseIP(ipStr)
|
||||||
if ip != nil && !ip.IsGlobalUnicast() {
|
if ip != nil && !ip.IsGlobalUnicast() {
|
||||||
return nil
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return c.Control(func(fd uintptr) {
|
return c.Control(func(fd uintptr) {
|
||||||
syscall.BindToDevice(int(fd), ifaceName)
|
unix.BindToDevice(int(fd), ifaceName)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func bindIfaceToDialer(dialer *net.Dialer, ifaceName string) error {
|
func bindIfaceToDialer(ifaceName string, dialer *net.Dialer, _ string, _ net.IP) error {
|
||||||
dialer.Control = bindControl(ifaceName)
|
dialer.Control = bindControl(ifaceName, dialer.Control)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func bindIfaceToListenConfig(lc *net.ListenConfig, ifaceName string) error {
|
func bindIfaceToListenConfig(ifaceName string, lc *net.ListenConfig, _, address string) (string, error) {
|
||||||
lc.Control = bindControl(ifaceName)
|
lc.Control = bindControl(ifaceName, lc.Control)
|
||||||
|
|
||||||
return nil
|
return address, nil
|
||||||
}
|
}
|
||||||
|
@ -1,13 +1,93 @@
|
|||||||
|
//go:build !linux && !darwin
|
||||||
// +build !linux,!darwin
|
// +build !linux,!darwin
|
||||||
|
|
||||||
package dialer
|
package dialer
|
||||||
|
|
||||||
import "net"
|
import (
|
||||||
|
"net"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
func bindIfaceToDialer(dialer *net.Dialer, ifaceName string) error {
|
"github.com/Dreamacro/clash/component/iface"
|
||||||
return errPlatformNotSupport
|
)
|
||||||
|
|
||||||
|
func lookupLocalAddr(ifaceName string, network string, destination net.IP, port int) (net.Addr, error) {
|
||||||
|
ifaceObj, err := iface.ResolveInterface(ifaceName)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var addr *net.IPNet
|
||||||
|
switch network {
|
||||||
|
case "udp4", "tcp4":
|
||||||
|
addr, err = ifaceObj.PickIPv4Addr(destination)
|
||||||
|
case "tcp6", "udp6":
|
||||||
|
addr, err = ifaceObj.PickIPv6Addr(destination)
|
||||||
|
default:
|
||||||
|
if destination != nil {
|
||||||
|
if destination.To4() != nil {
|
||||||
|
addr, err = ifaceObj.PickIPv4Addr(destination)
|
||||||
|
} else {
|
||||||
|
addr, err = ifaceObj.PickIPv6Addr(destination)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
addr, err = ifaceObj.PickIPv4Addr(destination)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.HasPrefix(network, "tcp") {
|
||||||
|
return &net.TCPAddr{
|
||||||
|
IP: addr.IP,
|
||||||
|
Port: port,
|
||||||
|
}, nil
|
||||||
|
} else if strings.HasPrefix(network, "udp") {
|
||||||
|
return &net.UDPAddr{
|
||||||
|
IP: addr.IP,
|
||||||
|
Port: port,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, iface.ErrAddrNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
func bindIfaceToListenConfig(lc *net.ListenConfig, ifaceName string) error {
|
func bindIfaceToDialer(ifaceName string, dialer *net.Dialer, network string, destination net.IP) error {
|
||||||
return errPlatformNotSupport
|
if !destination.IsGlobalUnicast() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
local := 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
|
||||||
|
}
|
@ -16,14 +16,14 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// from https://github.com/vishvananda/netlink/blob/bca67dfc8220b44ef582c9da4e9172bf1c9ec973/nl/nl_linux.go#L52-L62
|
// from https://github.com/vishvananda/netlink/blob/bca67dfc8220b44ef582c9da4e9172bf1c9ec973/nl/nl_linux.go#L52-L62
|
||||||
func init() {
|
var nativeEndian = func() binary.ByteOrder {
|
||||||
var x uint32 = 0x01020304
|
var x uint32 = 0x01020304
|
||||||
if *(*byte)(unsafe.Pointer(&x)) == 0x01 {
|
if *(*byte)(unsafe.Pointer(&x)) == 0x01 {
|
||||||
nativeEndian = binary.BigEndian
|
return binary.BigEndian
|
||||||
} else {
|
|
||||||
nativeEndian = binary.LittleEndian
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
return binary.LittleEndian
|
||||||
|
}()
|
||||||
|
|
||||||
type SocketResolver func(network string, ip net.IP, srcPort int) (inode, uid int, err error)
|
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)
|
||||||
@ -40,8 +40,6 @@ const (
|
|||||||
pathProc = "/proc"
|
pathProc = "/proc"
|
||||||
)
|
)
|
||||||
|
|
||||||
var nativeEndian binary.ByteOrder = binary.LittleEndian
|
|
||||||
|
|
||||||
func findProcessName(network string, ip net.IP, srcPort int) (string, error) {
|
func findProcessName(network string, ip net.IP, srcPort int) (string, error) {
|
||||||
inode, uid, err := DefaultSocketResolver(network, ip, srcPort)
|
inode, uid, err := DefaultSocketResolver(network, ip, srcPort)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -1,4 +1,7 @@
|
|||||||
// +build !darwin,!linux,!windows
|
//go:build !darwin && !linux && !windows && (!freebsd || !amd64)
|
||||||
|
// +build !darwin
|
||||||
|
// +build !linux
|
||||||
|
// +build !windows
|
||||||
// +build !freebsd !amd64
|
// +build !freebsd !amd64
|
||||||
|
|
||||||
package process
|
package process
|
||||||
|
@ -16,12 +16,13 @@ import (
|
|||||||
"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/rule"
|
R "github.com/Dreamacro/clash/rule"
|
||||||
T "github.com/Dreamacro/clash/tunnel"
|
T "github.com/Dreamacro/clash/tunnel"
|
||||||
|
|
||||||
yaml "gopkg.in/yaml.v2"
|
"gopkg.in/yaml.v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
// General config
|
// General config
|
||||||
@ -70,9 +71,10 @@ type DNS struct {
|
|||||||
|
|
||||||
// FallbackFilter config
|
// FallbackFilter config
|
||||||
type FallbackFilter struct {
|
type FallbackFilter struct {
|
||||||
GeoIP bool `yaml:"geoip"`
|
GeoIP bool `yaml:"geoip"`
|
||||||
IPCIDR []*net.IPNet `yaml:"ipcidr"`
|
GeoIPCode string `yaml:"geoip-code"`
|
||||||
Domain []string `yaml:"domain"`
|
IPCIDR []*net.IPNet `yaml:"ipcidr"`
|
||||||
|
Domain []string `yaml:"domain"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Profile config
|
// Profile config
|
||||||
@ -93,7 +95,7 @@ type Config struct {
|
|||||||
Rules []C.Rule
|
Rules []C.Rule
|
||||||
Users []auth.AuthUser
|
Users []auth.AuthUser
|
||||||
Proxies map[string]C.Proxy
|
Proxies map[string]C.Proxy
|
||||||
Providers map[string]provider.ProxyProvider
|
Providers map[string]providerTypes.ProxyProvider
|
||||||
}
|
}
|
||||||
|
|
||||||
type RawDNS struct {
|
type RawDNS struct {
|
||||||
@ -112,9 +114,10 @@ type RawDNS struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
||||||
@ -171,8 +174,9 @@ func UnmarshalRawConfig(buf []byte) (*RawConfig, error) {
|
|||||||
UseHosts: true,
|
UseHosts: true,
|
||||||
FakeIPRange: "198.18.0.1/16",
|
FakeIPRange: "198.18.0.1/16",
|
||||||
FallbackFilter: RawFallbackFilter{
|
FallbackFilter: RawFallbackFilter{
|
||||||
GeoIP: true,
|
GeoIP: true,
|
||||||
IPCIDR: []string{},
|
GeoIPCode: "CN",
|
||||||
|
IPCIDR: []string{},
|
||||||
},
|
},
|
||||||
DefaultNameserver: []string{
|
DefaultNameserver: []string{
|
||||||
"114.114.114.114",
|
"114.114.114.114",
|
||||||
@ -184,7 +188,7 @@ func UnmarshalRawConfig(buf []byte) (*RawConfig, error) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := yaml.Unmarshal(buf, &rawCfg); err != nil {
|
if err := yaml.Unmarshal(buf, rawCfg); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -267,9 +271,9 @@ 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
|
||||||
@ -345,7 +349,7 @@ func parseProxies(cfg *RawConfig) (proxies map[string]C.Proxy, providersMap map[
|
|||||||
|
|
||||||
// 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
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -367,7 +371,7 @@ 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"] = adapter.NewProxy(global)
|
proxies["GLOBAL"] = adapter.NewProxy(global)
|
||||||
return proxies, providersMap, nil
|
return proxies, providersMap, nil
|
||||||
@ -484,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)
|
||||||
}
|
}
|
||||||
@ -599,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
|
||||||
}
|
}
|
||||||
|
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"
|
||||||
|
@ -9,21 +9,19 @@ import (
|
|||||||
const Name = "clash"
|
const Name = "clash"
|
||||||
|
|
||||||
// Path is used to get the configuration path
|
// Path is used to get the configuration path
|
||||||
var Path *path
|
var Path = func() *path {
|
||||||
|
|
||||||
type path struct {
|
|
||||||
homeDir string
|
|
||||||
configFile string
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
homeDir, err := os.UserHomeDir()
|
homeDir, err := os.UserHomeDir()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
homeDir, _ = os.Getwd()
|
homeDir, _ = os.Getwd()
|
||||||
}
|
}
|
||||||
|
|
||||||
homeDir = P.Join(homeDir, ".config", Name)
|
homeDir = P.Join(homeDir, ".config", Name)
|
||||||
Path = &path{homeDir: homeDir, configFile: "config.yaml"}
|
return &path{homeDir: homeDir, configFile: "config.yaml"}
|
||||||
|
}()
|
||||||
|
|
||||||
|
type path struct {
|
||||||
|
homeDir string
|
||||||
|
configFile string
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetHomeDir is used to set the configuration path
|
// SetHomeDir is used to set the configuration path
|
||||||
|
105
constant/provider/interface.go
Normal file
105
constant/provider/interface.go
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
package provider
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/Dreamacro/clash/constant"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Vehicle Type
|
||||||
|
const (
|
||||||
|
File VehicleType = iota
|
||||||
|
HTTP
|
||||||
|
Compatible
|
||||||
|
)
|
||||||
|
|
||||||
|
// VehicleType defined
|
||||||
|
type VehicleType int
|
||||||
|
|
||||||
|
func (v VehicleType) String() string {
|
||||||
|
switch v {
|
||||||
|
case File:
|
||||||
|
return "File"
|
||||||
|
case HTTP:
|
||||||
|
return "HTTP"
|
||||||
|
case Compatible:
|
||||||
|
return "Compatible"
|
||||||
|
default:
|
||||||
|
return "Unknown"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Vehicle interface {
|
||||||
|
Read() ([]byte, error)
|
||||||
|
Path() string
|
||||||
|
Type() VehicleType
|
||||||
|
}
|
||||||
|
|
||||||
|
// Provider Type
|
||||||
|
const (
|
||||||
|
Proxy ProviderType = iota
|
||||||
|
Rule
|
||||||
|
)
|
||||||
|
|
||||||
|
// ProviderType defined
|
||||||
|
type ProviderType int
|
||||||
|
|
||||||
|
func (pt ProviderType) String() string {
|
||||||
|
switch pt {
|
||||||
|
case Proxy:
|
||||||
|
return "Proxy"
|
||||||
|
case Rule:
|
||||||
|
return "Rule"
|
||||||
|
default:
|
||||||
|
return "Unknown"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Provider interface
|
||||||
|
type Provider interface {
|
||||||
|
Name() string
|
||||||
|
VehicleType() VehicleType
|
||||||
|
Type() ProviderType
|
||||||
|
Initial() error
|
||||||
|
Update() error
|
||||||
|
}
|
||||||
|
|
||||||
|
// ProxyProvider interface
|
||||||
|
type ProxyProvider interface {
|
||||||
|
Provider
|
||||||
|
Proxies() []constant.Proxy
|
||||||
|
// ProxiesWithTouch is used to inform the provider that the proxy is actually being used while getting the list of proxies.
|
||||||
|
// Commonly used in Dial and DialUDP
|
||||||
|
ProxiesWithTouch() []constant.Proxy
|
||||||
|
HealthCheck()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rule Type
|
||||||
|
const (
|
||||||
|
Domain RuleType = iota
|
||||||
|
IPCIDR
|
||||||
|
Classical
|
||||||
|
)
|
||||||
|
|
||||||
|
// RuleType defined
|
||||||
|
type RuleType int
|
||||||
|
|
||||||
|
func (rt RuleType) String() string {
|
||||||
|
switch rt {
|
||||||
|
case Domain:
|
||||||
|
return "Domain"
|
||||||
|
case IPCIDR:
|
||||||
|
return "IPCIDR"
|
||||||
|
case Classical:
|
||||||
|
return "Classical"
|
||||||
|
default:
|
||||||
|
return "Unknown"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// RuleProvider interface
|
||||||
|
type RuleProvider interface {
|
||||||
|
Provider
|
||||||
|
Behavior() RuleType
|
||||||
|
Match(*constant.Metadata) bool
|
||||||
|
ShouldResolveIP() bool
|
||||||
|
AsRule(adaptor string) constant.Rule
|
||||||
|
}
|
@ -2,6 +2,7 @@ package dns
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"crypto/tls"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"strings"
|
"strings"
|
||||||
@ -19,37 +20,36 @@ type client struct {
|
|||||||
host string
|
host string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *client) Exchange(m *D.Msg) (msg *D.Msg, err error) {
|
func (c *client) Exchange(m *D.Msg) (*D.Msg, error) {
|
||||||
return c.ExchangeContext(context.Background(), m)
|
return c.ExchangeContext(context.Background(), m)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *client) ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg, err error) {
|
func (c *client) ExchangeContext(ctx context.Context, m *D.Msg) (*D.Msg, error) {
|
||||||
var ip net.IP
|
var (
|
||||||
|
ip net.IP
|
||||||
|
err error
|
||||||
|
)
|
||||||
if c.r == nil {
|
if c.r == nil {
|
||||||
// a default ip dns
|
// a default ip dns
|
||||||
ip = net.ParseIP(c.host)
|
if ip = net.ParseIP(c.host); ip == nil {
|
||||||
|
return nil, fmt.Errorf("dns %s not a valid ip", c.host)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
if ip, err = resolver.ResolveIPWithResolver(c.host, c.r); err != nil {
|
if ip, err = resolver.ResolveIPWithResolver(c.host, c.r); err != nil {
|
||||||
return nil, fmt.Errorf("use default dns resolve failed: %w", err)
|
return nil, fmt.Errorf("use default dns resolve failed: %w", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
d, err := dialer.Dialer()
|
network := "udp"
|
||||||
|
if strings.HasPrefix(c.Client.Net, "tcp") {
|
||||||
|
network = "tcp"
|
||||||
|
}
|
||||||
|
|
||||||
|
conn, err := dialer.DialContext(ctx, network, net.JoinHostPort(ip.String(), c.port))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
defer conn.Close()
|
||||||
if ip != nil && ip.IsGlobalUnicast() && dialer.DialHook != nil {
|
|
||||||
network := "udp"
|
|
||||||
if strings.HasPrefix(c.Client.Net, "tcp") {
|
|
||||||
network = "tcp"
|
|
||||||
}
|
|
||||||
if err := dialer.DialHook(d, network, ip); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
c.Client.Dialer = d
|
|
||||||
|
|
||||||
// miekg/dns ExchangeContext doesn't respond to context cancel.
|
// miekg/dns ExchangeContext doesn't respond to context cancel.
|
||||||
// this is a workaround
|
// this is a workaround
|
||||||
@ -59,7 +59,17 @@ func (c *client) ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg, err
|
|||||||
}
|
}
|
||||||
ch := make(chan result, 1)
|
ch := make(chan result, 1)
|
||||||
go func() {
|
go func() {
|
||||||
msg, _, err := c.Client.Exchange(m, net.JoinHostPort(ip.String(), c.port))
|
if strings.HasSuffix(c.Client.Net, "tls") {
|
||||||
|
conn = tls.Client(conn, c.Client.TLSConfig)
|
||||||
|
}
|
||||||
|
|
||||||
|
msg, _, err := c.Client.ExchangeWithConn(m, &D.Conn{
|
||||||
|
Conn: conn,
|
||||||
|
UDPSize: c.Client.UDPSize,
|
||||||
|
TsigSecret: c.Client.TsigSecret,
|
||||||
|
TsigProvider: c.Client.TsigProvider,
|
||||||
|
})
|
||||||
|
|
||||||
ch <- result{msg, err}
|
ch <- result{msg, err}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
144
dns/dhcp.go
Normal file
144
dns/dhcp.go
Normal file
@ -0,0 +1,144 @@
|
|||||||
|
package dns
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"net"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/Dreamacro/clash/component/dhcp"
|
||||||
|
"github.com/Dreamacro/clash/component/iface"
|
||||||
|
"github.com/Dreamacro/clash/component/resolver"
|
||||||
|
|
||||||
|
D "github.com/miekg/dns"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
IfaceTTL = time.Second * 20
|
||||||
|
DHCPTTL = time.Hour
|
||||||
|
DHCPTimeout = time.Minute
|
||||||
|
)
|
||||||
|
|
||||||
|
type dhcpClient struct {
|
||||||
|
ifaceName string
|
||||||
|
|
||||||
|
lock sync.Mutex
|
||||||
|
ifaceInvalidate time.Time
|
||||||
|
dnsInvalidate time.Time
|
||||||
|
|
||||||
|
ifaceAddr *net.IPNet
|
||||||
|
done chan struct{}
|
||||||
|
resolver *Resolver
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *dhcpClient) Exchange(m *D.Msg) (msg *D.Msg, err error) {
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), resolver.DefaultDNSTimeout)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
return d.ExchangeContext(ctx, m)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *dhcpClient) ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg, err error) {
|
||||||
|
res, err := d.resolve(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.ExchangeContext(ctx, m)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *dhcpClient) resolve(ctx context.Context) (*Resolver, error) {
|
||||||
|
d.lock.Lock()
|
||||||
|
|
||||||
|
invalidated, err := d.invalidate()
|
||||||
|
if err != nil {
|
||||||
|
d.err = err
|
||||||
|
} else if invalidated {
|
||||||
|
done := make(chan struct{})
|
||||||
|
|
||||||
|
d.done = done
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), DHCPTimeout)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
var res *Resolver
|
||||||
|
dns, err := dhcp.ResolveDNSFromDHCP(ctx, d.ifaceName)
|
||||||
|
if err == nil {
|
||||||
|
nameserver := make([]NameServer, 0, len(dns))
|
||||||
|
for _, d := range dns {
|
||||||
|
nameserver = append(nameserver, NameServer{Addr: net.JoinHostPort(d.String(), "53")})
|
||||||
|
}
|
||||||
|
|
||||||
|
res = NewResolver(Config{
|
||||||
|
Main: nameserver,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
d.lock.Lock()
|
||||||
|
defer d.lock.Unlock()
|
||||||
|
|
||||||
|
close(done)
|
||||||
|
|
||||||
|
d.done = nil
|
||||||
|
d.resolver = res
|
||||||
|
d.err = err
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
d.lock.Unlock()
|
||||||
|
|
||||||
|
for {
|
||||||
|
d.lock.Lock()
|
||||||
|
|
||||||
|
res, err, done := d.resolver, d.err, d.done
|
||||||
|
|
||||||
|
d.lock.Unlock()
|
||||||
|
|
||||||
|
// initializing
|
||||||
|
if res == nil && err == nil {
|
||||||
|
select {
|
||||||
|
case <-done:
|
||||||
|
continue
|
||||||
|
case <-ctx.Done():
|
||||||
|
return nil, ctx.Err()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// dirty return
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *dhcpClient) invalidate() (bool, error) {
|
||||||
|
if time.Now().Before(d.ifaceInvalidate) {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
d.ifaceInvalidate = time.Now().Add(IfaceTTL)
|
||||||
|
|
||||||
|
ifaceObj, err := iface.ResolveInterface(d.ifaceName)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
addr, err := ifaceObj.PickIPv4Addr(nil)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if time.Now().Before(d.dnsInvalidate) && d.ifaceAddr.IP.Equal(addr.IP) && bytes.Equal(d.ifaceAddr.Mask, addr.Mask) {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
d.dnsInvalidate = time.Now().Add(DHCPTTL)
|
||||||
|
d.ifaceAddr = addr
|
||||||
|
|
||||||
|
return d.done == nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func newDHCPClient(ifaceName string) *dhcpClient {
|
||||||
|
return &dhcpClient{ifaceName: ifaceName}
|
||||||
|
}
|
@ -2,6 +2,7 @@ package dns
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"net"
|
"net"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/component/mmdb"
|
"github.com/Dreamacro/clash/component/mmdb"
|
||||||
"github.com/Dreamacro/clash/component/trie"
|
"github.com/Dreamacro/clash/component/trie"
|
||||||
@ -11,11 +12,13 @@ type fallbackIPFilter interface {
|
|||||||
Match(net.IP) bool
|
Match(net.IP) bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type geoipFilter struct{}
|
type geoipFilter struct {
|
||||||
|
code string
|
||||||
|
}
|
||||||
|
|
||||||
func (gf *geoipFilter) Match(ip net.IP) bool {
|
func (gf *geoipFilter) Match(ip net.IP) bool {
|
||||||
record, _ := mmdb.Instance().Country(ip)
|
record, _ := mmdb.Instance().Country(ip)
|
||||||
return record.Country.IsoCode != "CN" && record.Country.IsoCode != ""
|
return !strings.EqualFold(record.Country.IsoCode, gf.code) && !ip.IsPrivate()
|
||||||
}
|
}
|
||||||
|
|
||||||
type ipnetFilter struct {
|
type ipnetFilter struct {
|
||||||
|
@ -87,6 +87,11 @@ func (r *Resolver) shouldIPFallback(ip net.IP) bool {
|
|||||||
|
|
||||||
// Exchange a batch of dns request, and it use cache
|
// Exchange a batch of dns request, and it use cache
|
||||||
func (r *Resolver) Exchange(m *D.Msg) (msg *D.Msg, err error) {
|
func (r *Resolver) Exchange(m *D.Msg) (msg *D.Msg, err error) {
|
||||||
|
return r.ExchangeContext(context.Background(), m)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExchangeContext a batch of dns request with context.Context, and it use cache
|
||||||
|
func (r *Resolver) ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg, err error) {
|
||||||
if len(m.Question) == 0 {
|
if len(m.Question) == 0 {
|
||||||
return nil, errors.New("should have one question at least")
|
return nil, errors.New("should have one question at least")
|
||||||
}
|
}
|
||||||
@ -98,17 +103,17 @@ func (r *Resolver) Exchange(m *D.Msg) (msg *D.Msg, err error) {
|
|||||||
msg = cache.(*D.Msg).Copy()
|
msg = cache.(*D.Msg).Copy()
|
||||||
if expireTime.Before(now) {
|
if expireTime.Before(now) {
|
||||||
setMsgTTL(msg, uint32(1)) // Continue fetch
|
setMsgTTL(msg, uint32(1)) // Continue fetch
|
||||||
go r.exchangeWithoutCache(m)
|
go r.exchangeWithoutCache(ctx, m)
|
||||||
} else {
|
} else {
|
||||||
setMsgTTL(msg, uint32(time.Until(expireTime).Seconds()))
|
setMsgTTL(msg, uint32(time.Until(expireTime).Seconds()))
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
return r.exchangeWithoutCache(m)
|
return r.exchangeWithoutCache(ctx, m)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExchangeWithoutCache a batch of dns request, and it do NOT GET from cache
|
// ExchangeWithoutCache a batch of dns request, and it do NOT GET from cache
|
||||||
func (r *Resolver) exchangeWithoutCache(m *D.Msg) (msg *D.Msg, err error) {
|
func (r *Resolver) exchangeWithoutCache(ctx context.Context, m *D.Msg) (msg *D.Msg, err error) {
|
||||||
q := m.Question[0]
|
q := m.Question[0]
|
||||||
|
|
||||||
ret, err, shared := r.group.Do(q.String(), func() (result interface{}, err error) {
|
ret, err, shared := r.group.Do(q.String(), func() (result interface{}, err error) {
|
||||||
@ -124,13 +129,13 @@ func (r *Resolver) exchangeWithoutCache(m *D.Msg) (msg *D.Msg, err error) {
|
|||||||
|
|
||||||
isIPReq := isIPRequest(q)
|
isIPReq := isIPRequest(q)
|
||||||
if isIPReq {
|
if isIPReq {
|
||||||
return r.ipExchange(m)
|
return r.ipExchange(ctx, m)
|
||||||
}
|
}
|
||||||
|
|
||||||
if matched := r.matchPolicy(m); len(matched) != 0 {
|
if matched := r.matchPolicy(m); len(matched) != 0 {
|
||||||
return r.batchExchange(matched, m)
|
return r.batchExchange(ctx, matched, m)
|
||||||
}
|
}
|
||||||
return r.batchExchange(r.main, m)
|
return r.batchExchange(ctx, r.main, m)
|
||||||
})
|
})
|
||||||
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
@ -143,8 +148,8 @@ func (r *Resolver) exchangeWithoutCache(m *D.Msg) (msg *D.Msg, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Resolver) batchExchange(clients []dnsClient, m *D.Msg) (msg *D.Msg, err error) {
|
func (r *Resolver) batchExchange(ctx context.Context, clients []dnsClient, m *D.Msg) (msg *D.Msg, err error) {
|
||||||
fast, ctx := picker.WithTimeout(context.Background(), resolver.DefaultDNSTimeout)
|
fast, ctx := picker.WithTimeout(ctx, resolver.DefaultDNSTimeout)
|
||||||
for _, client := range clients {
|
for _, client := range clients {
|
||||||
r := client
|
r := client
|
||||||
fast.Go(func() (interface{}, error) {
|
fast.Go(func() (interface{}, error) {
|
||||||
@ -209,21 +214,21 @@ func (r *Resolver) shouldOnlyQueryFallback(m *D.Msg) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Resolver) ipExchange(m *D.Msg) (msg *D.Msg, err error) {
|
func (r *Resolver) ipExchange(ctx context.Context, m *D.Msg) (msg *D.Msg, err error) {
|
||||||
|
|
||||||
if matched := r.matchPolicy(m); len(matched) != 0 {
|
if matched := r.matchPolicy(m); len(matched) != 0 {
|
||||||
res := <-r.asyncExchange(matched, m)
|
res := <-r.asyncExchange(ctx, matched, m)
|
||||||
return res.Msg, res.Error
|
return res.Msg, res.Error
|
||||||
}
|
}
|
||||||
|
|
||||||
onlyFallback := r.shouldOnlyQueryFallback(m)
|
onlyFallback := r.shouldOnlyQueryFallback(m)
|
||||||
|
|
||||||
if onlyFallback {
|
if onlyFallback {
|
||||||
res := <-r.asyncExchange(r.fallback, m)
|
res := <-r.asyncExchange(ctx, r.fallback, m)
|
||||||
return res.Msg, res.Error
|
return res.Msg, res.Error
|
||||||
}
|
}
|
||||||
|
|
||||||
msgCh := r.asyncExchange(r.main, m)
|
msgCh := r.asyncExchange(ctx, r.main, m)
|
||||||
|
|
||||||
if r.fallback == nil { // directly return if no fallback servers are available
|
if r.fallback == nil { // directly return if no fallback servers are available
|
||||||
res := <-msgCh
|
res := <-msgCh
|
||||||
@ -231,7 +236,7 @@ func (r *Resolver) ipExchange(m *D.Msg) (msg *D.Msg, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
fallbackMsg := r.asyncExchange(r.fallback, m)
|
fallbackMsg := r.asyncExchange(ctx, r.fallback, m)
|
||||||
res := <-msgCh
|
res := <-msgCh
|
||||||
if res.Error == nil {
|
if res.Error == nil {
|
||||||
if ips := msgToIP(res.Msg); len(ips) != 0 {
|
if ips := msgToIP(res.Msg); len(ips) != 0 {
|
||||||
@ -287,10 +292,10 @@ func (r *Resolver) msgToDomain(msg *D.Msg) string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Resolver) asyncExchange(client []dnsClient, msg *D.Msg) <-chan *result {
|
func (r *Resolver) asyncExchange(ctx context.Context, client []dnsClient, msg *D.Msg) <-chan *result {
|
||||||
ch := make(chan *result, 1)
|
ch := make(chan *result, 1)
|
||||||
go func() {
|
go func() {
|
||||||
res, err := r.batchExchange(client, msg)
|
res, err := r.batchExchange(ctx, client, msg)
|
||||||
ch <- &result{Msg: res, Error: err}
|
ch <- &result{Msg: res, Error: err}
|
||||||
}()
|
}()
|
||||||
return ch
|
return ch
|
||||||
@ -302,9 +307,10 @@ type NameServer struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type FallbackFilter struct {
|
type FallbackFilter struct {
|
||||||
GeoIP bool
|
GeoIP bool
|
||||||
IPCIDR []*net.IPNet
|
GeoIPCode string
|
||||||
Domain []string
|
IPCIDR []*net.IPNet
|
||||||
|
Domain []string
|
||||||
}
|
}
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
@ -344,7 +350,9 @@ func NewResolver(config Config) *Resolver {
|
|||||||
|
|
||||||
fallbackIPFilters := []fallbackIPFilter{}
|
fallbackIPFilters := []fallbackIPFilter{}
|
||||||
if config.FallbackFilter.GeoIP {
|
if config.FallbackFilter.GeoIP {
|
||||||
fallbackIPFilters = append(fallbackIPFilters, &geoipFilter{})
|
fallbackIPFilters = append(fallbackIPFilters, &geoipFilter{
|
||||||
|
code: config.FallbackFilter.GeoIPCode,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
for _, ipnet := range config.FallbackFilter.IPCIDR {
|
for _, ipnet := range config.FallbackFilter.IPCIDR {
|
||||||
fallbackIPFilters = append(fallbackIPFilters, &ipnetFilter{ipnet: ipnet})
|
fallbackIPFilters = append(fallbackIPFilters, &ipnetFilter{ipnet: ipnet})
|
||||||
|
@ -117,9 +117,13 @@ func isIPRequest(q D.Question) bool {
|
|||||||
func transform(servers []NameServer, resolver *Resolver) []dnsClient {
|
func transform(servers []NameServer, resolver *Resolver) []dnsClient {
|
||||||
ret := []dnsClient{}
|
ret := []dnsClient{}
|
||||||
for _, s := range servers {
|
for _, s := range servers {
|
||||||
if s.Net == "https" {
|
switch s.Net {
|
||||||
|
case "https":
|
||||||
ret = append(ret, newDoHClient(s.Addr, resolver))
|
ret = append(ret, newDoHClient(s.Addr, resolver))
|
||||||
continue
|
continue
|
||||||
|
case "dhcp":
|
||||||
|
ret = append(ret, newDHCPClient(s.Addr))
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
host, port, _ := net.SplitHostPort(s.Addr)
|
host, port, _ := net.SplitHostPort(s.Addr)
|
||||||
|
22
go.mod
22
go.mod
@ -1,22 +1,32 @@
|
|||||||
module github.com/Dreamacro/clash
|
module github.com/Dreamacro/clash
|
||||||
|
|
||||||
go 1.16
|
go 1.17
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/Dreamacro/go-shadowsocks2 v0.1.7
|
github.com/Dreamacro/go-shadowsocks2 v0.1.7
|
||||||
github.com/go-chi/chi/v5 v5.0.3
|
github.com/go-chi/chi/v5 v5.0.4
|
||||||
github.com/go-chi/cors v1.2.0
|
github.com/go-chi/cors v1.2.0
|
||||||
github.com/go-chi/render v1.0.1
|
github.com/go-chi/render v1.0.1
|
||||||
github.com/gofrs/uuid v4.0.0+incompatible
|
github.com/gofrs/uuid v4.0.0+incompatible
|
||||||
github.com/gorilla/websocket v1.4.2
|
github.com/gorilla/websocket v1.4.2
|
||||||
|
github.com/insomniacslk/dhcp v0.0.0-20210827173440-b95caade3eac
|
||||||
github.com/miekg/dns v1.1.43
|
github.com/miekg/dns v1.1.43
|
||||||
github.com/oschwald/geoip2-golang v1.5.0
|
github.com/oschwald/geoip2-golang v1.5.0
|
||||||
github.com/sirupsen/logrus v1.8.1
|
github.com/sirupsen/logrus v1.8.1
|
||||||
github.com/stretchr/testify v1.7.0
|
github.com/stretchr/testify v1.7.0
|
||||||
go.uber.org/atomic v1.8.0
|
go.uber.org/atomic v1.9.0
|
||||||
golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e
|
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5
|
||||||
golang.org/x/net v0.0.0-20210614182718-04defd469f4e
|
golang.org/x/net v0.0.0-20210903162142-ad29c8ab022f
|
||||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
|
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
|
||||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c
|
golang.org/x/sys v0.0.0-20210906170528-6f6e22806c34
|
||||||
gopkg.in/yaml.v2 v2.4.0
|
gopkg.in/yaml.v2 v2.4.0
|
||||||
)
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
|
github.com/oschwald/maxminddb-golang v1.8.0 // indirect
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
|
github.com/u-root/uio v0.0.0-20210528114334-82958018845c // indirect
|
||||||
|
golang.org/x/text v0.3.6 // indirect
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect
|
||||||
|
)
|
||||||
|
73
go.sum
73
go.sum
@ -3,16 +3,38 @@ github.com/Dreamacro/go-shadowsocks2 v0.1.7/go.mod h1:8p5G4cAj5ZlXwUR+Ww63gfSikr
|
|||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/go-chi/chi/v5 v5.0.3 h1:khYQBdPivkYG1s1TAzDQG1f6eX4kD2TItYVZexL5rS4=
|
github.com/fanliao/go-promise v0.0.0-20141029170127-1890db352a72/go.mod h1:PjfxuH4FZdUyfMdtBio2lsRr1AKEaVPwelzuHuh8Lqc=
|
||||||
github.com/go-chi/chi/v5 v5.0.3/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
|
github.com/go-chi/chi/v5 v5.0.4 h1:5e494iHzsYBiyXQAHHuI4tyJS9M3V84OuX3ufIIGHFo=
|
||||||
|
github.com/go-chi/chi/v5 v5.0.4/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
|
||||||
github.com/go-chi/cors v1.2.0 h1:tV1g1XENQ8ku4Bq3K9ub2AtgG+p16SmzeMSGTwrOKdE=
|
github.com/go-chi/cors v1.2.0 h1:tV1g1XENQ8ku4Bq3K9ub2AtgG+p16SmzeMSGTwrOKdE=
|
||||||
github.com/go-chi/cors v1.2.0/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58=
|
github.com/go-chi/cors v1.2.0/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58=
|
||||||
github.com/go-chi/render v1.0.1 h1:4/5tis2cKaNdnv9zFLfXzcquC9HbeZgCnxGnKrltBS8=
|
github.com/go-chi/render v1.0.1 h1:4/5tis2cKaNdnv9zFLfXzcquC9HbeZgCnxGnKrltBS8=
|
||||||
github.com/go-chi/render v1.0.1/go.mod h1:pq4Rr7HbnsdaeHagklXub+p6Wd16Af5l9koip1OvJns=
|
github.com/go-chi/render v1.0.1/go.mod h1:pq4Rr7HbnsdaeHagklXub+p6Wd16Af5l9koip1OvJns=
|
||||||
github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw=
|
github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw=
|
||||||
github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
||||||
|
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||||
|
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||||
|
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||||
|
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||||
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
|
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
|
||||||
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||||
|
github.com/hugelgupf/socketpair v0.0.0-20190730060125-05d35a94e714/go.mod h1:2Goc3h8EklBH5mspfHFxBnEoURQCGzQQH1ga9Myjvis=
|
||||||
|
github.com/insomniacslk/dhcp v0.0.0-20210827173440-b95caade3eac h1:IO6EfdRnPhxgKOsk9DbewdtQZHKZKnGlW7QCUttvNys=
|
||||||
|
github.com/insomniacslk/dhcp v0.0.0-20210827173440-b95caade3eac/go.mod h1:h+MxyHxRg9NH3terB1nfRIUaQEcI0XOVkdR9LNBlp8E=
|
||||||
|
github.com/jsimonetti/rtnetlink v0.0.0-20190606172950-9527aa82566a/go.mod h1:Oz+70psSo5OFh8DBl0Zv2ACw7Esh6pPUphlvZG9x7uw=
|
||||||
|
github.com/jsimonetti/rtnetlink v0.0.0-20200117123717-f846d4f6c1f4/go.mod h1:WGuG/smIU4J/54PblvSbh+xvCZmpJnFgr3ds6Z55XMQ=
|
||||||
|
github.com/jsimonetti/rtnetlink v0.0.0-20201009170750-9c6f07d100c1/go.mod h1:hqoO/u39cqLeBLebZ8fWdE96O7FxrAsRYhnVOdgHxok=
|
||||||
|
github.com/jsimonetti/rtnetlink v0.0.0-20201110080708-d2c240429e6c/go.mod h1:huN4d1phzjhlOsNIjFsw2SVRbwIHj3fJDMEU2SDPTmg=
|
||||||
|
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||||
|
github.com/mdlayher/ethernet v0.0.0-20190606142754-0394541c37b7/go.mod h1:U6ZQobyTjI/tJyq2HG+i/dfSoFUt8/aZCM+GKtmFk/Y=
|
||||||
|
github.com/mdlayher/netlink v0.0.0-20190409211403-11939a169225/go.mod h1:eQB3mZE4aiYnlUsyGGCOpPETfdQq4Jhsgf1fk3cwQaA=
|
||||||
|
github.com/mdlayher/netlink v1.0.0/go.mod h1:KxeJAFOFLG6AjpyDkQ/iIhxygIUKD+vcwqcnu43w/+M=
|
||||||
|
github.com/mdlayher/netlink v1.1.0/go.mod h1:H4WCitaheIsdF9yOYu8CFmCgQthAPIWZmcKp9uZHgmY=
|
||||||
|
github.com/mdlayher/netlink v1.1.1/go.mod h1:WTYpFb/WTvlRJAyKhZL5/uy69TDDpHHu2VZmb2XgV7o=
|
||||||
|
github.com/mdlayher/raw v0.0.0-20190606142536-fef19f00fc18/go.mod h1:7EpbotpCmVZcu+KCX4g9WaRNuu11uyhiW7+Le1dKawg=
|
||||||
|
github.com/mdlayher/raw v0.0.0-20191009151244-50f2db8cc065/go.mod h1:7EpbotpCmVZcu+KCX4g9WaRNuu11uyhiW7+Le1dKawg=
|
||||||
github.com/miekg/dns v1.1.43 h1:JKfpVSCB84vrAmHzyrsxB5NAr5kLoMXZArPSw7Qlgyg=
|
github.com/miekg/dns v1.1.43 h1:JKfpVSCB84vrAmHzyrsxB5NAr5kLoMXZArPSw7Qlgyg=
|
||||||
github.com/miekg/dns v1.1.43/go.mod h1:+evo5L0630/F6ca/Z9+GAqzhjGyn8/c+TBaOyfEl0V4=
|
github.com/miekg/dns v1.1.43/go.mod h1:+evo5L0630/F6ca/Z9+GAqzhjGyn8/c+TBaOyfEl0V4=
|
||||||
github.com/oschwald/geoip2-golang v1.5.0 h1:igg2yQIrrcRccB1ytFXqBfOHCjXWIoMv85lVJ1ONZzw=
|
github.com/oschwald/geoip2-golang v1.5.0 h1:igg2yQIrrcRccB1ytFXqBfOHCjXWIoMv85lVJ1ONZzw=
|
||||||
@ -23,35 +45,66 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
|
|||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
|
github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
|
||||||
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
||||||
|
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||||
|
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
go.uber.org/atomic v1.8.0 h1:CUhrE4N1rqSE6FM9ecihEjRkLQu8cDfgDyoOs83mEY4=
|
github.com/u-root/uio v0.0.0-20210528114334-82958018845c h1:BFvcl34IGnw8yvJi8hlqLFo9EshRInwWBs2M5fGWzQA=
|
||||||
go.uber.org/atomic v1.8.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
github.com/u-root/uio v0.0.0-20210528114334-82958018845c/go.mod h1:LpEX5FO/cB+WF4TYGY1V5qktpaZLkKkSegbr0V4eYXA=
|
||||||
|
go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
|
||||||
|
go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||||
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/crypto v0.0.0-20210317152858-513c2a44f670/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
golang.org/x/crypto v0.0.0-20210317152858-513c2a44f670/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
||||||
golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e h1:gsTQYXdTw2Gq7RBsWvlQ91b+aEQ6bXFUngBGuR8sPpI=
|
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 h1:HWj/xjIHfjYU5nVXpTM0s39J9CbLn7Cc5a7IC5rwsMQ=
|
||||||
golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
|
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
|
golang.org/x/net v0.0.0-20190419010253-1f3472d942ba/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
|
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||||
|
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20191007182048-72f939374954/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||||
|
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
golang.org/x/net v0.0.0-20210614182718-04defd469f4e h1:XpT3nA5TvE525Ne3hInMh6+GETgn27Zfm9dxsThnX2Q=
|
golang.org/x/net v0.0.0-20210903162142-ad29c8ab022f h1:w6wWR0H+nyVpbSAQbzVEIACVyr/h8l/BEkY6Sokc7Eg=
|
||||||
golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
golang.org/x/net v0.0.0-20210903162142-ad29c8ab022f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
|
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
|
||||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20190411185658-b44545bcd369/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20190418153312-f0ce4c0180be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20190606122018-79a91cf218c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20191224085550-c709ea063b76/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20191224085550-c709ea063b76/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20201009025420-dfb3f7c4e634/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20201101102859-da207088b7d1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210525143221-35b2ab0089ea/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c h1:F1jZWGFhYfh0Ci55sIpILtKKK8p3i2/krTr0H1rg74I=
|
golang.org/x/sys v0.0.0-20210906170528-6f6e22806c34 h1:GkvMjFtXUmahfDtashnc1mnrCtuBVcwse5QV2lUk/tI=
|
||||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210906170528-6f6e22806c34/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
|
golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
|
||||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||||
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||||
|
@ -8,15 +8,16 @@ import (
|
|||||||
|
|
||||||
"github.com/Dreamacro/clash/adapter"
|
"github.com/Dreamacro/clash/adapter"
|
||||||
"github.com/Dreamacro/clash/adapter/outboundgroup"
|
"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/dialer"
|
"github.com/Dreamacro/clash/component/dialer"
|
||||||
|
"github.com/Dreamacro/clash/component/iface"
|
||||||
"github.com/Dreamacro/clash/component/profile"
|
"github.com/Dreamacro/clash/component/profile"
|
||||||
"github.com/Dreamacro/clash/component/profile/cachefile"
|
"github.com/Dreamacro/clash/component/profile/cachefile"
|
||||||
"github.com/Dreamacro/clash/component/resolver"
|
"github.com/Dreamacro/clash/component/resolver"
|
||||||
"github.com/Dreamacro/clash/component/trie"
|
"github.com/Dreamacro/clash/component/trie"
|
||||||
"github.com/Dreamacro/clash/config"
|
"github.com/Dreamacro/clash/config"
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
|
"github.com/Dreamacro/clash/constant/provider"
|
||||||
"github.com/Dreamacro/clash/dns"
|
"github.com/Dreamacro/clash/dns"
|
||||||
P "github.com/Dreamacro/clash/listener"
|
P "github.com/Dreamacro/clash/listener"
|
||||||
authStore "github.com/Dreamacro/clash/listener/auth"
|
authStore "github.com/Dreamacro/clash/listener/auth"
|
||||||
@ -70,13 +71,13 @@ func ApplyConfig(cfg *config.Config, force bool) {
|
|||||||
defer mux.Unlock()
|
defer mux.Unlock()
|
||||||
|
|
||||||
updateUsers(cfg.Users)
|
updateUsers(cfg.Users)
|
||||||
updateGeneral(cfg.General, force)
|
|
||||||
updateProxies(cfg.Proxies, cfg.Providers)
|
updateProxies(cfg.Proxies, cfg.Providers)
|
||||||
updateRules(cfg.Rules)
|
updateRules(cfg.Rules)
|
||||||
updateDNS(cfg.DNS)
|
|
||||||
updateHosts(cfg.Hosts)
|
updateHosts(cfg.Hosts)
|
||||||
updateExperimental(cfg)
|
|
||||||
updateProfile(cfg)
|
updateProfile(cfg)
|
||||||
|
updateGeneral(cfg.General, force)
|
||||||
|
updateDNS(cfg.DNS)
|
||||||
|
updateExperimental(cfg)
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetGeneral() *config.General {
|
func GetGeneral() *config.General {
|
||||||
@ -123,9 +124,10 @@ func updateDNS(c *config.DNS) {
|
|||||||
Pool: c.FakeIPRange,
|
Pool: c.FakeIPRange,
|
||||||
Hosts: c.Hosts,
|
Hosts: c.Hosts,
|
||||||
FallbackFilter: dns.FallbackFilter{
|
FallbackFilter: dns.FallbackFilter{
|
||||||
GeoIP: c.FallbackFilter.GeoIP,
|
GeoIP: c.FallbackFilter.GeoIP,
|
||||||
IPCIDR: c.FallbackFilter.IPCIDR,
|
GeoIPCode: c.FallbackFilter.GeoIPCode,
|
||||||
Domain: c.FallbackFilter.Domain,
|
IPCIDR: c.FallbackFilter.IPCIDR,
|
||||||
|
Domain: c.FallbackFilter.Domain,
|
||||||
},
|
},
|
||||||
Default: c.DefaultNameserver,
|
Default: c.DefaultNameserver,
|
||||||
Policy: c.NameServerPolicy,
|
Policy: c.NameServerPolicy,
|
||||||
@ -170,13 +172,13 @@ func updateGeneral(general *config.General, force bool) {
|
|||||||
resolver.DisableIPv6 = !general.IPv6
|
resolver.DisableIPv6 = !general.IPv6
|
||||||
|
|
||||||
if general.Interface != "" {
|
if general.Interface != "" {
|
||||||
dialer.DialHook = dialer.DialerWithInterface(general.Interface)
|
dialer.DefaultOptions = []dialer.Option{dialer.WithInterface(general.Interface)}
|
||||||
dialer.ListenPacketHook = dialer.ListenPacketWithInterface(general.Interface)
|
|
||||||
} else {
|
} else {
|
||||||
dialer.DialHook = nil
|
dialer.DefaultOptions = nil
|
||||||
dialer.ListenPacketHook = nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
iface.FlushCache()
|
||||||
|
|
||||||
if !force {
|
if !force {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -195,7 +197,7 @@ func updateGeneral(general *config.General, force bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := P.ReCreateSocks(general.SocksPort, tcpIn, udpIn); err != nil {
|
if err := P.ReCreateSocks(general.SocksPort, tcpIn, udpIn); err != nil {
|
||||||
log.Errorln("Start SOCKS5 server error: %s", err.Error())
|
log.Errorln("Start SOCKS server error: %s", err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := P.ReCreateRedir(general.RedirPort, tcpIn, udpIn); err != nil {
|
if err := P.ReCreateRedir(general.RedirPort, tcpIn, udpIn); err != nil {
|
||||||
@ -207,7 +209,7 @@ func updateGeneral(general *config.General, force bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := P.ReCreateMixed(general.MixedPort, tcpIn, udpIn); err != nil {
|
if err := P.ReCreateMixed(general.MixedPort, tcpIn, udpIn); err != nil {
|
||||||
log.Errorln("Start Mixed(http and socks5) server error: %s", err.Error())
|
log.Errorln("Start Mixed(http and socks) server error: %s", err.Error())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,7 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/adapter/provider"
|
"github.com/Dreamacro/clash/constant/provider"
|
||||||
"github.com/Dreamacro/clash/tunnel"
|
"github.com/Dreamacro/clash/tunnel"
|
||||||
|
|
||||||
"github.com/go-chi/chi/v5"
|
"github.com/go-chi/chi/v5"
|
||||||
|
@ -3,6 +3,7 @@ package route
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@ -81,10 +82,15 @@ func Start(addr string, secret string) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Infoln("RESTful API listening at: %s", addr)
|
l, err := net.Listen("tcp", addr)
|
||||||
err := http.ListenAndServe(addr, r)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorln("External controller error: %s", err.Error())
|
log.Errorln("External controller listen error: %s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
serverAddr = l.Addr().String()
|
||||||
|
log.Infoln("RESTful API listening at: %s", serverAddr)
|
||||||
|
if err = http.Serve(l, r); err != nil {
|
||||||
|
log.Errorln("External controller serve error: %s", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -9,6 +9,7 @@ import (
|
|||||||
|
|
||||||
"github.com/Dreamacro/clash/adapter/inbound"
|
"github.com/Dreamacro/clash/adapter/inbound"
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
|
"github.com/Dreamacro/clash/transport/socks5"
|
||||||
)
|
)
|
||||||
|
|
||||||
func newClient(source net.Addr, in chan<- C.ConnContext) *http.Client {
|
func newClient(source net.Addr, in chan<- C.ConnContext) *http.Client {
|
||||||
@ -25,9 +26,14 @@ func newClient(source net.Addr, in chan<- C.ConnContext) *http.Client {
|
|||||||
return nil, errors.New("unsupported network " + network)
|
return nil, errors.New("unsupported network " + network)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
dstAddr := socks5.ParseAddr(address)
|
||||||
|
if dstAddr == nil {
|
||||||
|
return nil, socks5.ErrAddressNotSupported
|
||||||
|
}
|
||||||
|
|
||||||
left, right := net.Pipe()
|
left, right := net.Pipe()
|
||||||
|
|
||||||
in <- inbound.NewHTTP(address, source, right)
|
in <- inbound.NewHTTP(dstAddr, source, right)
|
||||||
|
|
||||||
return left, nil
|
return left, nil
|
||||||
},
|
},
|
||||||
|
@ -7,4 +7,4 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
//go:linkname ReadRequest net/http.readRequest
|
//go:linkname ReadRequest net/http.readRequest
|
||||||
func ReadRequest(b *bufio.Reader, deleteHostHeader bool) (req *http.Request, err error)
|
func ReadRequest(b *bufio.Reader) (req *http.Request, err error)
|
||||||
|
@ -24,7 +24,7 @@ func HandleConn(c net.Conn, in chan<- C.ConnContext, cache *cache.Cache) {
|
|||||||
trusted := cache == nil // disable authenticate if cache is nil
|
trusted := cache == nil // disable authenticate if cache is nil
|
||||||
|
|
||||||
for keepAlive {
|
for keepAlive {
|
||||||
request, err := ReadRequest(conn.Reader(), false)
|
request, err := ReadRequest(conn.Reader())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@ -45,6 +45,7 @@ func HandleConn(c net.Conn, in chan<- C.ConnContext, cache *cache.Cache) {
|
|||||||
if request.Method == http.MethodConnect {
|
if request.Method == http.MethodConnect {
|
||||||
resp = responseWith(200)
|
resp = responseWith(200)
|
||||||
resp.Status = "Connection established"
|
resp.Status = "Connection established"
|
||||||
|
resp.ContentLength = -1
|
||||||
|
|
||||||
if resp.Write(conn) != nil {
|
if resp.Write(conn) != nil {
|
||||||
break // close connection
|
break // close connection
|
||||||
@ -62,8 +63,8 @@ func HandleConn(c net.Conn, in chan<- C.ConnContext, cache *cache.Cache) {
|
|||||||
|
|
||||||
request.RequestURI = ""
|
request.RequestURI = ""
|
||||||
|
|
||||||
RemoveHopByHopHeaders(request.Header)
|
removeHopByHopHeaders(request.Header)
|
||||||
RemoveExtraHTTPHostPort(request)
|
removeExtraHTTPHostPort(request)
|
||||||
|
|
||||||
if request.URL.Scheme == "" || request.URL.Host == "" {
|
if request.URL.Scheme == "" || request.URL.Host == "" {
|
||||||
resp = responseWith(http.StatusBadRequest)
|
resp = responseWith(http.StatusBadRequest)
|
||||||
@ -73,9 +74,9 @@ func HandleConn(c net.Conn, in chan<- C.ConnContext, cache *cache.Cache) {
|
|||||||
resp = responseWith(http.StatusBadGateway)
|
resp = responseWith(http.StatusBadGateway)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
RemoveHopByHopHeaders(resp.Header)
|
removeHopByHopHeaders(resp.Header)
|
||||||
|
}
|
||||||
|
|
||||||
if keepAlive {
|
if keepAlive {
|
||||||
resp.Header.Set("Proxy-Connection", "keep-alive")
|
resp.Header.Set("Proxy-Connection", "keep-alive")
|
||||||
@ -97,7 +98,7 @@ func HandleConn(c net.Conn, in chan<- C.ConnContext, cache *cache.Cache) {
|
|||||||
func authenticate(request *http.Request, cache *cache.Cache) *http.Response {
|
func authenticate(request *http.Request, cache *cache.Cache) *http.Response {
|
||||||
authenticator := authStore.Authenticator()
|
authenticator := authStore.Authenticator()
|
||||||
if authenticator != nil {
|
if authenticator != nil {
|
||||||
credential := ParseBasicProxyAuthorization(request)
|
credential := parseBasicProxyAuthorization(request)
|
||||||
if credential == "" {
|
if credential == "" {
|
||||||
resp := responseWith(http.StatusProxyAuthRequired)
|
resp := responseWith(http.StatusProxyAuthRequired)
|
||||||
resp.Header.Set("Proxy-Authenticate", "Basic")
|
resp.Header.Set("Proxy-Authenticate", "Basic")
|
||||||
@ -106,7 +107,7 @@ func authenticate(request *http.Request, cache *cache.Cache) *http.Response {
|
|||||||
|
|
||||||
var authed interface{}
|
var authed interface{}
|
||||||
if authed = cache.Get(credential); authed == nil {
|
if authed = cache.Get(credential); authed == nil {
|
||||||
user, pass, err := DecodeBasicProxyAuthorization(credential)
|
user, pass, err := decodeBasicProxyAuthorization(credential)
|
||||||
authed = err == nil && authenticator.Verify(user, pass)
|
authed = err == nil && authenticator.Verify(user, pass)
|
||||||
cache.Put(credential, authed, time.Minute)
|
cache.Put(credential, authed, time.Minute)
|
||||||
}
|
}
|
||||||
|
@ -10,10 +10,26 @@ import (
|
|||||||
|
|
||||||
type Listener struct {
|
type Listener struct {
|
||||||
listener net.Listener
|
listener net.Listener
|
||||||
address string
|
addr string
|
||||||
closed bool
|
closed bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RawAddress implements C.Listener
|
||||||
|
func (l *Listener) RawAddress() string {
|
||||||
|
return l.addr
|
||||||
|
}
|
||||||
|
|
||||||
|
// Address implements C.Listener
|
||||||
|
func (l *Listener) Address() string {
|
||||||
|
return l.listener.Addr().String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close implements C.Listener
|
||||||
|
func (l *Listener) Close() error {
|
||||||
|
l.closed = true
|
||||||
|
return l.listener.Close()
|
||||||
|
}
|
||||||
|
|
||||||
func New(addr string, in chan<- C.ConnContext) (*Listener, error) {
|
func New(addr string, in chan<- C.ConnContext) (*Listener, error) {
|
||||||
return NewWithAuthenticate(addr, in, true)
|
return NewWithAuthenticate(addr, in, true)
|
||||||
}
|
}
|
||||||
@ -31,7 +47,7 @@ func NewWithAuthenticate(addr string, in chan<- C.ConnContext, authenticate bool
|
|||||||
|
|
||||||
hl := &Listener{
|
hl := &Listener{
|
||||||
listener: l,
|
listener: l,
|
||||||
address: addr,
|
addr: addr,
|
||||||
}
|
}
|
||||||
go func() {
|
go func() {
|
||||||
for {
|
for {
|
||||||
@ -48,12 +64,3 @@ func NewWithAuthenticate(addr string, in chan<- C.ConnContext, authenticate bool
|
|||||||
|
|
||||||
return hl, nil
|
return hl, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *Listener) Close() {
|
|
||||||
l.closed = true
|
|
||||||
l.listener.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *Listener) Address() string {
|
|
||||||
return l.address
|
|
||||||
}
|
|
||||||
|
@ -8,8 +8,8 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
// RemoveHopByHopHeaders remove hop-by-hop header
|
// removeHopByHopHeaders remove hop-by-hop header
|
||||||
func RemoveHopByHopHeaders(header http.Header) {
|
func removeHopByHopHeaders(header http.Header) {
|
||||||
// Strip hop-by-hop header based on RFC:
|
// Strip hop-by-hop header based on RFC:
|
||||||
// http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.5.1
|
// http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.5.1
|
||||||
// https://www.mnot.net/blog/2011/07/11/what_proxies_must_do
|
// https://www.mnot.net/blog/2011/07/11/what_proxies_must_do
|
||||||
@ -32,9 +32,9 @@ func RemoveHopByHopHeaders(header http.Header) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// RemoveExtraHTTPHostPort remove extra host port (example.com:80 --> example.com)
|
// removeExtraHTTPHostPort remove extra host port (example.com:80 --> example.com)
|
||||||
// It resolves the behavior of some HTTP servers that do not handle host:80 (e.g. baidu.com)
|
// It resolves the behavior of some HTTP servers that do not handle host:80 (e.g. baidu.com)
|
||||||
func RemoveExtraHTTPHostPort(req *http.Request) {
|
func removeExtraHTTPHostPort(req *http.Request) {
|
||||||
host := req.Host
|
host := req.Host
|
||||||
if host == "" {
|
if host == "" {
|
||||||
host = req.URL.Host
|
host = req.URL.Host
|
||||||
@ -48,8 +48,8 @@ func RemoveExtraHTTPHostPort(req *http.Request) {
|
|||||||
req.URL.Host = host
|
req.URL.Host = host
|
||||||
}
|
}
|
||||||
|
|
||||||
// ParseBasicProxyAuthorization parse header Proxy-Authorization and return base64-encoded credential
|
// parseBasicProxyAuthorization parse header Proxy-Authorization and return base64-encoded credential
|
||||||
func ParseBasicProxyAuthorization(request *http.Request) string {
|
func parseBasicProxyAuthorization(request *http.Request) string {
|
||||||
value := request.Header.Get("Proxy-Authorization")
|
value := request.Header.Get("Proxy-Authorization")
|
||||||
if !strings.HasPrefix(value, "Basic ") {
|
if !strings.HasPrefix(value, "Basic ") {
|
||||||
return ""
|
return ""
|
||||||
@ -58,8 +58,8 @@ func ParseBasicProxyAuthorization(request *http.Request) string {
|
|||||||
return value[6:] // value[len("Basic "):]
|
return value[6:] // value[len("Basic "):]
|
||||||
}
|
}
|
||||||
|
|
||||||
// DecodeBasicProxyAuthorization decode base64-encoded credential
|
// decodeBasicProxyAuthorization decode base64-encoded credential
|
||||||
func DecodeBasicProxyAuthorization(credential string) (string, string, error) {
|
func decodeBasicProxyAuthorization(credential string) (string, string, error) {
|
||||||
plain, err := base64.StdEncoding.DecodeString(credential)
|
plain, err := base64.StdEncoding.DecodeString(credential)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", err
|
return "", "", err
|
||||||
|
@ -69,7 +69,7 @@ func ReCreateHTTP(port int, tcpIn chan<- C.ConnContext) error {
|
|||||||
addr := genAddr(bindAddress, port, allowLan)
|
addr := genAddr(bindAddress, port, allowLan)
|
||||||
|
|
||||||
if httpListener != nil {
|
if httpListener != nil {
|
||||||
if httpListener.Address() == addr {
|
if httpListener.RawAddress() == addr {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
httpListener.Close()
|
httpListener.Close()
|
||||||
@ -100,7 +100,7 @@ func ReCreateSocks(port int, tcpIn chan<- C.ConnContext, udpIn chan<- *inbound.P
|
|||||||
shouldUDPIgnore := false
|
shouldUDPIgnore := false
|
||||||
|
|
||||||
if socksListener != nil {
|
if socksListener != nil {
|
||||||
if socksListener.Address() != addr {
|
if socksListener.RawAddress() != addr {
|
||||||
socksListener.Close()
|
socksListener.Close()
|
||||||
socksListener = nil
|
socksListener = nil
|
||||||
} else {
|
} else {
|
||||||
@ -109,7 +109,7 @@ func ReCreateSocks(port int, tcpIn chan<- C.ConnContext, udpIn chan<- *inbound.P
|
|||||||
}
|
}
|
||||||
|
|
||||||
if socksUDPListener != nil {
|
if socksUDPListener != nil {
|
||||||
if socksUDPListener.Address() != addr {
|
if socksUDPListener.RawAddress() != addr {
|
||||||
socksUDPListener.Close()
|
socksUDPListener.Close()
|
||||||
socksUDPListener = nil
|
socksUDPListener = nil
|
||||||
} else {
|
} else {
|
||||||
@ -139,7 +139,7 @@ func ReCreateSocks(port int, tcpIn chan<- C.ConnContext, udpIn chan<- *inbound.P
|
|||||||
socksListener = tcpListener
|
socksListener = tcpListener
|
||||||
socksUDPListener = udpListener
|
socksUDPListener = udpListener
|
||||||
|
|
||||||
log.Infoln("SOCKS5 proxy listening at: %s", socksListener.Address())
|
log.Infoln("SOCKS proxy listening at: %s", socksListener.Address())
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -150,7 +150,7 @@ func ReCreateRedir(port int, tcpIn chan<- C.ConnContext, udpIn chan<- *inbound.P
|
|||||||
addr := genAddr(bindAddress, port, allowLan)
|
addr := genAddr(bindAddress, port, allowLan)
|
||||||
|
|
||||||
if redirListener != nil {
|
if redirListener != nil {
|
||||||
if redirListener.Address() == addr {
|
if redirListener.RawAddress() == addr {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
redirListener.Close()
|
redirListener.Close()
|
||||||
@ -158,7 +158,7 @@ func ReCreateRedir(port int, tcpIn chan<- C.ConnContext, udpIn chan<- *inbound.P
|
|||||||
}
|
}
|
||||||
|
|
||||||
if redirUDPListener != nil {
|
if redirUDPListener != nil {
|
||||||
if redirUDPListener.Address() == addr {
|
if redirUDPListener.RawAddress() == addr {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
redirUDPListener.Close()
|
redirUDPListener.Close()
|
||||||
@ -191,7 +191,7 @@ func ReCreateTProxy(port int, tcpIn chan<- C.ConnContext, udpIn chan<- *inbound.
|
|||||||
addr := genAddr(bindAddress, port, allowLan)
|
addr := genAddr(bindAddress, port, allowLan)
|
||||||
|
|
||||||
if tproxyListener != nil {
|
if tproxyListener != nil {
|
||||||
if tproxyListener.Address() == addr {
|
if tproxyListener.RawAddress() == addr {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
tproxyListener.Close()
|
tproxyListener.Close()
|
||||||
@ -199,7 +199,7 @@ func ReCreateTProxy(port int, tcpIn chan<- C.ConnContext, udpIn chan<- *inbound.
|
|||||||
}
|
}
|
||||||
|
|
||||||
if tproxyUDPListener != nil {
|
if tproxyUDPListener != nil {
|
||||||
if tproxyUDPListener.Address() == addr {
|
if tproxyUDPListener.RawAddress() == addr {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
tproxyUDPListener.Close()
|
tproxyUDPListener.Close()
|
||||||
@ -235,7 +235,7 @@ func ReCreateMixed(port int, tcpIn chan<- C.ConnContext, udpIn chan<- *inbound.P
|
|||||||
shouldUDPIgnore := false
|
shouldUDPIgnore := false
|
||||||
|
|
||||||
if mixedListener != nil {
|
if mixedListener != nil {
|
||||||
if mixedListener.Address() != addr {
|
if mixedListener.RawAddress() != addr {
|
||||||
mixedListener.Close()
|
mixedListener.Close()
|
||||||
mixedListener = nil
|
mixedListener = nil
|
||||||
} else {
|
} else {
|
||||||
@ -243,7 +243,7 @@ func ReCreateMixed(port int, tcpIn chan<- C.ConnContext, udpIn chan<- *inbound.P
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if mixedUDPLister != nil {
|
if mixedUDPLister != nil {
|
||||||
if mixedUDPLister.Address() != addr {
|
if mixedUDPLister.RawAddress() != addr {
|
||||||
mixedUDPLister.Close()
|
mixedUDPLister.Close()
|
||||||
mixedUDPLister = nil
|
mixedUDPLister = nil
|
||||||
} else {
|
} else {
|
||||||
@ -271,7 +271,7 @@ func ReCreateMixed(port int, tcpIn chan<- C.ConnContext, udpIn chan<- *inbound.P
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Infoln("Mixed(http+socks5) proxy listening at: %s", mixedListener.Address())
|
log.Infoln("Mixed(http+socks) proxy listening at: %s", mixedListener.Address())
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -9,14 +9,31 @@ import (
|
|||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
"github.com/Dreamacro/clash/listener/http"
|
"github.com/Dreamacro/clash/listener/http"
|
||||||
"github.com/Dreamacro/clash/listener/socks"
|
"github.com/Dreamacro/clash/listener/socks"
|
||||||
|
"github.com/Dreamacro/clash/transport/socks4"
|
||||||
"github.com/Dreamacro/clash/transport/socks5"
|
"github.com/Dreamacro/clash/transport/socks5"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Listener struct {
|
type Listener struct {
|
||||||
listener net.Listener
|
listener net.Listener
|
||||||
address string
|
addr string
|
||||||
closed bool
|
|
||||||
cache *cache.Cache
|
cache *cache.Cache
|
||||||
|
closed bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// RawAddress implements C.Listener
|
||||||
|
func (l *Listener) RawAddress() string {
|
||||||
|
return l.addr
|
||||||
|
}
|
||||||
|
|
||||||
|
// Address implements C.Listener
|
||||||
|
func (l *Listener) Address() string {
|
||||||
|
return l.listener.Addr().String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close implements C.Listener
|
||||||
|
func (l *Listener) Close() error {
|
||||||
|
l.closed = true
|
||||||
|
return l.listener.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(addr string, in chan<- C.ConnContext) (*Listener, error) {
|
func New(addr string, in chan<- C.ConnContext) (*Listener, error) {
|
||||||
@ -25,7 +42,11 @@ func New(addr string, in chan<- C.ConnContext) (*Listener, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
ml := &Listener{l, addr, false, cache.New(30 * time.Second)}
|
ml := &Listener{
|
||||||
|
listener: l,
|
||||||
|
addr: addr,
|
||||||
|
cache: cache.New(30 * time.Second),
|
||||||
|
}
|
||||||
go func() {
|
go func() {
|
||||||
for {
|
for {
|
||||||
c, err := ml.listener.Accept()
|
c, err := ml.listener.Accept()
|
||||||
@ -42,15 +63,6 @@ func New(addr string, in chan<- C.ConnContext) (*Listener, error) {
|
|||||||
return ml, nil
|
return ml, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *Listener) Close() {
|
|
||||||
l.closed = true
|
|
||||||
l.listener.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *Listener) Address() string {
|
|
||||||
return l.address
|
|
||||||
}
|
|
||||||
|
|
||||||
func handleConn(conn net.Conn, in chan<- C.ConnContext, cache *cache.Cache) {
|
func handleConn(conn net.Conn, in chan<- C.ConnContext, cache *cache.Cache) {
|
||||||
bufConn := N.NewBufferedConn(conn)
|
bufConn := N.NewBufferedConn(conn)
|
||||||
head, err := bufConn.Peek(1)
|
head, err := bufConn.Peek(1)
|
||||||
@ -58,10 +70,12 @@ func handleConn(conn net.Conn, in chan<- C.ConnContext, cache *cache.Cache) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if head[0] == socks5.Version {
|
switch head[0] {
|
||||||
socks.HandleSocks(bufConn, in)
|
case socks4.Version:
|
||||||
return
|
socks.HandleSocks4(bufConn, in)
|
||||||
|
case socks5.Version:
|
||||||
|
socks.HandleSocks5(bufConn, in)
|
||||||
|
default:
|
||||||
|
http.HandleConn(bufConn, in, cache)
|
||||||
}
|
}
|
||||||
|
|
||||||
http.HandleConn(bufConn, in, cache)
|
|
||||||
}
|
}
|
||||||
|
@ -9,16 +9,35 @@ import (
|
|||||||
|
|
||||||
type Listener struct {
|
type Listener struct {
|
||||||
listener net.Listener
|
listener net.Listener
|
||||||
address string
|
addr string
|
||||||
closed bool
|
closed bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RawAddress implements C.Listener
|
||||||
|
func (l *Listener) RawAddress() string {
|
||||||
|
return l.addr
|
||||||
|
}
|
||||||
|
|
||||||
|
// Address implements C.Listener
|
||||||
|
func (l *Listener) Address() string {
|
||||||
|
return l.listener.Addr().String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close implements C.Listener
|
||||||
|
func (l *Listener) Close() error {
|
||||||
|
l.closed = true
|
||||||
|
return l.listener.Close()
|
||||||
|
}
|
||||||
|
|
||||||
func New(addr string, in chan<- C.ConnContext) (*Listener, error) {
|
func New(addr string, in chan<- C.ConnContext) (*Listener, error) {
|
||||||
l, err := net.Listen("tcp", addr)
|
l, err := net.Listen("tcp", addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
rl := &Listener{l, addr, false}
|
rl := &Listener{
|
||||||
|
listener: l,
|
||||||
|
addr: addr,
|
||||||
|
}
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
for {
|
for {
|
||||||
@ -36,15 +55,6 @@ func New(addr string, in chan<- C.ConnContext) (*Listener, error) {
|
|||||||
return rl, nil
|
return rl, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *Listener) Close() {
|
|
||||||
l.closed = true
|
|
||||||
l.listener.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *Listener) Address() string {
|
|
||||||
return l.address
|
|
||||||
}
|
|
||||||
|
|
||||||
func handleRedir(conn net.Conn, in chan<- C.ConnContext) {
|
func handleRedir(conn net.Conn, in chan<- C.ConnContext) {
|
||||||
target, err := parserPacket(conn)
|
target, err := parserPacket(conn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
//go:build linux && !386
|
||||||
// +build linux,!386
|
// +build linux,!386
|
||||||
|
|
||||||
package redir
|
package redir
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
//go:build !darwin && !linux && !freebsd
|
||||||
// +build !darwin,!linux,!freebsd
|
// +build !darwin,!linux,!freebsd
|
||||||
|
|
||||||
package redir
|
package redir
|
||||||
|
@ -6,24 +6,45 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/adapter/inbound"
|
"github.com/Dreamacro/clash/adapter/inbound"
|
||||||
|
N "github.com/Dreamacro/clash/common/net"
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
authStore "github.com/Dreamacro/clash/listener/auth"
|
authStore "github.com/Dreamacro/clash/listener/auth"
|
||||||
|
"github.com/Dreamacro/clash/transport/socks4"
|
||||||
"github.com/Dreamacro/clash/transport/socks5"
|
"github.com/Dreamacro/clash/transport/socks5"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Listener struct {
|
type Listener struct {
|
||||||
listener net.Listener
|
listener net.Listener
|
||||||
address string
|
addr string
|
||||||
closed bool
|
closed bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RawAddress implements C.Listener
|
||||||
|
func (l *Listener) RawAddress() string {
|
||||||
|
return l.addr
|
||||||
|
}
|
||||||
|
|
||||||
|
// Address implements C.Listener
|
||||||
|
func (l *Listener) Address() string {
|
||||||
|
return l.listener.Addr().String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close implements C.Listener
|
||||||
|
func (l *Listener) Close() error {
|
||||||
|
l.closed = true
|
||||||
|
return l.listener.Close()
|
||||||
|
}
|
||||||
|
|
||||||
func New(addr string, in chan<- C.ConnContext) (*Listener, error) {
|
func New(addr string, in chan<- C.ConnContext) (*Listener, error) {
|
||||||
l, err := net.Listen("tcp", addr)
|
l, err := net.Listen("tcp", addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
sl := &Listener{l, addr, false}
|
sl := &Listener{
|
||||||
|
listener: l,
|
||||||
|
addr: addr,
|
||||||
|
}
|
||||||
go func() {
|
go func() {
|
||||||
for {
|
for {
|
||||||
c, err := l.Accept()
|
c, err := l.Accept()
|
||||||
@ -33,23 +54,44 @@ func New(addr string, in chan<- C.ConnContext) (*Listener, error) {
|
|||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
go HandleSocks(c, in)
|
go handleSocks(c, in)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
return sl, nil
|
return sl, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *Listener) Close() {
|
func handleSocks(conn net.Conn, in chan<- C.ConnContext) {
|
||||||
l.closed = true
|
bufConn := N.NewBufferedConn(conn)
|
||||||
l.listener.Close()
|
head, err := bufConn.Peek(1)
|
||||||
|
if err != nil {
|
||||||
|
conn.Close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
switch head[0] {
|
||||||
|
case socks4.Version:
|
||||||
|
HandleSocks4(bufConn, in)
|
||||||
|
case socks5.Version:
|
||||||
|
HandleSocks5(bufConn, in)
|
||||||
|
default:
|
||||||
|
conn.Close()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *Listener) Address() string {
|
func HandleSocks4(conn net.Conn, in chan<- C.ConnContext) {
|
||||||
return l.address
|
addr, _, err := socks4.ServerHandshake(conn, authStore.Authenticator())
|
||||||
|
if err != nil {
|
||||||
|
conn.Close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if c, ok := conn.(*net.TCPConn); ok {
|
||||||
|
c.SetKeepAlive(true)
|
||||||
|
}
|
||||||
|
in <- inbound.NewSocket(socks5.ParseAddr(addr), conn, C.SOCKS4)
|
||||||
}
|
}
|
||||||
|
|
||||||
func HandleSocks(conn net.Conn, in chan<- C.ConnContext) {
|
func HandleSocks5(conn net.Conn, in chan<- C.ConnContext) {
|
||||||
target, command, err := socks5.ServerHandshake(conn, authStore.Authenticator())
|
target, command, err := socks5.ServerHandshake(conn, authStore.Authenticator())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
conn.Close()
|
conn.Close()
|
||||||
@ -63,5 +105,5 @@ func HandleSocks(conn net.Conn, in chan<- C.ConnContext) {
|
|||||||
io.Copy(ioutil.Discard, conn)
|
io.Copy(ioutil.Discard, conn)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
in <- inbound.NewSocket(target, conn, C.SOCKS)
|
in <- inbound.NewSocket(target, conn, C.SOCKS5)
|
||||||
}
|
}
|
||||||
|
@ -13,10 +13,26 @@ import (
|
|||||||
|
|
||||||
type UDPListener struct {
|
type UDPListener struct {
|
||||||
packetConn net.PacketConn
|
packetConn net.PacketConn
|
||||||
address string
|
addr string
|
||||||
closed bool
|
closed bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RawAddress implements C.Listener
|
||||||
|
func (l *UDPListener) RawAddress() string {
|
||||||
|
return l.addr
|
||||||
|
}
|
||||||
|
|
||||||
|
// Address implements C.Listener
|
||||||
|
func (l *UDPListener) Address() string {
|
||||||
|
return l.packetConn.LocalAddr().String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close implements C.Listener
|
||||||
|
func (l *UDPListener) Close() error {
|
||||||
|
l.closed = true
|
||||||
|
return l.packetConn.Close()
|
||||||
|
}
|
||||||
|
|
||||||
func NewUDP(addr string, in chan<- *inbound.PacketAdapter) (*UDPListener, error) {
|
func NewUDP(addr string, in chan<- *inbound.PacketAdapter) (*UDPListener, error) {
|
||||||
l, err := net.ListenPacket("udp", addr)
|
l, err := net.ListenPacket("udp", addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -27,7 +43,10 @@ func NewUDP(addr string, in chan<- *inbound.PacketAdapter) (*UDPListener, error)
|
|||||||
log.Warnln("Failed to Reuse UDP Address: %s", err)
|
log.Warnln("Failed to Reuse UDP Address: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
sl := &UDPListener{l, addr, false}
|
sl := &UDPListener{
|
||||||
|
packetConn: l,
|
||||||
|
addr: addr,
|
||||||
|
}
|
||||||
go func() {
|
go func() {
|
||||||
for {
|
for {
|
||||||
buf := pool.Get(pool.RelayBufferSize)
|
buf := pool.Get(pool.RelayBufferSize)
|
||||||
@ -46,15 +65,6 @@ func NewUDP(addr string, in chan<- *inbound.PacketAdapter) (*UDPListener, error)
|
|||||||
return sl, nil
|
return sl, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *UDPListener) Close() error {
|
|
||||||
l.closed = true
|
|
||||||
return l.packetConn.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *UDPListener) Address() string {
|
|
||||||
return l.address
|
|
||||||
}
|
|
||||||
|
|
||||||
func handleSocksUDP(pc net.PacketConn, in chan<- *inbound.PacketAdapter, buf []byte, addr net.Addr) {
|
func handleSocksUDP(pc net.PacketConn, in chan<- *inbound.PacketAdapter, buf []byte, addr net.Addr) {
|
||||||
target, payload, err := socks5.DecodeUDPPacket(buf)
|
target, payload, err := socks5.DecodeUDPPacket(buf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -69,7 +79,7 @@ func handleSocksUDP(pc net.PacketConn, in chan<- *inbound.PacketAdapter, buf []b
|
|||||||
bufRef: buf,
|
bufRef: buf,
|
||||||
}
|
}
|
||||||
select {
|
select {
|
||||||
case in <- inbound.NewPacket(target, packet, C.TPROXY):
|
case in <- inbound.NewPacket(target, packet, C.SOCKS5):
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
//go:build linux
|
||||||
// +build linux
|
// +build linux
|
||||||
|
|
||||||
package tproxy
|
package tproxy
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
//go:build !linux
|
||||||
// +build !linux
|
// +build !linux
|
||||||
|
|
||||||
package tproxy
|
package tproxy
|
||||||
|
@ -10,10 +10,32 @@ import (
|
|||||||
|
|
||||||
type Listener struct {
|
type Listener struct {
|
||||||
listener net.Listener
|
listener net.Listener
|
||||||
address string
|
addr string
|
||||||
closed bool
|
closed bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RawAddress implements C.Listener
|
||||||
|
func (l *Listener) RawAddress() string {
|
||||||
|
return l.addr
|
||||||
|
}
|
||||||
|
|
||||||
|
// Address implements C.Listener
|
||||||
|
func (l *Listener) Address() string {
|
||||||
|
return l.listener.Addr().String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close implements C.Listener
|
||||||
|
func (l *Listener) Close() error {
|
||||||
|
l.closed = true
|
||||||
|
return l.listener.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Listener) handleTProxy(conn net.Conn, in chan<- C.ConnContext) {
|
||||||
|
target := socks5.ParseAddrToSocksAddr(conn.LocalAddr())
|
||||||
|
conn.(*net.TCPConn).SetKeepAlive(true)
|
||||||
|
in <- inbound.NewSocket(target, conn, C.TPROXY)
|
||||||
|
}
|
||||||
|
|
||||||
func New(addr string, in chan<- C.ConnContext) (*Listener, error) {
|
func New(addr string, in chan<- C.ConnContext) (*Listener, error) {
|
||||||
l, err := net.Listen("tcp", addr)
|
l, err := net.Listen("tcp", addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -33,7 +55,7 @@ func New(addr string, in chan<- C.ConnContext) (*Listener, error) {
|
|||||||
|
|
||||||
rl := &Listener{
|
rl := &Listener{
|
||||||
listener: l,
|
listener: l,
|
||||||
address: addr,
|
addr: addr,
|
||||||
}
|
}
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
@ -51,18 +73,3 @@ func New(addr string, in chan<- C.ConnContext) (*Listener, error) {
|
|||||||
|
|
||||||
return rl, nil
|
return rl, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *Listener) Close() {
|
|
||||||
l.closed = true
|
|
||||||
l.listener.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *Listener) Address() string {
|
|
||||||
return l.address
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *Listener) handleTProxy(conn net.Conn, in chan<- C.ConnContext) {
|
|
||||||
target := socks5.ParseAddrToSocksAddr(conn.LocalAddr())
|
|
||||||
conn.(*net.TCPConn).SetKeepAlive(true)
|
|
||||||
in <- inbound.NewSocket(target, conn, C.TPROXY)
|
|
||||||
}
|
|
||||||
|
@ -11,17 +11,36 @@ import (
|
|||||||
|
|
||||||
type UDPListener struct {
|
type UDPListener struct {
|
||||||
packetConn net.PacketConn
|
packetConn net.PacketConn
|
||||||
address string
|
addr string
|
||||||
closed bool
|
closed bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RawAddress implements C.Listener
|
||||||
|
func (l *UDPListener) RawAddress() string {
|
||||||
|
return l.addr
|
||||||
|
}
|
||||||
|
|
||||||
|
// Address implements C.Listener
|
||||||
|
func (l *UDPListener) Address() string {
|
||||||
|
return l.packetConn.LocalAddr().String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close implements C.Listener
|
||||||
|
func (l *UDPListener) Close() error {
|
||||||
|
l.closed = true
|
||||||
|
return l.packetConn.Close()
|
||||||
|
}
|
||||||
|
|
||||||
func NewUDP(addr string, in chan<- *inbound.PacketAdapter) (*UDPListener, error) {
|
func NewUDP(addr string, in chan<- *inbound.PacketAdapter) (*UDPListener, error) {
|
||||||
l, err := net.ListenPacket("udp", addr)
|
l, err := net.ListenPacket("udp", addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
rl := &UDPListener{l, addr, false}
|
rl := &UDPListener{
|
||||||
|
packetConn: l,
|
||||||
|
addr: addr,
|
||||||
|
}
|
||||||
|
|
||||||
c := l.(*net.UDPConn)
|
c := l.(*net.UDPConn)
|
||||||
|
|
||||||
@ -59,15 +78,6 @@ func NewUDP(addr string, in chan<- *inbound.PacketAdapter) (*UDPListener, error)
|
|||||||
return rl, nil
|
return rl, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *UDPListener) Close() error {
|
|
||||||
l.closed = true
|
|
||||||
return l.packetConn.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *UDPListener) Address() string {
|
|
||||||
return l.address
|
|
||||||
}
|
|
||||||
|
|
||||||
func handlePacketConn(pc net.PacketConn, in chan<- *inbound.PacketAdapter, buf []byte, lAddr *net.UDPAddr, rAddr *net.UDPAddr) {
|
func handlePacketConn(pc net.PacketConn, in chan<- *inbound.PacketAdapter, buf []byte, lAddr *net.UDPAddr, rAddr *net.UDPAddr) {
|
||||||
target := socks5.ParseAddrToSocksAddr(rAddr)
|
target := socks5.ParseAddrToSocksAddr(rAddr)
|
||||||
pkt := &packet{
|
pkt := &packet{
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
//go:build linux
|
||||||
// +build linux
|
// +build linux
|
||||||
|
|
||||||
package tproxy
|
package tproxy
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
//go:build !linux
|
||||||
// +build !linux
|
// +build !linux
|
||||||
|
|
||||||
package tproxy
|
package tproxy
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
package rules
|
package rules
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/component/mmdb"
|
"github.com/Dreamacro/clash/component/mmdb"
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
)
|
)
|
||||||
@ -20,8 +22,12 @@ func (g *GEOIP) Match(metadata *C.Metadata) bool {
|
|||||||
if ip == nil {
|
if ip == nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if strings.EqualFold(g.country, "LAN") {
|
||||||
|
return ip.IsPrivate()
|
||||||
|
}
|
||||||
record, _ := mmdb.Instance().Country(ip)
|
record, _ := mmdb.Instance().Country(ip)
|
||||||
return record.Country.IsoCode == g.country
|
return strings.EqualFold(record.Country.IsoCode, g.country)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *GEOIP) Adapter() string {
|
func (g *GEOIP) Adapter() string {
|
||||||
|
@ -47,3 +47,13 @@ Prerequisite
|
|||||||
```
|
```
|
||||||
$ go test -p 1 -v
|
$ go test -p 1 -v
|
||||||
```
|
```
|
||||||
|
|
||||||
|
benchmark (Linux)
|
||||||
|
|
||||||
|
> Cannot represent the throughput of the protocol on your machine
|
||||||
|
> but you can compare the corresponding throughput of the protocol on clash
|
||||||
|
> (change chunkSize to measure the maximum throughput of clash on your machine)
|
||||||
|
|
||||||
|
```
|
||||||
|
$ go test -benchmem -run=^$ -bench .
|
||||||
|
```
|
||||||
|
@ -15,6 +15,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/Dreamacro/clash/adapter/outbound"
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
"github.com/Dreamacro/clash/hub/executor"
|
"github.com/Dreamacro/clash/hub/executor"
|
||||||
"github.com/Dreamacro/clash/transport/socks5"
|
"github.com/Dreamacro/clash/transport/socks5"
|
||||||
@ -26,11 +27,12 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
ImageShadowsocks = "mritd/shadowsocks:latest"
|
ImageShadowsocks = "mritd/shadowsocks:latest"
|
||||||
ImageVmess = "v2fly/v2fly-core:latest"
|
ImageShadowsocksRust = "ghcr.io/shadowsocks/ssserver-rust:latest"
|
||||||
ImageTrojan = "trojangfw/trojan:latest"
|
ImageVmess = "v2fly/v2fly-core:latest"
|
||||||
ImageSnell = "icpz/snell-server:latest"
|
ImageTrojan = "trojangfw/trojan:latest"
|
||||||
ImageXray = "teddysun/xray:latest"
|
ImageSnell = "icpz/snell-server:latest"
|
||||||
|
ImageXray = "teddysun/xray:latest"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -70,7 +72,7 @@ func init() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
c, err := client.NewClientWithOpts(client.FromEnv)
|
c, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
@ -208,10 +210,11 @@ func newLargeDataPair() (chan hashPair, chan hashPair, func(t *testing.T) error)
|
|||||||
func testPingPongWithSocksPort(t *testing.T, port int) {
|
func testPingPongWithSocksPort(t *testing.T, port int) {
|
||||||
pingCh, pongCh, test := newPingPongPair()
|
pingCh, pongCh, test := newPingPongPair()
|
||||||
go func() {
|
go func() {
|
||||||
l, err := net.Listen("tcp", ":10001")
|
l, err := Listen("tcp", ":10001")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
assert.FailNow(t, err.Error())
|
assert.FailNow(t, err.Error())
|
||||||
}
|
}
|
||||||
|
defer l.Close()
|
||||||
|
|
||||||
c, err := l.Accept()
|
c, err := l.Accept()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -227,7 +230,6 @@ func testPingPongWithSocksPort(t *testing.T, port int) {
|
|||||||
if _, err := c.Write([]byte("pong")); err != nil {
|
if _, err := c.Write([]byte("pong")); err != nil {
|
||||||
assert.FailNow(t, err.Error())
|
assert.FailNow(t, err.Error())
|
||||||
}
|
}
|
||||||
l.Close()
|
|
||||||
}()
|
}()
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
@ -235,6 +237,7 @@ func testPingPongWithSocksPort(t *testing.T, port int) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
assert.FailNow(t, err.Error())
|
assert.FailNow(t, err.Error())
|
||||||
}
|
}
|
||||||
|
defer c.Close()
|
||||||
|
|
||||||
if _, err := socks5.ClientHandshake(c, socks5.ParseAddr("127.0.0.1:10001"), socks5.CmdConnect, nil); err != nil {
|
if _, err := socks5.ClientHandshake(c, socks5.ParseAddr("127.0.0.1:10001"), socks5.CmdConnect, nil); err != nil {
|
||||||
assert.FailNow(t, err.Error())
|
assert.FailNow(t, err.Error())
|
||||||
@ -250,14 +253,13 @@ func testPingPongWithSocksPort(t *testing.T, port int) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pongCh <- buf
|
pongCh <- buf
|
||||||
c.Close()
|
|
||||||
}()
|
}()
|
||||||
|
|
||||||
test(t)
|
test(t)
|
||||||
}
|
}
|
||||||
|
|
||||||
func testPingPongWithConn(t *testing.T, c net.Conn) error {
|
func testPingPongWithConn(t *testing.T, c net.Conn) error {
|
||||||
l, err := net.Listen("tcp", ":10001")
|
l, err := Listen("tcp", ":10001")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -298,7 +300,7 @@ func testPingPongWithConn(t *testing.T, c net.Conn) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func testPingPongWithPacketConn(t *testing.T, pc net.PacketConn) error {
|
func testPingPongWithPacketConn(t *testing.T, pc net.PacketConn) error {
|
||||||
l, err := net.ListenPacket("udp", ":10001")
|
l, err := ListenPacket("udp", ":10001")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -343,7 +345,7 @@ type hashPair struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func testLargeDataWithConn(t *testing.T, c net.Conn) error {
|
func testLargeDataWithConn(t *testing.T, c net.Conn) error {
|
||||||
l, err := net.Listen("tcp", ":10001")
|
l, err := Listen("tcp", ":10001")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -378,6 +380,7 @@ func testLargeDataWithConn(t *testing.T, c net.Conn) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
defer c.Close()
|
||||||
|
|
||||||
hashMap := map[int][]byte{}
|
hashMap := map[int][]byte{}
|
||||||
buf := make([]byte, chunkSize)
|
buf := make([]byte, chunkSize)
|
||||||
@ -436,7 +439,7 @@ func testLargeDataWithConn(t *testing.T, c net.Conn) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func testLargeDataWithPacketConn(t *testing.T, pc net.PacketConn) error {
|
func testLargeDataWithPacketConn(t *testing.T, pc net.PacketConn) error {
|
||||||
l, err := net.ListenPacket("udp", ":10001")
|
l, err := ListenPacket("udp", ":10001")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -619,6 +622,44 @@ func testSuit(t *testing.T, proxy C.ProxyAdapter) {
|
|||||||
assert.NoError(t, testPacketConnTimeout(t, pc))
|
assert.NoError(t, testPacketConnTimeout(t, pc))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func benchmarkProxy(b *testing.B, proxy C.ProxyAdapter) {
|
||||||
|
l, err := Listen("tcp", ":10001")
|
||||||
|
if err != nil {
|
||||||
|
assert.FailNow(b, err.Error())
|
||||||
|
}
|
||||||
|
defer l.Close()
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
c, err := l.Accept()
|
||||||
|
if err != nil {
|
||||||
|
assert.FailNow(b, err.Error())
|
||||||
|
}
|
||||||
|
defer c.Close()
|
||||||
|
|
||||||
|
io.Copy(io.Discard, c)
|
||||||
|
}()
|
||||||
|
|
||||||
|
chunkSize := int64(16 * 1024)
|
||||||
|
chunk := make([]byte, chunkSize)
|
||||||
|
rand.Read(chunk)
|
||||||
|
conn, err := proxy.DialContext(context.Background(), &C.Metadata{
|
||||||
|
Host: localIP.String(),
|
||||||
|
DstPort: "10001",
|
||||||
|
AddrType: socks5.AtypDomainName,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
assert.FailNow(b, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
b.SetBytes(chunkSize)
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
if _, err := conn.Write(chunk); err != nil {
|
||||||
|
assert.FailNow(b, err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestClash_Basic(t *testing.T) {
|
func TestClash_Basic(t *testing.T) {
|
||||||
basic := `
|
basic := `
|
||||||
mixed-port: 10000
|
mixed-port: 10000
|
||||||
@ -633,3 +674,8 @@ log-level: silent
|
|||||||
time.Sleep(waitTime)
|
time.Sleep(waitTime)
|
||||||
testPingPongWithSocksPort(t, 10000)
|
testPingPongWithSocksPort(t, 10000)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Benchmark_Direct(b *testing.B) {
|
||||||
|
proxy := outbound.NewDirect()
|
||||||
|
benchmarkProxy(b, proxy)
|
||||||
|
}
|
||||||
|
@ -24,7 +24,7 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"grpcSettings": {
|
"grpcSettings": {
|
||||||
"serviceName": "example"
|
"serviceName": "example!"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
30
test/config/vmess-ws-0rtt.json
Normal file
30
test/config/vmess-ws-0rtt.json
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
{
|
||||||
|
"inbounds": [
|
||||||
|
{
|
||||||
|
"port": 10002,
|
||||||
|
"listen": "0.0.0.0",
|
||||||
|
"protocol": "vmess",
|
||||||
|
"settings": {
|
||||||
|
"clients": [
|
||||||
|
{
|
||||||
|
"id": "b831381d-6324-4d53-ad4f-8cda48b30811",
|
||||||
|
"alterId": 32
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"streamSettings": {
|
||||||
|
"network": "ws",
|
||||||
|
"security": "none",
|
||||||
|
"wsSettings": {
|
||||||
|
"maxEarlyData": 128,
|
||||||
|
"earlyDataHeaderName": "Sec-WebSocket-Protocol"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"outbounds": [
|
||||||
|
{
|
||||||
|
"protocol": "freedom"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
@ -11,7 +11,7 @@ import (
|
|||||||
var isDarwin = false
|
var isDarwin = false
|
||||||
|
|
||||||
func startContainer(cfg *container.Config, hostCfg *container.HostConfig, name string) (string, error) {
|
func startContainer(cfg *container.Config, hostCfg *container.HostConfig, name string) (string, error) {
|
||||||
c, err := client.NewClientWithOpts(client.FromEnv)
|
c, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
@ -34,7 +34,7 @@ func startContainer(cfg *container.Config, hostCfg *container.HostConfig, name s
|
|||||||
}
|
}
|
||||||
|
|
||||||
func cleanContainer(id string) error {
|
func cleanContainer(id string) error {
|
||||||
c, err := client.NewClientWithOpts(client.FromEnv)
|
c, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
52
test/go.mod
52
test/go.mod
@ -1,26 +1,50 @@
|
|||||||
module clash-test
|
module clash-test
|
||||||
|
|
||||||
go 1.16
|
go 1.17
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/Dreamacro/clash v1.6.1-0.20210516120541-06fdd3abe0ab
|
github.com/Dreamacro/clash v1.6.6-0.20210905062555-c7b718f6512d
|
||||||
github.com/Microsoft/go-winio v0.4.16 // indirect
|
github.com/docker/docker v20.10.8+incompatible
|
||||||
github.com/containerd/containerd v1.4.4 // indirect
|
|
||||||
github.com/docker/distribution v2.7.1+incompatible // indirect
|
|
||||||
github.com/docker/docker v20.10.6+incompatible
|
|
||||||
github.com/docker/go-connections v0.4.0
|
github.com/docker/go-connections v0.4.0
|
||||||
|
github.com/miekg/dns v1.1.43
|
||||||
|
github.com/stretchr/testify v1.7.0
|
||||||
|
golang.org/x/net v0.0.0-20210903162142-ad29c8ab022f
|
||||||
|
)
|
||||||
|
|
||||||
|
replace github.com/Dreamacro/clash => ../
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/Dreamacro/go-shadowsocks2 v0.1.7 // indirect
|
||||||
|
github.com/Microsoft/go-winio v0.5.0 // indirect
|
||||||
|
github.com/containerd/containerd v1.5.5 // indirect
|
||||||
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
|
github.com/docker/distribution v2.7.1+incompatible // indirect
|
||||||
github.com/docker/go-units v0.4.0 // indirect
|
github.com/docker/go-units v0.4.0 // indirect
|
||||||
|
github.com/gofrs/uuid v4.0.0+incompatible // indirect
|
||||||
github.com/gogo/protobuf v1.3.2 // indirect
|
github.com/gogo/protobuf v1.3.2 // indirect
|
||||||
github.com/google/go-cmp v0.5.5 // indirect
|
github.com/golang/protobuf v1.5.0 // indirect
|
||||||
github.com/gorilla/mux v1.8.0 // indirect
|
github.com/gorilla/mux v1.8.0 // indirect
|
||||||
github.com/miekg/dns v1.1.42
|
github.com/gorilla/websocket v1.4.2 // indirect
|
||||||
github.com/moby/term v0.0.0-20201216013528-df9cb8a40635 // indirect
|
github.com/insomniacslk/dhcp v0.0.0-20210827173440-b95caade3eac // indirect
|
||||||
|
github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6 // indirect
|
||||||
github.com/morikuni/aec v1.0.0 // indirect
|
github.com/morikuni/aec v1.0.0 // indirect
|
||||||
github.com/opencontainers/go-digest v1.0.0 // indirect
|
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||||
github.com/opencontainers/image-spec v1.0.1 // indirect
|
github.com/opencontainers/image-spec v1.0.1 // indirect
|
||||||
github.com/stretchr/testify v1.7.0
|
github.com/oschwald/geoip2-golang v1.5.0 // indirect
|
||||||
golang.org/x/net v0.0.0-20210510120150-4163338589ed
|
github.com/oschwald/maxminddb-golang v1.8.0 // indirect
|
||||||
golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba // indirect
|
github.com/pkg/errors v0.9.1 // indirect
|
||||||
google.golang.org/grpc v1.36.0 // indirect
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
gotest.tools/v3 v3.0.3 // indirect
|
github.com/sirupsen/logrus v1.8.1 // indirect
|
||||||
|
github.com/u-root/uio v0.0.0-20210528114334-82958018845c // indirect
|
||||||
|
go.uber.org/atomic v1.9.0 // indirect
|
||||||
|
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 // indirect
|
||||||
|
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect
|
||||||
|
golang.org/x/sys v0.0.0-20210906170528-6f6e22806c34 // indirect
|
||||||
|
golang.org/x/text v0.3.6 // indirect
|
||||||
|
golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac // indirect
|
||||||
|
google.golang.org/genproto v0.0.0-20201110150050-8816d57aaa9a // indirect
|
||||||
|
google.golang.org/grpc v1.40.0 // indirect
|
||||||
|
google.golang.org/protobuf v1.26.0 // indirect
|
||||||
|
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect
|
||||||
)
|
)
|
||||||
|
883
test/go.sum
883
test/go.sum
File diff suppressed because it is too large
Load Diff
@ -119,3 +119,40 @@ func TestClash_Snell(t *testing.T) {
|
|||||||
time.Sleep(waitTime)
|
time.Sleep(waitTime)
|
||||||
testSuit(t, proxy)
|
testSuit(t, proxy)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Benchmark_Snell(b *testing.B) {
|
||||||
|
cfg := &container.Config{
|
||||||
|
Image: ImageSnell,
|
||||||
|
ExposedPorts: defaultExposedPorts,
|
||||||
|
Cmd: []string{"-c", "/config.conf"},
|
||||||
|
}
|
||||||
|
hostCfg := &container.HostConfig{
|
||||||
|
PortBindings: defaultPortBindings,
|
||||||
|
Binds: []string{fmt.Sprintf("%s:/config.conf", C.Path.Resolve("snell-http.conf"))},
|
||||||
|
}
|
||||||
|
|
||||||
|
id, err := startContainer(cfg, hostCfg, "snell-http")
|
||||||
|
if err != nil {
|
||||||
|
assert.FailNow(b, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
b.Cleanup(func() {
|
||||||
|
cleanContainer(id)
|
||||||
|
})
|
||||||
|
|
||||||
|
proxy, err := outbound.NewSnell(outbound.SnellOption{
|
||||||
|
Name: "snell",
|
||||||
|
Server: localIP.String(),
|
||||||
|
Port: 10002,
|
||||||
|
Psk: "password",
|
||||||
|
ObfsOpts: map[string]interface{}{
|
||||||
|
"mode": "http",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
assert.FailNow(b, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
time.Sleep(waitTime)
|
||||||
|
benchmarkProxy(b, proxy)
|
||||||
|
}
|
||||||
|
@ -12,8 +12,9 @@ import (
|
|||||||
|
|
||||||
func TestClash_Shadowsocks(t *testing.T) {
|
func TestClash_Shadowsocks(t *testing.T) {
|
||||||
cfg := &container.Config{
|
cfg := &container.Config{
|
||||||
Image: ImageShadowsocks,
|
Image: ImageShadowsocksRust,
|
||||||
Env: []string{"SS_MODULE=ss-server", "SS_CONFIG=-s 0.0.0.0 -u -v -p 10002 -m chacha20-ietf-poly1305 -k FzcLbKs2dY9mhL"},
|
Entrypoint: []string{"ssserver"},
|
||||||
|
Cmd: []string{"-s", "0.0.0.0:10002", "-m", "chacha20-ietf-poly1305", "-k", "FzcLbKs2dY9mhL", "-U"},
|
||||||
ExposedPorts: defaultExposedPorts,
|
ExposedPorts: defaultExposedPorts,
|
||||||
}
|
}
|
||||||
hostCfg := &container.HostConfig{
|
hostCfg := &container.HostConfig{
|
||||||
@ -170,3 +171,39 @@ func TestClash_ShadowsocksV2RayPlugin(t *testing.T) {
|
|||||||
time.Sleep(waitTime)
|
time.Sleep(waitTime)
|
||||||
testSuit(t, proxy)
|
testSuit(t, proxy)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Benchmark_Shadowsocks(b *testing.B) {
|
||||||
|
cfg := &container.Config{
|
||||||
|
Image: ImageShadowsocksRust,
|
||||||
|
Entrypoint: []string{"ssserver"},
|
||||||
|
Cmd: []string{"-s", "0.0.0.0:10002", "-m", "aes-256-gcm", "-k", "FzcLbKs2dY9mhL", "-U"},
|
||||||
|
ExposedPorts: defaultExposedPorts,
|
||||||
|
}
|
||||||
|
hostCfg := &container.HostConfig{
|
||||||
|
PortBindings: defaultPortBindings,
|
||||||
|
}
|
||||||
|
|
||||||
|
id, err := startContainer(cfg, hostCfg, "ss")
|
||||||
|
if err != nil {
|
||||||
|
assert.FailNow(b, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
b.Cleanup(func() {
|
||||||
|
cleanContainer(id)
|
||||||
|
})
|
||||||
|
|
||||||
|
proxy, err := outbound.NewShadowSocks(outbound.ShadowSocksOption{
|
||||||
|
Name: "ss",
|
||||||
|
Server: localIP.String(),
|
||||||
|
Port: 10002,
|
||||||
|
Password: "FzcLbKs2dY9mhL",
|
||||||
|
Cipher: "aes-256-gcm",
|
||||||
|
UDP: true,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
assert.FailNow(b, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
time.Sleep(waitTime)
|
||||||
|
benchmarkProxy(b, proxy)
|
||||||
|
}
|
||||||
|
@ -92,3 +92,43 @@ func TestClash_TrojanGrpc(t *testing.T) {
|
|||||||
time.Sleep(waitTime)
|
time.Sleep(waitTime)
|
||||||
testSuit(t, proxy)
|
testSuit(t, proxy)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Benchmark_Trojan(b *testing.B) {
|
||||||
|
cfg := &container.Config{
|
||||||
|
Image: ImageTrojan,
|
||||||
|
ExposedPorts: defaultExposedPorts,
|
||||||
|
}
|
||||||
|
hostCfg := &container.HostConfig{
|
||||||
|
PortBindings: defaultPortBindings,
|
||||||
|
Binds: []string{
|
||||||
|
fmt.Sprintf("%s:/config/config.json", C.Path.Resolve("trojan.json")),
|
||||||
|
fmt.Sprintf("%s:/path/to/certificate.crt", C.Path.Resolve("example.org.pem")),
|
||||||
|
fmt.Sprintf("%s:/path/to/private.key", C.Path.Resolve("example.org-key.pem")),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
id, err := startContainer(cfg, hostCfg, "trojan")
|
||||||
|
if err != nil {
|
||||||
|
assert.FailNow(b, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
b.Cleanup(func() {
|
||||||
|
cleanContainer(id)
|
||||||
|
})
|
||||||
|
|
||||||
|
proxy, err := outbound.NewTrojan(outbound.TrojanOption{
|
||||||
|
Name: "trojan",
|
||||||
|
Server: localIP.String(),
|
||||||
|
Port: 10002,
|
||||||
|
Password: "password",
|
||||||
|
SNI: "example.org",
|
||||||
|
SkipCertVerify: true,
|
||||||
|
UDP: true,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
assert.FailNow(b, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
time.Sleep(waitTime)
|
||||||
|
benchmarkProxy(b, proxy)
|
||||||
|
}
|
||||||
|
37
test/util.go
Normal file
37
test/util.go
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Listen(network, address string) (net.Listener, error) {
|
||||||
|
lc := net.ListenConfig{}
|
||||||
|
|
||||||
|
var lastErr error
|
||||||
|
for i := 0; i < 5; i++ {
|
||||||
|
l, err := lc.Listen(context.Background(), network, address)
|
||||||
|
if err == nil {
|
||||||
|
return l, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
lastErr = err
|
||||||
|
time.Sleep(time.Millisecond * 200)
|
||||||
|
}
|
||||||
|
return nil, lastErr
|
||||||
|
}
|
||||||
|
|
||||||
|
func ListenPacket(network, address string) (net.PacketConn, error) {
|
||||||
|
var lastErr error
|
||||||
|
for i := 0; i < 5; i++ {
|
||||||
|
l, err := net.ListenPacket(network, address)
|
||||||
|
if err == nil {
|
||||||
|
return l, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
lastErr = err
|
||||||
|
time.Sleep(time.Millisecond * 200)
|
||||||
|
}
|
||||||
|
return nil, lastErr
|
||||||
|
}
|
13
test/util_other_test.go
Normal file
13
test/util_other_test.go
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
//go:build !darwin
|
||||||
|
// +build !darwin
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"net"
|
||||||
|
)
|
||||||
|
|
||||||
|
func defaultRouteIP() (net.IP, error) {
|
||||||
|
return nil, errors.New("not supported")
|
||||||
|
}
|
@ -335,7 +335,7 @@ func TestClash_VmessGrpc(t *testing.T) {
|
|||||||
UDP: true,
|
UDP: true,
|
||||||
ServerName: "example.org",
|
ServerName: "example.org",
|
||||||
GrpcOpts: outbound.GrpcOptions{
|
GrpcOpts: outbound.GrpcOptions{
|
||||||
GrpcServiceName: "example",
|
GrpcServiceName: "example!",
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -345,3 +345,122 @@ func TestClash_VmessGrpc(t *testing.T) {
|
|||||||
time.Sleep(waitTime)
|
time.Sleep(waitTime)
|
||||||
testSuit(t, proxy)
|
testSuit(t, proxy)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestClash_VmessWebsocket0RTT(t *testing.T) {
|
||||||
|
cfg := &container.Config{
|
||||||
|
Image: ImageVmess,
|
||||||
|
ExposedPorts: defaultExposedPorts,
|
||||||
|
}
|
||||||
|
hostCfg := &container.HostConfig{
|
||||||
|
PortBindings: defaultPortBindings,
|
||||||
|
Binds: []string{
|
||||||
|
fmt.Sprintf("%s:/etc/v2ray/config.json", C.Path.Resolve("vmess-ws-0rtt.json")),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
id, err := startContainer(cfg, hostCfg, "vmess-ws-0rtt")
|
||||||
|
if err != nil {
|
||||||
|
assert.FailNow(t, err.Error())
|
||||||
|
}
|
||||||
|
defer cleanContainer(id)
|
||||||
|
|
||||||
|
proxy, err := outbound.NewVmess(outbound.VmessOption{
|
||||||
|
Name: "vmess",
|
||||||
|
Server: localIP.String(),
|
||||||
|
Port: 10002,
|
||||||
|
UUID: "b831381d-6324-4d53-ad4f-8cda48b30811",
|
||||||
|
Cipher: "auto",
|
||||||
|
AlterID: 32,
|
||||||
|
Network: "ws",
|
||||||
|
UDP: true,
|
||||||
|
ServerName: "example.org",
|
||||||
|
WSOpts: outbound.WSOptions{
|
||||||
|
MaxEarlyData: 2048,
|
||||||
|
EarlyDataHeaderName: "Sec-WebSocket-Protocol",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
assert.FailNow(t, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
time.Sleep(waitTime)
|
||||||
|
testSuit(t, proxy)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestClash_VmessWebsocketXray0RTT(t *testing.T) {
|
||||||
|
cfg := &container.Config{
|
||||||
|
Image: ImageXray,
|
||||||
|
ExposedPorts: defaultExposedPorts,
|
||||||
|
}
|
||||||
|
hostCfg := &container.HostConfig{
|
||||||
|
PortBindings: defaultPortBindings,
|
||||||
|
Binds: []string{
|
||||||
|
fmt.Sprintf("%s:/etc/xray/config.json", C.Path.Resolve("vmess-ws-0rtt.json")),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
id, err := startContainer(cfg, hostCfg, "vmess-xray-ws-0rtt")
|
||||||
|
if err != nil {
|
||||||
|
assert.FailNow(t, err.Error())
|
||||||
|
}
|
||||||
|
defer cleanContainer(id)
|
||||||
|
|
||||||
|
proxy, err := outbound.NewVmess(outbound.VmessOption{
|
||||||
|
Name: "vmess",
|
||||||
|
Server: localIP.String(),
|
||||||
|
Port: 10002,
|
||||||
|
UUID: "b831381d-6324-4d53-ad4f-8cda48b30811",
|
||||||
|
Cipher: "auto",
|
||||||
|
AlterID: 32,
|
||||||
|
Network: "ws",
|
||||||
|
UDP: true,
|
||||||
|
ServerName: "example.org",
|
||||||
|
WSOpts: outbound.WSOptions{
|
||||||
|
Path: "/?ed=2048",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
assert.FailNow(t, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
time.Sleep(waitTime)
|
||||||
|
testSuit(t, proxy)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Benchmark_Vmess(b *testing.B) {
|
||||||
|
configPath := C.Path.Resolve("vmess-aead.json")
|
||||||
|
|
||||||
|
cfg := &container.Config{
|
||||||
|
Image: ImageVmess,
|
||||||
|
ExposedPorts: defaultExposedPorts,
|
||||||
|
}
|
||||||
|
hostCfg := &container.HostConfig{
|
||||||
|
PortBindings: defaultPortBindings,
|
||||||
|
Binds: []string{fmt.Sprintf("%s:/etc/v2ray/config.json", configPath)},
|
||||||
|
}
|
||||||
|
|
||||||
|
id, err := startContainer(cfg, hostCfg, "vmess-aead")
|
||||||
|
if err != nil {
|
||||||
|
assert.FailNow(b, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
b.Cleanup(func() {
|
||||||
|
cleanContainer(id)
|
||||||
|
})
|
||||||
|
|
||||||
|
proxy, err := outbound.NewVmess(outbound.VmessOption{
|
||||||
|
Name: "vmess",
|
||||||
|
Server: localIP.String(),
|
||||||
|
Port: 10002,
|
||||||
|
UUID: "b831381d-6324-4d53-ad4f-8cda48b30811",
|
||||||
|
Cipher: "auto",
|
||||||
|
AlterID: 0,
|
||||||
|
UDP: true,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
assert.FailNow(b, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
time.Sleep(waitTime)
|
||||||
|
benchmarkProxy(b, proxy)
|
||||||
|
}
|
||||||
|
@ -211,6 +211,8 @@ func StreamGunWithTransport(transport *http2.Transport, cfg *Config) (net.Conn,
|
|||||||
Scheme: "https",
|
Scheme: "https",
|
||||||
Host: cfg.Host,
|
Host: cfg.Host,
|
||||||
Path: fmt.Sprintf("/%s/Tun", serviceName),
|
Path: fmt.Sprintf("/%s/Tun", serviceName),
|
||||||
|
// for unescape path
|
||||||
|
Opaque: fmt.Sprintf("//%s/%s/Tun", cfg.Host, serviceName),
|
||||||
},
|
},
|
||||||
Proto: "HTTP/2",
|
Proto: "HTTP/2",
|
||||||
ProtoMajor: 2,
|
ProtoMajor: 2,
|
||||||
|
199
transport/socks4/socks4.go
Normal file
199
transport/socks4/socks4.go
Normal file
@ -0,0 +1,199 @@
|
|||||||
|
package socks4
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/Dreamacro/clash/component/auth"
|
||||||
|
)
|
||||||
|
|
||||||
|
const Version = 0x04
|
||||||
|
|
||||||
|
type Command = uint8
|
||||||
|
|
||||||
|
const (
|
||||||
|
CmdConnect Command = 0x01
|
||||||
|
CmdBind Command = 0x02
|
||||||
|
)
|
||||||
|
|
||||||
|
type Code = uint8
|
||||||
|
|
||||||
|
const (
|
||||||
|
RequestGranted Code = 90
|
||||||
|
RequestRejected Code = 91
|
||||||
|
RequestIdentdFailed Code = 92
|
||||||
|
RequestIdentdMismatched Code = 93
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
errVersionMismatched = errors.New("version code mismatched")
|
||||||
|
errCommandNotSupported = errors.New("command not supported")
|
||||||
|
errIPv6NotSupported = errors.New("IPv6 not supported")
|
||||||
|
|
||||||
|
ErrRequestRejected = errors.New("request rejected or failed")
|
||||||
|
ErrRequestIdentdFailed = errors.New("request rejected because SOCKS server cannot connect to identd on the client")
|
||||||
|
ErrRequestIdentdMismatched = errors.New("request rejected because the client program and identd report different user-ids")
|
||||||
|
ErrRequestUnknownCode = errors.New("request failed with unknown code")
|
||||||
|
)
|
||||||
|
|
||||||
|
func ServerHandshake(rw io.ReadWriter, authenticator auth.Authenticator) (addr string, command Command, err error) {
|
||||||
|
var req [8]byte
|
||||||
|
if _, err = io.ReadFull(rw, req[:]); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if req[0] != Version {
|
||||||
|
err = errVersionMismatched
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if command = req[1]; command != CmdConnect {
|
||||||
|
err = errCommandNotSupported
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
dstIP = req[4:8] // [4]byte
|
||||||
|
dstPort = req[2:4] // [2]byte
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
host string
|
||||||
|
port string
|
||||||
|
code uint8
|
||||||
|
userID []byte
|
||||||
|
)
|
||||||
|
if userID, err = readUntilNull(rw); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if isReservedIP(dstIP) {
|
||||||
|
var target []byte
|
||||||
|
if target, err = readUntilNull(rw); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
host = string(target)
|
||||||
|
}
|
||||||
|
|
||||||
|
port = strconv.Itoa(int(binary.BigEndian.Uint16(dstPort)))
|
||||||
|
if host != "" {
|
||||||
|
addr = net.JoinHostPort(host, port)
|
||||||
|
} else {
|
||||||
|
addr = net.JoinHostPort(net.IP(dstIP).String(), port)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SOCKS4 only support USERID auth.
|
||||||
|
if authenticator == nil || authenticator.Verify(string(userID), "") {
|
||||||
|
code = RequestGranted
|
||||||
|
} else {
|
||||||
|
code = RequestIdentdMismatched
|
||||||
|
err = ErrRequestIdentdMismatched
|
||||||
|
}
|
||||||
|
|
||||||
|
var reply [8]byte
|
||||||
|
reply[0] = 0x00 // reply code
|
||||||
|
reply[1] = code // result code
|
||||||
|
copy(reply[4:8], dstIP)
|
||||||
|
copy(reply[2:4], dstPort)
|
||||||
|
|
||||||
|
_, wErr := rw.Write(reply[:])
|
||||||
|
if err == nil {
|
||||||
|
err = wErr
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func ClientHandshake(rw io.ReadWriter, addr string, command Command, userID string) (err error) {
|
||||||
|
host, portStr, err := net.SplitHostPort(addr)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
port, err := strconv.ParseUint(portStr, 10, 16)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
ip := net.ParseIP(host)
|
||||||
|
if ip == nil /* HOST */ {
|
||||||
|
ip = net.IPv4(0, 0, 0, 1).To4()
|
||||||
|
} else if ip.To4() == nil /* IPv6 */ {
|
||||||
|
return errIPv6NotSupported
|
||||||
|
}
|
||||||
|
|
||||||
|
dstIP := ip.To4()
|
||||||
|
|
||||||
|
req := &bytes.Buffer{}
|
||||||
|
req.WriteByte(Version)
|
||||||
|
req.WriteByte(command)
|
||||||
|
binary.Write(req, binary.BigEndian, uint16(port))
|
||||||
|
req.Write(dstIP)
|
||||||
|
req.WriteString(userID)
|
||||||
|
req.WriteByte(0) /* NULL */
|
||||||
|
|
||||||
|
if isReservedIP(dstIP) /* SOCKS4A */ {
|
||||||
|
req.WriteString(host)
|
||||||
|
req.WriteByte(0) /* NULL */
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err = rw.Write(req.Bytes()); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var resp [8]byte
|
||||||
|
if _, err = io.ReadFull(rw, resp[:]); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp[0] != 0x00 {
|
||||||
|
return errVersionMismatched
|
||||||
|
}
|
||||||
|
|
||||||
|
switch resp[1] {
|
||||||
|
case RequestGranted:
|
||||||
|
return nil
|
||||||
|
case RequestRejected:
|
||||||
|
return ErrRequestRejected
|
||||||
|
case RequestIdentdFailed:
|
||||||
|
return ErrRequestIdentdFailed
|
||||||
|
case RequestIdentdMismatched:
|
||||||
|
return ErrRequestIdentdMismatched
|
||||||
|
default:
|
||||||
|
return ErrRequestUnknownCode
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// For version 4A, if the client cannot resolve the destination host's
|
||||||
|
// domain name to find its IP address, it should set the first three bytes
|
||||||
|
// of DSTIP to NULL and the last byte to a non-zero value. (This corresponds
|
||||||
|
// to IP address 0.0.0.x, with x nonzero. As decreed by IANA -- The
|
||||||
|
// Internet Assigned Numbers Authority -- such an address is inadmissible
|
||||||
|
// as a destination IP address and thus should never occur if the client
|
||||||
|
// can resolve the domain name.)
|
||||||
|
func isReservedIP(ip net.IP) bool {
|
||||||
|
subnet := net.IPNet{
|
||||||
|
IP: net.IPv4zero,
|
||||||
|
Mask: net.IPv4Mask(0xff, 0xff, 0xff, 0x00),
|
||||||
|
}
|
||||||
|
|
||||||
|
return !ip.IsUnspecified() && subnet.Contains(ip)
|
||||||
|
}
|
||||||
|
|
||||||
|
func readUntilNull(r io.Reader) ([]byte, error) {
|
||||||
|
var buf = &bytes.Buffer{}
|
||||||
|
var data [1]byte
|
||||||
|
|
||||||
|
for {
|
||||||
|
if _, err := r.Read(data[:]); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if data[0] == 0 {
|
||||||
|
return buf.Bytes(), nil
|
||||||
|
}
|
||||||
|
buf.WriteByte(data[0])
|
||||||
|
}
|
||||||
|
}
|
@ -152,7 +152,7 @@ func (a *authAES128) Encode(buf *bytes.Buffer, b []byte) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a *authAES128) DecodePacket(b []byte) ([]byte, error) {
|
func (a *authAES128) DecodePacket(b []byte) ([]byte, error) {
|
||||||
if !bytes.Equal(a.hmac(a.Key, b[:len(b)-4])[:4], b[len(b)-4:]) {
|
if !bytes.Equal(a.hmac(a.userKey, b[:len(b)-4])[:4], b[len(b)-4:]) {
|
||||||
return nil, errAuthAES128ChksumError
|
return nil, errAuthAES128ChksumError
|
||||||
}
|
}
|
||||||
return b[:len(b)-4], nil
|
return b[:len(b)-4], nil
|
||||||
|
@ -278,7 +278,7 @@ func getRandStartPos(length int, random *tools.XorShift128Plus) int {
|
|||||||
if length == 0 {
|
if length == 0 {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
return int(random.Next()%8589934609) % length
|
return int(int64(random.Next()%8589934609) % int64(length))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *authChainA) getRandLength(length int, lastHash []byte, random *tools.XorShift128Plus) int {
|
func (a *authChainA) getRandLength(length int, lastHash []byte, random *tools.XorShift128Plus) int {
|
||||||
|
@ -59,12 +59,12 @@ func (vc *Conn) Read(b []byte) (int, error) {
|
|||||||
func (vc *Conn) sendRequest() error {
|
func (vc *Conn) sendRequest() error {
|
||||||
timestamp := time.Now()
|
timestamp := time.Now()
|
||||||
|
|
||||||
|
mbuf := &bytes.Buffer{}
|
||||||
|
|
||||||
if !vc.isAead {
|
if !vc.isAead {
|
||||||
h := hmac.New(md5.New, vc.id.UUID.Bytes())
|
h := hmac.New(md5.New, vc.id.UUID.Bytes())
|
||||||
binary.Write(h, binary.BigEndian, uint64(timestamp.Unix()))
|
binary.Write(h, binary.BigEndian, uint64(timestamp.Unix()))
|
||||||
if _, err := vc.Conn.Write(h.Sum(nil)); err != nil {
|
mbuf.Write(h.Sum(nil))
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
buf := &bytes.Buffer{}
|
buf := &bytes.Buffer{}
|
||||||
@ -110,7 +110,8 @@ func (vc *Conn) sendRequest() error {
|
|||||||
|
|
||||||
stream := cipher.NewCFBEncrypter(block, hashTimestamp(timestamp))
|
stream := cipher.NewCFBEncrypter(block, hashTimestamp(timestamp))
|
||||||
stream.XORKeyStream(buf.Bytes(), buf.Bytes())
|
stream.XORKeyStream(buf.Bytes(), buf.Bytes())
|
||||||
_, err = vc.Conn.Write(buf.Bytes())
|
mbuf.Write(buf.Bytes())
|
||||||
|
_, err = vc.Conn.Write(mbuf.Bytes())
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user