Compare commits
102 Commits
Author | SHA1 | Date | |
---|---|---|---|
5b7f0de48b | |||
a5b950a779 | |||
a2d59d6ef5 | |||
8ef5cdb8be | |||
c7b718f651 | |||
ff56e5c5de | |||
661c417fce | |||
7d20097465 | |||
a20b9a3960 | |||
e0d3f926b7 | |||
121bc910f6 | |||
4522cdc551 | |||
410772e81c | |||
0267b2efad | |||
c6d375eda2 | |||
847f41952e | |||
47044ec0d8 | |||
426ca36118 | |||
571d2a0075 | |||
1be09f5751 | |||
2663cb2e6e | |||
9b0bbb90ff | |||
588645a2c3 | |||
1bfebd0d03 | |||
3705996974 | |||
09697b7679 | |||
4578b2c826 | |||
b3a293ab07 | |||
507ba16065 | |||
aa9f8a39a3 | |||
8d37220566 | |||
53e17a916b | |||
247dd84970 | |||
c2f3111922 | |||
44872300e9 | |||
91ed0118f6 | |||
a461c2306a | |||
46f4f84442 | |||
250a9f4f84 | |||
b4292d0972 | |||
d755383e39 | |||
dff1e8f1ce | |||
995aa7a8fc | |||
3ca5d17c40 | |||
244cb370a4 | |||
c35cb24bda | |||
b6ff08074c | |||
70d53fd45a | |||
f231a63e93 | |||
6091fcdfec | |||
bcfc15e398 | |||
045edc188c | |||
0778591524 | |||
d5e52bed43 | |||
06fdd3abe0 | |||
4e5898197a | |||
f96ebab99f | |||
3c54f99fea | |||
824f5bd731 | |||
3f3db8476e | |||
f375f080da | |||
e19e9ef5a4 | |||
682e65cb54 | |||
16a6d409d9 | |||
4186bcf1b2 | |||
df5112175f | |||
d9341a49ea | |||
4e9e4b6cde | |||
936b7012ba | |||
a9cbd9ec98 | |||
c9943fb857 | |||
a40274e2a2 | |||
b59d45c660 | |||
7b01e103c2 | |||
93a8acecce | |||
586bb91c0c | |||
baf03b81e3 | |||
9807e1189c | |||
3d5a0d9f73 | |||
cc96187f58 | |||
3aefa1d924 | |||
42e21b3733 | |||
0a35237915 | |||
a1f3a5ea26 | |||
e63f995258 | |||
d0c829c578 | |||
4ad9761b32 | |||
1f593d37fb | |||
109bfcb0f9 | |||
7ee49f5171 | |||
d759d16944 | |||
807d53c1e7 | |||
1355196b7c | |||
573316bcde | |||
784c28266c | |||
5da1b2a8aa | |||
0976d27cb1 | |||
6c83ff3496 | |||
f7f97ef625 | |||
5acdd72a1d | |||
f53686103d | |||
f63c9eb22f |
100
.github/ISSUE_TEMPLATE/bug_report.md
vendored
100
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@ -1,100 +0,0 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: "[Bug]"
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
<!--
|
||||
感谢你向 Clash Core 提交 issue!
|
||||
在提交之前,请确认:
|
||||
|
||||
- [ ] 如果你可以自己 debug 并解决的话,提交 PR 吧!
|
||||
- [ ] 我已经在 [Issue Tracker](……/) 中找过我要提出的问题
|
||||
- [ ] 我已经使用 dev 分支版本测试过,问题依旧存在
|
||||
- [ ] 我已经仔细看过 [Documentation](https://github.com/Dreamacro/clash/wiki/) 并无法自行解决问题
|
||||
- [ ] 这是 Clash 核心的问题,并非我所使用的 Clash 衍生版本(如 OpenClash、KoolClash 等)的特定问题
|
||||
|
||||
请注意,如果你并没有遵照这个 issue template 填写内容,我们将直接关闭这个 issue。
|
||||
|
||||
Thanks for opening an issue towards the Clash core!
|
||||
But before so, please do the following checklist:
|
||||
|
||||
- [ ] Is this something you can **debug and fix**? Send a pull request! Bug fixes and documentation fixes are welcome.
|
||||
- [ ] I have searched on the [issue tracker](……/) for a related issue.
|
||||
- [ ] I have tested using the dev branch, and the issue still exists.
|
||||
- [ ] I have read the [documentation](https://github.com/Dreamacro/clash/wiki/) and was unable to solve the issue
|
||||
- [ ] This is an issue of the Clash core *per se*, not to the derivatives of Clash, like OpenClash or KoolClash
|
||||
|
||||
Please understand that we close issues that fail to follow this issue template.
|
||||
-->
|
||||
|
||||
------------------------------------------------------------------
|
||||
|
||||
<!--
|
||||
请附上任何可以帮助我们解决这个问题的信息,如果我们收到的信息不足,我们将对这个 issue 加上 *Needs more information* 标记并在收到更多资讯之前关闭 issue。
|
||||
Make sure to add **all the information needed to understand the bug** so that someone can help. If the info is missing we'll add the 'Needs more information' label and close the issue until there is enough information.
|
||||
-->
|
||||
|
||||
### Clash config
|
||||
<!--
|
||||
在下方附上 Clash core 脱敏后配置文件的内容
|
||||
Paste the Clash core configuration below.
|
||||
-->
|
||||
<details>
|
||||
<summary>config.yaml</summary>
|
||||
|
||||
```yaml
|
||||
……
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
### Clash log
|
||||
<!--
|
||||
在下方附上 Clash Core 的日志,log level 使用 DEBUG
|
||||
Paste the Clash core log below with the log level set to `DEBUG`.
|
||||
-->
|
||||
```
|
||||
……
|
||||
```
|
||||
|
||||
### 环境 Environment
|
||||
|
||||
* 操作系统 (the OS that the Clash core is running on)
|
||||
……
|
||||
* 网路环境或拓扑 (network conditions/topology)
|
||||
……
|
||||
* iptables,如果适用 (if applicable)
|
||||
……
|
||||
* ISP 有没有进行 DNS 污染 (is your ISP performing DNS pollution?)
|
||||
……
|
||||
* 其他 (any other information that would be useful)
|
||||
……
|
||||
|
||||
### 说明 Description
|
||||
|
||||
<!--
|
||||
请详细、清晰地表达你要提出的论述,例如这个问题如何影响到你?你想实现什么功能?
|
||||
-->
|
||||
|
||||
### 重现问题的具体布骤 Steps to Reproduce
|
||||
|
||||
1. [First Step]
|
||||
2. [Second Step]
|
||||
3. ……
|
||||
|
||||
**我预期会发生……?**
|
||||
<!-- **Expected behavior:** [What you expected to happen] -->
|
||||
|
||||
**实际上发生了什么?**
|
||||
<!-- **Actual behavior:** [What actually happened] -->
|
||||
|
||||
### 可能的解决方案 Possible Solution
|
||||
<!-- 此项非必须,但是如果你有想法的话欢迎提出。 -->
|
||||
<!-- Not obligatory, but suggest a fix/reason for the bug, -->
|
||||
<!-- or ideas how to implement the addition or change -->
|
||||
|
||||
### 更多信息 More Information
|
76
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
Normal file
76
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
Normal file
@ -0,0 +1,76 @@
|
||||
name: Bug report
|
||||
description: Create a report to help us improve
|
||||
title: "[Bug] "
|
||||
body:
|
||||
- type: checkboxes
|
||||
id: ensure
|
||||
attributes:
|
||||
label: Verify steps
|
||||
description: "
|
||||
在提交之前,请确认
|
||||
Please verify that you've followed these steps
|
||||
"
|
||||
options:
|
||||
- label: "
|
||||
如果你可以自己 debug 并解决的话,提交 PR 吧
|
||||
Is this something you can **debug and fix**? Send a pull request! Bug fixes and documentation fixes are welcome.
|
||||
"
|
||||
required: true
|
||||
- label: "
|
||||
我已经在 [Issue Tracker](……/) 中找过我要提出的问题
|
||||
I have searched on the [issue tracker](……/) for a related issue.
|
||||
"
|
||||
required: true
|
||||
- label: "
|
||||
我已经使用 dev 分支版本测试过,问题依旧存在
|
||||
I have tested using the dev branch, and the issue still exists.
|
||||
"
|
||||
required: true
|
||||
- label: "
|
||||
我已经仔细看过 [Documentation](https://github.com/Dreamacro/clash/wiki/) 并无法自行解决问题
|
||||
I have read the [documentation](https://github.com/Dreamacro/clash/wiki/) and was unable to solve the issue.
|
||||
"
|
||||
required: true
|
||||
- label: "
|
||||
这是 Clash 核心的问题,并非我所使用的 Clash 衍生版本(如 OpenClash、KoolClash 等)的特定问题
|
||||
This is an issue of the Clash core *per se*, not to the derivatives of Clash, like OpenClash or KoolClash.
|
||||
"
|
||||
required: true
|
||||
- type: input
|
||||
attributes:
|
||||
label: Clash version
|
||||
validations:
|
||||
required: true
|
||||
- type: dropdown
|
||||
id: os
|
||||
attributes:
|
||||
label: What OS are you seeing the problem on?
|
||||
multiple: true
|
||||
options:
|
||||
- macOS
|
||||
- Windows
|
||||
- Linux
|
||||
- OpenBSD/FreeBSD
|
||||
- type: textarea
|
||||
attributes:
|
||||
render: yaml
|
||||
label: "Clash config"
|
||||
description: "
|
||||
在下方附上 Clash core 脱敏后配置文件的内容
|
||||
Paste the Clash core configuration below.
|
||||
"
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
render: shell
|
||||
label: Clash log
|
||||
description: "
|
||||
在下方附上 Clash Core 的日志,log level 使用 DEBUG
|
||||
Paste the Clash core log below with the log level set to `DEBUG`.
|
||||
"
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Description
|
||||
validations:
|
||||
required: true
|
6
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
6
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
blank_issues_enabled: false
|
||||
|
||||
contact_links:
|
||||
- name: Get help in GitHub Discussions
|
||||
url: https://github.com/Dreamacro/clash/discussions
|
||||
about: Have a question? Not sure if your issue affects everyone reproducibly? The quickest way to get help is on Clash's GitHub Discussions!
|
78
.github/ISSUE_TEMPLATE/feature_request.md
vendored
78
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@ -1,78 +0,0 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
title: "[Feature]"
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
<!-- The English version is available. -->
|
||||
感谢你向 Clash Core 提交 Feature Request!
|
||||
在提交之前,请确认:
|
||||
|
||||
- [ ] 我已经在 [Issue Tracker](……/) 中找过我要提出的请求
|
||||
|
||||
请注意,如果你并没有遵照这个 issue template 填写内容,我们将直接关闭这个 issue。
|
||||
|
||||
<!--
|
||||
Thanks for submitting a feature request towards the Clash core!
|
||||
But before so, please do the following checklist:
|
||||
|
||||
- [ ] I have searched on the [issue tracker](……/) before creating the issue.
|
||||
|
||||
Please understand that we close issues that fail to follow the issue template.
|
||||
-->
|
||||
|
||||
我都确认过了,我要继续提交。
|
||||
<!-- None of the above, create a feature request -->
|
||||
------------------------------------------------------------------
|
||||
|
||||
请附上任何可以帮助我们解决这个问题的信息,如果我们收到的信息不足,我们将对这个 issue 加上 *Needs more information* 标记并在收到更多资讯之前关闭 issue。
|
||||
<!-- Make sure to add **all the information needed to understand the bug** so that someone can help. If the info is missing we'll add the 'Needs more information' label and close the issue until there is enough information. -->
|
||||
|
||||
### Clash core config
|
||||
<!--
|
||||
在下方附上 Clash Core 脱敏后的配置内容
|
||||
Paste the Clash core configuration below.
|
||||
-->
|
||||
```
|
||||
……
|
||||
```
|
||||
|
||||
### Clash log
|
||||
<!--
|
||||
在下方附上 Clash Core 的日志,log level 请使用 DEBUG
|
||||
Paste the Clash core log below with the log level set to `DEBUG`.
|
||||
-->
|
||||
```
|
||||
……
|
||||
```
|
||||
|
||||
### 环境 Environment
|
||||
|
||||
* Clash Core 的操作系统 (the OS that the Clash core is running on)
|
||||
……
|
||||
* 使用者的操作系统 (the OS running on the client)
|
||||
……
|
||||
* 网路环境或拓扑 (network conditions/topology)
|
||||
……
|
||||
* iptables,如果适用 (if applicable)
|
||||
……
|
||||
* ISP 有没有进行 DNS 污染 (is your ISP performing DNS pollution?)
|
||||
……
|
||||
* 其他
|
||||
……
|
||||
|
||||
### 说明 Description
|
||||
|
||||
<!--
|
||||
请详细、清晰地表达你要提出的论述,例如这个问题如何影响到你?你想实现什么功能?目前 Clash Core 的行为是什麽?
|
||||
-->
|
||||
|
||||
### 可能的解决方案 Possible Solution
|
||||
<!-- 此项非必须,但是如果你有想法的话欢迎提出。 -->
|
||||
<!-- Not obligatory, but suggest a fix/reason for the bug, -->
|
||||
<!-- or ideas how to implement the addition or change -->
|
||||
|
||||
### 更多信息 More Information
|
36
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
Normal file
36
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
Normal file
@ -0,0 +1,36 @@
|
||||
name: Feature request
|
||||
description: Suggest an idea for this project
|
||||
title: "[Feature] "
|
||||
body:
|
||||
- type: checkboxes
|
||||
id: ensure
|
||||
attributes:
|
||||
label: Verify steps
|
||||
description: "
|
||||
在提交之前,请确认
|
||||
Please verify that you've followed these steps
|
||||
"
|
||||
options:
|
||||
- label: "
|
||||
我已经在 [Issue Tracker](……/) 中找过我要提出的请求
|
||||
I have searched on the [issue tracker](……/) for a related feature request.
|
||||
"
|
||||
required: true
|
||||
- label: "
|
||||
我已经仔细看过 [Documentation](https://github.com/Dreamacro/clash/wiki/) 并无法自行解决问题
|
||||
I have read the [documentation](https://github.com/Dreamacro/clash/wiki/) and was unable to solve the issue.
|
||||
"
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Description
|
||||
description: 请详细、清晰地表达你要提出的论述,例如这个问题如何影响到你?你想实现什么功能?目前 Clash Core 的行为是什麽?
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Possible Solution
|
||||
description: "
|
||||
此项非必须,但是如果你有想法的话欢迎提出。
|
||||
Not obligatory, but suggest a fix/reason for the bug, or ideas how to implement the addition or change
|
||||
"
|
30
.github/workflows/codeql-analysis.yml
vendored
Normal file
30
.github/workflows/codeql-analysis.yml
vendored
Normal file
@ -0,0 +1,30 @@
|
||||
name: "CodeQL"
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master, dev ]
|
||||
|
||||
jobs:
|
||||
analyze:
|
||||
name: Analyze
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
language: [ 'go' ]
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v1
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@v1
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v1
|
2
.github/workflows/docker.yml
vendored
2
.github/workflows/docker.yml
vendored
@ -52,7 +52,7 @@ jobs:
|
||||
|
||||
- name: Get all docker tags
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
uses: actions/github-script@v3
|
||||
uses: actions/github-script@v4
|
||||
id: tags
|
||||
with:
|
||||
script: |
|
||||
|
7
.github/workflows/go.yml
vendored
7
.github/workflows/go.yml
vendored
@ -9,7 +9,7 @@ jobs:
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: 1.16
|
||||
go-version: 1.17
|
||||
|
||||
- name: Check out code into the Go module directory
|
||||
uses: actions/checkout@v2
|
||||
@ -26,7 +26,7 @@ jobs:
|
||||
run: |
|
||||
go test ./...
|
||||
go vet ./...
|
||||
go get -u honnef.co/go/tools/cmd/staticcheck
|
||||
go install honnef.co/go/tools/cmd/staticcheck@latest
|
||||
staticcheck -- $(go list ./...)
|
||||
|
||||
- name: Build
|
||||
@ -39,9 +39,6 @@ jobs:
|
||||
- name: Upload Release
|
||||
uses: softprops/action-gh-release@v1
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
files: bin/*
|
||||
draft: true
|
||||
prerelease: true
|
||||
|
3
.github/workflows/stale.yml
vendored
3
.github/workflows/stale.yml
vendored
@ -11,9 +11,8 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/stale@v3
|
||||
- uses: actions/stale@v4
|
||||
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'
|
||||
days-before-stale: 60
|
||||
days-before-close: 5
|
||||
|
10
Makefile
10
Makefile
@ -22,11 +22,13 @@ PLATFORM_LIST = \
|
||||
linux-mips64 \
|
||||
linux-mips64le \
|
||||
freebsd-386 \
|
||||
freebsd-amd64
|
||||
freebsd-amd64 \
|
||||
freebsd-arm64
|
||||
|
||||
WINDOWS_ARCH_LIST = \
|
||||
windows-386 \
|
||||
windows-amd64 \
|
||||
windows-arm64 \
|
||||
windows-arm32v7
|
||||
|
||||
all: linux-amd64 darwin-amd64 windows-amd64 # Most used
|
||||
@ -82,12 +84,18 @@ freebsd-386:
|
||||
freebsd-amd64:
|
||||
GOARCH=amd64 GOOS=freebsd $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
||||
|
||||
freebsd-arm64:
|
||||
GOARCH=arm64 GOOS=freebsd $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
||||
|
||||
windows-386:
|
||||
GOARCH=386 GOOS=windows $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.exe
|
||||
|
||||
windows-amd64:
|
||||
GOARCH=amd64 GOOS=windows $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.exe
|
||||
|
||||
windows-arm64:
|
||||
GOARCH=arm64 GOOS=windows $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.exe
|
||||
|
||||
windows-arm32v7:
|
||||
GOARCH=arm GOOS=windows GOARM=7 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.exe
|
||||
|
||||
|
@ -40,6 +40,9 @@ Documentations are now moved to [GitHub Wiki](https://github.com/Dreamacro/clash
|
||||
## Premium Release
|
||||
[Release](https://github.com/Dreamacro/clash/releases/tag/premium)
|
||||
|
||||
## Development
|
||||
If you want to build an application that uses clash as a library, check out the the [GitHub Wiki](https://github.com/Dreamacro/clash/wiki/use-clash-as-a-library)
|
||||
|
||||
## Credits
|
||||
|
||||
* [riobard/go-shadowsocks2](https://github.com/riobard/go-shadowsocks2)
|
||||
@ -57,4 +60,3 @@ This software is released under the GPL-3.0 license.
|
||||
- [x] Redir proxy
|
||||
- [x] UDP support
|
||||
- [x] Connection manager
|
||||
- [ ] ~~Event API~~
|
||||
|
@ -1,11 +1,12 @@
|
||||
package outbound
|
||||
package adapter
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"github.com/Dreamacro/clash/common/queue"
|
||||
@ -14,101 +15,25 @@ import (
|
||||
"go.uber.org/atomic"
|
||||
)
|
||||
|
||||
type Base struct {
|
||||
name string
|
||||
addr string
|
||||
tp C.AdapterType
|
||||
udp bool
|
||||
}
|
||||
|
||||
func (b *Base) Name() string {
|
||||
return b.name
|
||||
}
|
||||
|
||||
func (b *Base) Type() C.AdapterType {
|
||||
return b.tp
|
||||
}
|
||||
|
||||
func (b *Base) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
||||
return c, errors.New("no support")
|
||||
}
|
||||
|
||||
func (b *Base) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
|
||||
return nil, errors.New("no support")
|
||||
}
|
||||
|
||||
func (b *Base) SupportUDP() bool {
|
||||
return b.udp
|
||||
}
|
||||
|
||||
func (b *Base) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(map[string]string{
|
||||
"type": b.Type().String(),
|
||||
})
|
||||
}
|
||||
|
||||
func (b *Base) Addr() string {
|
||||
return b.addr
|
||||
}
|
||||
|
||||
func (b *Base) Unwrap(metadata *C.Metadata) C.Proxy {
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewBase(name string, addr string, tp C.AdapterType, udp bool) *Base {
|
||||
return &Base{name, addr, tp, udp}
|
||||
}
|
||||
|
||||
type conn struct {
|
||||
net.Conn
|
||||
chain C.Chain
|
||||
}
|
||||
|
||||
func (c *conn) Chains() C.Chain {
|
||||
return c.chain
|
||||
}
|
||||
|
||||
func (c *conn) AppendToChains(a C.ProxyAdapter) {
|
||||
c.chain = append(c.chain, a.Name())
|
||||
}
|
||||
|
||||
func NewConn(c net.Conn, a C.ProxyAdapter) C.Conn {
|
||||
return &conn{c, []string{a.Name()}}
|
||||
}
|
||||
|
||||
type packetConn struct {
|
||||
net.PacketConn
|
||||
chain C.Chain
|
||||
}
|
||||
|
||||
func (c *packetConn) Chains() C.Chain {
|
||||
return c.chain
|
||||
}
|
||||
|
||||
func (c *packetConn) AppendToChains(a C.ProxyAdapter) {
|
||||
c.chain = append(c.chain, a.Name())
|
||||
}
|
||||
|
||||
func newPacketConn(pc net.PacketConn, a C.ProxyAdapter) C.PacketConn {
|
||||
return &packetConn{pc, []string{a.Name()}}
|
||||
}
|
||||
|
||||
type Proxy struct {
|
||||
C.ProxyAdapter
|
||||
history *queue.Queue
|
||||
alive *atomic.Bool
|
||||
}
|
||||
|
||||
// Alive implements C.Proxy
|
||||
func (p *Proxy) Alive() bool {
|
||||
return p.alive.Load()
|
||||
}
|
||||
|
||||
// Dial implements C.Proxy
|
||||
func (p *Proxy) Dial(metadata *C.Metadata) (C.Conn, error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), tcpTimeout)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), C.DefaultTCPTimeout)
|
||||
defer cancel()
|
||||
return p.DialContext(ctx, metadata)
|
||||
}
|
||||
|
||||
// DialContext implements C.ProxyAdapter
|
||||
func (p *Proxy) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
|
||||
conn, err := p.ProxyAdapter.DialContext(ctx, metadata)
|
||||
if err != nil {
|
||||
@ -117,6 +42,7 @@ func (p *Proxy) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn,
|
||||
return conn, err
|
||||
}
|
||||
|
||||
// DelayHistory implements C.Proxy
|
||||
func (p *Proxy) DelayHistory() []C.DelayHistory {
|
||||
queue := p.history.Copy()
|
||||
histories := []C.DelayHistory{}
|
||||
@ -127,6 +53,7 @@ func (p *Proxy) DelayHistory() []C.DelayHistory {
|
||||
}
|
||||
|
||||
// LastDelay return last history record. if proxy is not alive, return the max value of uint16.
|
||||
// implements C.Proxy
|
||||
func (p *Proxy) LastDelay() (delay uint16) {
|
||||
var max uint16 = 0xffff
|
||||
if !p.alive.Load() {
|
||||
@ -144,6 +71,7 @@ func (p *Proxy) LastDelay() (delay uint16) {
|
||||
return history.Delay
|
||||
}
|
||||
|
||||
// MarshalJSON implements C.ProxyAdapter
|
||||
func (p *Proxy) MarshalJSON() ([]byte, error) {
|
||||
inner, err := p.ProxyAdapter.MarshalJSON()
|
||||
if err != nil {
|
||||
@ -154,10 +82,12 @@ func (p *Proxy) MarshalJSON() ([]byte, error) {
|
||||
json.Unmarshal(inner, &mapping)
|
||||
mapping["history"] = p.DelayHistory()
|
||||
mapping["name"] = p.Name()
|
||||
mapping["udp"] = p.SupportUDP()
|
||||
return json.Marshal(mapping)
|
||||
}
|
||||
|
||||
// URLTest get the delay for the specified URL
|
||||
// implements C.Proxy
|
||||
func (p *Proxy) URLTest(ctx context.Context, url string) (t uint16, err error) {
|
||||
defer func() {
|
||||
p.alive.Store(err == nil)
|
||||
@ -206,6 +136,8 @@ func (p *Proxy) URLTest(ctx context.Context, url string) (t uint16, err error) {
|
||||
return http.ErrUseLastResponse
|
||||
},
|
||||
}
|
||||
defer client.CloseIdleConnections()
|
||||
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return
|
||||
@ -218,3 +150,31 @@ func (p *Proxy) URLTest(ctx context.Context, url string) (t uint16, err error) {
|
||||
func NewProxy(adapter C.ProxyAdapter) *Proxy {
|
||||
return &Proxy{adapter, queue.New(10), atomic.NewBool(true)}
|
||||
}
|
||||
|
||||
func urlToMetadata(rawURL string) (addr C.Metadata, err error) {
|
||||
u, err := url.Parse(rawURL)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
port := u.Port()
|
||||
if port == "" {
|
||||
switch u.Scheme {
|
||||
case "https":
|
||||
port = "443"
|
||||
case "http":
|
||||
port = "80"
|
||||
default:
|
||||
err = fmt.Errorf("%s scheme not Support", rawURL)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
addr = C.Metadata{
|
||||
AddrType: C.AtypDomainName,
|
||||
Host: u.Hostname(),
|
||||
DstIP: nil,
|
||||
DstPort: port,
|
||||
}
|
||||
return
|
||||
}
|
21
adapter/inbound/http.go
Normal file
21
adapter/inbound/http.go
Normal file
@ -0,0 +1,21 @@
|
||||
package inbound
|
||||
|
||||
import (
|
||||
"net"
|
||||
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/context"
|
||||
"github.com/Dreamacro/clash/transport/socks5"
|
||||
)
|
||||
|
||||
// NewHTTP receive normal http request and return HTTPContext
|
||||
func NewHTTP(target string, source net.Addr, conn net.Conn) *context.ConnContext {
|
||||
metadata := parseSocksAddr(socks5.ParseAddr(target))
|
||||
metadata.NetWork = C.TCP
|
||||
metadata.Type = C.HTTP
|
||||
if ip, port, err := parseAddr(source.String()); err == nil {
|
||||
metadata.SrcIP = ip
|
||||
metadata.SrcPort = port
|
||||
}
|
||||
return context.NewConnContext(conn, metadata)
|
||||
}
|
@ -8,7 +8,7 @@ import (
|
||||
"github.com/Dreamacro/clash/context"
|
||||
)
|
||||
|
||||
// NewHTTPS recieve CONNECT request and return ConnContext
|
||||
// NewHTTPS receive CONNECT request and return ConnContext
|
||||
func NewHTTPS(request *http.Request, conn net.Conn) *context.ConnContext {
|
||||
metadata := parseHTTPAddr(request)
|
||||
metadata.Type = C.HTTPCONNECT
|
@ -1,8 +1,8 @@
|
||||
package inbound
|
||||
|
||||
import (
|
||||
"github.com/Dreamacro/clash/component/socks5"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/transport/socks5"
|
||||
)
|
||||
|
||||
// PacketAdapter is a UDP Packet adapter for socks/redir/tun
|
@ -3,12 +3,12 @@ package inbound
|
||||
import (
|
||||
"net"
|
||||
|
||||
"github.com/Dreamacro/clash/component/socks5"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/context"
|
||||
"github.com/Dreamacro/clash/transport/socks5"
|
||||
)
|
||||
|
||||
// NewSocket recieve TCP inbound and return ConnContext
|
||||
// NewSocket receive TCP inbound and return ConnContext
|
||||
func NewSocket(target socks5.Addr, conn net.Conn, source C.Type) *context.ConnContext {
|
||||
metadata := parseSocksAddr(target)
|
||||
metadata.NetWork = C.TCP
|
@ -6,8 +6,8 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/Dreamacro/clash/component/socks5"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/transport/socks5"
|
||||
)
|
||||
|
||||
func parseSocksAddr(target socks5.Addr) *C.Metadata {
|
100
adapter/outbound/base.go
Normal file
100
adapter/outbound/base.go
Normal file
@ -0,0 +1,100 @@
|
||||
package outbound
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"net"
|
||||
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
)
|
||||
|
||||
type Base struct {
|
||||
name string
|
||||
addr string
|
||||
tp C.AdapterType
|
||||
udp bool
|
||||
}
|
||||
|
||||
// Name implements C.ProxyAdapter
|
||||
func (b *Base) Name() string {
|
||||
return b.name
|
||||
}
|
||||
|
||||
// Type implements C.ProxyAdapter
|
||||
func (b *Base) Type() C.AdapterType {
|
||||
return b.tp
|
||||
}
|
||||
|
||||
// StreamConn implements C.ProxyAdapter
|
||||
func (b *Base) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
||||
return c, errors.New("no support")
|
||||
}
|
||||
|
||||
// DialUDP implements C.ProxyAdapter
|
||||
func (b *Base) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
|
||||
return nil, errors.New("no support")
|
||||
}
|
||||
|
||||
// SupportUDP implements C.ProxyAdapter
|
||||
func (b *Base) SupportUDP() bool {
|
||||
return b.udp
|
||||
}
|
||||
|
||||
// MarshalJSON implements C.ProxyAdapter
|
||||
func (b *Base) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(map[string]string{
|
||||
"type": b.Type().String(),
|
||||
})
|
||||
}
|
||||
|
||||
// Addr implements C.ProxyAdapter
|
||||
func (b *Base) Addr() string {
|
||||
return b.addr
|
||||
}
|
||||
|
||||
// Unwrap implements C.ProxyAdapter
|
||||
func (b *Base) Unwrap(metadata *C.Metadata) C.Proxy {
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewBase(name string, addr string, tp C.AdapterType, udp bool) *Base {
|
||||
return &Base{name, addr, tp, udp}
|
||||
}
|
||||
|
||||
type conn struct {
|
||||
net.Conn
|
||||
chain C.Chain
|
||||
}
|
||||
|
||||
// Chains implements C.Connection
|
||||
func (c *conn) Chains() C.Chain {
|
||||
return c.chain
|
||||
}
|
||||
|
||||
// AppendToChains implements C.Connection
|
||||
func (c *conn) AppendToChains(a C.ProxyAdapter) {
|
||||
c.chain = append(c.chain, a.Name())
|
||||
}
|
||||
|
||||
func NewConn(c net.Conn, a C.ProxyAdapter) C.Conn {
|
||||
return &conn{c, []string{a.Name()}}
|
||||
}
|
||||
|
||||
type packetConn struct {
|
||||
net.PacketConn
|
||||
chain C.Chain
|
||||
}
|
||||
|
||||
// Chains implements C.Connection
|
||||
func (c *packetConn) Chains() C.Chain {
|
||||
return c.chain
|
||||
}
|
||||
|
||||
// AppendToChains implements C.Connection
|
||||
func (c *packetConn) AppendToChains(a C.ProxyAdapter) {
|
||||
c.chain = append(c.chain, a.Name())
|
||||
}
|
||||
|
||||
func newPacketConn(pc net.PacketConn, a C.ProxyAdapter) C.PacketConn {
|
||||
return &packetConn{pc, []string{a.Name()}}
|
||||
}
|
@ -12,10 +12,9 @@ type Direct struct {
|
||||
*Base
|
||||
}
|
||||
|
||||
// DialContext implements C.ProxyAdapter
|
||||
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", address)
|
||||
c, err := dialer.DialContext(ctx, "tcp", metadata.RemoteAddress())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -23,8 +22,9 @@ func (d *Direct) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn,
|
||||
return NewConn(c, d), nil
|
||||
}
|
||||
|
||||
// DialUDP implements C.ProxyAdapter
|
||||
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 {
|
||||
return nil, err
|
||||
}
|
@ -35,6 +35,7 @@ type HttpOption struct {
|
||||
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
|
||||
}
|
||||
|
||||
// StreamConn implements C.ProxyAdapter
|
||||
func (h *Http) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
||||
if h.tlsConfig != nil {
|
||||
cc := tls.Client(c, h.tlsConfig)
|
||||
@ -51,13 +52,16 @@ func (h *Http) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func (h *Http) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
|
||||
// DialContext implements C.ProxyAdapter
|
||||
func (h *Http) DialContext(ctx context.Context, metadata *C.Metadata) (_ C.Conn, err error) {
|
||||
c, err := dialer.DialContext(ctx, "tcp", h.addr)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s connect error: %w", h.addr, err)
|
||||
}
|
||||
tcpKeepAlive(c)
|
||||
|
||||
defer safeConnClose(c, err)
|
||||
|
||||
c, err = h.StreamConn(c, metadata)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -121,7 +125,6 @@ func NewHttp(option HttpOption) *Http {
|
||||
}
|
||||
tlsConfig = &tls.Config{
|
||||
InsecureSkipVerify: option.SkipCertVerify,
|
||||
ClientSessionCache: getClientSessionCache(),
|
||||
ServerName: sni,
|
||||
}
|
||||
}
|
@ -14,10 +14,12 @@ type Reject struct {
|
||||
*Base
|
||||
}
|
||||
|
||||
// DialContext implements C.ProxyAdapter
|
||||
func (r *Reject) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
|
||||
return NewConn(&NopConn{}, r), nil
|
||||
}
|
||||
|
||||
// DialUDP implements C.ProxyAdapter
|
||||
func (r *Reject) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
|
||||
return nil, errors.New("match reject rule")
|
||||
}
|
@ -2,7 +2,6 @@ package outbound
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
@ -10,10 +9,10 @@ import (
|
||||
|
||||
"github.com/Dreamacro/clash/common/structure"
|
||||
"github.com/Dreamacro/clash/component/dialer"
|
||||
obfs "github.com/Dreamacro/clash/component/simple-obfs"
|
||||
"github.com/Dreamacro/clash/component/socks5"
|
||||
v2rayObfs "github.com/Dreamacro/clash/component/v2ray-plugin"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
obfs "github.com/Dreamacro/clash/transport/simple-obfs"
|
||||
"github.com/Dreamacro/clash/transport/socks5"
|
||||
v2rayObfs "github.com/Dreamacro/clash/transport/v2ray-plugin"
|
||||
|
||||
"github.com/Dreamacro/go-shadowsocks2/core"
|
||||
)
|
||||
@ -54,6 +53,7 @@ type v2rayObfsOption struct {
|
||||
Mux bool `obfs:"mux,omitempty"`
|
||||
}
|
||||
|
||||
// StreamConn implements C.ProxyAdapter
|
||||
func (ss *ShadowSocks) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
||||
switch ss.obfsMode {
|
||||
case "tls":
|
||||
@ -73,25 +73,30 @@ func (ss *ShadowSocks) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, e
|
||||
return c, err
|
||||
}
|
||||
|
||||
func (ss *ShadowSocks) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
|
||||
// DialContext implements C.ProxyAdapter
|
||||
func (ss *ShadowSocks) DialContext(ctx context.Context, metadata *C.Metadata) (_ C.Conn, err error) {
|
||||
c, err := dialer.DialContext(ctx, "tcp", ss.addr)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s connect error: %w", ss.addr, err)
|
||||
}
|
||||
tcpKeepAlive(c)
|
||||
|
||||
defer safeConnClose(c, err)
|
||||
|
||||
c, err = ss.StreamConn(c, metadata)
|
||||
return NewConn(c, ss), err
|
||||
}
|
||||
|
||||
// DialUDP implements C.ProxyAdapter
|
||||
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 {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
addr, err := resolveUDPAddr("udp", ss.addr)
|
||||
if err != nil {
|
||||
pc.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@ -99,12 +104,6 @@ func (ss *ShadowSocks) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
|
||||
return newPacketConn(&ssPacketConn{PacketConn: pc, rAddr: addr}, ss), nil
|
||||
}
|
||||
|
||||
func (ss *ShadowSocks) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(map[string]string{
|
||||
"type": ss.Type().String(),
|
||||
})
|
||||
}
|
||||
|
||||
func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) {
|
||||
addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port))
|
||||
cipher := option.Cipher
|
||||
@ -150,7 +149,6 @@ func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) {
|
||||
if opts.TLS {
|
||||
v2rayOption.TLS = true
|
||||
v2rayOption.SkipCertVerify = opts.SkipCertVerify
|
||||
v2rayOption.SessionCache = getClientSessionCache()
|
||||
}
|
||||
}
|
||||
|
@ -2,15 +2,15 @@ package outbound
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net"
|
||||
"strconv"
|
||||
|
||||
"github.com/Dreamacro/clash/component/dialer"
|
||||
"github.com/Dreamacro/clash/component/ssr/obfs"
|
||||
"github.com/Dreamacro/clash/component/ssr/protocol"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/transport/ssr/obfs"
|
||||
"github.com/Dreamacro/clash/transport/ssr/protocol"
|
||||
|
||||
"github.com/Dreamacro/go-shadowsocks2/core"
|
||||
"github.com/Dreamacro/go-shadowsocks2/shadowaead"
|
||||
"github.com/Dreamacro/go-shadowsocks2/shadowstream"
|
||||
@ -36,6 +36,7 @@ type ShadowSocksROption struct {
|
||||
UDP bool `proxy:"udp,omitempty"`
|
||||
}
|
||||
|
||||
// StreamConn implements C.ProxyAdapter
|
||||
func (ssr *ShadowSocksR) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
||||
c = ssr.obfs.StreamConn(c)
|
||||
c = ssr.cipher.StreamConn(c)
|
||||
@ -57,25 +58,30 @@ func (ssr *ShadowSocksR) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn,
|
||||
return c, err
|
||||
}
|
||||
|
||||
func (ssr *ShadowSocksR) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
|
||||
// DialContext implements C.ProxyAdapter
|
||||
func (ssr *ShadowSocksR) DialContext(ctx context.Context, metadata *C.Metadata) (_ C.Conn, err error) {
|
||||
c, err := dialer.DialContext(ctx, "tcp", ssr.addr)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s connect error: %w", ssr.addr, err)
|
||||
}
|
||||
tcpKeepAlive(c)
|
||||
|
||||
defer safeConnClose(c, err)
|
||||
|
||||
c, err = ssr.StreamConn(c, metadata)
|
||||
return NewConn(c, ssr), err
|
||||
}
|
||||
|
||||
// DialUDP implements C.ProxyAdapter
|
||||
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 {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
addr, err := resolveUDPAddr("udp", ssr.addr)
|
||||
if err != nil {
|
||||
pc.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@ -84,12 +90,6 @@ func (ssr *ShadowSocksR) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
|
||||
return newPacketConn(&ssPacketConn{PacketConn: pc, rAddr: addr}, ssr), nil
|
||||
}
|
||||
|
||||
func (ssr *ShadowSocksR) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(map[string]string{
|
||||
"type": ssr.Type().String(),
|
||||
})
|
||||
}
|
||||
|
||||
func NewShadowSocksR(option ShadowSocksROption) (*ShadowSocksR, error) {
|
||||
addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port))
|
||||
cipher := option.Cipher
|
@ -8,9 +8,9 @@ import (
|
||||
|
||||
"github.com/Dreamacro/clash/common/structure"
|
||||
"github.com/Dreamacro/clash/component/dialer"
|
||||
obfs "github.com/Dreamacro/clash/component/simple-obfs"
|
||||
"github.com/Dreamacro/clash/component/snell"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
obfs "github.com/Dreamacro/clash/transport/simple-obfs"
|
||||
"github.com/Dreamacro/clash/transport/snell"
|
||||
)
|
||||
|
||||
type Snell struct {
|
||||
@ -48,6 +48,7 @@ func streamConn(c net.Conn, option streamOption) *snell.Snell {
|
||||
return snell.StreamConn(c, option.psk, option.version)
|
||||
}
|
||||
|
||||
// StreamConn implements C.ProxyAdapter
|
||||
func (s *Snell) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
||||
c = streamConn(c, streamOption{s.psk, s.version, s.addr, s.obfsOption})
|
||||
port, _ := strconv.Atoi(metadata.DstPort)
|
||||
@ -55,7 +56,8 @@ func (s *Snell) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
||||
return c, err
|
||||
}
|
||||
|
||||
func (s *Snell) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
|
||||
// DialContext implements C.ProxyAdapter
|
||||
func (s *Snell) DialContext(ctx context.Context, metadata *C.Metadata) (_ C.Conn, err error) {
|
||||
if s.version == snell.Version2 {
|
||||
c, err := s.pool.Get()
|
||||
if err != nil {
|
||||
@ -63,7 +65,10 @@ func (s *Snell) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn,
|
||||
}
|
||||
|
||||
port, _ := strconv.Atoi(metadata.DstPort)
|
||||
err = snell.WriteHeader(c, metadata.String(), uint(port), s.version)
|
||||
if err = snell.WriteHeader(c, metadata.String(), uint(port), s.version); err != nil {
|
||||
c.Close()
|
||||
return nil, err
|
||||
}
|
||||
return NewConn(c, s), err
|
||||
}
|
||||
|
||||
@ -73,6 +78,8 @@ func (s *Snell) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn,
|
||||
}
|
||||
tcpKeepAlive(c)
|
||||
|
||||
defer safeConnClose(c, err)
|
||||
|
||||
c, err = s.StreamConn(c, metadata)
|
||||
return NewConn(c, s), err
|
||||
}
|
@ -11,8 +11,8 @@ import (
|
||||
"strconv"
|
||||
|
||||
"github.com/Dreamacro/clash/component/dialer"
|
||||
"github.com/Dreamacro/clash/component/socks5"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/transport/socks5"
|
||||
)
|
||||
|
||||
type Socks5 struct {
|
||||
@ -35,6 +35,7 @@ type Socks5Option struct {
|
||||
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
|
||||
}
|
||||
|
||||
// StreamConn implements C.ProxyAdapter
|
||||
func (ss *Socks5) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
||||
if ss.tls {
|
||||
cc := tls.Client(c, ss.tlsConfig)
|
||||
@ -58,13 +59,16 @@ func (ss *Socks5) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error)
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func (ss *Socks5) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
|
||||
// DialContext implements C.ProxyAdapter
|
||||
func (ss *Socks5) DialContext(ctx context.Context, metadata *C.Metadata) (_ C.Conn, err error) {
|
||||
c, err := dialer.DialContext(ctx, "tcp", ss.addr)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s connect error: %w", ss.addr, err)
|
||||
}
|
||||
tcpKeepAlive(c)
|
||||
|
||||
defer safeConnClose(c, err)
|
||||
|
||||
c, err = ss.StreamConn(c, metadata)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -73,8 +77,9 @@ func (ss *Socks5) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn
|
||||
return NewConn(c, ss), nil
|
||||
}
|
||||
|
||||
// DialUDP implements C.ProxyAdapter
|
||||
func (ss *Socks5) DialUDP(metadata *C.Metadata) (_ C.PacketConn, err error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), tcpTimeout)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), C.DefaultTCPTimeout)
|
||||
defer cancel()
|
||||
c, err := dialer.DialContext(ctx, "tcp", ss.addr)
|
||||
if err != nil {
|
||||
@ -88,11 +93,7 @@ func (ss *Socks5) DialUDP(metadata *C.Metadata) (_ C.PacketConn, err error) {
|
||||
c = cc
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if err != nil {
|
||||
c.Close()
|
||||
}
|
||||
}()
|
||||
defer safeConnClose(c, err)
|
||||
|
||||
tcpKeepAlive(c)
|
||||
var user *socks5.User
|
||||
@ -109,7 +110,7 @@ func (ss *Socks5) DialUDP(metadata *C.Metadata) (_ C.PacketConn, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
pc, err := dialer.ListenPacket("udp", "")
|
||||
pc, err := dialer.ListenPacket(context.Background(), "udp", "")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@ -144,7 +145,6 @@ func NewSocks5(option Socks5Option) *Socks5 {
|
||||
if option.TLS {
|
||||
tlsConfig = &tls.Config{
|
||||
InsecureSkipVerify: option.SkipCertVerify,
|
||||
ClientSessionCache: getClientSessionCache(),
|
||||
ServerName: option.Server,
|
||||
}
|
||||
}
|
176
adapter/outbound/trojan.go
Normal file
176
adapter/outbound/trojan.go
Normal file
@ -0,0 +1,176 @@
|
||||
package outbound
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"net"
|
||||
"strconv"
|
||||
|
||||
"github.com/Dreamacro/clash/component/dialer"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/transport/gun"
|
||||
"github.com/Dreamacro/clash/transport/trojan"
|
||||
|
||||
"golang.org/x/net/http2"
|
||||
)
|
||||
|
||||
type Trojan struct {
|
||||
*Base
|
||||
instance *trojan.Trojan
|
||||
|
||||
// for gun mux
|
||||
gunTLSConfig *tls.Config
|
||||
gunConfig *gun.Config
|
||||
transport *http2.Transport
|
||||
}
|
||||
|
||||
type TrojanOption struct {
|
||||
Name string `proxy:"name"`
|
||||
Server string `proxy:"server"`
|
||||
Port int `proxy:"port"`
|
||||
Password string `proxy:"password"`
|
||||
ALPN []string `proxy:"alpn,omitempty"`
|
||||
SNI string `proxy:"sni,omitempty"`
|
||||
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
|
||||
UDP bool `proxy:"udp,omitempty"`
|
||||
Network string `proxy:"network,omitempty"`
|
||||
GrpcOpts GrpcOptions `proxy:"grpc-opts,omitempty"`
|
||||
}
|
||||
|
||||
// StreamConn implements C.ProxyAdapter
|
||||
func (t *Trojan) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
||||
var err error
|
||||
if t.transport != nil {
|
||||
c, err = gun.StreamGunWithConn(c, t.gunTLSConfig, t.gunConfig)
|
||||
} else {
|
||||
c, err = t.instance.StreamConn(c)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s connect error: %w", t.addr, err)
|
||||
}
|
||||
|
||||
err = t.instance.WriteHeader(c, trojan.CommandTCP, serializesSocksAddr(metadata))
|
||||
return c, err
|
||||
}
|
||||
|
||||
// DialContext implements C.ProxyAdapter
|
||||
func (t *Trojan) DialContext(ctx context.Context, metadata *C.Metadata) (_ C.Conn, err error) {
|
||||
// gun transport
|
||||
if t.transport != nil {
|
||||
c, err := gun.StreamGunWithTransport(t.transport, t.gunConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = t.instance.WriteHeader(c, trojan.CommandTCP, serializesSocksAddr(metadata)); err != nil {
|
||||
c.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return NewConn(c, t), nil
|
||||
}
|
||||
|
||||
c, err := dialer.DialContext(ctx, "tcp", t.addr)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s connect error: %w", t.addr, err)
|
||||
}
|
||||
tcpKeepAlive(c)
|
||||
|
||||
defer safeConnClose(c, err)
|
||||
|
||||
c, err = t.StreamConn(c, metadata)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return NewConn(c, t), err
|
||||
}
|
||||
|
||||
// DialUDP implements C.ProxyAdapter
|
||||
func (t *Trojan) DialUDP(metadata *C.Metadata) (_ C.PacketConn, err error) {
|
||||
var c net.Conn
|
||||
|
||||
// grpc transport
|
||||
if t.transport != nil {
|
||||
c, err = gun.StreamGunWithTransport(t.transport, t.gunConfig)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s connect error: %w", t.addr, err)
|
||||
}
|
||||
defer safeConnClose(c, err)
|
||||
} else {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), C.DefaultTCPTimeout)
|
||||
defer cancel()
|
||||
c, err = dialer.DialContext(ctx, "tcp", t.addr)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s connect error: %w", t.addr, err)
|
||||
}
|
||||
defer safeConnClose(c, err)
|
||||
tcpKeepAlive(c)
|
||||
c, err = t.instance.StreamConn(c)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s connect error: %w", t.addr, err)
|
||||
}
|
||||
}
|
||||
|
||||
err = t.instance.WriteHeader(c, trojan.CommandUDP, serializesSocksAddr(metadata))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pc := t.instance.PacketConn(c)
|
||||
return newPacketConn(pc, t), err
|
||||
}
|
||||
|
||||
func NewTrojan(option TrojanOption) (*Trojan, error) {
|
||||
addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port))
|
||||
|
||||
tOption := &trojan.Option{
|
||||
Password: option.Password,
|
||||
ALPN: option.ALPN,
|
||||
ServerName: option.Server,
|
||||
SkipCertVerify: option.SkipCertVerify,
|
||||
}
|
||||
|
||||
if option.SNI != "" {
|
||||
tOption.ServerName = option.SNI
|
||||
}
|
||||
|
||||
t := &Trojan{
|
||||
Base: &Base{
|
||||
name: option.Name,
|
||||
addr: addr,
|
||||
tp: C.Trojan,
|
||||
udp: option.UDP,
|
||||
},
|
||||
instance: trojan.New(tOption),
|
||||
}
|
||||
|
||||
if option.Network == "grpc" {
|
||||
dialFn := func(network, addr string) (net.Conn, error) {
|
||||
c, err := dialer.DialContext(context.Background(), "tcp", t.addr)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s connect error: %s", t.addr, err.Error())
|
||||
}
|
||||
tcpKeepAlive(c)
|
||||
return c, nil
|
||||
}
|
||||
|
||||
tlsConfig := &tls.Config{
|
||||
NextProtos: option.ALPN,
|
||||
MinVersion: tls.VersionTLS12,
|
||||
InsecureSkipVerify: tOption.SkipCertVerify,
|
||||
ServerName: tOption.ServerName,
|
||||
}
|
||||
|
||||
t.transport = gun.NewHTTP2Client(dialFn, tlsConfig)
|
||||
t.gunTLSConfig = tlsConfig
|
||||
t.gunConfig = &gun.Config{
|
||||
ServiceName: option.GrpcOpts.GrpcServiceName,
|
||||
Host: tOption.ServerName,
|
||||
}
|
||||
}
|
||||
|
||||
return t, nil
|
||||
}
|
@ -2,56 +2,15 @@ package outbound
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/Dreamacro/clash/component/resolver"
|
||||
"github.com/Dreamacro/clash/component/socks5"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/transport/socks5"
|
||||
)
|
||||
|
||||
const (
|
||||
tcpTimeout = 5 * time.Second
|
||||
)
|
||||
|
||||
var (
|
||||
globalClientSessionCache tls.ClientSessionCache
|
||||
once sync.Once
|
||||
)
|
||||
|
||||
func urlToMetadata(rawURL string) (addr C.Metadata, err error) {
|
||||
u, err := url.Parse(rawURL)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
port := u.Port()
|
||||
if port == "" {
|
||||
switch u.Scheme {
|
||||
case "https":
|
||||
port = "443"
|
||||
case "http":
|
||||
port = "80"
|
||||
default:
|
||||
err = fmt.Errorf("%s scheme not Support", rawURL)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
addr = C.Metadata{
|
||||
AddrType: C.AtypDomainName,
|
||||
Host: u.Hostname(),
|
||||
DstIP: nil,
|
||||
DstPort: port,
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func tcpKeepAlive(c net.Conn) {
|
||||
if tcp, ok := c.(*net.TCPConn); ok {
|
||||
tcp.SetKeepAlive(true)
|
||||
@ -59,13 +18,6 @@ func tcpKeepAlive(c net.Conn) {
|
||||
}
|
||||
}
|
||||
|
||||
func getClientSessionCache() tls.ClientSessionCache {
|
||||
once.Do(func() {
|
||||
globalClientSessionCache = tls.NewLRUClientSessionCache(128)
|
||||
})
|
||||
return globalClientSessionCache
|
||||
}
|
||||
|
||||
func serializesSocksAddr(metadata *C.Metadata) []byte {
|
||||
var buf [][]byte
|
||||
aType := uint8(metadata.AddrType)
|
||||
@ -98,3 +50,9 @@ func resolveUDPAddr(network, address string) (*net.UDPAddr, error) {
|
||||
}
|
||||
return net.ResolveUDPAddr(network, net.JoinHostPort(ip.String(), port))
|
||||
}
|
||||
|
||||
func safeConnClose(c net.Conn, err error) {
|
||||
if err != nil {
|
||||
c.Close()
|
||||
}
|
||||
}
|
@ -2,6 +2,7 @@ package outbound
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
@ -11,14 +12,22 @@ import (
|
||||
|
||||
"github.com/Dreamacro/clash/component/dialer"
|
||||
"github.com/Dreamacro/clash/component/resolver"
|
||||
"github.com/Dreamacro/clash/component/vmess"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/transport/gun"
|
||||
"github.com/Dreamacro/clash/transport/vmess"
|
||||
|
||||
"golang.org/x/net/http2"
|
||||
)
|
||||
|
||||
type Vmess struct {
|
||||
*Base
|
||||
client *vmess.Client
|
||||
option *VmessOption
|
||||
|
||||
// for gun mux
|
||||
gunTLSConfig *tls.Config
|
||||
gunConfig *gun.Config
|
||||
transport *http2.Transport
|
||||
}
|
||||
|
||||
type VmessOption struct {
|
||||
@ -33,6 +42,8 @@ type VmessOption struct {
|
||||
Network string `proxy:"network,omitempty"`
|
||||
HTTPOpts HTTPOptions `proxy:"http-opts,omitempty"`
|
||||
HTTP2Opts HTTP2Options `proxy:"h2-opts,omitempty"`
|
||||
GrpcOpts GrpcOptions `proxy:"grpc-opts,omitempty"`
|
||||
WSOpts WSOptions `proxy:"ws-opts,omitempty"`
|
||||
WSPath string `proxy:"ws-path,omitempty"`
|
||||
WSHeaders map[string]string `proxy:"ws-headers,omitempty"`
|
||||
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
|
||||
@ -50,18 +61,39 @@ type HTTP2Options struct {
|
||||
Path string `proxy:"path,omitempty"`
|
||||
}
|
||||
|
||||
type GrpcOptions struct {
|
||||
GrpcServiceName string `proxy:"grpc-service-name,omitempty"`
|
||||
}
|
||||
|
||||
type WSOptions struct {
|
||||
Path string `proxy:"path,omitempty"`
|
||||
Headers map[string]string `proxy:"headers,omitempty"`
|
||||
MaxEarlyData int `proxy:"max-early-data,omitempty"`
|
||||
EarlyDataHeaderName string `proxy:"early-data-header-name,omitempty"`
|
||||
}
|
||||
|
||||
// StreamConn implements C.ProxyAdapter
|
||||
func (v *Vmess) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
||||
var err error
|
||||
switch v.option.Network {
|
||||
case "ws":
|
||||
host, port, _ := net.SplitHostPort(v.addr)
|
||||
wsOpts := &vmess.WebsocketConfig{
|
||||
Host: host,
|
||||
Port: port,
|
||||
Path: v.option.WSPath,
|
||||
if v.option.WSOpts.Path == "" {
|
||||
v.option.WSOpts.Path = v.option.WSPath
|
||||
}
|
||||
if len(v.option.WSOpts.Headers) == 0 {
|
||||
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{}
|
||||
for key, value := range v.option.WSHeaders {
|
||||
header.Add(key, value)
|
||||
@ -71,7 +103,6 @@ func (v *Vmess) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
||||
|
||||
if v.option.TLS {
|
||||
wsOpts.TLS = true
|
||||
wsOpts.SessionCache = getClientSessionCache()
|
||||
wsOpts.SkipCertVerify = v.option.SkipCertVerify
|
||||
wsOpts.ServerName = v.option.ServerName
|
||||
}
|
||||
@ -83,7 +114,6 @@ func (v *Vmess) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
||||
tlsOpts := &vmess.TLSConfig{
|
||||
Host: host,
|
||||
SkipCertVerify: v.option.SkipCertVerify,
|
||||
SessionCache: getClientSessionCache(),
|
||||
}
|
||||
|
||||
if v.option.ServerName != "" {
|
||||
@ -110,7 +140,6 @@ func (v *Vmess) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
||||
tlsOpts := vmess.TLSConfig{
|
||||
Host: host,
|
||||
SkipCertVerify: v.option.SkipCertVerify,
|
||||
SessionCache: getClientSessionCache(),
|
||||
NextProtos: []string{"h2"},
|
||||
}
|
||||
|
||||
@ -129,6 +158,8 @@ func (v *Vmess) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
||||
}
|
||||
|
||||
c, err = vmess.StreamH2Conn(c, h2Opts)
|
||||
case "grpc":
|
||||
c, err = gun.StreamGunWithConn(c, v.gunTLSConfig, v.gunConfig)
|
||||
default:
|
||||
// handle TLS
|
||||
if v.option.TLS {
|
||||
@ -136,7 +167,6 @@ func (v *Vmess) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
||||
tlsOpts := &vmess.TLSConfig{
|
||||
Host: host,
|
||||
SkipCertVerify: v.option.SkipCertVerify,
|
||||
SessionCache: getClientSessionCache(),
|
||||
}
|
||||
|
||||
if v.option.ServerName != "" {
|
||||
@ -154,19 +184,38 @@ func (v *Vmess) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
||||
return v.client.StreamConn(c, parseVmessAddr(metadata))
|
||||
}
|
||||
|
||||
func (v *Vmess) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
|
||||
// DialContext implements C.ProxyAdapter
|
||||
func (v *Vmess) DialContext(ctx context.Context, metadata *C.Metadata) (_ C.Conn, err error) {
|
||||
// gun transport
|
||||
if v.transport != nil {
|
||||
c, err := gun.StreamGunWithTransport(v.transport, v.gunConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer safeConnClose(c, err)
|
||||
|
||||
c, err = v.client.StreamConn(c, parseVmessAddr(metadata))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return NewConn(c, v), nil
|
||||
}
|
||||
|
||||
c, err := dialer.DialContext(ctx, "tcp", v.addr)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error())
|
||||
}
|
||||
tcpKeepAlive(c)
|
||||
defer safeConnClose(c, err)
|
||||
|
||||
c, err = v.StreamConn(c, metadata)
|
||||
return NewConn(c, v), err
|
||||
}
|
||||
|
||||
func (v *Vmess) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
|
||||
// vmess use stream-oriented udp, so clash needs a net.UDPAddr
|
||||
// DialUDP implements C.ProxyAdapter
|
||||
func (v *Vmess) DialUDP(metadata *C.Metadata) (_ C.PacketConn, err error) {
|
||||
// vmess use stream-oriented udp with a special address, so we needs a net.UDPAddr
|
||||
if !metadata.Resolved() {
|
||||
ip, err := resolver.ResolveIP(metadata.Host)
|
||||
if err != nil {
|
||||
@ -175,17 +224,33 @@ func (v *Vmess) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
|
||||
metadata.DstIP = ip
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), tcpTimeout)
|
||||
defer cancel()
|
||||
c, err := dialer.DialContext(ctx, "tcp", v.addr)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error())
|
||||
var c net.Conn
|
||||
// gun transport
|
||||
if v.transport != nil {
|
||||
c, err = gun.StreamGunWithTransport(v.transport, v.gunConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer safeConnClose(c, err)
|
||||
|
||||
c, err = v.client.StreamConn(c, parseVmessAddr(metadata))
|
||||
} else {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), C.DefaultTCPTimeout)
|
||||
defer cancel()
|
||||
c, err = dialer.DialContext(ctx, "tcp", v.addr)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error())
|
||||
}
|
||||
tcpKeepAlive(c)
|
||||
defer safeConnClose(c, err)
|
||||
|
||||
c, err = v.StreamConn(c, metadata)
|
||||
}
|
||||
tcpKeepAlive(c)
|
||||
c, err = v.StreamConn(c, metadata)
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("new vmess client error: %v", err)
|
||||
}
|
||||
|
||||
return newPacketConn(&vmessPacketConn{Conn: c, rAddr: metadata.UDPAddr()}, v), nil
|
||||
}
|
||||
|
||||
@ -197,15 +262,20 @@ func NewVmess(option VmessOption) (*Vmess, error) {
|
||||
Security: security,
|
||||
HostName: option.Server,
|
||||
Port: strconv.Itoa(option.Port),
|
||||
IsAead: option.AlterID == 0,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if option.Network == "h2" && !option.TLS {
|
||||
return nil, fmt.Errorf("TLS must be true with h2 network")
|
||||
|
||||
switch option.Network {
|
||||
case "h2", "grpc":
|
||||
if !option.TLS {
|
||||
return nil, fmt.Errorf("TLS must be true with h2/grpc network")
|
||||
}
|
||||
}
|
||||
|
||||
return &Vmess{
|
||||
v := &Vmess{
|
||||
Base: &Base{
|
||||
name: option.Name,
|
||||
addr: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)),
|
||||
@ -214,7 +284,44 @@ func NewVmess(option VmessOption) (*Vmess, error) {
|
||||
},
|
||||
client: client,
|
||||
option: &option,
|
||||
}, nil
|
||||
}
|
||||
|
||||
switch option.Network {
|
||||
case "h2":
|
||||
if len(option.HTTP2Opts.Host) == 0 {
|
||||
option.HTTP2Opts.Host = append(option.HTTP2Opts.Host, "www.example.com")
|
||||
}
|
||||
case "grpc":
|
||||
dialFn := func(network, addr string) (net.Conn, error) {
|
||||
c, err := dialer.DialContext(context.Background(), "tcp", v.addr)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error())
|
||||
}
|
||||
tcpKeepAlive(c)
|
||||
return c, nil
|
||||
}
|
||||
|
||||
gunConfig := &gun.Config{
|
||||
ServiceName: v.option.GrpcOpts.GrpcServiceName,
|
||||
Host: v.option.ServerName,
|
||||
}
|
||||
tlsConfig := &tls.Config{
|
||||
InsecureSkipVerify: v.option.SkipCertVerify,
|
||||
ServerName: v.option.ServerName,
|
||||
}
|
||||
|
||||
if v.option.ServerName == "" {
|
||||
host, _, _ := net.SplitHostPort(v.addr)
|
||||
tlsConfig.ServerName = host
|
||||
gunConfig.Host = host
|
||||
}
|
||||
|
||||
v.gunTLSConfig = tlsConfig
|
||||
v.gunConfig = gunConfig
|
||||
v.transport = gun.NewHTTP2Client(dialFn, tlsConfig)
|
||||
}
|
||||
|
||||
return v, nil
|
||||
}
|
||||
|
||||
func parseVmessAddr(metadata *C.Metadata) *vmess.DstAddr {
|
@ -3,8 +3,8 @@ package outboundgroup
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/Dreamacro/clash/adapters/provider"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/constant/provider"
|
||||
)
|
||||
|
||||
const (
|
@ -4,10 +4,10 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
|
||||
"github.com/Dreamacro/clash/adapters/outbound"
|
||||
"github.com/Dreamacro/clash/adapters/provider"
|
||||
"github.com/Dreamacro/clash/adapter/outbound"
|
||||
"github.com/Dreamacro/clash/common/singledo"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/constant/provider"
|
||||
)
|
||||
|
||||
type Fallback struct {
|
||||
@ -22,6 +22,7 @@ func (f *Fallback) Now() string {
|
||||
return proxy.Name()
|
||||
}
|
||||
|
||||
// DialContext implements C.ProxyAdapter
|
||||
func (f *Fallback) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
|
||||
proxy := f.findAliveProxy(true)
|
||||
c, err := proxy.DialContext(ctx, metadata)
|
||||
@ -31,6 +32,7 @@ func (f *Fallback) DialContext(ctx context.Context, metadata *C.Metadata) (C.Con
|
||||
return c, err
|
||||
}
|
||||
|
||||
// DialUDP implements C.ProxyAdapter
|
||||
func (f *Fallback) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
|
||||
proxy := f.findAliveProxy(true)
|
||||
pc, err := proxy.DialUDP(metadata)
|
||||
@ -40,6 +42,7 @@ func (f *Fallback) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
|
||||
return pc, err
|
||||
}
|
||||
|
||||
// SupportUDP implements C.ProxyAdapter
|
||||
func (f *Fallback) SupportUDP() bool {
|
||||
if f.disableUDP {
|
||||
return false
|
||||
@ -49,6 +52,7 @@ func (f *Fallback) SupportUDP() bool {
|
||||
return proxy.SupportUDP()
|
||||
}
|
||||
|
||||
// MarshalJSON implements C.ProxyAdapter
|
||||
func (f *Fallback) MarshalJSON() ([]byte, error) {
|
||||
var all []string
|
||||
for _, proxy := range f.proxies(false) {
|
||||
@ -61,6 +65,7 @@ func (f *Fallback) MarshalJSON() ([]byte, error) {
|
||||
})
|
||||
}
|
||||
|
||||
// Unwrap implements C.ProxyAdapter
|
||||
func (f *Fallback) Unwrap(metadata *C.Metadata) C.Proxy {
|
||||
proxy := f.findAliveProxy(true)
|
||||
return proxy
|
@ -7,11 +7,11 @@ import (
|
||||
"fmt"
|
||||
"net"
|
||||
|
||||
"github.com/Dreamacro/clash/adapters/outbound"
|
||||
"github.com/Dreamacro/clash/adapters/provider"
|
||||
"github.com/Dreamacro/clash/adapter/outbound"
|
||||
"github.com/Dreamacro/clash/common/murmur3"
|
||||
"github.com/Dreamacro/clash/common/singledo"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/constant/provider"
|
||||
|
||||
"golang.org/x/net/publicsuffix"
|
||||
)
|
||||
@ -68,6 +68,7 @@ func jumpHash(key uint64, buckets int32) int32 {
|
||||
return int32(b)
|
||||
}
|
||||
|
||||
// DialContext implements C.ProxyAdapter
|
||||
func (lb *LoadBalance) DialContext(ctx context.Context, metadata *C.Metadata) (c C.Conn, err error) {
|
||||
defer func() {
|
||||
if err == nil {
|
||||
@ -81,6 +82,7 @@ func (lb *LoadBalance) DialContext(ctx context.Context, metadata *C.Metadata) (c
|
||||
return
|
||||
}
|
||||
|
||||
// DialUDP implements C.ProxyAdapter
|
||||
func (lb *LoadBalance) DialUDP(metadata *C.Metadata) (pc C.PacketConn, err error) {
|
||||
defer func() {
|
||||
if err == nil {
|
||||
@ -93,6 +95,7 @@ func (lb *LoadBalance) DialUDP(metadata *C.Metadata) (pc C.PacketConn, err error
|
||||
return proxy.DialUDP(metadata)
|
||||
}
|
||||
|
||||
// SupportUDP implements C.ProxyAdapter
|
||||
func (lb *LoadBalance) SupportUDP() bool {
|
||||
return !lb.disableUDP
|
||||
}
|
||||
@ -130,6 +133,7 @@ func strategyConsistentHashing() strategyFn {
|
||||
}
|
||||
}
|
||||
|
||||
// Unwrap implements C.ProxyAdapter
|
||||
func (lb *LoadBalance) Unwrap(metadata *C.Metadata) C.Proxy {
|
||||
proxies := lb.proxies(true)
|
||||
return lb.strategyFn(proxies, metadata)
|
||||
@ -143,6 +147,7 @@ func (lb *LoadBalance) proxies(touch bool) []C.Proxy {
|
||||
return elm.([]C.Proxy)
|
||||
}
|
||||
|
||||
// MarshalJSON implements C.ProxyAdapter
|
||||
func (lb *LoadBalance) MarshalJSON() ([]byte, error) {
|
||||
var all []string
|
||||
for _, proxy := range lb.proxies(false) {
|
@ -4,9 +4,10 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/Dreamacro/clash/adapters/provider"
|
||||
"github.com/Dreamacro/clash/adapter/provider"
|
||||
"github.com/Dreamacro/clash/common/structure"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
types "github.com/Dreamacro/clash/constant/provider"
|
||||
)
|
||||
|
||||
var (
|
||||
@ -28,7 +29,7 @@ type GroupCommonOption struct {
|
||||
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})
|
||||
|
||||
groupOption := &GroupCommonOption{
|
||||
@ -44,7 +45,7 @@ func ParseProxyGroup(config map[string]interface{}, proxyMap map[string]C.Proxy,
|
||||
|
||||
groupName := groupOption.Name
|
||||
|
||||
providers := []provider.ProxyProvider{}
|
||||
providers := []types.ProxyProvider{}
|
||||
|
||||
if len(groupOption.Proxies) == 0 && len(groupOption.Use) == 0 {
|
||||
return nil, errMissProxy
|
||||
@ -138,15 +139,15 @@ func getProxies(mapping map[string]C.Proxy, list []string) ([]C.Proxy, error) {
|
||||
return ps, nil
|
||||
}
|
||||
|
||||
func getProviders(mapping map[string]provider.ProxyProvider, list []string) ([]provider.ProxyProvider, error) {
|
||||
var ps []provider.ProxyProvider
|
||||
func getProviders(mapping map[string]types.ProxyProvider, list []string) ([]types.ProxyProvider, error) {
|
||||
var ps []types.ProxyProvider
|
||||
for _, name := range list {
|
||||
p, ok := mapping[name]
|
||||
if !ok {
|
||||
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)
|
||||
}
|
||||
ps = append(ps, p)
|
@ -3,14 +3,13 @@ package outboundgroup
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/Dreamacro/clash/adapters/outbound"
|
||||
"github.com/Dreamacro/clash/adapters/provider"
|
||||
"github.com/Dreamacro/clash/adapter/outbound"
|
||||
"github.com/Dreamacro/clash/common/singledo"
|
||||
"github.com/Dreamacro/clash/component/dialer"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/constant/provider"
|
||||
)
|
||||
|
||||
type Relay struct {
|
||||
@ -19,11 +18,22 @@ type Relay struct {
|
||||
providers []provider.ProxyProvider
|
||||
}
|
||||
|
||||
// DialContext implements C.ProxyAdapter
|
||||
func (r *Relay) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
|
||||
proxies := r.proxies(metadata, true)
|
||||
if len(proxies) == 0 {
|
||||
return nil, errors.New("proxy does not exist")
|
||||
var proxies []C.Proxy
|
||||
for _, proxy := range r.proxies(metadata, true) {
|
||||
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]
|
||||
last := proxies[len(proxies)-1]
|
||||
|
||||
@ -56,6 +66,7 @@ func (r *Relay) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn,
|
||||
return outbound.NewConn(c, r), nil
|
||||
}
|
||||
|
||||
// MarshalJSON implements C.ProxyAdapter
|
||||
func (r *Relay) MarshalJSON() ([]byte, error) {
|
||||
var all []string
|
||||
for _, proxy := range r.rawProxies(false) {
|
@ -5,10 +5,10 @@ import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
|
||||
"github.com/Dreamacro/clash/adapters/outbound"
|
||||
"github.com/Dreamacro/clash/adapters/provider"
|
||||
"github.com/Dreamacro/clash/adapter/outbound"
|
||||
"github.com/Dreamacro/clash/common/singledo"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/constant/provider"
|
||||
)
|
||||
|
||||
type Selector struct {
|
||||
@ -19,6 +19,7 @@ type Selector struct {
|
||||
providers []provider.ProxyProvider
|
||||
}
|
||||
|
||||
// DialContext implements C.ProxyAdapter
|
||||
func (s *Selector) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
|
||||
c, err := s.selectedProxy(true).DialContext(ctx, metadata)
|
||||
if err == nil {
|
||||
@ -27,6 +28,7 @@ func (s *Selector) DialContext(ctx context.Context, metadata *C.Metadata) (C.Con
|
||||
return c, err
|
||||
}
|
||||
|
||||
// DialUDP implements C.ProxyAdapter
|
||||
func (s *Selector) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
|
||||
pc, err := s.selectedProxy(true).DialUDP(metadata)
|
||||
if err == nil {
|
||||
@ -35,6 +37,7 @@ func (s *Selector) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
|
||||
return pc, err
|
||||
}
|
||||
|
||||
// SupportUDP implements C.ProxyAdapter
|
||||
func (s *Selector) SupportUDP() bool {
|
||||
if s.disableUDP {
|
||||
return false
|
||||
@ -43,6 +46,7 @@ func (s *Selector) SupportUDP() bool {
|
||||
return s.selectedProxy(false).SupportUDP()
|
||||
}
|
||||
|
||||
// MarshalJSON implements C.ProxyAdapter
|
||||
func (s *Selector) MarshalJSON() ([]byte, error) {
|
||||
var all []string
|
||||
for _, proxy := range getProvidersProxies(s.providers, false) {
|
||||
@ -72,6 +76,7 @@ func (s *Selector) Set(name string) error {
|
||||
return errors.New("proxy not exist")
|
||||
}
|
||||
|
||||
// Unwrap implements C.ProxyAdapter
|
||||
func (s *Selector) Unwrap(metadata *C.Metadata) C.Proxy {
|
||||
return s.selectedProxy(true)
|
||||
}
|
@ -5,10 +5,10 @@ import (
|
||||
"encoding/json"
|
||||
"time"
|
||||
|
||||
"github.com/Dreamacro/clash/adapters/outbound"
|
||||
"github.com/Dreamacro/clash/adapters/provider"
|
||||
"github.com/Dreamacro/clash/adapter/outbound"
|
||||
"github.com/Dreamacro/clash/common/singledo"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/constant/provider"
|
||||
)
|
||||
|
||||
type urlTestOption func(*URLTest)
|
||||
@ -33,6 +33,7 @@ func (u *URLTest) Now() string {
|
||||
return u.fast(false).Name()
|
||||
}
|
||||
|
||||
// DialContext implements C.ProxyAdapter
|
||||
func (u *URLTest) DialContext(ctx context.Context, metadata *C.Metadata) (c C.Conn, err error) {
|
||||
c, err = u.fast(true).DialContext(ctx, metadata)
|
||||
if err == nil {
|
||||
@ -41,6 +42,7 @@ func (u *URLTest) DialContext(ctx context.Context, metadata *C.Metadata) (c C.Co
|
||||
return c, err
|
||||
}
|
||||
|
||||
// DialUDP implements C.ProxyAdapter
|
||||
func (u *URLTest) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
|
||||
pc, err := u.fast(true).DialUDP(metadata)
|
||||
if err == nil {
|
||||
@ -49,6 +51,7 @@ func (u *URLTest) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
|
||||
return pc, err
|
||||
}
|
||||
|
||||
// Unwrap implements C.ProxyAdapter
|
||||
func (u *URLTest) Unwrap(metadata *C.Metadata) C.Proxy {
|
||||
return u.fast(true)
|
||||
}
|
||||
@ -66,7 +69,13 @@ func (u *URLTest) fast(touch bool) C.Proxy {
|
||||
proxies := u.proxies(touch)
|
||||
fast := proxies[0]
|
||||
min := fast.LastDelay()
|
||||
fastNotExist := true
|
||||
|
||||
for _, proxy := range proxies[1:] {
|
||||
if u.fastNode != nil && proxy.Name() == u.fastNode.Name() {
|
||||
fastNotExist = false
|
||||
}
|
||||
|
||||
if !proxy.Alive() {
|
||||
continue
|
||||
}
|
||||
@ -79,7 +88,7 @@ func (u *URLTest) fast(touch bool) C.Proxy {
|
||||
}
|
||||
|
||||
// tolerance
|
||||
if u.fastNode == nil || u.fastNode.LastDelay() > fast.LastDelay()+u.tolerance {
|
||||
if u.fastNode == nil || fastNotExist || !u.fastNode.Alive() || u.fastNode.LastDelay() > fast.LastDelay()+u.tolerance {
|
||||
u.fastNode = fast
|
||||
}
|
||||
|
||||
@ -89,6 +98,7 @@ func (u *URLTest) fast(touch bool) C.Proxy {
|
||||
return elm.(C.Proxy)
|
||||
}
|
||||
|
||||
// SupportUDP implements C.ProxyAdapter
|
||||
func (u *URLTest) SupportUDP() bool {
|
||||
if u.disableUDP {
|
||||
return false
|
||||
@ -97,6 +107,7 @@ func (u *URLTest) SupportUDP() bool {
|
||||
return u.fast(false).SupportUDP()
|
||||
}
|
||||
|
||||
// MarshalJSON implements C.ProxyAdapter
|
||||
func (u *URLTest) MarshalJSON() ([]byte, error) {
|
||||
var all []string
|
||||
for _, proxy := range u.proxies(false) {
|
@ -16,25 +16,7 @@ func addrToMetadata(rawAddress string) (addr *C.Metadata, err error) {
|
||||
}
|
||||
|
||||
ip := net.ParseIP(host)
|
||||
if ip != nil {
|
||||
if ip.To4() != nil {
|
||||
addr = &C.Metadata{
|
||||
AddrType: C.AtypIPv4,
|
||||
Host: "",
|
||||
DstIP: ip,
|
||||
DstPort: port,
|
||||
}
|
||||
return
|
||||
} else {
|
||||
addr = &C.Metadata{
|
||||
AddrType: C.AtypIPv6,
|
||||
Host: "",
|
||||
DstIP: ip,
|
||||
DstPort: port,
|
||||
}
|
||||
return
|
||||
}
|
||||
} else {
|
||||
if ip == nil {
|
||||
addr = &C.Metadata{
|
||||
AddrType: C.AtypDomainName,
|
||||
Host: host,
|
||||
@ -42,7 +24,23 @@ func addrToMetadata(rawAddress string) (addr *C.Metadata, err error) {
|
||||
DstPort: port,
|
||||
}
|
||||
return
|
||||
} else if ip4 := ip.To4(); ip4 != nil {
|
||||
addr = &C.Metadata{
|
||||
AddrType: C.AtypIPv4,
|
||||
Host: "",
|
||||
DstIP: ip4,
|
||||
DstPort: port,
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
addr = &C.Metadata{
|
||||
AddrType: C.AtypIPv6,
|
||||
Host: "",
|
||||
DstIP: ip,
|
||||
DstPort: port,
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func tcpKeepAlive(c net.Conn) {
|
@ -1,8 +1,9 @@
|
||||
package outbound
|
||||
package adapter
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/Dreamacro/clash/adapter/outbound"
|
||||
"github.com/Dreamacro/clash/common/structure"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
)
|
||||
@ -20,36 +21,36 @@ func ParseProxy(mapping map[string]interface{}) (C.Proxy, error) {
|
||||
)
|
||||
switch proxyType {
|
||||
case "ss":
|
||||
ssOption := &ShadowSocksOption{}
|
||||
ssOption := &outbound.ShadowSocksOption{}
|
||||
err = decoder.Decode(mapping, ssOption)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
proxy, err = NewShadowSocks(*ssOption)
|
||||
proxy, err = outbound.NewShadowSocks(*ssOption)
|
||||
case "ssr":
|
||||
ssrOption := &ShadowSocksROption{}
|
||||
ssrOption := &outbound.ShadowSocksROption{}
|
||||
err = decoder.Decode(mapping, ssrOption)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
proxy, err = NewShadowSocksR(*ssrOption)
|
||||
proxy, err = outbound.NewShadowSocksR(*ssrOption)
|
||||
case "socks5":
|
||||
socksOption := &Socks5Option{}
|
||||
socksOption := &outbound.Socks5Option{}
|
||||
err = decoder.Decode(mapping, socksOption)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
proxy = NewSocks5(*socksOption)
|
||||
proxy = outbound.NewSocks5(*socksOption)
|
||||
case "http":
|
||||
httpOption := &HttpOption{}
|
||||
httpOption := &outbound.HttpOption{}
|
||||
err = decoder.Decode(mapping, httpOption)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
proxy = NewHttp(*httpOption)
|
||||
proxy = outbound.NewHttp(*httpOption)
|
||||
case "vmess":
|
||||
vmessOption := &VmessOption{
|
||||
HTTPOpts: HTTPOptions{
|
||||
vmessOption := &outbound.VmessOption{
|
||||
HTTPOpts: outbound.HTTPOptions{
|
||||
Method: "GET",
|
||||
Path: []string{"/"},
|
||||
},
|
||||
@ -58,21 +59,21 @@ func ParseProxy(mapping map[string]interface{}) (C.Proxy, error) {
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
proxy, err = NewVmess(*vmessOption)
|
||||
proxy, err = outbound.NewVmess(*vmessOption)
|
||||
case "snell":
|
||||
snellOption := &SnellOption{}
|
||||
snellOption := &outbound.SnellOption{}
|
||||
err = decoder.Decode(mapping, snellOption)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
proxy, err = NewSnell(*snellOption)
|
||||
proxy, err = outbound.NewSnell(*snellOption)
|
||||
case "trojan":
|
||||
trojanOption := &TrojanOption{}
|
||||
trojanOption := &outbound.TrojanOption{}
|
||||
err = decoder.Decode(mapping, trojanOption)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
proxy, err = NewTrojan(*trojanOption)
|
||||
proxy, err = outbound.NewTrojan(*trojanOption)
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupport proxy type: %s", proxyType)
|
||||
}
|
@ -8,6 +8,7 @@ import (
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
types "github.com/Dreamacro/clash/constant/provider"
|
||||
"github.com/Dreamacro/clash/log"
|
||||
)
|
||||
|
||||
@ -20,7 +21,7 @@ type parser = func([]byte) (interface{}, error)
|
||||
|
||||
type fetcher struct {
|
||||
name string
|
||||
vehicle Vehicle
|
||||
vehicle types.Vehicle
|
||||
updatedAt *time.Time
|
||||
ticker *time.Ticker
|
||||
done chan struct{}
|
||||
@ -33,7 +34,7 @@ func (f *fetcher) Name() string {
|
||||
return f.name
|
||||
}
|
||||
|
||||
func (f *fetcher) VehicleType() VehicleType {
|
||||
func (f *fetcher) VehicleType() types.VehicleType {
|
||||
return f.vehicle.Type()
|
||||
}
|
||||
|
||||
@ -72,9 +73,11 @@ func (f *fetcher) Initial() (interface{}, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
isLocal = false
|
||||
}
|
||||
|
||||
if f.vehicle.Type() != File && !isLocal {
|
||||
if f.vehicle.Type() != types.File && !isLocal {
|
||||
if err := safeWrite(f.vehicle.Path(), buf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -108,7 +111,7 @@ func (f *fetcher) Update() (interface{}, bool, error) {
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
if f.vehicle.Type() != File {
|
||||
if f.vehicle.Type() != types.File {
|
||||
if err := safeWrite(f.vehicle.Path(), buf); err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
@ -165,7 +168,7 @@ func safeWrite(path string, buf []byte) error {
|
||||
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
|
||||
if interval != 0 {
|
||||
ticker = time.NewTicker(interval)
|
@ -2,9 +2,9 @@ package provider
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/Dreamacro/clash/common/batch"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
|
||||
"go.uber.org/atomic"
|
||||
@ -59,20 +59,17 @@ func (hc *HealthCheck) touch() {
|
||||
}
|
||||
|
||||
func (hc *HealthCheck) check() {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), defaultURLTestTimeout)
|
||||
wg := &sync.WaitGroup{}
|
||||
|
||||
b, _ := batch.New(context.Background(), batch.WithConcurrencyNum(10))
|
||||
for _, proxy := range hc.proxies {
|
||||
wg.Add(1)
|
||||
|
||||
go func(p C.Proxy) {
|
||||
p := proxy
|
||||
b.Go(p.Name(), func() (interface{}, error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), defaultURLTestTimeout)
|
||||
defer cancel()
|
||||
p.URLTest(ctx, hc.url)
|
||||
wg.Done()
|
||||
}(proxy)
|
||||
return nil, nil
|
||||
})
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
cancel()
|
||||
b.Wait()
|
||||
}
|
||||
|
||||
func (hc *HealthCheck) close() {
|
@ -7,6 +7,7 @@ import (
|
||||
|
||||
"github.com/Dreamacro/clash/common/structure"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
types "github.com/Dreamacro/clash/constant/provider"
|
||||
)
|
||||
|
||||
var (
|
||||
@ -28,7 +29,7 @@ type proxyProviderSchema struct {
|
||||
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})
|
||||
|
||||
schema := &proxyProviderSchema{
|
||||
@ -40,7 +41,7 @@ func ParseProxyProvider(name string, mapping map[string]interface{}) (ProxyProvi
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var hcInterval uint = 0
|
||||
var hcInterval uint
|
||||
if schema.HealthCheck.Enable {
|
||||
hcInterval = uint(schema.HealthCheck.Interval)
|
||||
}
|
||||
@ -48,7 +49,7 @@ func ParseProxyProvider(name string, mapping map[string]interface{}) (ProxyProvi
|
||||
|
||||
path := C.Path.Resolve(schema.Path)
|
||||
|
||||
var vehicle Vehicle
|
||||
var vehicle types.Vehicle
|
||||
switch schema.Type {
|
||||
case "file":
|
||||
vehicle = NewFileVehicle(path)
|
@ -7,8 +7,9 @@ import (
|
||||
"runtime"
|
||||
"time"
|
||||
|
||||
"github.com/Dreamacro/clash/adapters/outbound"
|
||||
"github.com/Dreamacro/clash/adapter"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
types "github.com/Dreamacro/clash/constant/provider"
|
||||
|
||||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
@ -17,45 +18,6 @@ const (
|
||||
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 {
|
||||
Proxies []map[string]interface{} `yaml:"proxies"`
|
||||
}
|
||||
@ -107,8 +69,8 @@ func (pp *proxySetProvider) Initial() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (pp *proxySetProvider) Type() ProviderType {
|
||||
return Proxy
|
||||
func (pp *proxySetProvider) Type() types.ProviderType {
|
||||
return types.Proxy
|
||||
}
|
||||
|
||||
func (pp *proxySetProvider) Proxies() []C.Proxy {
|
||||
@ -133,7 +95,7 @@ func proxiesParse(buf []byte) (interface{}, error) {
|
||||
|
||||
proxies := []C.Proxy{}
|
||||
for idx, mapping := range schema.Proxies {
|
||||
proxy, err := outbound.ParseProxy(mapping)
|
||||
proxy, err := adapter.ParseProxy(mapping)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("proxy %d error: %w", idx, err)
|
||||
}
|
||||
@ -160,7 +122,7 @@ func stopProxyProvider(pd *ProxySetProvider) {
|
||||
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() {
|
||||
go hc.process()
|
||||
}
|
||||
@ -219,12 +181,12 @@ func (cp *compatibleProvider) Initial() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cp *compatibleProvider) VehicleType() VehicleType {
|
||||
return Compatible
|
||||
func (cp *compatibleProvider) VehicleType() types.VehicleType {
|
||||
return types.Compatible
|
||||
}
|
||||
|
||||
func (cp *compatibleProvider) Type() ProviderType {
|
||||
return Proxy
|
||||
func (cp *compatibleProvider) Type() types.ProviderType {
|
||||
return types.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) {
|
||||
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() {
|
@ -3,48 +3,21 @@ package provider
|
||||
import (
|
||||
"context"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"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 {
|
||||
path string
|
||||
}
|
||||
|
||||
func (f *FileVehicle) Type() VehicleType {
|
||||
return File
|
||||
func (f *FileVehicle) Type() types.VehicleType {
|
||||
return types.File
|
||||
}
|
||||
|
||||
func (f *FileVehicle) Path() string {
|
||||
@ -64,8 +37,8 @@ type HTTPVehicle struct {
|
||||
path string
|
||||
}
|
||||
|
||||
func (h *HTTPVehicle) Type() VehicleType {
|
||||
return HTTP
|
||||
func (h *HTTPVehicle) Type() types.VehicleType {
|
||||
return types.HTTP
|
||||
}
|
||||
|
||||
func (h *HTTPVehicle) Path() string {
|
||||
@ -99,7 +72,9 @@ func (h *HTTPVehicle) Read() ([]byte, error) {
|
||||
IdleConnTimeout: 90 * time.Second,
|
||||
TLSHandshakeTimeout: 10 * 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}
|
@ -1,45 +0,0 @@
|
||||
package inbound
|
||||
|
||||
import (
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/context"
|
||||
)
|
||||
|
||||
// NewHTTP recieve normal http request and return HTTPContext
|
||||
func NewHTTP(request *http.Request, conn net.Conn) *context.HTTPContext {
|
||||
metadata := parseHTTPAddr(request)
|
||||
metadata.Type = C.HTTP
|
||||
if ip, port, err := parseAddr(conn.RemoteAddr().String()); err == nil {
|
||||
metadata.SrcIP = ip
|
||||
metadata.SrcPort = port
|
||||
}
|
||||
return context.NewHTTPContext(conn, request, metadata)
|
||||
}
|
||||
|
||||
// RemoveHopByHopHeaders remove hop-by-hop header
|
||||
func RemoveHopByHopHeaders(header http.Header) {
|
||||
// Strip hop-by-hop header based on RFC:
|
||||
// http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.5.1
|
||||
// https://www.mnot.net/blog/2011/07/11/what_proxies_must_do
|
||||
|
||||
header.Del("Proxy-Connection")
|
||||
header.Del("Proxy-Authenticate")
|
||||
header.Del("Proxy-Authorization")
|
||||
header.Del("TE")
|
||||
header.Del("Trailers")
|
||||
header.Del("Transfer-Encoding")
|
||||
header.Del("Upgrade")
|
||||
|
||||
connections := header.Get("Connection")
|
||||
header.Del("Connection")
|
||||
if len(connections) == 0 {
|
||||
return
|
||||
}
|
||||
for _, h := range strings.Split(connections, ",") {
|
||||
header.Del(strings.TrimSpace(h))
|
||||
}
|
||||
}
|
@ -1,107 +0,0 @@
|
||||
package outbound
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net"
|
||||
"strconv"
|
||||
|
||||
"github.com/Dreamacro/clash/component/dialer"
|
||||
"github.com/Dreamacro/clash/component/trojan"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
)
|
||||
|
||||
type Trojan struct {
|
||||
*Base
|
||||
instance *trojan.Trojan
|
||||
}
|
||||
|
||||
type TrojanOption struct {
|
||||
Name string `proxy:"name"`
|
||||
Server string `proxy:"server"`
|
||||
Port int `proxy:"port"`
|
||||
Password string `proxy:"password"`
|
||||
ALPN []string `proxy:"alpn,omitempty"`
|
||||
SNI string `proxy:"sni,omitempty"`
|
||||
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
|
||||
UDP bool `proxy:"udp,omitempty"`
|
||||
}
|
||||
|
||||
func (t *Trojan) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
||||
c, err := t.instance.StreamConn(c)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s connect error: %w", t.addr, err)
|
||||
}
|
||||
|
||||
err = t.instance.WriteHeader(c, trojan.CommandTCP, serializesSocksAddr(metadata))
|
||||
return c, err
|
||||
}
|
||||
|
||||
func (t *Trojan) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
|
||||
c, err := dialer.DialContext(ctx, "tcp", t.addr)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s connect error: %w", t.addr, err)
|
||||
}
|
||||
tcpKeepAlive(c)
|
||||
c, err = t.StreamConn(c, metadata)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return NewConn(c, t), err
|
||||
}
|
||||
|
||||
func (t *Trojan) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), tcpTimeout)
|
||||
defer cancel()
|
||||
c, err := dialer.DialContext(ctx, "tcp", t.addr)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s connect error: %w", t.addr, err)
|
||||
}
|
||||
tcpKeepAlive(c)
|
||||
c, err = t.instance.StreamConn(c)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s connect error: %w", t.addr, err)
|
||||
}
|
||||
|
||||
err = t.instance.WriteHeader(c, trojan.CommandUDP, serializesSocksAddr(metadata))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pc := t.instance.PacketConn(c)
|
||||
return newPacketConn(pc, t), err
|
||||
}
|
||||
|
||||
func (t *Trojan) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(map[string]string{
|
||||
"type": t.Type().String(),
|
||||
})
|
||||
}
|
||||
|
||||
func NewTrojan(option TrojanOption) (*Trojan, error) {
|
||||
addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port))
|
||||
|
||||
tOption := &trojan.Option{
|
||||
Password: option.Password,
|
||||
ALPN: option.ALPN,
|
||||
ServerName: option.Server,
|
||||
SkipCertVerify: option.SkipCertVerify,
|
||||
ClientSessionCache: getClientSessionCache(),
|
||||
}
|
||||
|
||||
if option.SNI != "" {
|
||||
tOption.ServerName = option.SNI
|
||||
}
|
||||
|
||||
return &Trojan{
|
||||
Base: &Base{
|
||||
name: option.Name,
|
||||
addr: addr,
|
||||
tp: C.Trojan,
|
||||
udp: option.UDP,
|
||||
},
|
||||
instance: trojan.New(tOption),
|
||||
}, nil
|
||||
}
|
105
common/batch/batch.go
Normal file
105
common/batch/batch.go
Normal file
@ -0,0 +1,105 @@
|
||||
package batch
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type Option = func(b *Batch)
|
||||
|
||||
type Result struct {
|
||||
Value interface{}
|
||||
Err error
|
||||
}
|
||||
|
||||
type Error struct {
|
||||
Key string
|
||||
Err error
|
||||
}
|
||||
|
||||
func WithConcurrencyNum(n int) Option {
|
||||
return func(b *Batch) {
|
||||
q := make(chan struct{}, n)
|
||||
for i := 0; i < n; i++ {
|
||||
q <- struct{}{}
|
||||
}
|
||||
b.queue = q
|
||||
}
|
||||
}
|
||||
|
||||
// Batch similar to errgroup, but can control the maximum number of concurrent
|
||||
type Batch struct {
|
||||
result map[string]Result
|
||||
queue chan struct{}
|
||||
wg sync.WaitGroup
|
||||
mux sync.Mutex
|
||||
err *Error
|
||||
once sync.Once
|
||||
cancel func()
|
||||
}
|
||||
|
||||
func (b *Batch) Go(key string, fn func() (interface{}, error)) {
|
||||
b.wg.Add(1)
|
||||
go func() {
|
||||
defer b.wg.Done()
|
||||
if b.queue != nil {
|
||||
<-b.queue
|
||||
defer func() {
|
||||
b.queue <- struct{}{}
|
||||
}()
|
||||
}
|
||||
|
||||
value, err := fn()
|
||||
if err != nil {
|
||||
b.once.Do(func() {
|
||||
b.err = &Error{key, err}
|
||||
if b.cancel != nil {
|
||||
b.cancel()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
ret := Result{value, err}
|
||||
b.mux.Lock()
|
||||
defer b.mux.Unlock()
|
||||
b.result[key] = ret
|
||||
}()
|
||||
}
|
||||
|
||||
func (b *Batch) Wait() *Error {
|
||||
b.wg.Wait()
|
||||
if b.cancel != nil {
|
||||
b.cancel()
|
||||
}
|
||||
return b.err
|
||||
}
|
||||
|
||||
func (b *Batch) WaitAndGetResult() (map[string]Result, *Error) {
|
||||
err := b.Wait()
|
||||
return b.Result(), err
|
||||
}
|
||||
|
||||
func (b *Batch) Result() map[string]Result {
|
||||
b.mux.Lock()
|
||||
defer b.mux.Unlock()
|
||||
copy := map[string]Result{}
|
||||
for k, v := range b.result {
|
||||
copy[k] = v
|
||||
}
|
||||
return copy
|
||||
}
|
||||
|
||||
func New(ctx context.Context, opts ...Option) (*Batch, context.Context) {
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
|
||||
b := &Batch{
|
||||
result: map[string]Result{},
|
||||
}
|
||||
|
||||
for _, o := range opts {
|
||||
o(b)
|
||||
}
|
||||
|
||||
b.cancel = cancel
|
||||
return b, ctx
|
||||
}
|
83
common/batch/batch_test.go
Normal file
83
common/batch/batch_test.go
Normal file
@ -0,0 +1,83 @@
|
||||
package batch
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestBatch(t *testing.T) {
|
||||
b, _ := New(context.Background())
|
||||
|
||||
now := time.Now()
|
||||
b.Go("foo", func() (interface{}, error) {
|
||||
time.Sleep(time.Millisecond * 100)
|
||||
return "foo", nil
|
||||
})
|
||||
b.Go("bar", func() (interface{}, error) {
|
||||
time.Sleep(time.Millisecond * 150)
|
||||
return "bar", nil
|
||||
})
|
||||
result, err := b.WaitAndGetResult()
|
||||
|
||||
assert.Nil(t, err)
|
||||
|
||||
duration := time.Since(now)
|
||||
assert.Less(t, duration, time.Millisecond*200)
|
||||
assert.Equal(t, 2, len(result))
|
||||
|
||||
for k, v := range result {
|
||||
assert.NoError(t, v.Err)
|
||||
assert.Equal(t, k, v.Value.(string))
|
||||
}
|
||||
}
|
||||
|
||||
func TestBatchWithConcurrencyNum(t *testing.T) {
|
||||
b, _ := New(
|
||||
context.Background(),
|
||||
WithConcurrencyNum(3),
|
||||
)
|
||||
|
||||
now := time.Now()
|
||||
for i := 0; i < 7; i++ {
|
||||
idx := i
|
||||
b.Go(strconv.Itoa(idx), func() (interface{}, error) {
|
||||
time.Sleep(time.Millisecond * 100)
|
||||
return strconv.Itoa(idx), nil
|
||||
})
|
||||
}
|
||||
result, _ := b.WaitAndGetResult()
|
||||
duration := time.Since(now)
|
||||
assert.Greater(t, duration, time.Millisecond*260)
|
||||
assert.Equal(t, 7, len(result))
|
||||
|
||||
for k, v := range result {
|
||||
assert.NoError(t, v.Err)
|
||||
assert.Equal(t, k, v.Value.(string))
|
||||
}
|
||||
}
|
||||
|
||||
func TestBatchContext(t *testing.T) {
|
||||
b, ctx := New(context.Background())
|
||||
|
||||
b.Go("error", func() (interface{}, error) {
|
||||
time.Sleep(time.Millisecond * 100)
|
||||
return nil, errors.New("test error")
|
||||
})
|
||||
|
||||
b.Go("ctx", func() (interface{}, error) {
|
||||
<-ctx.Done()
|
||||
return nil, ctx.Err()
|
||||
})
|
||||
|
||||
result, err := b.WaitAndGetResult()
|
||||
|
||||
assert.NotNil(t, err)
|
||||
assert.Equal(t, "error", err.Key)
|
||||
|
||||
assert.Equal(t, ctx.Err(), result["ctx"].Err)
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package mixed
|
||||
package net
|
||||
|
||||
import (
|
||||
"bufio"
|
@ -8,11 +8,7 @@ import (
|
||||
"sync"
|
||||
)
|
||||
|
||||
var defaultAllocator *Allocator
|
||||
|
||||
func init() {
|
||||
defaultAllocator = NewAllocator()
|
||||
}
|
||||
var defaultAllocator = NewAllocator()
|
||||
|
||||
// Allocator for incoming frames, optimized to prevent overwriting after zeroing
|
||||
type Allocator struct {
|
||||
|
@ -1,3 +1,4 @@
|
||||
//go:build !linux
|
||||
// +build !linux
|
||||
|
||||
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 (
|
||||
"net"
|
||||
"syscall"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
|
||||
"github.com/Dreamacro/clash/component/iface"
|
||||
)
|
||||
|
||||
type controlFn = func(network, address string, c syscall.RawConn) error
|
||||
|
||||
func bindControl(ifaceIdx int) controlFn {
|
||||
return func(network, address string, c syscall.RawConn) error {
|
||||
func bindControl(ifaceIdx int, chain controlFn) controlFn {
|
||||
return func(network, address string, c syscall.RawConn) (err error) {
|
||||
defer func() {
|
||||
if err == nil && chain != nil {
|
||||
err = chain(network, address, c)
|
||||
}
|
||||
}()
|
||||
|
||||
ipStr, _, err := net.SplitHostPort(address)
|
||||
if err == nil {
|
||||
ip := net.ParseIP(ipStr)
|
||||
if ip != nil && !ip.IsGlobalUnicast() {
|
||||
return nil
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
return c.Control(func(fd uintptr) {
|
||||
switch network {
|
||||
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":
|
||||
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 {
|
||||
iface, err, _ := ifaceSingle.Do(func() (interface{}, error) {
|
||||
return net.InterfaceByName(ifaceName)
|
||||
})
|
||||
func bindIfaceToDialer(ifaceName string, dialer *net.Dialer, _ string, _ net.IP) error {
|
||||
ifaceObj, err := iface.ResolveInterface(ifaceName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
dialer.Control = bindControl(iface.(*net.Interface).Index)
|
||||
dialer.Control = bindControl(ifaceObj.Index, dialer.Control)
|
||||
return nil
|
||||
}
|
||||
|
||||
func bindIfaceToListenConfig(lc *net.ListenConfig, ifaceName string) error {
|
||||
iface, err, _ := ifaceSingle.Do(func() (interface{}, error) {
|
||||
return net.InterfaceByName(ifaceName)
|
||||
})
|
||||
func bindIfaceToListenConfig(ifaceName string, lc *net.ListenConfig, _, address string) (string, error) {
|
||||
ifaceObj, err := iface.ResolveInterface(ifaceName)
|
||||
if err != nil {
|
||||
return err
|
||||
return "", err
|
||||
}
|
||||
|
||||
lc.Control = bindControl(iface.(*net.Interface).Index)
|
||||
return nil
|
||||
lc.Control = bindControl(ifaceObj.Index, lc.Control)
|
||||
return address, nil
|
||||
}
|
||||
|
@ -3,34 +3,42 @@ package dialer
|
||||
import (
|
||||
"net"
|
||||
"syscall"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
type controlFn = func(network, address string, c syscall.RawConn) error
|
||||
|
||||
func bindControl(ifaceName string) controlFn {
|
||||
return func(network, address string, c syscall.RawConn) error {
|
||||
func bindControl(ifaceName string, chain controlFn) controlFn {
|
||||
return func(network, address string, c syscall.RawConn) (err error) {
|
||||
defer func() {
|
||||
if err == nil && chain != nil {
|
||||
err = chain(network, address, c)
|
||||
}
|
||||
}()
|
||||
|
||||
ipStr, _, err := net.SplitHostPort(address)
|
||||
if err == nil {
|
||||
ip := net.ParseIP(ipStr)
|
||||
if ip != nil && !ip.IsGlobalUnicast() {
|
||||
return nil
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
return c.Control(func(fd uintptr) {
|
||||
syscall.BindToDevice(int(fd), ifaceName)
|
||||
unix.BindToDevice(int(fd), ifaceName)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func bindIfaceToDialer(dialer *net.Dialer, ifaceName string) error {
|
||||
dialer.Control = bindControl(ifaceName)
|
||||
func bindIfaceToDialer(ifaceName string, dialer *net.Dialer, _ string, _ net.IP) error {
|
||||
dialer.Control = bindControl(ifaceName, dialer.Control)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func bindIfaceToListenConfig(lc *net.ListenConfig, ifaceName string) error {
|
||||
lc.Control = bindControl(ifaceName)
|
||||
func bindIfaceToListenConfig(ifaceName string, lc *net.ListenConfig, _, address string) (string, error) {
|
||||
lc.Control = bindControl(ifaceName, lc.Control)
|
||||
|
||||
return nil
|
||||
return address, nil
|
||||
}
|
||||
|
@ -1,13 +1,93 @@
|
||||
//go:build !linux && !darwin
|
||||
// +build !linux,!darwin
|
||||
|
||||
package dialer
|
||||
|
||||
import "net"
|
||||
import (
|
||||
"net"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
func bindIfaceToDialer(dialer *net.Dialer, ifaceName string) error {
|
||||
return errPlatformNotSupport
|
||||
"github.com/Dreamacro/clash/component/iface"
|
||||
)
|
||||
|
||||
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 {
|
||||
return errPlatformNotSupport
|
||||
func bindIfaceToDialer(ifaceName string, dialer *net.Dialer, network string, destination net.IP) error {
|
||||
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"
|
||||
)
|
||||
|
||||
func Dialer() (*net.Dialer, 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) {
|
||||
func DialContext(ctx context.Context, network, address string, options ...Option) (net.Conn, error) {
|
||||
switch network {
|
||||
case "tcp4", "tcp6", "udp4", "udp6":
|
||||
host, port, err := net.SplitHostPort(address)
|
||||
@ -31,11 +16,6 @@ func DialContext(ctx context.Context, network, address string) (net.Conn, error)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
dialer, err := Dialer()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var ip net.IP
|
||||
switch network {
|
||||
case "tcp4", "udp4":
|
||||
@ -43,38 +23,70 @@ func DialContext(ctx context.Context, network, address string) (net.Conn, error)
|
||||
default:
|
||||
ip, err = resolver.ResolveIPv6(host)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if DialHook != nil {
|
||||
if err := DialHook(dialer, network, ip); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return dialer.DialContext(ctx, network, net.JoinHostPort(ip.String(), port))
|
||||
return dialContext(ctx, network, ip, port, options)
|
||||
case "tcp", "udp":
|
||||
return dualStackDialContext(ctx, network, address)
|
||||
return dualStackDialContext(ctx, network, address, options)
|
||||
default:
|
||||
return nil, errors.New("network invalid")
|
||||
}
|
||||
}
|
||||
|
||||
func ListenPacket(network, address string) (net.PacketConn, error) {
|
||||
cfg := &net.ListenConfig{}
|
||||
if ListenPacketHook != nil {
|
||||
var err error
|
||||
address, err = ListenPacketHook(cfg, address)
|
||||
func ListenPacket(ctx context.Context, network, address string, options ...Option) (net.PacketConn, error) {
|
||||
cfg := &config{}
|
||||
|
||||
if !cfg.skipDefault {
|
||||
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 {
|
||||
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)
|
||||
if err != nil {
|
||||
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
|
||||
if ipv6 {
|
||||
ip, result.error = resolver.ResolveIPv6(host)
|
||||
@ -122,12 +128,7 @@ func dualStackDialContext(ctx context.Context, network, address string) (net.Con
|
||||
}
|
||||
result.resolved = true
|
||||
|
||||
if DialHook != nil {
|
||||
if result.error = DialHook(dialer, network, ip); result.error != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
result.Conn, result.error = dialer.DialContext(ctx, network, net.JoinHostPort(ip.String(), port))
|
||||
result.Conn, result.error = dialContext(ctx, network, ip, port, options)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
@ -1,12 +1,13 @@
|
||||
package process
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"net"
|
||||
"path/filepath"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -94,12 +95,8 @@ func getExecPathFromPID(pid uint32) (string, error) {
|
||||
if errno != 0 {
|
||||
return "", errno
|
||||
}
|
||||
firstZero := bytes.IndexByte(buf, 0)
|
||||
if firstZero <= 0 {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
return filepath.Base(string(buf[:firstZero])), nil
|
||||
return filepath.Base(unix.ByteSliceToString(buf)), nil
|
||||
}
|
||||
|
||||
func readNativeUint32(b []byte) uint32 {
|
||||
|
@ -30,6 +30,10 @@ func findProcessName(network string, ip net.IP, srcPort int) (string, error) {
|
||||
}
|
||||
})
|
||||
|
||||
if defaultSearcher == nil {
|
||||
return "", ErrPlatformNotSupport
|
||||
}
|
||||
|
||||
var spath string
|
||||
isTCP := network == TCP
|
||||
switch network {
|
||||
@ -173,7 +177,7 @@ func (s *searcher) searchSocketPid(socket uint64) (uint32, error) {
|
||||
}
|
||||
|
||||
func newSearcher(major int) *searcher {
|
||||
var s *searcher = nil
|
||||
var s *searcher
|
||||
switch major {
|
||||
case 11:
|
||||
s = &searcher{
|
||||
@ -190,6 +194,8 @@ func newSearcher(major int) *searcher {
|
||||
udpInpOffset: 8,
|
||||
}
|
||||
case 12:
|
||||
fallthrough
|
||||
case 13:
|
||||
s = &searcher{
|
||||
headSize: 64,
|
||||
tcpItemSize: 744,
|
||||
|
@ -16,14 +16,14 @@ import (
|
||||
)
|
||||
|
||||
// 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
|
||||
if *(*byte)(unsafe.Pointer(&x)) == 0x01 {
|
||||
nativeEndian = binary.BigEndian
|
||||
} else {
|
||||
nativeEndian = binary.LittleEndian
|
||||
return binary.BigEndian
|
||||
}
|
||||
}
|
||||
|
||||
return binary.LittleEndian
|
||||
}()
|
||||
|
||||
type SocketResolver func(network string, ip net.IP, srcPort int) (inode, uid int, err error)
|
||||
type ProcessNameResolver func(inode, uid int) (name string, err error)
|
||||
@ -40,8 +40,6 @@ const (
|
||||
pathProc = "/proc"
|
||||
)
|
||||
|
||||
var nativeEndian binary.ByteOrder = binary.LittleEndian
|
||||
|
||||
func findProcessName(network string, ip net.IP, srcPort int) (string, error) {
|
||||
inode, uid, err := DefaultSocketResolver(network, ip, srcPort)
|
||||
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
|
||||
|
||||
package process
|
||||
|
@ -22,8 +22,8 @@ const (
|
||||
)
|
||||
|
||||
var (
|
||||
getExTcpTable uintptr
|
||||
getExUdpTable uintptr
|
||||
getExTCPTable uintptr
|
||||
getExUDPTable uintptr
|
||||
queryProcName uintptr
|
||||
|
||||
once sync.Once
|
||||
@ -35,12 +35,12 @@ func initWin32API() error {
|
||||
return fmt.Errorf("LoadLibrary iphlpapi.dll failed: %s", err.Error())
|
||||
}
|
||||
|
||||
getExTcpTable, err = windows.GetProcAddress(h, tcpTableFunc)
|
||||
getExTCPTable, err = windows.GetProcAddress(h, tcpTableFunc)
|
||||
if err != nil {
|
||||
return fmt.Errorf("GetProcAddress of %s failed: %s", tcpTableFunc, err.Error())
|
||||
}
|
||||
|
||||
getExUdpTable, err = windows.GetProcAddress(h, udpTableFunc)
|
||||
getExUDPTable, err = windows.GetProcAddress(h, udpTableFunc)
|
||||
if err != nil {
|
||||
return fmt.Errorf("GetProcAddress of %s failed: %s", udpTableFunc, err.Error())
|
||||
}
|
||||
@ -76,10 +76,10 @@ func findProcessName(network string, ip net.IP, srcPort int) (string, error) {
|
||||
var fn uintptr
|
||||
switch network {
|
||||
case TCP:
|
||||
fn = getExTcpTable
|
||||
fn = getExTCPTable
|
||||
class = tcpTablePidConn
|
||||
case UDP:
|
||||
fn = getExUdpTable
|
||||
fn = getExUDPTable
|
||||
class = udpTablePid
|
||||
default:
|
||||
return "", ErrInvalidNetwork
|
||||
|
@ -1,9 +1,12 @@
|
||||
package resolver
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"math/rand"
|
||||
"net"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/Dreamacro/clash/component/trie"
|
||||
)
|
||||
@ -18,6 +21,9 @@ var (
|
||||
|
||||
// DefaultHosts aim to resolve hosts
|
||||
DefaultHosts = trie.New()
|
||||
|
||||
// DefaultDNSTimeout defined the default dns request timeout
|
||||
DefaultDNSTimeout = time.Second * 5
|
||||
)
|
||||
|
||||
var (
|
||||
@ -52,18 +58,16 @@ func ResolveIPv4(host string) (net.IP, error) {
|
||||
return DefaultResolver.ResolveIPv4(host)
|
||||
}
|
||||
|
||||
ipAddrs, err := net.LookupIP(host)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), DefaultDNSTimeout)
|
||||
defer cancel()
|
||||
ipAddrs, err := net.DefaultResolver.LookupIP(ctx, "ip4", host)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if len(ipAddrs) == 0 {
|
||||
return nil, ErrIPNotFound
|
||||
}
|
||||
|
||||
for _, ip := range ipAddrs {
|
||||
if ip4 := ip.To4(); ip4 != nil {
|
||||
return ip4, nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, ErrIPNotFound
|
||||
return ipAddrs[rand.Intn(len(ipAddrs))], nil
|
||||
}
|
||||
|
||||
// ResolveIPv6 with a host, return ipv6
|
||||
@ -90,31 +94,29 @@ func ResolveIPv6(host string) (net.IP, error) {
|
||||
return DefaultResolver.ResolveIPv6(host)
|
||||
}
|
||||
|
||||
ipAddrs, err := net.LookupIP(host)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), DefaultDNSTimeout)
|
||||
defer cancel()
|
||||
ipAddrs, err := net.DefaultResolver.LookupIP(ctx, "ip6", host)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if len(ipAddrs) == 0 {
|
||||
return nil, ErrIPNotFound
|
||||
}
|
||||
|
||||
for _, ip := range ipAddrs {
|
||||
if ip.To4() == nil {
|
||||
return ip, nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, ErrIPNotFound
|
||||
return ipAddrs[rand.Intn(len(ipAddrs))], nil
|
||||
}
|
||||
|
||||
// ResolveIP with a host, return ip
|
||||
func ResolveIP(host string) (net.IP, error) {
|
||||
// ResolveIPWithResolver same as ResolveIP, but with a resolver
|
||||
func ResolveIPWithResolver(host string, r Resolver) (net.IP, error) {
|
||||
if node := DefaultHosts.Search(host); node != nil {
|
||||
return node.Data.(net.IP), nil
|
||||
}
|
||||
|
||||
if DefaultResolver != nil {
|
||||
if r != nil {
|
||||
if DisableIPv6 {
|
||||
return DefaultResolver.ResolveIPv4(host)
|
||||
return r.ResolveIPv4(host)
|
||||
}
|
||||
return DefaultResolver.ResolveIP(host)
|
||||
return r.ResolveIP(host)
|
||||
} else if DisableIPv6 {
|
||||
return ResolveIPv4(host)
|
||||
}
|
||||
@ -131,3 +133,8 @@ func ResolveIP(host string) (net.IP, error) {
|
||||
|
||||
return ipAddr.IP, nil
|
||||
}
|
||||
|
||||
// ResolveIP with a host, return ip
|
||||
func ResolveIP(host string) (net.IP, error) {
|
||||
return ResolveIPWithResolver(host, DefaultResolver)
|
||||
}
|
||||
|
@ -23,7 +23,7 @@ type DomainTrie struct {
|
||||
root *Node
|
||||
}
|
||||
|
||||
func validAndSplitDomain(domain string) ([]string, bool) {
|
||||
func ValidAndSplitDomain(domain string) ([]string, bool) {
|
||||
if domain != "" && domain[len(domain)-1] == '.' {
|
||||
return nil, false
|
||||
}
|
||||
@ -54,7 +54,7 @@ func validAndSplitDomain(domain string) ([]string, bool) {
|
||||
// 4. .example.com
|
||||
// 5. +.example.com
|
||||
func (t *DomainTrie) Insert(domain string, data interface{}) error {
|
||||
parts, valid := validAndSplitDomain(domain)
|
||||
parts, valid := ValidAndSplitDomain(domain)
|
||||
if !valid {
|
||||
return ErrInvalidDomain
|
||||
}
|
||||
@ -91,7 +91,7 @@ func (t *DomainTrie) insert(parts []string, data interface{}) {
|
||||
// 2. wildcard domain
|
||||
// 2. dot wildcard domain
|
||||
func (t *DomainTrie) Search(domain string) *Node {
|
||||
parts, valid := validAndSplitDomain(domain)
|
||||
parts, valid := ValidAndSplitDomain(domain)
|
||||
if !valid || parts[0] == "" {
|
||||
return nil
|
||||
}
|
||||
@ -122,11 +122,7 @@ func (t *DomainTrie) search(node *Node, parts []string) *Node {
|
||||
}
|
||||
}
|
||||
|
||||
if c := node.getChild(dotWildcard); c != nil {
|
||||
return c
|
||||
}
|
||||
|
||||
return nil
|
||||
return node.getChild(dotWildcard)
|
||||
}
|
||||
|
||||
// New returns a new, empty Trie.
|
||||
|
@ -1,169 +0,0 @@
|
||||
package vmess
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
)
|
||||
|
||||
type websocketConn struct {
|
||||
conn *websocket.Conn
|
||||
reader io.Reader
|
||||
remoteAddr net.Addr
|
||||
|
||||
// https://godoc.org/github.com/gorilla/websocket#hdr-Concurrency
|
||||
rMux sync.Mutex
|
||||
wMux sync.Mutex
|
||||
}
|
||||
|
||||
type WebsocketConfig struct {
|
||||
Host string
|
||||
Port string
|
||||
Path string
|
||||
Headers http.Header
|
||||
TLS bool
|
||||
SkipCertVerify bool
|
||||
ServerName string
|
||||
SessionCache tls.ClientSessionCache
|
||||
}
|
||||
|
||||
// Read implements net.Conn.Read()
|
||||
func (wsc *websocketConn) Read(b []byte) (int, error) {
|
||||
wsc.rMux.Lock()
|
||||
defer wsc.rMux.Unlock()
|
||||
for {
|
||||
reader, err := wsc.getReader()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
nBytes, err := reader.Read(b)
|
||||
if err == io.EOF {
|
||||
wsc.reader = nil
|
||||
continue
|
||||
}
|
||||
return nBytes, err
|
||||
}
|
||||
}
|
||||
|
||||
// Write implements io.Writer.
|
||||
func (wsc *websocketConn) Write(b []byte) (int, error) {
|
||||
wsc.wMux.Lock()
|
||||
defer wsc.wMux.Unlock()
|
||||
if err := wsc.conn.WriteMessage(websocket.BinaryMessage, b); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return len(b), nil
|
||||
}
|
||||
|
||||
func (wsc *websocketConn) Close() error {
|
||||
var errors []string
|
||||
if err := wsc.conn.WriteControl(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, ""), time.Now().Add(time.Second*5)); err != nil {
|
||||
errors = append(errors, err.Error())
|
||||
}
|
||||
if err := wsc.conn.Close(); err != nil {
|
||||
errors = append(errors, err.Error())
|
||||
}
|
||||
if len(errors) > 0 {
|
||||
return fmt.Errorf("failed to close connection: %s", strings.Join(errors, ","))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (wsc *websocketConn) getReader() (io.Reader, error) {
|
||||
if wsc.reader != nil {
|
||||
return wsc.reader, nil
|
||||
}
|
||||
|
||||
_, reader, err := wsc.conn.NextReader()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
wsc.reader = reader
|
||||
return reader, nil
|
||||
}
|
||||
|
||||
func (wsc *websocketConn) LocalAddr() net.Addr {
|
||||
return wsc.conn.LocalAddr()
|
||||
}
|
||||
|
||||
func (wsc *websocketConn) RemoteAddr() net.Addr {
|
||||
return wsc.remoteAddr
|
||||
}
|
||||
|
||||
func (wsc *websocketConn) SetDeadline(t time.Time) error {
|
||||
if err := wsc.SetReadDeadline(t); err != nil {
|
||||
return err
|
||||
}
|
||||
return wsc.SetWriteDeadline(t)
|
||||
}
|
||||
|
||||
func (wsc *websocketConn) SetReadDeadline(t time.Time) error {
|
||||
return wsc.conn.SetReadDeadline(t)
|
||||
}
|
||||
|
||||
func (wsc *websocketConn) SetWriteDeadline(t time.Time) error {
|
||||
return wsc.conn.SetWriteDeadline(t)
|
||||
}
|
||||
|
||||
func StreamWebsocketConn(conn net.Conn, c *WebsocketConfig) (net.Conn, error) {
|
||||
dialer := &websocket.Dialer{
|
||||
NetDial: func(network, addr string) (net.Conn, error) {
|
||||
return conn, nil
|
||||
},
|
||||
ReadBufferSize: 4 * 1024,
|
||||
WriteBufferSize: 4 * 1024,
|
||||
HandshakeTimeout: time.Second * 8,
|
||||
}
|
||||
|
||||
scheme := "ws"
|
||||
if c.TLS {
|
||||
scheme = "wss"
|
||||
dialer.TLSClientConfig = &tls.Config{
|
||||
ServerName: c.Host,
|
||||
InsecureSkipVerify: c.SkipCertVerify,
|
||||
ClientSessionCache: c.SessionCache,
|
||||
}
|
||||
|
||||
if c.ServerName != "" {
|
||||
dialer.TLSClientConfig.ServerName = c.ServerName
|
||||
} else if host := c.Headers.Get("Host"); host != "" {
|
||||
dialer.TLSClientConfig.ServerName = host
|
||||
}
|
||||
}
|
||||
|
||||
uri := url.URL{
|
||||
Scheme: scheme,
|
||||
Host: net.JoinHostPort(c.Host, c.Port),
|
||||
Path: c.Path,
|
||||
}
|
||||
|
||||
headers := http.Header{}
|
||||
if c.Headers != nil {
|
||||
for k := range c.Headers {
|
||||
headers.Add(k, c.Headers.Get(k))
|
||||
}
|
||||
}
|
||||
|
||||
wsConn, resp, err := dialer.Dial(uri.String(), headers)
|
||||
if err != nil {
|
||||
reason := err.Error()
|
||||
if resp != nil {
|
||||
reason = resp.Status
|
||||
}
|
||||
return nil, fmt.Errorf("dial %s error: %s", uri.Host, reason)
|
||||
}
|
||||
|
||||
return &websocketConn{
|
||||
conn: wsConn,
|
||||
remoteAddr: conn.RemoteAddr(),
|
||||
}, nil
|
||||
}
|
@ -8,16 +8,18 @@ import (
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/Dreamacro/clash/adapters/outbound"
|
||||
"github.com/Dreamacro/clash/adapters/outboundgroup"
|
||||
"github.com/Dreamacro/clash/adapters/provider"
|
||||
"github.com/Dreamacro/clash/adapter"
|
||||
"github.com/Dreamacro/clash/adapter/outbound"
|
||||
"github.com/Dreamacro/clash/adapter/outboundgroup"
|
||||
"github.com/Dreamacro/clash/adapter/provider"
|
||||
"github.com/Dreamacro/clash/component/auth"
|
||||
"github.com/Dreamacro/clash/component/fakeip"
|
||||
"github.com/Dreamacro/clash/component/trie"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
providerTypes "github.com/Dreamacro/clash/constant/provider"
|
||||
"github.com/Dreamacro/clash/dns"
|
||||
"github.com/Dreamacro/clash/log"
|
||||
R "github.com/Dreamacro/clash/rules"
|
||||
R "github.com/Dreamacro/clash/rule"
|
||||
T "github.com/Dreamacro/clash/tunnel"
|
||||
|
||||
yaml "gopkg.in/yaml.v2"
|
||||
@ -64,13 +66,15 @@ type DNS struct {
|
||||
DefaultNameserver []dns.NameServer `yaml:"default-nameserver"`
|
||||
FakeIPRange *fakeip.Pool
|
||||
Hosts *trie.DomainTrie
|
||||
NameServerPolicy map[string]dns.NameServer
|
||||
}
|
||||
|
||||
// FallbackFilter config
|
||||
type FallbackFilter struct {
|
||||
GeoIP bool `yaml:"geoip"`
|
||||
IPCIDR []*net.IPNet `yaml:"ipcidr"`
|
||||
Domain []string `yaml:"domain"`
|
||||
GeoIP bool `yaml:"geoip"`
|
||||
GeoIPCode string `yaml:"geoip-code"`
|
||||
IPCIDR []*net.IPNet `yaml:"ipcidr"`
|
||||
Domain []string `yaml:"domain"`
|
||||
}
|
||||
|
||||
// Profile config
|
||||
@ -91,7 +95,7 @@ type Config struct {
|
||||
Rules []C.Rule
|
||||
Users []auth.AuthUser
|
||||
Proxies map[string]C.Proxy
|
||||
Providers map[string]provider.ProxyProvider
|
||||
Providers map[string]providerTypes.ProxyProvider
|
||||
}
|
||||
|
||||
type RawDNS struct {
|
||||
@ -106,12 +110,14 @@ type RawDNS struct {
|
||||
FakeIPRange string `yaml:"fake-ip-range"`
|
||||
FakeIPFilter []string `yaml:"fake-ip-filter"`
|
||||
DefaultNameserver []string `yaml:"default-nameserver"`
|
||||
NameServerPolicy map[string]string `yaml:"nameserver-policy"`
|
||||
}
|
||||
|
||||
type RawFallbackFilter struct {
|
||||
GeoIP bool `yaml:"geoip"`
|
||||
IPCIDR []string `yaml:"ipcidr"`
|
||||
Domain []string `yaml:"domain"`
|
||||
GeoIP bool `yaml:"geoip"`
|
||||
GeoIPCode string `yaml:"geoip-code"`
|
||||
IPCIDR []string `yaml:"ipcidr"`
|
||||
Domain []string `yaml:"domain"`
|
||||
}
|
||||
|
||||
type RawConfig struct {
|
||||
@ -168,8 +174,9 @@ func UnmarshalRawConfig(buf []byte) (*RawConfig, error) {
|
||||
UseHosts: true,
|
||||
FakeIPRange: "198.18.0.1/16",
|
||||
FallbackFilter: RawFallbackFilter{
|
||||
GeoIP: true,
|
||||
IPCIDR: []string{},
|
||||
GeoIP: true,
|
||||
GeoIPCode: "CN",
|
||||
IPCIDR: []string{},
|
||||
},
|
||||
DefaultNameserver: []string{
|
||||
"114.114.114.114",
|
||||
@ -264,21 +271,21 @@ func parseGeneral(cfg *RawConfig) (*General, error) {
|
||||
}, 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)
|
||||
providersMap = make(map[string]provider.ProxyProvider)
|
||||
providersMap = make(map[string]providerTypes.ProxyProvider)
|
||||
proxyList := []string{}
|
||||
proxiesConfig := cfg.Proxy
|
||||
groupsConfig := cfg.ProxyGroup
|
||||
providersConfig := cfg.ProxyProvider
|
||||
|
||||
proxies["DIRECT"] = outbound.NewProxy(outbound.NewDirect())
|
||||
proxies["REJECT"] = outbound.NewProxy(outbound.NewReject())
|
||||
proxies["DIRECT"] = adapter.NewProxy(outbound.NewDirect())
|
||||
proxies["REJECT"] = adapter.NewProxy(outbound.NewReject())
|
||||
proxyList = append(proxyList, "DIRECT", "REJECT")
|
||||
|
||||
// parse proxy
|
||||
for idx, mapping := range proxiesConfig {
|
||||
proxy, err := outbound.ParseProxy(mapping)
|
||||
proxy, err := adapter.ParseProxy(mapping)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("proxy %d: %w", idx, err)
|
||||
}
|
||||
@ -337,12 +344,12 @@ func parseProxies(cfg *RawConfig) (proxies map[string]C.Proxy, providersMap map[
|
||||
return nil, nil, fmt.Errorf("proxy group %s: the duplicate name", groupName)
|
||||
}
|
||||
|
||||
proxies[groupName] = outbound.NewProxy(group)
|
||||
proxies[groupName] = adapter.NewProxy(group)
|
||||
}
|
||||
|
||||
// initial compatible provider
|
||||
for _, pd := range providersMap {
|
||||
if pd.VehicleType() != provider.Compatible {
|
||||
if pd.VehicleType() != providerTypes.Compatible {
|
||||
continue
|
||||
}
|
||||
|
||||
@ -364,9 +371,9 @@ func parseProxies(cfg *RawConfig) (proxies map[string]C.Proxy, providersMap map[
|
||||
&outboundgroup.GroupCommonOption{
|
||||
Name: "GLOBAL",
|
||||
},
|
||||
[]provider.ProxyProvider{pd},
|
||||
[]providerTypes.ProxyProvider{pd},
|
||||
)
|
||||
proxies["GLOBAL"] = outbound.NewProxy(global)
|
||||
proxies["GLOBAL"] = adapter.NewProxy(global)
|
||||
return proxies, providersMap, nil
|
||||
}
|
||||
|
||||
@ -481,6 +488,9 @@ func parseNameServer(servers []string) ([]dns.NameServer, error) {
|
||||
clearURL := url.URL{Scheme: "https", Host: u.Host, Path: u.Path}
|
||||
addr = clearURL.String()
|
||||
dnsNetType = "https" // DNS over HTTPS
|
||||
case "dhcp":
|
||||
addr = u.Host
|
||||
dnsNetType = "dhcp" // UDP from DHCP
|
||||
default:
|
||||
return nil, fmt.Errorf("DNS NameServer[%d] unsupport scheme: %s", idx, u.Scheme)
|
||||
}
|
||||
@ -500,6 +510,23 @@ func parseNameServer(servers []string) ([]dns.NameServer, error) {
|
||||
return nameservers, nil
|
||||
}
|
||||
|
||||
func parseNameServerPolicy(nsPolicy map[string]string) (map[string]dns.NameServer, error) {
|
||||
policy := map[string]dns.NameServer{}
|
||||
|
||||
for domain, server := range nsPolicy {
|
||||
nameservers, err := parseNameServer([]string{server})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if _, valid := trie.ValidAndSplitDomain(domain); !valid {
|
||||
return nil, fmt.Errorf("DNS ResoverRule invalid domain: %s", domain)
|
||||
}
|
||||
policy[domain] = nameservers[0]
|
||||
}
|
||||
|
||||
return policy, nil
|
||||
}
|
||||
|
||||
func parseFallbackIPCIDR(ips []string) ([]*net.IPNet, error) {
|
||||
ipNets := []*net.IPNet{}
|
||||
|
||||
@ -537,6 +564,10 @@ func parseDNS(cfg RawDNS, hosts *trie.DomainTrie) (*DNS, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if dnsCfg.NameServerPolicy, err = parseNameServerPolicy(cfg.NameServerPolicy); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(cfg.DefaultNameserver) == 0 {
|
||||
return nil, errors.New("default nameserver should have at least one nameserver")
|
||||
}
|
||||
@ -575,6 +606,7 @@ func parseDNS(cfg RawDNS, hosts *trie.DomainTrie) (*DNS, error) {
|
||||
}
|
||||
|
||||
dnsCfg.FallbackFilter.GeoIP = cfg.FallbackFilter.GeoIP
|
||||
dnsCfg.FallbackFilter.GeoIPCode = cfg.FallbackFilter.GeoIPCode
|
||||
if fallbackip, err := parseFallbackIPCIDR(cfg.FallbackFilter.IPCIDR); err == nil {
|
||||
dnsCfg.FallbackFilter.IPCIDR = fallbackip
|
||||
}
|
||||
|
@ -66,7 +66,7 @@ func Init(dir string) error {
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't create file %s: %s", C.Path.Config(), err.Error())
|
||||
}
|
||||
f.Write([]byte(`port: 7890`))
|
||||
f.Write([]byte(`mixed-port: 7890`))
|
||||
f.Close()
|
||||
}
|
||||
|
||||
|
@ -4,7 +4,7 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/Dreamacro/clash/adapters/outboundgroup"
|
||||
"github.com/Dreamacro/clash/adapter/outboundgroup"
|
||||
"github.com/Dreamacro/clash/common/structure"
|
||||
)
|
||||
|
||||
|
@ -27,6 +27,10 @@ const (
|
||||
LoadBalance
|
||||
)
|
||||
|
||||
const (
|
||||
DefaultTCPTimeout = 5 * time.Second
|
||||
)
|
||||
|
||||
type Connection interface {
|
||||
Chains() Chain
|
||||
AppendToChains(adapter ProxyAdapter)
|
||||
@ -69,8 +73,21 @@ type PacketConn interface {
|
||||
type ProxyAdapter interface {
|
||||
Name() string
|
||||
Type() AdapterType
|
||||
|
||||
// StreamConn wraps a protocol around net.Conn with Metadata.
|
||||
//
|
||||
// Examples:
|
||||
// conn, _ := net.Dial("tcp", "host:port")
|
||||
// conn, _ = adapter.StreamConn(conn, metadata)
|
||||
//
|
||||
// It returns a C.Conn with protocol which start with
|
||||
// a new session (if any)
|
||||
StreamConn(c net.Conn, metadata *Metadata) (net.Conn, error)
|
||||
|
||||
// DialContext return a C.Conn with protocol which
|
||||
// contains multiplexing-related reuse logic (if any)
|
||||
DialContext(ctx context.Context, metadata *Metadata) (Conn, error)
|
||||
|
||||
DialUDP(metadata *Metadata) (PacketConn, error)
|
||||
SupportUDP() bool
|
||||
MarshalJSON() ([]byte, error)
|
||||
|
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
|
||||
HTTPCONNECT
|
||||
SOCKS
|
||||
SOCKS4
|
||||
SOCKS5
|
||||
REDIR
|
||||
TPROXY
|
||||
)
|
||||
@ -43,7 +44,9 @@ func (t Type) String() string {
|
||||
return "HTTP"
|
||||
case HTTPCONNECT:
|
||||
return "HTTP Connect"
|
||||
case SOCKS:
|
||||
case SOCKS4:
|
||||
return "Socks4"
|
||||
case SOCKS5:
|
||||
return "Socks5"
|
||||
case REDIR:
|
||||
return "Redir"
|
||||
|
@ -9,21 +9,19 @@ import (
|
||||
const Name = "clash"
|
||||
|
||||
// Path is used to get the configuration path
|
||||
var Path *path
|
||||
|
||||
type path struct {
|
||||
homeDir string
|
||||
configFile string
|
||||
}
|
||||
|
||||
func init() {
|
||||
var Path = func() *path {
|
||||
homeDir, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
homeDir, _ = os.Getwd()
|
||||
}
|
||||
|
||||
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
|
||||
|
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
|
||||
}
|
@ -1,47 +0,0 @@
|
||||
package context
|
||||
|
||||
import (
|
||||
"net"
|
||||
"net/http"
|
||||
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
|
||||
"github.com/gofrs/uuid"
|
||||
)
|
||||
|
||||
type HTTPContext struct {
|
||||
id uuid.UUID
|
||||
metadata *C.Metadata
|
||||
conn net.Conn
|
||||
req *http.Request
|
||||
}
|
||||
|
||||
func NewHTTPContext(conn net.Conn, req *http.Request, metadata *C.Metadata) *HTTPContext {
|
||||
id, _ := uuid.NewV4()
|
||||
return &HTTPContext{
|
||||
id: id,
|
||||
metadata: metadata,
|
||||
conn: conn,
|
||||
req: req,
|
||||
}
|
||||
}
|
||||
|
||||
// ID implement C.ConnContext ID
|
||||
func (hc *HTTPContext) ID() uuid.UUID {
|
||||
return hc.id
|
||||
}
|
||||
|
||||
// Metadata implement C.ConnContext Metadata
|
||||
func (hc *HTTPContext) Metadata() *C.Metadata {
|
||||
return hc.metadata
|
||||
}
|
||||
|
||||
// Conn implement C.ConnContext Conn
|
||||
func (hc *HTTPContext) Conn() net.Conn {
|
||||
return hc.conn
|
||||
}
|
||||
|
||||
// Request return the http request struct
|
||||
func (hc *HTTPContext) Request() *http.Request {
|
||||
return hc.req
|
||||
}
|
@ -2,11 +2,13 @@ package dns
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
|
||||
"github.com/Dreamacro/clash/component/dialer"
|
||||
"github.com/Dreamacro/clash/component/resolver"
|
||||
|
||||
D "github.com/miekg/dns"
|
||||
)
|
||||
@ -28,28 +30,21 @@ func (c *client) ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg, err
|
||||
// a default ip dns
|
||||
ip = net.ParseIP(c.host)
|
||||
} else {
|
||||
var err error
|
||||
if ip, err = c.r.ResolveIP(c.host); err != nil {
|
||||
if ip, err = resolver.ResolveIPWithResolver(c.host, c.r); err != nil {
|
||||
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 {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
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
|
||||
defer conn.Close()
|
||||
|
||||
// miekg/dns ExchangeContext doesn't respond to context cancel.
|
||||
// this is a workaround
|
||||
@ -59,7 +54,17 @@ func (c *client) ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg, err
|
||||
}
|
||||
ch := make(chan result, 1)
|
||||
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}
|
||||
}()
|
||||
|
||||
|
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}
|
||||
}
|
@ -3,12 +3,12 @@ package dns
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
|
||||
"github.com/Dreamacro/clash/component/dialer"
|
||||
"github.com/Dreamacro/clash/component/resolver"
|
||||
|
||||
D "github.com/miekg/dns"
|
||||
)
|
||||
@ -75,7 +75,6 @@ func newDoHClient(url string, r *Resolver) *dohClient {
|
||||
return &dohClient{
|
||||
url: url,
|
||||
transport: &http.Transport{
|
||||
TLSClientConfig: &tls.Config{ClientSessionCache: globalSessionCache},
|
||||
ForceAttemptHTTP2: true,
|
||||
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||
host, port, err := net.SplitHostPort(addr)
|
||||
@ -83,12 +82,12 @@ func newDoHClient(url string, r *Resolver) *dohClient {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ip, err := r.ResolveIPv4(host)
|
||||
ip, err := resolver.ResolveIPWithResolver(host, r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return dialer.DialContext(ctx, "tcp4", net.JoinHostPort(ip.String(), port))
|
||||
return dialer.DialContext(ctx, "tcp", net.JoinHostPort(ip.String(), port))
|
||||
},
|
||||
},
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ package dns
|
||||
|
||||
import (
|
||||
"net"
|
||||
"strings"
|
||||
|
||||
"github.com/Dreamacro/clash/component/mmdb"
|
||||
"github.com/Dreamacro/clash/component/trie"
|
||||
@ -11,11 +12,13 @@ type fallbackIPFilter interface {
|
||||
Match(net.IP) bool
|
||||
}
|
||||
|
||||
type geoipFilter struct{}
|
||||
type geoipFilter struct {
|
||||
code string
|
||||
}
|
||||
|
||||
func (gf *geoipFilter) Match(ip net.IP) bool {
|
||||
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 {
|
||||
|
@ -2,7 +2,6 @@ package dns
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
@ -20,10 +19,6 @@ import (
|
||||
"golang.org/x/sync/singleflight"
|
||||
)
|
||||
|
||||
var (
|
||||
globalSessionCache = tls.NewLRUClientSessionCache(64)
|
||||
)
|
||||
|
||||
type dnsClient interface {
|
||||
Exchange(m *D.Msg) (msg *D.Msg, err error)
|
||||
ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg, err error)
|
||||
@ -43,6 +38,7 @@ type Resolver struct {
|
||||
fallbackIPFilters []fallbackIPFilter
|
||||
group singleflight.Group
|
||||
lruCache *cache.LruCache
|
||||
policy *trie.DomainTrie
|
||||
}
|
||||
|
||||
// ResolveIP request with TypeA and TypeAAAA, priority return TypeA
|
||||
@ -91,6 +87,11 @@ func (r *Resolver) shouldIPFallback(ip net.IP) bool {
|
||||
|
||||
// Exchange a batch of dns request, and it use cache
|
||||
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 {
|
||||
return nil, errors.New("should have one question at least")
|
||||
}
|
||||
@ -102,17 +103,17 @@ func (r *Resolver) Exchange(m *D.Msg) (msg *D.Msg, err error) {
|
||||
msg = cache.(*D.Msg).Copy()
|
||||
if expireTime.Before(now) {
|
||||
setMsgTTL(msg, uint32(1)) // Continue fetch
|
||||
go r.exchangeWithoutCache(m)
|
||||
go r.exchangeWithoutCache(ctx, m)
|
||||
} else {
|
||||
setMsgTTL(msg, uint32(time.Until(expireTime).Seconds()))
|
||||
}
|
||||
return
|
||||
}
|
||||
return r.exchangeWithoutCache(m)
|
||||
return r.exchangeWithoutCache(ctx, m)
|
||||
}
|
||||
|
||||
// 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]
|
||||
|
||||
ret, err, shared := r.group.Do(q.String(), func() (result interface{}, err error) {
|
||||
@ -128,10 +129,13 @@ func (r *Resolver) exchangeWithoutCache(m *D.Msg) (msg *D.Msg, err error) {
|
||||
|
||||
isIPReq := isIPRequest(q)
|
||||
if isIPReq {
|
||||
return r.ipExchange(m)
|
||||
return r.ipExchange(ctx, m)
|
||||
}
|
||||
|
||||
return r.batchExchange(r.main, m)
|
||||
if matched := r.matchPolicy(m); len(matched) != 0 {
|
||||
return r.batchExchange(ctx, matched, m)
|
||||
}
|
||||
return r.batchExchange(ctx, r.main, m)
|
||||
})
|
||||
|
||||
if err == nil {
|
||||
@ -144,8 +148,8 @@ func (r *Resolver) exchangeWithoutCache(m *D.Msg) (msg *D.Msg, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func (r *Resolver) batchExchange(clients []dnsClient, m *D.Msg) (msg *D.Msg, err error) {
|
||||
fast, ctx := picker.WithTimeout(context.Background(), time.Second*5)
|
||||
func (r *Resolver) batchExchange(ctx context.Context, clients []dnsClient, m *D.Msg) (msg *D.Msg, err error) {
|
||||
fast, ctx := picker.WithTimeout(ctx, resolver.DefaultDNSTimeout)
|
||||
for _, client := range clients {
|
||||
r := client
|
||||
fast.Go(func() (interface{}, error) {
|
||||
@ -172,6 +176,24 @@ func (r *Resolver) batchExchange(clients []dnsClient, m *D.Msg) (msg *D.Msg, err
|
||||
return
|
||||
}
|
||||
|
||||
func (r *Resolver) matchPolicy(m *D.Msg) []dnsClient {
|
||||
if r.policy == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
domain := r.msgToDomain(m)
|
||||
if domain == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
record := r.policy.Search(domain)
|
||||
if record == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return record.Data.([]dnsClient)
|
||||
}
|
||||
|
||||
func (r *Resolver) shouldOnlyQueryFallback(m *D.Msg) bool {
|
||||
if r.fallback == nil || len(r.fallbackDomainFilters) == 0 {
|
||||
return false
|
||||
@ -192,16 +214,21 @@ func (r *Resolver) shouldOnlyQueryFallback(m *D.Msg) bool {
|
||||
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 {
|
||||
res := <-r.asyncExchange(ctx, matched, m)
|
||||
return res.Msg, res.Error
|
||||
}
|
||||
|
||||
onlyFallback := r.shouldOnlyQueryFallback(m)
|
||||
|
||||
if onlyFallback {
|
||||
res := <-r.asyncExchange(r.fallback, m)
|
||||
res := <-r.asyncExchange(ctx, r.fallback, m)
|
||||
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
|
||||
res := <-msgCh
|
||||
@ -209,7 +236,7 @@ func (r *Resolver) ipExchange(m *D.Msg) (msg *D.Msg, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
fallbackMsg := r.asyncExchange(r.fallback, m)
|
||||
fallbackMsg := r.asyncExchange(ctx, r.fallback, m)
|
||||
res := <-msgCh
|
||||
if res.Error == nil {
|
||||
if ips := msgToIP(res.Msg); len(ips) != 0 {
|
||||
@ -265,10 +292,10 @@ func (r *Resolver) msgToDomain(msg *D.Msg) string {
|
||||
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)
|
||||
go func() {
|
||||
res, err := r.batchExchange(client, msg)
|
||||
res, err := r.batchExchange(ctx, client, msg)
|
||||
ch <- &result{Msg: res, Error: err}
|
||||
}()
|
||||
return ch
|
||||
@ -280,9 +307,10 @@ type NameServer struct {
|
||||
}
|
||||
|
||||
type FallbackFilter struct {
|
||||
GeoIP bool
|
||||
IPCIDR []*net.IPNet
|
||||
Domain []string
|
||||
GeoIP bool
|
||||
GeoIPCode string
|
||||
IPCIDR []*net.IPNet
|
||||
Domain []string
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
@ -293,6 +321,7 @@ type Config struct {
|
||||
FallbackFilter FallbackFilter
|
||||
Pool *fakeip.Pool
|
||||
Hosts *trie.DomainTrie
|
||||
Policy map[string]NameServer
|
||||
}
|
||||
|
||||
func NewResolver(config Config) *Resolver {
|
||||
@ -312,9 +341,18 @@ func NewResolver(config Config) *Resolver {
|
||||
r.fallback = transform(config.Fallback, defaultResolver)
|
||||
}
|
||||
|
||||
if len(config.Policy) != 0 {
|
||||
r.policy = trie.New()
|
||||
for domain, nameserver := range config.Policy {
|
||||
r.policy.Insert(domain, transform([]NameServer{nameserver}, defaultResolver))
|
||||
}
|
||||
}
|
||||
|
||||
fallbackIPFilters := []fallbackIPFilter{}
|
||||
if config.FallbackFilter.GeoIP {
|
||||
fallbackIPFilters = append(fallbackIPFilters, &geoipFilter{})
|
||||
fallbackIPFilters = append(fallbackIPFilters, &geoipFilter{
|
||||
code: config.FallbackFilter.GeoIPCode,
|
||||
})
|
||||
}
|
||||
for _, ipnet := range config.FallbackFilter.IPCIDR {
|
||||
fallbackIPFilters = append(fallbackIPFilters, &ipnetFilter{ipnet: ipnet})
|
||||
|
@ -30,6 +30,7 @@ func (s *Server) ServeDNS(w D.ResponseWriter, r *D.Msg) {
|
||||
D.HandleFailed(w, r)
|
||||
return
|
||||
}
|
||||
msg.Compress = true
|
||||
w.WriteMsg(msg)
|
||||
}
|
||||
|
||||
|
@ -117,9 +117,13 @@ func isIPRequest(q D.Question) bool {
|
||||
func transform(servers []NameServer, resolver *Resolver) []dnsClient {
|
||||
ret := []dnsClient{}
|
||||
for _, s := range servers {
|
||||
if s.Net == "https" {
|
||||
switch s.Net {
|
||||
case "https":
|
||||
ret = append(ret, newDoHClient(s.Addr, resolver))
|
||||
continue
|
||||
case "dhcp":
|
||||
ret = append(ret, newDHCPClient(s.Addr))
|
||||
continue
|
||||
}
|
||||
|
||||
host, port, _ := net.SplitHostPort(s.Addr)
|
||||
@ -127,7 +131,6 @@ func transform(servers []NameServer, resolver *Resolver) []dnsClient {
|
||||
Client: &D.Client{
|
||||
Net: s.Net,
|
||||
TLSConfig: &tls.Config{
|
||||
ClientSessionCache: globalSessionCache,
|
||||
// alpn identifier, see https://tools.ietf.org/html/draft-hoffman-dprive-dns-tls-alpn-00#page-6
|
||||
NextProtos: []string{"dns"},
|
||||
ServerName: host,
|
||||
|
34
go.mod
34
go.mod
@ -1,22 +1,32 @@
|
||||
module github.com/Dreamacro/clash
|
||||
|
||||
go 1.16
|
||||
go 1.17
|
||||
|
||||
require (
|
||||
github.com/Dreamacro/go-shadowsocks2 v0.1.6
|
||||
github.com/go-chi/chi v4.1.2+incompatible
|
||||
github.com/go-chi/cors v1.1.1
|
||||
github.com/Dreamacro/go-shadowsocks2 v0.1.7
|
||||
github.com/go-chi/chi/v5 v5.0.4
|
||||
github.com/go-chi/cors v1.2.0
|
||||
github.com/go-chi/render v1.0.1
|
||||
github.com/gofrs/uuid v4.0.0+incompatible
|
||||
github.com/gorilla/websocket v1.4.2
|
||||
github.com/miekg/dns v1.1.38
|
||||
github.com/oschwald/geoip2-golang v1.4.0
|
||||
github.com/sirupsen/logrus v1.8.0
|
||||
github.com/insomniacslk/dhcp v0.0.0-20210827173440-b95caade3eac
|
||||
github.com/miekg/dns v1.1.43
|
||||
github.com/oschwald/geoip2-golang v1.5.0
|
||||
github.com/sirupsen/logrus v1.8.1
|
||||
github.com/stretchr/testify v1.7.0
|
||||
go.uber.org/atomic v1.7.0
|
||||
golang.org/x/crypto v0.0.0-20210218145215-b8e89b74b9df
|
||||
golang.org/x/net v0.0.0-20210119194325-5f4716e94777
|
||||
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a
|
||||
golang.org/x/sys v0.0.0-20210218155724-8ebf48af031b
|
||||
go.uber.org/atomic v1.9.0
|
||||
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5
|
||||
golang.org/x/net v0.0.0-20210903162142-ad29c8ab022f
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
|
||||
golang.org/x/sys v0.0.0-20210906170528-6f6e22806c34
|
||||
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
|
||||
)
|
||||
|
119
go.sum
119
go.sum
@ -1,71 +1,112 @@
|
||||
github.com/Dreamacro/go-shadowsocks2 v0.1.6 h1:PysSf9sLT3Qn8jhlin5v7Rk68gOQG4K5BZFY1nxLGxI=
|
||||
github.com/Dreamacro/go-shadowsocks2 v0.1.6/go.mod h1:LSXCjyHesPY3pLjhwff1mQX72ItcBT/N2xNC685cYeU=
|
||||
github.com/Dreamacro/go-shadowsocks2 v0.1.7 h1:8CtbE1HoPPMfrQZGXmlluq6dO2lL31W6WRRE8fabc4Q=
|
||||
github.com/Dreamacro/go-shadowsocks2 v0.1.7/go.mod h1:8p5G4cAj5ZlXwUR+Ww63gfSikr8kvw8uw3TDwLAJpUc=
|
||||
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/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/go-chi/chi v4.1.2+incompatible h1:fGFk2Gmi/YKXk0OmGfBh0WgmN3XB8lVnEyNz34tQRec=
|
||||
github.com/go-chi/chi v4.1.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ=
|
||||
github.com/go-chi/cors v1.1.1 h1:eHuqxsIw89iXcWnWUN8R72JMibABJTN/4IOYI5WERvw=
|
||||
github.com/go-chi/cors v1.1.1/go.mod h1:K2Yje0VW/SJzxiyMYu6iPQYa7hMjQX2i/F491VChg1I=
|
||||
github.com/fanliao/go-promise v0.0.0-20141029170127-1890db352a72/go.mod h1:PjfxuH4FZdUyfMdtBio2lsRr1AKEaVPwelzuHuh8Lqc=
|
||||
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/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58=
|
||||
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/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/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/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/magefile/mage v1.10.0 h1:3HiXzCUY12kh9bIuyXShaVe529fJfyqoVM42o/uom2g=
|
||||
github.com/magefile/mage v1.10.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A=
|
||||
github.com/miekg/dns v1.1.38 h1:MtIY+fmHUVVgv1AXzmKMWcwdCYxTRPG1EDjpqF4RCEw=
|
||||
github.com/miekg/dns v1.1.38/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
|
||||
github.com/oschwald/geoip2-golang v1.4.0 h1:5RlrjCgRyIGDz/mBmPfnAF4h8k0IAcRv9PvrpOfz+Ug=
|
||||
github.com/oschwald/geoip2-golang v1.4.0/go.mod h1:8QwxJvRImBH+Zl6Aa6MaIcs5YdlZSTKtzmPGzQqi9ng=
|
||||
github.com/oschwald/maxminddb-golang v1.6.0 h1:KAJSjdHQ8Kv45nFIbtoLGrGWqHFajOIm7skTyz/+Dls=
|
||||
github.com/oschwald/maxminddb-golang v1.6.0/go.mod h1:DUJFucBg2cvqx42YmDa/+xHvb0elJtOm3o4aFQ/nb/w=
|
||||
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/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/go.mod h1:xdvYt5xQzB8ORWFqPnqMwZpCpgNagttWdoZLlJQzg7s=
|
||||
github.com/oschwald/maxminddb-golang v1.8.0 h1:Uh/DSnGoxsyp/KYbY1AuP0tYEwfs0sCph9p/UMXK/Hk=
|
||||
github.com/oschwald/maxminddb-golang v1.8.0/go.mod h1:RXZtst0N6+FY/3qCNmZMBApR19cdQj43/NM9VkrNAis=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/sirupsen/logrus v1.8.0 h1:nfhvjKcUMhBMVqbKHJlk5RPrrfYr/NMo3692g0dwfWU=
|
||||
github.com/sirupsen/logrus v1.8.0/go.mod h1:4GuYW9TZmE769R5STWrRakJc4UqQ3+QQ95fyz7ENv1A=
|
||||
github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
|
||||
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/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.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
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/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw=
|
||||
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||
github.com/u-root/uio v0.0.0-20210528114334-82958018845c h1:BFvcl34IGnw8yvJi8hlqLFo9EshRInwWBs2M5fGWzQA=
|
||||
github.com/u-root/uio v0.0.0-20210528114334-82958018845c/go.mod h1:LpEX5FO/cB+WF4TYGY1V5qktpaZLkKkSegbr0V4eYXA=
|
||||
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-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20210218145215-b8e89b74b9df h1:y7QZzfUiTwWam+xBn29Ulb8CBwVN5UdzmMDavl9Whlw=
|
||||
golang.org/x/crypto v0.0.0-20210218145215-b8e89b74b9df/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
|
||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
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-20210817164053-32db794688a5 h1:HWj/xjIHfjYU5nVXpTM0s39J9CbLn7Cc5a7IC5rwsMQ=
|
||||
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-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20210119194325-5f4716e94777 h1:003p0dJM77cxMSyCPFphvZf/Y5/NXf5fzg6ufd1/Oew=
|
||||
golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a h1:DcqTD9SDLc+1P/r1EmRBwnVsrOwW+kk2vWf9n+1sGhs=
|
||||
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
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-20210903162142-ad29c8ab022f h1:w6wWR0H+nyVpbSAQbzVEIACVyr/h8l/BEkY6Sokc7Eg=
|
||||
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/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-20190924154521-2837fb4f24fe/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-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-20210218155724-8ebf48af031b h1:lAZ0/chPUDWwjqosYR0X4M490zQhMsiJ4K3DbA7o+3g=
|
||||
golang.org/x/sys v0.0.0-20210218155724-8ebf48af031b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||
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-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-20210906170528-6f6e22806c34 h1:GkvMjFtXUmahfDtashnc1mnrCtuBVcwse5QV2lUk/tI=
|
||||
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/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
|
||||
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/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-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
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/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
||||
|
@ -6,21 +6,22 @@ import (
|
||||
"os"
|
||||
"sync"
|
||||
|
||||
"github.com/Dreamacro/clash/adapters/outbound"
|
||||
"github.com/Dreamacro/clash/adapters/outboundgroup"
|
||||
"github.com/Dreamacro/clash/adapters/provider"
|
||||
"github.com/Dreamacro/clash/adapter"
|
||||
"github.com/Dreamacro/clash/adapter/outboundgroup"
|
||||
"github.com/Dreamacro/clash/component/auth"
|
||||
"github.com/Dreamacro/clash/component/dialer"
|
||||
"github.com/Dreamacro/clash/component/iface"
|
||||
"github.com/Dreamacro/clash/component/profile"
|
||||
"github.com/Dreamacro/clash/component/profile/cachefile"
|
||||
"github.com/Dreamacro/clash/component/resolver"
|
||||
"github.com/Dreamacro/clash/component/trie"
|
||||
"github.com/Dreamacro/clash/config"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/constant/provider"
|
||||
"github.com/Dreamacro/clash/dns"
|
||||
P "github.com/Dreamacro/clash/listener"
|
||||
authStore "github.com/Dreamacro/clash/listener/auth"
|
||||
"github.com/Dreamacro/clash/log"
|
||||
P "github.com/Dreamacro/clash/proxy"
|
||||
authStore "github.com/Dreamacro/clash/proxy/auth"
|
||||
"github.com/Dreamacro/clash/tunnel"
|
||||
)
|
||||
|
||||
@ -70,13 +71,13 @@ func ApplyConfig(cfg *config.Config, force bool) {
|
||||
defer mux.Unlock()
|
||||
|
||||
updateUsers(cfg.Users)
|
||||
updateGeneral(cfg.General, force)
|
||||
updateProxies(cfg.Proxies, cfg.Providers)
|
||||
updateRules(cfg.Rules)
|
||||
updateDNS(cfg.DNS)
|
||||
updateHosts(cfg.Hosts)
|
||||
updateExperimental(cfg)
|
||||
updateProfile(cfg)
|
||||
updateGeneral(cfg.General, force)
|
||||
updateDNS(cfg.DNS)
|
||||
updateExperimental(cfg)
|
||||
}
|
||||
|
||||
func GetGeneral() *config.General {
|
||||
@ -123,11 +124,13 @@ func updateDNS(c *config.DNS) {
|
||||
Pool: c.FakeIPRange,
|
||||
Hosts: c.Hosts,
|
||||
FallbackFilter: dns.FallbackFilter{
|
||||
GeoIP: c.FallbackFilter.GeoIP,
|
||||
IPCIDR: c.FallbackFilter.IPCIDR,
|
||||
Domain: c.FallbackFilter.Domain,
|
||||
GeoIP: c.FallbackFilter.GeoIP,
|
||||
GeoIPCode: c.FallbackFilter.GeoIPCode,
|
||||
IPCIDR: c.FallbackFilter.IPCIDR,
|
||||
Domain: c.FallbackFilter.Domain,
|
||||
},
|
||||
Default: c.DefaultNameserver,
|
||||
Policy: c.NameServerPolicy,
|
||||
}
|
||||
|
||||
r := dns.NewResolver(cfg)
|
||||
@ -169,13 +172,13 @@ func updateGeneral(general *config.General, force bool) {
|
||||
resolver.DisableIPv6 = !general.IPv6
|
||||
|
||||
if general.Interface != "" {
|
||||
dialer.DialHook = dialer.DialerWithInterface(general.Interface)
|
||||
dialer.ListenPacketHook = dialer.ListenPacketWithInterface(general.Interface)
|
||||
dialer.DefaultOptions = []dialer.Option{dialer.WithInterface(general.Interface)}
|
||||
} else {
|
||||
dialer.DialHook = nil
|
||||
dialer.ListenPacketHook = nil
|
||||
dialer.DefaultOptions = nil
|
||||
}
|
||||
|
||||
iface.FlushCache()
|
||||
|
||||
if !force {
|
||||
return
|
||||
}
|
||||
@ -186,24 +189,27 @@ func updateGeneral(general *config.General, force bool) {
|
||||
bindAddress := general.BindAddress
|
||||
P.SetBindAddress(bindAddress)
|
||||
|
||||
if err := P.ReCreateHTTP(general.Port); err != nil {
|
||||
tcpIn := tunnel.TCPIn()
|
||||
udpIn := tunnel.UDPIn()
|
||||
|
||||
if err := P.ReCreateHTTP(general.Port, tcpIn); err != nil {
|
||||
log.Errorln("Start HTTP server error: %s", err.Error())
|
||||
}
|
||||
|
||||
if err := P.ReCreateSocks(general.SocksPort); err != nil {
|
||||
log.Errorln("Start SOCKS5 server error: %s", err.Error())
|
||||
if err := P.ReCreateSocks(general.SocksPort, tcpIn, udpIn); err != nil {
|
||||
log.Errorln("Start SOCKS server error: %s", err.Error())
|
||||
}
|
||||
|
||||
if err := P.ReCreateRedir(general.RedirPort); err != nil {
|
||||
if err := P.ReCreateRedir(general.RedirPort, tcpIn, udpIn); err != nil {
|
||||
log.Errorln("Start Redir server error: %s", err.Error())
|
||||
}
|
||||
|
||||
if err := P.ReCreateTProxy(general.TProxyPort); err != nil {
|
||||
if err := P.ReCreateTProxy(general.TProxyPort, tcpIn, udpIn); err != nil {
|
||||
log.Errorln("Start TProxy server error: %s", err.Error())
|
||||
}
|
||||
|
||||
if err := P.ReCreateMixed(general.MixedPort); err != nil {
|
||||
log.Errorln("Start Mixed(http and socks5) server error: %s", err.Error())
|
||||
if err := P.ReCreateMixed(general.MixedPort, tcpIn, udpIn); err != nil {
|
||||
log.Errorln("Start Mixed(http and socks) server error: %s", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
@ -231,7 +237,7 @@ func patchSelectGroup(proxies map[string]C.Proxy) {
|
||||
}
|
||||
|
||||
for name, proxy := range proxies {
|
||||
outbound, ok := proxy.(*outbound.Proxy)
|
||||
outbound, ok := proxy.(*adapter.Proxy)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
@ -4,7 +4,7 @@ import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
"github.com/go-chi/chi"
|
||||
"github.com/go-chi/chi/v5"
|
||||
)
|
||||
|
||||
// When name is composed of a partial escape string, Golang does not unescape it
|
||||
|
@ -6,12 +6,13 @@ import (
|
||||
|
||||
"github.com/Dreamacro/clash/component/resolver"
|
||||
"github.com/Dreamacro/clash/config"
|
||||
"github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/hub/executor"
|
||||
P "github.com/Dreamacro/clash/listener"
|
||||
"github.com/Dreamacro/clash/log"
|
||||
P "github.com/Dreamacro/clash/proxy"
|
||||
"github.com/Dreamacro/clash/tunnel"
|
||||
|
||||
"github.com/go-chi/chi"
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/go-chi/render"
|
||||
)
|
||||
|
||||
@ -66,11 +67,15 @@ func patchConfigs(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
ports := P.GetPorts()
|
||||
P.ReCreateHTTP(pointerOrDefault(general.Port, ports.Port))
|
||||
P.ReCreateSocks(pointerOrDefault(general.SocksPort, ports.SocksPort))
|
||||
P.ReCreateRedir(pointerOrDefault(general.RedirPort, ports.RedirPort))
|
||||
P.ReCreateTProxy(pointerOrDefault(general.TProxyPort, ports.TProxyPort))
|
||||
P.ReCreateMixed(pointerOrDefault(general.MixedPort, ports.MixedPort))
|
||||
|
||||
tcpIn := tunnel.TCPIn()
|
||||
udpIn := tunnel.UDPIn()
|
||||
|
||||
P.ReCreateHTTP(pointerOrDefault(general.Port, ports.Port), tcpIn)
|
||||
P.ReCreateSocks(pointerOrDefault(general.SocksPort, ports.SocksPort), tcpIn, udpIn)
|
||||
P.ReCreateRedir(pointerOrDefault(general.RedirPort, ports.RedirPort), tcpIn, udpIn)
|
||||
P.ReCreateTProxy(pointerOrDefault(general.TProxyPort, ports.TProxyPort), tcpIn, udpIn)
|
||||
P.ReCreateMixed(pointerOrDefault(general.MixedPort, ports.MixedPort), tcpIn, udpIn)
|
||||
|
||||
if general.Mode != nil {
|
||||
tunnel.SetMode(*general.Mode)
|
||||
@ -112,6 +117,9 @@ func updateConfigs(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
} else {
|
||||
if req.Path == "" {
|
||||
req.Path = constant.Path.Config()
|
||||
}
|
||||
if !filepath.IsAbs(req.Path) {
|
||||
render.Status(r, http.StatusBadRequest)
|
||||
render.JSON(w, r, newError("path is not a absolute path"))
|
||||
|
@ -8,10 +8,10 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/Dreamacro/clash/tunnel/statistic"
|
||||
"github.com/gorilla/websocket"
|
||||
|
||||
"github.com/go-chi/chi"
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/go-chi/render"
|
||||
"github.com/gorilla/websocket"
|
||||
)
|
||||
|
||||
func connectionRouter() http.Handler {
|
||||
|
@ -4,10 +4,10 @@ import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
"github.com/Dreamacro/clash/adapters/provider"
|
||||
"github.com/Dreamacro/clash/constant/provider"
|
||||
"github.com/Dreamacro/clash/tunnel"
|
||||
|
||||
"github.com/go-chi/chi"
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/go-chi/render"
|
||||
)
|
||||
|
||||
|
@ -7,13 +7,13 @@ import (
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/Dreamacro/clash/adapters/outbound"
|
||||
"github.com/Dreamacro/clash/adapters/outboundgroup"
|
||||
"github.com/Dreamacro/clash/adapter"
|
||||
"github.com/Dreamacro/clash/adapter/outboundgroup"
|
||||
"github.com/Dreamacro/clash/component/profile/cachefile"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/tunnel"
|
||||
|
||||
"github.com/go-chi/chi"
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/go-chi/render"
|
||||
)
|
||||
|
||||
@ -78,7 +78,7 @@ func updateProxy(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
proxy := r.Context().Value(CtxKeyProxy).(*outbound.Proxy)
|
||||
proxy := r.Context().Value(CtxKeyProxy).(*adapter.Proxy)
|
||||
selector, ok := proxy.ProxyAdapter.(*outboundgroup.Selector)
|
||||
if !ok {
|
||||
render.Status(r, http.StatusBadRequest)
|
||||
|
@ -5,7 +5,7 @@ import (
|
||||
|
||||
"github.com/Dreamacro/clash/tunnel"
|
||||
|
||||
"github.com/go-chi/chi"
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/go-chi/render"
|
||||
)
|
||||
|
||||
|
@ -3,6 +3,7 @@ package route
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
@ -11,7 +12,7 @@ import (
|
||||
"github.com/Dreamacro/clash/log"
|
||||
"github.com/Dreamacro/clash/tunnel/statistic"
|
||||
|
||||
"github.com/go-chi/chi"
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/go-chi/cors"
|
||||
"github.com/go-chi/render"
|
||||
"github.com/gorilla/websocket"
|
||||
@ -81,10 +82,15 @@ func Start(addr string, secret string) {
|
||||
})
|
||||
}
|
||||
|
||||
log.Infoln("RESTful API listening at: %s", addr)
|
||||
err := http.ListenAndServe(addr, r)
|
||||
l, err := net.Listen("tcp", addr)
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
|
39
listener/http/client.go
Normal file
39
listener/http/client.go
Normal file
@ -0,0 +1,39 @@
|
||||
package http
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/Dreamacro/clash/adapter/inbound"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
)
|
||||
|
||||
func newClient(source net.Addr, in chan<- C.ConnContext) *http.Client {
|
||||
return &http.Client{
|
||||
Transport: &http.Transport{
|
||||
// from http.DefaultTransport
|
||||
MaxIdleConns: 100,
|
||||
IdleConnTimeout: 90 * time.Second,
|
||||
TLSHandshakeTimeout: 10 * time.Second,
|
||||
ResponseHeaderTimeout: 10 * time.Second,
|
||||
ExpectContinueTimeout: 1 * time.Second,
|
||||
DialContext: func(context context.Context, network, address string) (net.Conn, error) {
|
||||
if network != "tcp" && network != "tcp4" && network != "tcp6" {
|
||||
return nil, errors.New("unsupported network " + network)
|
||||
}
|
||||
|
||||
left, right := net.Pipe()
|
||||
|
||||
in <- inbound.NewHTTP(address, source, right)
|
||||
|
||||
return left, nil
|
||||
},
|
||||
},
|
||||
CheckRedirect: func(req *http.Request, via []*http.Request) error {
|
||||
return http.ErrUseLastResponse
|
||||
},
|
||||
}
|
||||
}
|
10
listener/http/hack.go
Normal file
10
listener/http/hack.go
Normal file
@ -0,0 +1,10 @@
|
||||
package http
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"net/http"
|
||||
_ "unsafe"
|
||||
)
|
||||
|
||||
//go:linkname ReadRequest net/http.readRequest
|
||||
func ReadRequest(b *bufio.Reader) (req *http.Request, err error)
|
133
listener/http/proxy.go
Normal file
133
listener/http/proxy.go
Normal file
@ -0,0 +1,133 @@
|
||||
package http
|
||||
|
||||
import (
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/Dreamacro/clash/adapter/inbound"
|
||||
"github.com/Dreamacro/clash/common/cache"
|
||||
N "github.com/Dreamacro/clash/common/net"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
authStore "github.com/Dreamacro/clash/listener/auth"
|
||||
"github.com/Dreamacro/clash/log"
|
||||
)
|
||||
|
||||
func HandleConn(c net.Conn, in chan<- C.ConnContext, cache *cache.Cache) {
|
||||
client := newClient(c.RemoteAddr(), in)
|
||||
defer client.CloseIdleConnections()
|
||||
|
||||
conn := N.NewBufferedConn(c)
|
||||
|
||||
keepAlive := true
|
||||
trusted := cache == nil // disable authenticate if cache is nil
|
||||
|
||||
for keepAlive {
|
||||
request, err := ReadRequest(conn.Reader())
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
|
||||
request.RemoteAddr = conn.RemoteAddr().String()
|
||||
|
||||
keepAlive = strings.TrimSpace(strings.ToLower(request.Header.Get("Proxy-Connection"))) == "keep-alive"
|
||||
|
||||
var resp *http.Response
|
||||
|
||||
if !trusted {
|
||||
resp = authenticate(request, cache)
|
||||
|
||||
trusted = resp == nil
|
||||
}
|
||||
|
||||
if trusted {
|
||||
if request.Method == http.MethodConnect {
|
||||
resp = responseWith(200)
|
||||
resp.Status = "Connection established"
|
||||
resp.ContentLength = -1
|
||||
|
||||
if resp.Write(conn) != nil {
|
||||
break // close connection
|
||||
}
|
||||
|
||||
in <- inbound.NewHTTPS(request, conn)
|
||||
|
||||
return // hijack connection
|
||||
}
|
||||
|
||||
host := request.Header.Get("Host")
|
||||
if host != "" {
|
||||
request.Host = host
|
||||
}
|
||||
|
||||
request.RequestURI = ""
|
||||
|
||||
RemoveHopByHopHeaders(request.Header)
|
||||
RemoveExtraHTTPHostPort(request)
|
||||
|
||||
if request.URL.Scheme == "" || request.URL.Host == "" {
|
||||
resp = responseWith(http.StatusBadRequest)
|
||||
} else {
|
||||
resp, err = client.Do(request)
|
||||
if err != nil {
|
||||
resp = responseWith(http.StatusBadGateway)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RemoveHopByHopHeaders(resp.Header)
|
||||
|
||||
if keepAlive {
|
||||
resp.Header.Set("Proxy-Connection", "keep-alive")
|
||||
resp.Header.Set("Connection", "keep-alive")
|
||||
resp.Header.Set("Keep-Alive", "timeout=4")
|
||||
}
|
||||
|
||||
resp.Close = !keepAlive
|
||||
|
||||
err = resp.Write(conn)
|
||||
if err != nil {
|
||||
break // close connection
|
||||
}
|
||||
}
|
||||
|
||||
conn.Close()
|
||||
}
|
||||
|
||||
func authenticate(request *http.Request, cache *cache.Cache) *http.Response {
|
||||
authenticator := authStore.Authenticator()
|
||||
if authenticator != nil {
|
||||
credential := ParseBasicProxyAuthorization(request)
|
||||
if credential == "" {
|
||||
resp := responseWith(http.StatusProxyAuthRequired)
|
||||
resp.Header.Set("Proxy-Authenticate", "Basic")
|
||||
return resp
|
||||
}
|
||||
|
||||
var authed interface{}
|
||||
if authed = cache.Get(credential); authed == nil {
|
||||
user, pass, err := DecodeBasicProxyAuthorization(credential)
|
||||
authed = err == nil && authenticator.Verify(user, pass)
|
||||
cache.Put(credential, authed, time.Minute)
|
||||
}
|
||||
if !authed.(bool) {
|
||||
log.Infoln("Auth failed from %s", request.RemoteAddr)
|
||||
|
||||
return responseWith(http.StatusForbidden)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func responseWith(statusCode int) *http.Response {
|
||||
return &http.Response{
|
||||
StatusCode: statusCode,
|
||||
Status: http.StatusText(statusCode),
|
||||
Proto: "HTTP/1.1",
|
||||
ProtoMajor: 1,
|
||||
ProtoMinor: 1,
|
||||
Header: http.Header{},
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user