Compare commits

...

105 Commits

Author SHA1 Message Date
b3cd4ebbd3 Fix: use 1.17.x on github actions 2021-09-15 20:21:30 +08:00
b0f83e401f Fix: socks4 request continues after authentication failed (#1624) 2021-09-15 16:45:57 +08:00
f5806d9263 Fix: http/https proxy authentication (#1613) 2021-09-14 00:08:23 +08:00
55600c49c9 Fix: potential pitfalls 2021-09-13 23:58:48 +08:00
beb88cc46f Fix: should not trust address of http.Client (#1616) 2021-09-13 23:46:39 +08:00
d49b38b00f Fix: should not unmarshal to pointer (#1615) 2021-09-13 23:43:28 +08:00
0c79d1207e Fix: potential overflow in ssr (#1600) 2021-09-09 20:30:34 +08:00
400dc923e0 Fix: vmess ws headers not set properly (#1595) 2021-09-08 14:44:24 +08:00
5b7f0de48b Chore: update dependencies 2021-09-07 20:16:07 +08:00
a5b950a779 Feature: add dhcp type dns client (#1509) 2021-09-06 23:07:34 +08:00
a2d59d6ef5 Feature: skip DIRECT proxies in relay (#1583) 2021-09-06 21:39:28 +08:00
8ef5cdb8be Test: use local clash pkg 2021-09-05 14:38:07 +08:00
c7b718f651 Test: release resources correctly 2021-09-05 14:25:55 +08:00
ff56e5c5de Test: fix direct listen fail 2021-09-04 22:44:18 +08:00
661c417fce Test: use shadowsocks-rust for ss benchmark 2021-09-04 22:31:08 +08:00
7d20097465 Fix: ssr auth aes128 udp hmac verify 2021-08-30 00:15:57 +08:00
a20b9a3960 Chore: make geoip match case-insensitive (#1574) 2021-08-29 22:19:22 +08:00
e0d3f926b7 Feature: add geoip-code option 2021-08-25 15:15:13 +08:00
121bc910f6 Chore: adjust vmess 0rtt code and split xray test 2021-08-22 16:16:45 +08:00
4522cdc551 Feature: support xray's ws-0rtt path (#1558) 2021-08-22 16:03:46 +08:00
410772e81c Test: add vmess ws 0-rtt test 2021-08-22 01:17:29 +08:00
0267b2efad Feature: add vmess WebSocket early data (#1505)
Co-authored-by: ShinyGwyn <79344143+ShinyGwyn@users.noreply.github.com>
2021-08-22 00:25:29 +08:00
c6d375eda2 Fix: HTTP proxy internal linkage signature (#1555) 2021-08-20 23:38:47 +08:00
847f41952e Fix: grpc transport path should not escape 2021-08-19 22:11:56 +08:00
47044ec0d8 Fix: dependabot alerts 2021-08-18 20:20:00 +08:00
426ca36118 Chore: upgrade test package 2021-08-18 13:31:34 +08:00
571d2a0075 Migration: go 1.17 2021-08-18 13:26:23 +08:00
1be09f5751 Chore: fix issue template render type 2021-08-13 22:44:22 +08:00
2663cb2e6e Chore: update github issue template 2021-08-13 22:35:48 +08:00
9b0bbb90ff Chore: upgrade github actions 2021-08-07 22:27:23 +08:00
588645a2c3 Fix: interface nil check panic from previous commit 2021-08-04 23:52:50 +08:00
1bfebd0d03 Fix: listener patch diff 2021-08-01 00:35:37 +08:00
3705996974 Chore: split SOCKS version inbound metadata type (#1513) 2021-07-27 13:58:29 +08:00
09697b7679 Chore: adjust batch 2021-07-23 00:30:23 +08:00
4578b2c826 Fix: remove Content-Length from CONNECT response (#1502) 2021-07-22 18:06:03 +08:00
b3a293ab07 Chore: benchmark explanation 2021-07-22 00:01:11 +08:00
507ba16065 Fix: incorrect use batch 2021-07-21 23:53:31 +08:00
aa9f8a39a3 Fix: socks inbound packet typo 2021-07-21 23:08:52 +08:00
8d37220566 Fix: limit concurrency number of provider health check 2021-07-21 17:01:15 +08:00
53e17a916b Chore: logging remote port on request (#1494) 2021-07-19 15:31:38 +08:00
247dd84970 Chore: logging real listen port (#1492) 2021-07-19 14:07:51 +08:00
c2f3111922 Test: add direct benchmark 2021-07-18 19:26:51 +08:00
44872300e9 Test: ss use aes-256-gcm and vmess-aead for benchmark 2021-07-18 17:37:23 +08:00
91ed0118f6 Test: add basic protocol benchmark 2021-07-18 17:23:30 +08:00
a461c2306a Feature: SOCKS4/SOCKS4A Inbound Compatible Support (#1491) 2021-07-18 16:09:09 +08:00
46f4f84442 Chore: use iife replace init in some cases 2021-07-11 19:43:25 +08:00
250a9f4f84 Fix: reorder apply config to ensure update proxies and rules 2021-07-10 17:01:40 +08:00
b4292d0972 Fix: staticcheck error 2021-07-06 00:33:13 +08:00
d755383e39 Chore: move provider interface to constant 2021-07-06 00:31:13 +08:00
dff1e8f1ce Chore: update dependencies 2021-07-03 21:01:41 +08:00
995aa7a8fc Fix: remove ClientSessionCache and add NextProtos for vmess to fix #1468 2021-07-03 20:34:44 +08:00
3ca5d17c40 Fix: enable DNS server message compression (#1451) 2021-06-24 13:38:44 +08:00
244cb370a4 Change: config reload API use default path when both path and payload don't exist (#1447) 2021-06-21 17:33:34 +08:00
c35cb24bda Chore: use unix.ByteSliceToString transform cstring 2021-06-15 21:03:47 +08:00
b6ff08074c Refactor: plain http proxy (#1443) 2021-06-15 17:13:40 +08:00
70d53fd45a Chore: update development wiki to README.md 2021-06-13 23:11:49 +08:00
f231a63e93 Chore: Listener should not expose original net.Listener 2021-06-13 23:05:22 +08:00
6091fcdfec Style: code style 2021-06-13 17:23:10 +08:00
bcfc15e398 chore: expose udp field to proxies API (#1441) 2021-06-10 15:08:33 +08:00
045edc188c Style: code style 2021-06-10 14:05:56 +08:00
0778591524 Feature: dns resolve domain through nameserver-policy (#1406) 2021-05-19 11:17:35 +08:00
d5e52bed43 Feature: add protocol test 2021-05-17 20:33:00 +08:00
06fdd3abe0 Fix: vmess http should use Host header on request 2021-05-16 20:05:41 +08:00
4e5898197a Fix: build broken 2021-05-13 22:39:33 +08:00
f96ebab99f Chore: split component to transport 2021-05-13 22:19:34 +08:00
3c54f99fea Chore: update dependencies 2021-05-08 19:29:12 +08:00
824f5bd731 Fix: reuse http connection broken on previous commit 2021-05-07 11:08:46 +08:00
3f3db8476e Fix: HTTP inbound leak 2021-05-06 22:34:37 +08:00
f375f080da Fix: skip deleted node from url-test group (#1378)
Co-authored-by: fish <fish@youme.im>
2021-05-01 17:21:09 +08:00
e19e9ef5a4 Style: code style 2021-04-29 11:23:14 +08:00
682e65cb54 Style: code style 2021-04-26 20:42:17 +08:00
16a6d409d9 Feature: add freebsd arm64 to Makefile (#1370) 2021-04-22 16:38:13 +08:00
4186bcf1b2 Fix: should write file if provider initialize from HTTP (#1365) 2021-04-19 17:40:38 +08:00
df5112175f Fix: io timeout when snell v2 reuse connection (#1362) 2021-04-19 14:36:06 +08:00
d9341a49ea Fix: trojan should safe close connection 2021-04-19 12:20:37 +08:00
4e9e4b6cde Fix: grpc transport concurrent write 2021-04-14 21:46:05 +08:00
936b7012ba Feature: PROCESS-NAME support freebsd 13, fix panic on unsupported platforms (#1351) 2021-04-14 17:57:17 +08:00
a9cbd9ec98 Fix: use bufio.Reader on grpc to avoid panic 2021-04-14 00:16:59 +08:00
c9943fb857 Fix: grpc implementation SetDeadline for udp issue 2021-04-13 23:34:33 +08:00
a40274e2a2 Fix: vmess aead writer concurrent write (#1350) 2021-04-13 23:32:53 +08:00
b59d45c660 Feature: add CodeQL security checks (#1349) 2021-04-13 21:25:55 +08:00
7b01e103c2 Chore: use correctly vmess http2 default host 2021-04-10 12:10:10 +08:00
93a8acecce Fix: vmess h2 use server as host if host option is empty 2021-04-09 18:15:46 +08:00
586bb91c0c Fix: grpc transport panic 2021-04-09 18:11:07 +08:00
baf03b81e3 Fix: remove unused function 2021-04-08 22:27:41 +08:00
9807e1189c Chore: update dependencies 2021-04-08 22:15:30 +08:00
3d5a0d9f73 Fix: trojan/vmess grpc broken 2021-04-07 22:57:46 +08:00
cc96187f58 Fix: trojan grpc udp broken 2021-04-05 23:26:13 +08:00
3aefa1d924 Chore: some chores 2021-04-05 13:31:10 +08:00
42e21b3733 Chore: refine go import 2021-04-05 13:00:49 +08:00
0a35237915 Fix: should reset fast node when tolerance enable and not alive on url-test group 2021-04-04 17:40:25 +08:00
a1f3a5ea26 Chore: -v add golang version 2021-04-04 17:36:22 +08:00
e63f995258 Chore: update dependencies (#1331) 2021-04-03 14:59:03 +08:00
d0c829c578 Fix: domain dns should follow hosts config, close #1318 2021-04-01 21:20:44 +08:00
4ad9761b32 Fix: don't resolve AAAA record when ipv6 is false and use go dns resolver 2021-04-01 18:03:30 +08:00
1f593d37fb Chore: use mixed-port instead of port when initial config (#1319) 2021-04-01 15:35:33 +08:00
109bfcb0f9 Feature: add vmess aead header support 2021-03-30 17:34:16 +08:00
7ee49f5171 Fix: HTTP server should close when Connection is close 2021-03-30 16:33:49 +08:00
d759d16944 Style: cleanup code 2021-03-24 01:00:21 +08:00
807d53c1e7 Chore: Clarify the definition of StreamConn and DialContext 2021-03-22 23:26:20 +08:00
1355196b7c Fix: grpc connection panic 2021-03-18 23:19:00 +08:00
573316bcde Feature: add gRPC Transport for vmess/trojan (#1287)
Co-authored-by: eMeab <32988354+eMeab@users.noreply.github.com>
Co-authored-by: Dreamacro <8615343+Dreamacro@users.noreply.github.com>
2021-03-18 19:40:34 +08:00
784c28266c Fix: vmess http broken 2021-03-18 17:11:10 +08:00
5da1b2a8aa Fix: set metadata.AddrType if host is ip string after remove host (#1291) 2021-03-12 17:41:37 +08:00
0976d27cb1 Fix: github actions remove prerelease option 2021-03-10 21:22:22 +08:00
214 changed files with 7677 additions and 2216 deletions

View File

@ -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
View 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
View 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!

View File

@ -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

View 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
View 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

View File

@ -52,7 +52,7 @@ jobs:
- name: Get all docker tags - name: Get all docker tags
if: startsWith(github.ref, 'refs/tags/') if: startsWith(github.ref, 'refs/tags/')
uses: actions/github-script@v3 uses: actions/github-script@v4
id: tags id: tags
with: with:
script: | script: |

View File

@ -9,7 +9,7 @@ jobs:
- name: Setup Go - name: Setup Go
uses: actions/setup-go@v2 uses: actions/setup-go@v2
with: with:
go-version: 1.16 go-version: 1.17.x
- name: Check out code into the Go module directory - name: Check out code into the Go module directory
uses: actions/checkout@v2 uses: actions/checkout@v2
@ -39,9 +39,6 @@ jobs:
- name: Upload Release - name: Upload Release
uses: softprops/action-gh-release@v1 uses: softprops/action-gh-release@v1
if: startsWith(github.ref, 'refs/tags/') if: startsWith(github.ref, 'refs/tags/')
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with: with:
files: bin/* files: bin/*
draft: true draft: true
prerelease: true

View File

@ -11,9 +11,8 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/stale@v3 - uses: actions/stale@v4
with: with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
stale-issue-message: 'This issue is stale because it has been open 60 days with no activity. Remove stale label or comment or this will be closed in 5 days' stale-issue-message: 'This issue is stale because it has been open 60 days with no activity. Remove stale label or comment or this will be closed in 5 days'
days-before-stale: 60 days-before-stale: 60
days-before-close: 5 days-before-close: 5

View File

@ -22,11 +22,13 @@ PLATFORM_LIST = \
linux-mips64 \ linux-mips64 \
linux-mips64le \ linux-mips64le \
freebsd-386 \ freebsd-386 \
freebsd-amd64 freebsd-amd64 \
freebsd-arm64
WINDOWS_ARCH_LIST = \ WINDOWS_ARCH_LIST = \
windows-386 \ windows-386 \
windows-amd64 \ windows-amd64 \
windows-arm64 \
windows-arm32v7 windows-arm32v7
all: linux-amd64 darwin-amd64 windows-amd64 # Most used all: linux-amd64 darwin-amd64 windows-amd64 # Most used
@ -82,12 +84,18 @@ freebsd-386:
freebsd-amd64: freebsd-amd64:
GOARCH=amd64 GOOS=freebsd $(GOBUILD) -o $(BINDIR)/$(NAME)-$@ GOARCH=amd64 GOOS=freebsd $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
freebsd-arm64:
GOARCH=arm64 GOOS=freebsd $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
windows-386: windows-386:
GOARCH=386 GOOS=windows $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.exe GOARCH=386 GOOS=windows $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.exe
windows-amd64: windows-amd64:
GOARCH=amd64 GOOS=windows $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.exe GOARCH=amd64 GOOS=windows $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.exe
windows-arm64:
GOARCH=arm64 GOOS=windows $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.exe
windows-arm32v7: windows-arm32v7:
GOARCH=arm GOOS=windows GOARM=7 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.exe GOARCH=arm GOOS=windows GOARM=7 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.exe

View File

@ -40,6 +40,9 @@ Documentations are now moved to [GitHub Wiki](https://github.com/Dreamacro/clash
## Premium Release ## Premium Release
[Release](https://github.com/Dreamacro/clash/releases/tag/premium) [Release](https://github.com/Dreamacro/clash/releases/tag/premium)
## Development
If you want to build an application that uses clash as a library, check out the the [GitHub Wiki](https://github.com/Dreamacro/clash/wiki/use-clash-as-a-library)
## Credits ## Credits
* [riobard/go-shadowsocks2](https://github.com/riobard/go-shadowsocks2) * [riobard/go-shadowsocks2](https://github.com/riobard/go-shadowsocks2)
@ -57,4 +60,3 @@ This software is released under the GPL-3.0 license.
- [x] Redir proxy - [x] Redir proxy
- [x] UDP support - [x] UDP support
- [x] Connection manager - [x] Connection manager
- [ ] ~~Event API~~

View File

@ -1,11 +1,12 @@
package outbound package adapter
import ( import (
"context" "context"
"encoding/json" "encoding/json"
"errors" "fmt"
"net" "net"
"net/http" "net/http"
"net/url"
"time" "time"
"github.com/Dreamacro/clash/common/queue" "github.com/Dreamacro/clash/common/queue"
@ -14,101 +15,25 @@ import (
"go.uber.org/atomic" "go.uber.org/atomic"
) )
type Base struct {
name string
addr string
tp C.AdapterType
udp bool
}
func (b *Base) Name() string {
return b.name
}
func (b *Base) Type() C.AdapterType {
return b.tp
}
func (b *Base) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
return c, errors.New("no support")
}
func (b *Base) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
return nil, errors.New("no support")
}
func (b *Base) SupportUDP() bool {
return b.udp
}
func (b *Base) MarshalJSON() ([]byte, error) {
return json.Marshal(map[string]string{
"type": b.Type().String(),
})
}
func (b *Base) Addr() string {
return b.addr
}
func (b *Base) Unwrap(metadata *C.Metadata) C.Proxy {
return nil
}
func NewBase(name string, addr string, tp C.AdapterType, udp bool) *Base {
return &Base{name, addr, tp, udp}
}
type conn struct {
net.Conn
chain C.Chain
}
func (c *conn) Chains() C.Chain {
return c.chain
}
func (c *conn) AppendToChains(a C.ProxyAdapter) {
c.chain = append(c.chain, a.Name())
}
func NewConn(c net.Conn, a C.ProxyAdapter) C.Conn {
return &conn{c, []string{a.Name()}}
}
type packetConn struct {
net.PacketConn
chain C.Chain
}
func (c *packetConn) Chains() C.Chain {
return c.chain
}
func (c *packetConn) AppendToChains(a C.ProxyAdapter) {
c.chain = append(c.chain, a.Name())
}
func newPacketConn(pc net.PacketConn, a C.ProxyAdapter) C.PacketConn {
return &packetConn{pc, []string{a.Name()}}
}
type Proxy struct { type Proxy struct {
C.ProxyAdapter C.ProxyAdapter
history *queue.Queue history *queue.Queue
alive *atomic.Bool alive *atomic.Bool
} }
// Alive implements C.Proxy
func (p *Proxy) Alive() bool { func (p *Proxy) Alive() bool {
return p.alive.Load() return p.alive.Load()
} }
// Dial implements C.Proxy
func (p *Proxy) Dial(metadata *C.Metadata) (C.Conn, error) { func (p *Proxy) Dial(metadata *C.Metadata) (C.Conn, error) {
ctx, cancel := context.WithTimeout(context.Background(), tcpTimeout) ctx, cancel := context.WithTimeout(context.Background(), C.DefaultTCPTimeout)
defer cancel() defer cancel()
return p.DialContext(ctx, metadata) return p.DialContext(ctx, metadata)
} }
// DialContext implements C.ProxyAdapter
func (p *Proxy) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) { func (p *Proxy) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
conn, err := p.ProxyAdapter.DialContext(ctx, metadata) conn, err := p.ProxyAdapter.DialContext(ctx, metadata)
if err != nil { if err != nil {
@ -117,6 +42,7 @@ func (p *Proxy) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn,
return conn, err return conn, err
} }
// DelayHistory implements C.Proxy
func (p *Proxy) DelayHistory() []C.DelayHistory { func (p *Proxy) DelayHistory() []C.DelayHistory {
queue := p.history.Copy() queue := p.history.Copy()
histories := []C.DelayHistory{} histories := []C.DelayHistory{}
@ -127,6 +53,7 @@ func (p *Proxy) DelayHistory() []C.DelayHistory {
} }
// LastDelay return last history record. if proxy is not alive, return the max value of uint16. // LastDelay return last history record. if proxy is not alive, return the max value of uint16.
// implements C.Proxy
func (p *Proxy) LastDelay() (delay uint16) { func (p *Proxy) LastDelay() (delay uint16) {
var max uint16 = 0xffff var max uint16 = 0xffff
if !p.alive.Load() { if !p.alive.Load() {
@ -144,6 +71,7 @@ func (p *Proxy) LastDelay() (delay uint16) {
return history.Delay return history.Delay
} }
// MarshalJSON implements C.ProxyAdapter
func (p *Proxy) MarshalJSON() ([]byte, error) { func (p *Proxy) MarshalJSON() ([]byte, error) {
inner, err := p.ProxyAdapter.MarshalJSON() inner, err := p.ProxyAdapter.MarshalJSON()
if err != nil { if err != nil {
@ -154,10 +82,12 @@ func (p *Proxy) MarshalJSON() ([]byte, error) {
json.Unmarshal(inner, &mapping) json.Unmarshal(inner, &mapping)
mapping["history"] = p.DelayHistory() mapping["history"] = p.DelayHistory()
mapping["name"] = p.Name() mapping["name"] = p.Name()
mapping["udp"] = p.SupportUDP()
return json.Marshal(mapping) return json.Marshal(mapping)
} }
// URLTest get the delay for the specified URL // URLTest get the delay for the specified URL
// implements C.Proxy
func (p *Proxy) URLTest(ctx context.Context, url string) (t uint16, err error) { func (p *Proxy) URLTest(ctx context.Context, url string) (t uint16, err error) {
defer func() { defer func() {
p.alive.Store(err == nil) p.alive.Store(err == nil)
@ -206,6 +136,8 @@ func (p *Proxy) URLTest(ctx context.Context, url string) (t uint16, err error) {
return http.ErrUseLastResponse return http.ErrUseLastResponse
}, },
} }
defer client.CloseIdleConnections()
resp, err := client.Do(req) resp, err := client.Do(req)
if err != nil { if err != nil {
return return
@ -218,3 +150,31 @@ func (p *Proxy) URLTest(ctx context.Context, url string) (t uint16, err error) {
func NewProxy(adapter C.ProxyAdapter) *Proxy { func NewProxy(adapter C.ProxyAdapter) *Proxy {
return &Proxy{adapter, queue.New(10), atomic.NewBool(true)} return &Proxy{adapter, queue.New(10), atomic.NewBool(true)}
} }
func urlToMetadata(rawURL string) (addr C.Metadata, err error) {
u, err := url.Parse(rawURL)
if err != nil {
return
}
port := u.Port()
if port == "" {
switch u.Scheme {
case "https":
port = "443"
case "http":
port = "80"
default:
err = fmt.Errorf("%s scheme not Support", rawURL)
return
}
}
addr = C.Metadata{
AddrType: C.AtypDomainName,
Host: u.Hostname(),
DstIP: nil,
DstPort: port,
}
return
}

21
adapter/inbound/http.go Normal file
View File

@ -0,0 +1,21 @@
package inbound
import (
"net"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/context"
"github.com/Dreamacro/clash/transport/socks5"
)
// NewHTTP receive normal http request and return HTTPContext
func NewHTTP(target socks5.Addr, source net.Addr, conn net.Conn) *context.ConnContext {
metadata := parseSocksAddr(target)
metadata.NetWork = C.TCP
metadata.Type = C.HTTP
if ip, port, err := parseAddr(source.String()); err == nil {
metadata.SrcIP = ip
metadata.SrcPort = port
}
return context.NewConnContext(conn, metadata)
}

View File

@ -8,7 +8,7 @@ import (
"github.com/Dreamacro/clash/context" "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 { func NewHTTPS(request *http.Request, conn net.Conn) *context.ConnContext {
metadata := parseHTTPAddr(request) metadata := parseHTTPAddr(request)
metadata.Type = C.HTTPCONNECT metadata.Type = C.HTTPCONNECT

View File

@ -1,8 +1,8 @@
package inbound package inbound
import ( import (
"github.com/Dreamacro/clash/component/socks5"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/transport/socks5"
) )
// PacketAdapter is a UDP Packet adapter for socks/redir/tun // PacketAdapter is a UDP Packet adapter for socks/redir/tun

View File

@ -3,12 +3,12 @@ package inbound
import ( import (
"net" "net"
"github.com/Dreamacro/clash/component/socks5"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/context" "github.com/Dreamacro/clash/context"
"github.com/Dreamacro/clash/transport/socks5"
) )
// NewSocket 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 { func NewSocket(target socks5.Addr, conn net.Conn, source C.Type) *context.ConnContext {
metadata := parseSocksAddr(target) metadata := parseSocksAddr(target)
metadata.NetWork = C.TCP metadata.NetWork = C.TCP

View File

@ -6,8 +6,8 @@ import (
"strconv" "strconv"
"strings" "strings"
"github.com/Dreamacro/clash/component/socks5"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/transport/socks5"
) )
func parseSocksAddr(target socks5.Addr) *C.Metadata { func parseSocksAddr(target socks5.Addr) *C.Metadata {

100
adapter/outbound/base.go Normal file
View 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()}}
}

View File

@ -12,10 +12,9 @@ type Direct struct {
*Base *Base
} }
// DialContext implements C.ProxyAdapter
func (d *Direct) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) { func (d *Direct) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
address := net.JoinHostPort(metadata.String(), metadata.DstPort) c, err := dialer.DialContext(ctx, "tcp", metadata.RemoteAddress())
c, err := dialer.DialContext(ctx, "tcp", address)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -23,8 +22,9 @@ func (d *Direct) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn,
return NewConn(c, d), nil return NewConn(c, d), nil
} }
// DialUDP implements C.ProxyAdapter
func (d *Direct) DialUDP(metadata *C.Metadata) (C.PacketConn, error) { func (d *Direct) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
pc, err := dialer.ListenPacket("udp", "") pc, err := dialer.ListenPacket(context.Background(), "udp", "")
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -35,6 +35,7 @@ type HttpOption struct {
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"` SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
} }
// StreamConn implements C.ProxyAdapter
func (h *Http) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) { func (h *Http) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
if h.tlsConfig != nil { if h.tlsConfig != nil {
cc := tls.Client(c, h.tlsConfig) cc := tls.Client(c, h.tlsConfig)
@ -51,13 +52,16 @@ func (h *Http) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
return c, nil return c, nil
} }
func (h *Http) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) { // DialContext implements C.ProxyAdapter
func (h *Http) DialContext(ctx context.Context, metadata *C.Metadata) (_ C.Conn, err error) {
c, err := dialer.DialContext(ctx, "tcp", h.addr) c, err := dialer.DialContext(ctx, "tcp", h.addr)
if err != nil { if err != nil {
return nil, fmt.Errorf("%s connect error: %w", h.addr, err) return nil, fmt.Errorf("%s connect error: %w", h.addr, err)
} }
tcpKeepAlive(c) tcpKeepAlive(c)
defer safeConnClose(c, err)
c, err = h.StreamConn(c, metadata) c, err = h.StreamConn(c, metadata)
if err != nil { if err != nil {
return nil, err return nil, err
@ -121,7 +125,6 @@ func NewHttp(option HttpOption) *Http {
} }
tlsConfig = &tls.Config{ tlsConfig = &tls.Config{
InsecureSkipVerify: option.SkipCertVerify, InsecureSkipVerify: option.SkipCertVerify,
ClientSessionCache: getClientSessionCache(),
ServerName: sni, ServerName: sni,
} }
} }

View File

@ -14,10 +14,12 @@ type Reject struct {
*Base *Base
} }
// DialContext implements C.ProxyAdapter
func (r *Reject) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) { func (r *Reject) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
return NewConn(&NopConn{}, r), nil return NewConn(&NopConn{}, r), nil
} }
// DialUDP implements C.ProxyAdapter
func (r *Reject) DialUDP(metadata *C.Metadata) (C.PacketConn, error) { func (r *Reject) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
return nil, errors.New("match reject rule") return nil, errors.New("match reject rule")
} }

View File

@ -2,7 +2,6 @@ package outbound
import ( import (
"context" "context"
"encoding/json"
"errors" "errors"
"fmt" "fmt"
"net" "net"
@ -10,10 +9,10 @@ import (
"github.com/Dreamacro/clash/common/structure" "github.com/Dreamacro/clash/common/structure"
"github.com/Dreamacro/clash/component/dialer" "github.com/Dreamacro/clash/component/dialer"
obfs "github.com/Dreamacro/clash/component/simple-obfs"
"github.com/Dreamacro/clash/component/socks5"
v2rayObfs "github.com/Dreamacro/clash/component/v2ray-plugin"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
obfs "github.com/Dreamacro/clash/transport/simple-obfs"
"github.com/Dreamacro/clash/transport/socks5"
v2rayObfs "github.com/Dreamacro/clash/transport/v2ray-plugin"
"github.com/Dreamacro/go-shadowsocks2/core" "github.com/Dreamacro/go-shadowsocks2/core"
) )
@ -54,6 +53,7 @@ type v2rayObfsOption struct {
Mux bool `obfs:"mux,omitempty"` Mux bool `obfs:"mux,omitempty"`
} }
// StreamConn implements C.ProxyAdapter
func (ss *ShadowSocks) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) { func (ss *ShadowSocks) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
switch ss.obfsMode { switch ss.obfsMode {
case "tls": case "tls":
@ -73,25 +73,30 @@ func (ss *ShadowSocks) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, e
return c, err return c, err
} }
func (ss *ShadowSocks) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) { // DialContext implements C.ProxyAdapter
func (ss *ShadowSocks) DialContext(ctx context.Context, metadata *C.Metadata) (_ C.Conn, err error) {
c, err := dialer.DialContext(ctx, "tcp", ss.addr) c, err := dialer.DialContext(ctx, "tcp", ss.addr)
if err != nil { if err != nil {
return nil, fmt.Errorf("%s connect error: %w", ss.addr, err) return nil, fmt.Errorf("%s connect error: %w", ss.addr, err)
} }
tcpKeepAlive(c) tcpKeepAlive(c)
defer safeConnClose(c, err)
c, err = ss.StreamConn(c, metadata) c, err = ss.StreamConn(c, metadata)
return NewConn(c, ss), err return NewConn(c, ss), err
} }
// DialUDP implements C.ProxyAdapter
func (ss *ShadowSocks) DialUDP(metadata *C.Metadata) (C.PacketConn, error) { func (ss *ShadowSocks) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
pc, err := dialer.ListenPacket("udp", "") pc, err := dialer.ListenPacket(context.Background(), "udp", "")
if err != nil { if err != nil {
return nil, err return nil, err
} }
addr, err := resolveUDPAddr("udp", ss.addr) addr, err := resolveUDPAddr("udp", ss.addr)
if err != nil { if err != nil {
pc.Close()
return nil, err return nil, err
} }
@ -99,12 +104,6 @@ func (ss *ShadowSocks) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
return newPacketConn(&ssPacketConn{PacketConn: pc, rAddr: addr}, ss), nil return newPacketConn(&ssPacketConn{PacketConn: pc, rAddr: addr}, ss), nil
} }
func (ss *ShadowSocks) MarshalJSON() ([]byte, error) {
return json.Marshal(map[string]string{
"type": ss.Type().String(),
})
}
func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) { func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) {
addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port)) addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port))
cipher := option.Cipher cipher := option.Cipher
@ -150,7 +149,6 @@ func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) {
if opts.TLS { if opts.TLS {
v2rayOption.TLS = true v2rayOption.TLS = true
v2rayOption.SkipCertVerify = opts.SkipCertVerify v2rayOption.SkipCertVerify = opts.SkipCertVerify
v2rayOption.SessionCache = getClientSessionCache()
} }
} }

View File

@ -2,15 +2,15 @@ package outbound
import ( import (
"context" "context"
"encoding/json"
"fmt" "fmt"
"net" "net"
"strconv" "strconv"
"github.com/Dreamacro/clash/component/dialer" "github.com/Dreamacro/clash/component/dialer"
"github.com/Dreamacro/clash/component/ssr/obfs"
"github.com/Dreamacro/clash/component/ssr/protocol"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/transport/ssr/obfs"
"github.com/Dreamacro/clash/transport/ssr/protocol"
"github.com/Dreamacro/go-shadowsocks2/core" "github.com/Dreamacro/go-shadowsocks2/core"
"github.com/Dreamacro/go-shadowsocks2/shadowaead" "github.com/Dreamacro/go-shadowsocks2/shadowaead"
"github.com/Dreamacro/go-shadowsocks2/shadowstream" "github.com/Dreamacro/go-shadowsocks2/shadowstream"
@ -36,6 +36,7 @@ type ShadowSocksROption struct {
UDP bool `proxy:"udp,omitempty"` UDP bool `proxy:"udp,omitempty"`
} }
// StreamConn implements C.ProxyAdapter
func (ssr *ShadowSocksR) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) { func (ssr *ShadowSocksR) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
c = ssr.obfs.StreamConn(c) c = ssr.obfs.StreamConn(c)
c = ssr.cipher.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 return c, err
} }
func (ssr *ShadowSocksR) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) { // DialContext implements C.ProxyAdapter
func (ssr *ShadowSocksR) DialContext(ctx context.Context, metadata *C.Metadata) (_ C.Conn, err error) {
c, err := dialer.DialContext(ctx, "tcp", ssr.addr) c, err := dialer.DialContext(ctx, "tcp", ssr.addr)
if err != nil { if err != nil {
return nil, fmt.Errorf("%s connect error: %w", ssr.addr, err) return nil, fmt.Errorf("%s connect error: %w", ssr.addr, err)
} }
tcpKeepAlive(c) tcpKeepAlive(c)
defer safeConnClose(c, err)
c, err = ssr.StreamConn(c, metadata) c, err = ssr.StreamConn(c, metadata)
return NewConn(c, ssr), err return NewConn(c, ssr), err
} }
// DialUDP implements C.ProxyAdapter
func (ssr *ShadowSocksR) DialUDP(metadata *C.Metadata) (C.PacketConn, error) { func (ssr *ShadowSocksR) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
pc, err := dialer.ListenPacket("udp", "") pc, err := dialer.ListenPacket(context.Background(), "udp", "")
if err != nil { if err != nil {
return nil, err return nil, err
} }
addr, err := resolveUDPAddr("udp", ssr.addr) addr, err := resolveUDPAddr("udp", ssr.addr)
if err != nil { if err != nil {
pc.Close()
return nil, err return nil, err
} }
@ -84,12 +90,6 @@ func (ssr *ShadowSocksR) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
return newPacketConn(&ssPacketConn{PacketConn: pc, rAddr: addr}, ssr), nil return newPacketConn(&ssPacketConn{PacketConn: pc, rAddr: addr}, ssr), nil
} }
func (ssr *ShadowSocksR) MarshalJSON() ([]byte, error) {
return json.Marshal(map[string]string{
"type": ssr.Type().String(),
})
}
func NewShadowSocksR(option ShadowSocksROption) (*ShadowSocksR, error) { func NewShadowSocksR(option ShadowSocksROption) (*ShadowSocksR, error) {
addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port)) addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port))
cipher := option.Cipher cipher := option.Cipher

View File

@ -8,9 +8,9 @@ import (
"github.com/Dreamacro/clash/common/structure" "github.com/Dreamacro/clash/common/structure"
"github.com/Dreamacro/clash/component/dialer" "github.com/Dreamacro/clash/component/dialer"
obfs "github.com/Dreamacro/clash/component/simple-obfs"
"github.com/Dreamacro/clash/component/snell"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
obfs "github.com/Dreamacro/clash/transport/simple-obfs"
"github.com/Dreamacro/clash/transport/snell"
) )
type Snell struct { type Snell struct {
@ -48,6 +48,7 @@ func streamConn(c net.Conn, option streamOption) *snell.Snell {
return snell.StreamConn(c, option.psk, option.version) return snell.StreamConn(c, option.psk, option.version)
} }
// StreamConn implements C.ProxyAdapter
func (s *Snell) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) { func (s *Snell) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
c = streamConn(c, streamOption{s.psk, s.version, s.addr, s.obfsOption}) c = streamConn(c, streamOption{s.psk, s.version, s.addr, s.obfsOption})
port, _ := strconv.Atoi(metadata.DstPort) port, _ := strconv.Atoi(metadata.DstPort)
@ -55,7 +56,8 @@ func (s *Snell) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
return c, err return c, err
} }
func (s *Snell) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) { // DialContext implements C.ProxyAdapter
func (s *Snell) DialContext(ctx context.Context, metadata *C.Metadata) (_ C.Conn, err error) {
if s.version == snell.Version2 { if s.version == snell.Version2 {
c, err := s.pool.Get() c, err := s.pool.Get()
if err != nil { if err != nil {
@ -63,7 +65,10 @@ func (s *Snell) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn,
} }
port, _ := strconv.Atoi(metadata.DstPort) port, _ := strconv.Atoi(metadata.DstPort)
err = snell.WriteHeader(c, metadata.String(), uint(port), s.version) if err = snell.WriteHeader(c, metadata.String(), uint(port), s.version); err != nil {
c.Close()
return nil, err
}
return NewConn(c, s), err return NewConn(c, s), err
} }
@ -73,6 +78,8 @@ func (s *Snell) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn,
} }
tcpKeepAlive(c) tcpKeepAlive(c)
defer safeConnClose(c, err)
c, err = s.StreamConn(c, metadata) c, err = s.StreamConn(c, metadata)
return NewConn(c, s), err return NewConn(c, s), err
} }

View File

@ -11,8 +11,8 @@ import (
"strconv" "strconv"
"github.com/Dreamacro/clash/component/dialer" "github.com/Dreamacro/clash/component/dialer"
"github.com/Dreamacro/clash/component/socks5"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/transport/socks5"
) )
type Socks5 struct { type Socks5 struct {
@ -35,6 +35,7 @@ type Socks5Option struct {
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"` SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
} }
// StreamConn implements C.ProxyAdapter
func (ss *Socks5) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) { func (ss *Socks5) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
if ss.tls { if ss.tls {
cc := tls.Client(c, ss.tlsConfig) cc := tls.Client(c, ss.tlsConfig)
@ -58,13 +59,16 @@ func (ss *Socks5) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error)
return c, nil return c, nil
} }
func (ss *Socks5) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) { // DialContext implements C.ProxyAdapter
func (ss *Socks5) DialContext(ctx context.Context, metadata *C.Metadata) (_ C.Conn, err error) {
c, err := dialer.DialContext(ctx, "tcp", ss.addr) c, err := dialer.DialContext(ctx, "tcp", ss.addr)
if err != nil { if err != nil {
return nil, fmt.Errorf("%s connect error: %w", ss.addr, err) return nil, fmt.Errorf("%s connect error: %w", ss.addr, err)
} }
tcpKeepAlive(c) tcpKeepAlive(c)
defer safeConnClose(c, err)
c, err = ss.StreamConn(c, metadata) c, err = ss.StreamConn(c, metadata)
if err != nil { if err != nil {
return nil, err return nil, err
@ -73,8 +77,9 @@ func (ss *Socks5) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn
return NewConn(c, ss), nil return NewConn(c, ss), nil
} }
// DialUDP implements C.ProxyAdapter
func (ss *Socks5) DialUDP(metadata *C.Metadata) (_ C.PacketConn, err error) { func (ss *Socks5) DialUDP(metadata *C.Metadata) (_ C.PacketConn, err error) {
ctx, cancel := context.WithTimeout(context.Background(), tcpTimeout) ctx, cancel := context.WithTimeout(context.Background(), C.DefaultTCPTimeout)
defer cancel() defer cancel()
c, err := dialer.DialContext(ctx, "tcp", ss.addr) c, err := dialer.DialContext(ctx, "tcp", ss.addr)
if err != nil { if err != nil {
@ -88,11 +93,7 @@ func (ss *Socks5) DialUDP(metadata *C.Metadata) (_ C.PacketConn, err error) {
c = cc c = cc
} }
defer func() { defer safeConnClose(c, err)
if err != nil {
c.Close()
}
}()
tcpKeepAlive(c) tcpKeepAlive(c)
var user *socks5.User var user *socks5.User
@ -109,7 +110,7 @@ func (ss *Socks5) DialUDP(metadata *C.Metadata) (_ C.PacketConn, err error) {
return return
} }
pc, err := dialer.ListenPacket("udp", "") pc, err := dialer.ListenPacket(context.Background(), "udp", "")
if err != nil { if err != nil {
return return
} }
@ -144,7 +145,6 @@ func NewSocks5(option Socks5Option) *Socks5 {
if option.TLS { if option.TLS {
tlsConfig = &tls.Config{ tlsConfig = &tls.Config{
InsecureSkipVerify: option.SkipCertVerify, InsecureSkipVerify: option.SkipCertVerify,
ClientSessionCache: getClientSessionCache(),
ServerName: option.Server, ServerName: option.Server,
} }
} }

176
adapter/outbound/trojan.go Normal file
View 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
}

View File

@ -2,56 +2,15 @@ package outbound
import ( import (
"bytes" "bytes"
"crypto/tls"
"fmt"
"net" "net"
"net/url"
"strconv" "strconv"
"sync"
"time" "time"
"github.com/Dreamacro/clash/component/resolver" "github.com/Dreamacro/clash/component/resolver"
"github.com/Dreamacro/clash/component/socks5"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/transport/socks5"
) )
const (
tcpTimeout = 5 * time.Second
)
var (
globalClientSessionCache tls.ClientSessionCache
once sync.Once
)
func urlToMetadata(rawURL string) (addr C.Metadata, err error) {
u, err := url.Parse(rawURL)
if err != nil {
return
}
port := u.Port()
if port == "" {
switch u.Scheme {
case "https":
port = "443"
case "http":
port = "80"
default:
err = fmt.Errorf("%s scheme not Support", rawURL)
return
}
}
addr = C.Metadata{
AddrType: C.AtypDomainName,
Host: u.Hostname(),
DstIP: nil,
DstPort: port,
}
return
}
func tcpKeepAlive(c net.Conn) { func tcpKeepAlive(c net.Conn) {
if tcp, ok := c.(*net.TCPConn); ok { if tcp, ok := c.(*net.TCPConn); ok {
tcp.SetKeepAlive(true) tcp.SetKeepAlive(true)
@ -59,13 +18,6 @@ func tcpKeepAlive(c net.Conn) {
} }
} }
func getClientSessionCache() tls.ClientSessionCache {
once.Do(func() {
globalClientSessionCache = tls.NewLRUClientSessionCache(128)
})
return globalClientSessionCache
}
func serializesSocksAddr(metadata *C.Metadata) []byte { func serializesSocksAddr(metadata *C.Metadata) []byte {
var buf [][]byte var buf [][]byte
aType := uint8(metadata.AddrType) aType := uint8(metadata.AddrType)
@ -98,3 +50,9 @@ func resolveUDPAddr(network, address string) (*net.UDPAddr, error) {
} }
return net.ResolveUDPAddr(network, net.JoinHostPort(ip.String(), port)) return net.ResolveUDPAddr(network, net.JoinHostPort(ip.String(), port))
} }
func safeConnClose(c net.Conn, err error) {
if err != nil {
c.Close()
}
}

View File

@ -2,6 +2,7 @@ package outbound
import ( import (
"context" "context"
"crypto/tls"
"errors" "errors"
"fmt" "fmt"
"net" "net"
@ -11,14 +12,22 @@ import (
"github.com/Dreamacro/clash/component/dialer" "github.com/Dreamacro/clash/component/dialer"
"github.com/Dreamacro/clash/component/resolver" "github.com/Dreamacro/clash/component/resolver"
"github.com/Dreamacro/clash/component/vmess"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/transport/gun"
"github.com/Dreamacro/clash/transport/vmess"
"golang.org/x/net/http2"
) )
type Vmess struct { type Vmess struct {
*Base *Base
client *vmess.Client client *vmess.Client
option *VmessOption option *VmessOption
// for gun mux
gunTLSConfig *tls.Config
gunConfig *gun.Config
transport *http2.Transport
} }
type VmessOption struct { type VmessOption struct {
@ -33,6 +42,8 @@ type VmessOption struct {
Network string `proxy:"network,omitempty"` Network string `proxy:"network,omitempty"`
HTTPOpts HTTPOptions `proxy:"http-opts,omitempty"` HTTPOpts HTTPOptions `proxy:"http-opts,omitempty"`
HTTP2Opts HTTP2Options `proxy:"h2-opts,omitempty"` HTTP2Opts HTTP2Options `proxy:"h2-opts,omitempty"`
GrpcOpts GrpcOptions `proxy:"grpc-opts,omitempty"`
WSOpts WSOptions `proxy:"ws-opts,omitempty"`
WSPath string `proxy:"ws-path,omitempty"` WSPath string `proxy:"ws-path,omitempty"`
WSHeaders map[string]string `proxy:"ws-headers,omitempty"` WSHeaders map[string]string `proxy:"ws-headers,omitempty"`
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"` SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
@ -50,20 +61,41 @@ type HTTP2Options struct {
Path string `proxy:"path,omitempty"` Path string `proxy:"path,omitempty"`
} }
type GrpcOptions struct {
GrpcServiceName string `proxy:"grpc-service-name,omitempty"`
}
type WSOptions struct {
Path string `proxy:"path,omitempty"`
Headers map[string]string `proxy:"headers,omitempty"`
MaxEarlyData int `proxy:"max-early-data,omitempty"`
EarlyDataHeaderName string `proxy:"early-data-header-name,omitempty"`
}
// StreamConn implements C.ProxyAdapter
func (v *Vmess) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) { func (v *Vmess) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
var err error var err error
switch v.option.Network { switch v.option.Network {
case "ws": case "ws":
host, port, _ := net.SplitHostPort(v.addr) if v.option.WSOpts.Path == "" {
wsOpts := &vmess.WebsocketConfig{ v.option.WSOpts.Path = v.option.WSPath
Host: host, }
Port: port, if len(v.option.WSOpts.Headers) == 0 {
Path: v.option.WSPath, v.option.WSOpts.Headers = v.option.WSHeaders
} }
if len(v.option.WSHeaders) != 0 { host, port, _ := net.SplitHostPort(v.addr)
wsOpts := &vmess.WebsocketConfig{
Host: host,
Port: port,
Path: v.option.WSOpts.Path,
MaxEarlyData: v.option.WSOpts.MaxEarlyData,
EarlyDataHeaderName: v.option.WSOpts.EarlyDataHeaderName,
}
if len(v.option.WSOpts.Headers) != 0 {
header := http.Header{} header := http.Header{}
for key, value := range v.option.WSHeaders { for key, value := range v.option.WSOpts.Headers {
header.Add(key, value) header.Add(key, value)
} }
wsOpts.Headers = header wsOpts.Headers = header
@ -71,7 +103,6 @@ func (v *Vmess) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
if v.option.TLS { if v.option.TLS {
wsOpts.TLS = true wsOpts.TLS = true
wsOpts.SessionCache = getClientSessionCache()
wsOpts.SkipCertVerify = v.option.SkipCertVerify wsOpts.SkipCertVerify = v.option.SkipCertVerify
wsOpts.ServerName = v.option.ServerName wsOpts.ServerName = v.option.ServerName
} }
@ -83,7 +114,6 @@ func (v *Vmess) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
tlsOpts := &vmess.TLSConfig{ tlsOpts := &vmess.TLSConfig{
Host: host, Host: host,
SkipCertVerify: v.option.SkipCertVerify, SkipCertVerify: v.option.SkipCertVerify,
SessionCache: getClientSessionCache(),
} }
if v.option.ServerName != "" { if v.option.ServerName != "" {
@ -110,7 +140,6 @@ func (v *Vmess) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
tlsOpts := vmess.TLSConfig{ tlsOpts := vmess.TLSConfig{
Host: host, Host: host,
SkipCertVerify: v.option.SkipCertVerify, SkipCertVerify: v.option.SkipCertVerify,
SessionCache: getClientSessionCache(),
NextProtos: []string{"h2"}, NextProtos: []string{"h2"},
} }
@ -129,6 +158,8 @@ func (v *Vmess) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
} }
c, err = vmess.StreamH2Conn(c, h2Opts) c, err = vmess.StreamH2Conn(c, h2Opts)
case "grpc":
c, err = gun.StreamGunWithConn(c, v.gunTLSConfig, v.gunConfig)
default: default:
// handle TLS // handle TLS
if v.option.TLS { if v.option.TLS {
@ -136,7 +167,6 @@ func (v *Vmess) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
tlsOpts := &vmess.TLSConfig{ tlsOpts := &vmess.TLSConfig{
Host: host, Host: host,
SkipCertVerify: v.option.SkipCertVerify, SkipCertVerify: v.option.SkipCertVerify,
SessionCache: getClientSessionCache(),
} }
if v.option.ServerName != "" { if v.option.ServerName != "" {
@ -154,19 +184,38 @@ func (v *Vmess) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
return v.client.StreamConn(c, parseVmessAddr(metadata)) return v.client.StreamConn(c, parseVmessAddr(metadata))
} }
func (v *Vmess) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) { // DialContext implements C.ProxyAdapter
func (v *Vmess) DialContext(ctx context.Context, metadata *C.Metadata) (_ C.Conn, err error) {
// gun transport
if v.transport != nil {
c, err := gun.StreamGunWithTransport(v.transport, v.gunConfig)
if err != nil {
return nil, err
}
defer safeConnClose(c, err)
c, err = v.client.StreamConn(c, parseVmessAddr(metadata))
if err != nil {
return nil, err
}
return NewConn(c, v), nil
}
c, err := dialer.DialContext(ctx, "tcp", v.addr) c, err := dialer.DialContext(ctx, "tcp", v.addr)
if err != nil { if err != nil {
return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error()) return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error())
} }
tcpKeepAlive(c) tcpKeepAlive(c)
defer safeConnClose(c, err)
c, err = v.StreamConn(c, metadata) c, err = v.StreamConn(c, metadata)
return NewConn(c, v), err return NewConn(c, v), err
} }
func (v *Vmess) DialUDP(metadata *C.Metadata) (C.PacketConn, error) { // DialUDP implements C.ProxyAdapter
// vmess use stream-oriented udp, so clash needs a net.UDPAddr func (v *Vmess) DialUDP(metadata *C.Metadata) (_ C.PacketConn, err error) {
// vmess use stream-oriented udp with a special address, so we needs a net.UDPAddr
if !metadata.Resolved() { if !metadata.Resolved() {
ip, err := resolver.ResolveIP(metadata.Host) ip, err := resolver.ResolveIP(metadata.Host)
if err != nil { if err != nil {
@ -175,17 +224,33 @@ func (v *Vmess) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
metadata.DstIP = ip metadata.DstIP = ip
} }
ctx, cancel := context.WithTimeout(context.Background(), tcpTimeout) var c net.Conn
defer cancel() // gun transport
c, err := dialer.DialContext(ctx, "tcp", v.addr) if v.transport != nil {
if err != nil { c, err = gun.StreamGunWithTransport(v.transport, v.gunConfig)
return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error()) if err != nil {
return nil, err
}
defer safeConnClose(c, err)
c, err = v.client.StreamConn(c, parseVmessAddr(metadata))
} else {
ctx, cancel := context.WithTimeout(context.Background(), C.DefaultTCPTimeout)
defer cancel()
c, err = dialer.DialContext(ctx, "tcp", v.addr)
if err != nil {
return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error())
}
tcpKeepAlive(c)
defer safeConnClose(c, err)
c, err = v.StreamConn(c, metadata)
} }
tcpKeepAlive(c)
c, err = v.StreamConn(c, metadata)
if err != nil { if err != nil {
return nil, fmt.Errorf("new vmess client error: %v", err) return nil, fmt.Errorf("new vmess client error: %v", err)
} }
return newPacketConn(&vmessPacketConn{Conn: c, rAddr: metadata.UDPAddr()}, v), nil return newPacketConn(&vmessPacketConn{Conn: c, rAddr: metadata.UDPAddr()}, v), nil
} }
@ -197,15 +262,20 @@ func NewVmess(option VmessOption) (*Vmess, error) {
Security: security, Security: security,
HostName: option.Server, HostName: option.Server,
Port: strconv.Itoa(option.Port), Port: strconv.Itoa(option.Port),
IsAead: option.AlterID == 0,
}) })
if err != nil { if err != nil {
return nil, err return nil, err
} }
if option.Network == "h2" && !option.TLS {
return nil, fmt.Errorf("TLS must be true with h2 network") switch option.Network {
case "h2", "grpc":
if !option.TLS {
return nil, fmt.Errorf("TLS must be true with h2/grpc network")
}
} }
return &Vmess{ v := &Vmess{
Base: &Base{ Base: &Base{
name: option.Name, name: option.Name,
addr: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)), addr: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)),
@ -214,7 +284,44 @@ func NewVmess(option VmessOption) (*Vmess, error) {
}, },
client: client, client: client,
option: &option, option: &option,
}, nil }
switch option.Network {
case "h2":
if len(option.HTTP2Opts.Host) == 0 {
option.HTTP2Opts.Host = append(option.HTTP2Opts.Host, "www.example.com")
}
case "grpc":
dialFn := func(network, addr string) (net.Conn, error) {
c, err := dialer.DialContext(context.Background(), "tcp", v.addr)
if err != nil {
return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error())
}
tcpKeepAlive(c)
return c, nil
}
gunConfig := &gun.Config{
ServiceName: v.option.GrpcOpts.GrpcServiceName,
Host: v.option.ServerName,
}
tlsConfig := &tls.Config{
InsecureSkipVerify: v.option.SkipCertVerify,
ServerName: v.option.ServerName,
}
if v.option.ServerName == "" {
host, _, _ := net.SplitHostPort(v.addr)
tlsConfig.ServerName = host
gunConfig.Host = host
}
v.gunTLSConfig = tlsConfig
v.gunConfig = gunConfig
v.transport = gun.NewHTTP2Client(dialFn, tlsConfig)
}
return v, nil
} }
func parseVmessAddr(metadata *C.Metadata) *vmess.DstAddr { func parseVmessAddr(metadata *C.Metadata) *vmess.DstAddr {

View File

@ -3,8 +3,8 @@ package outboundgroup
import ( import (
"time" "time"
"github.com/Dreamacro/clash/adapters/provider"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/constant/provider"
) )
const ( const (

View File

@ -4,10 +4,10 @@ import (
"context" "context"
"encoding/json" "encoding/json"
"github.com/Dreamacro/clash/adapters/outbound" "github.com/Dreamacro/clash/adapter/outbound"
"github.com/Dreamacro/clash/adapters/provider"
"github.com/Dreamacro/clash/common/singledo" "github.com/Dreamacro/clash/common/singledo"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/constant/provider"
) )
type Fallback struct { type Fallback struct {
@ -22,6 +22,7 @@ func (f *Fallback) Now() string {
return proxy.Name() return proxy.Name()
} }
// DialContext implements C.ProxyAdapter
func (f *Fallback) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) { func (f *Fallback) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
proxy := f.findAliveProxy(true) proxy := f.findAliveProxy(true)
c, err := proxy.DialContext(ctx, metadata) c, err := proxy.DialContext(ctx, metadata)
@ -31,6 +32,7 @@ func (f *Fallback) DialContext(ctx context.Context, metadata *C.Metadata) (C.Con
return c, err return c, err
} }
// DialUDP implements C.ProxyAdapter
func (f *Fallback) DialUDP(metadata *C.Metadata) (C.PacketConn, error) { func (f *Fallback) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
proxy := f.findAliveProxy(true) proxy := f.findAliveProxy(true)
pc, err := proxy.DialUDP(metadata) pc, err := proxy.DialUDP(metadata)
@ -40,6 +42,7 @@ func (f *Fallback) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
return pc, err return pc, err
} }
// SupportUDP implements C.ProxyAdapter
func (f *Fallback) SupportUDP() bool { func (f *Fallback) SupportUDP() bool {
if f.disableUDP { if f.disableUDP {
return false return false
@ -49,6 +52,7 @@ func (f *Fallback) SupportUDP() bool {
return proxy.SupportUDP() return proxy.SupportUDP()
} }
// MarshalJSON implements C.ProxyAdapter
func (f *Fallback) MarshalJSON() ([]byte, error) { func (f *Fallback) MarshalJSON() ([]byte, error) {
var all []string var all []string
for _, proxy := range f.proxies(false) { for _, proxy := range f.proxies(false) {
@ -61,6 +65,7 @@ func (f *Fallback) MarshalJSON() ([]byte, error) {
}) })
} }
// Unwrap implements C.ProxyAdapter
func (f *Fallback) Unwrap(metadata *C.Metadata) C.Proxy { func (f *Fallback) Unwrap(metadata *C.Metadata) C.Proxy {
proxy := f.findAliveProxy(true) proxy := f.findAliveProxy(true)
return proxy return proxy

View File

@ -7,11 +7,11 @@ import (
"fmt" "fmt"
"net" "net"
"github.com/Dreamacro/clash/adapters/outbound" "github.com/Dreamacro/clash/adapter/outbound"
"github.com/Dreamacro/clash/adapters/provider"
"github.com/Dreamacro/clash/common/murmur3" "github.com/Dreamacro/clash/common/murmur3"
"github.com/Dreamacro/clash/common/singledo" "github.com/Dreamacro/clash/common/singledo"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/constant/provider"
"golang.org/x/net/publicsuffix" "golang.org/x/net/publicsuffix"
) )
@ -68,6 +68,7 @@ func jumpHash(key uint64, buckets int32) int32 {
return int32(b) return int32(b)
} }
// DialContext implements C.ProxyAdapter
func (lb *LoadBalance) DialContext(ctx context.Context, metadata *C.Metadata) (c C.Conn, err error) { func (lb *LoadBalance) DialContext(ctx context.Context, metadata *C.Metadata) (c C.Conn, err error) {
defer func() { defer func() {
if err == nil { if err == nil {
@ -81,6 +82,7 @@ func (lb *LoadBalance) DialContext(ctx context.Context, metadata *C.Metadata) (c
return return
} }
// DialUDP implements C.ProxyAdapter
func (lb *LoadBalance) DialUDP(metadata *C.Metadata) (pc C.PacketConn, err error) { func (lb *LoadBalance) DialUDP(metadata *C.Metadata) (pc C.PacketConn, err error) {
defer func() { defer func() {
if err == nil { if err == nil {
@ -93,6 +95,7 @@ func (lb *LoadBalance) DialUDP(metadata *C.Metadata) (pc C.PacketConn, err error
return proxy.DialUDP(metadata) return proxy.DialUDP(metadata)
} }
// SupportUDP implements C.ProxyAdapter
func (lb *LoadBalance) SupportUDP() bool { func (lb *LoadBalance) SupportUDP() bool {
return !lb.disableUDP return !lb.disableUDP
} }
@ -130,6 +133,7 @@ func strategyConsistentHashing() strategyFn {
} }
} }
// Unwrap implements C.ProxyAdapter
func (lb *LoadBalance) Unwrap(metadata *C.Metadata) C.Proxy { func (lb *LoadBalance) Unwrap(metadata *C.Metadata) C.Proxy {
proxies := lb.proxies(true) proxies := lb.proxies(true)
return lb.strategyFn(proxies, metadata) return lb.strategyFn(proxies, metadata)
@ -143,6 +147,7 @@ func (lb *LoadBalance) proxies(touch bool) []C.Proxy {
return elm.([]C.Proxy) return elm.([]C.Proxy)
} }
// MarshalJSON implements C.ProxyAdapter
func (lb *LoadBalance) MarshalJSON() ([]byte, error) { func (lb *LoadBalance) MarshalJSON() ([]byte, error) {
var all []string var all []string
for _, proxy := range lb.proxies(false) { for _, proxy := range lb.proxies(false) {

View File

@ -4,9 +4,10 @@ import (
"errors" "errors"
"fmt" "fmt"
"github.com/Dreamacro/clash/adapters/provider" "github.com/Dreamacro/clash/adapter/provider"
"github.com/Dreamacro/clash/common/structure" "github.com/Dreamacro/clash/common/structure"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
types "github.com/Dreamacro/clash/constant/provider"
) )
var ( var (
@ -28,7 +29,7 @@ type GroupCommonOption struct {
DisableUDP bool `group:"disable-udp,omitempty"` DisableUDP bool `group:"disable-udp,omitempty"`
} }
func ParseProxyGroup(config map[string]interface{}, proxyMap map[string]C.Proxy, providersMap map[string]provider.ProxyProvider) (C.ProxyAdapter, error) { func ParseProxyGroup(config map[string]interface{}, proxyMap map[string]C.Proxy, providersMap map[string]types.ProxyProvider) (C.ProxyAdapter, error) {
decoder := structure.NewDecoder(structure.Option{TagName: "group", WeaklyTypedInput: true}) decoder := structure.NewDecoder(structure.Option{TagName: "group", WeaklyTypedInput: true})
groupOption := &GroupCommonOption{ groupOption := &GroupCommonOption{
@ -44,7 +45,7 @@ func ParseProxyGroup(config map[string]interface{}, proxyMap map[string]C.Proxy,
groupName := groupOption.Name groupName := groupOption.Name
providers := []provider.ProxyProvider{} providers := []types.ProxyProvider{}
if len(groupOption.Proxies) == 0 && len(groupOption.Use) == 0 { if len(groupOption.Proxies) == 0 && len(groupOption.Use) == 0 {
return nil, errMissProxy return nil, errMissProxy
@ -138,15 +139,15 @@ func getProxies(mapping map[string]C.Proxy, list []string) ([]C.Proxy, error) {
return ps, nil return ps, nil
} }
func getProviders(mapping map[string]provider.ProxyProvider, list []string) ([]provider.ProxyProvider, error) { func getProviders(mapping map[string]types.ProxyProvider, list []string) ([]types.ProxyProvider, error) {
var ps []provider.ProxyProvider var ps []types.ProxyProvider
for _, name := range list { for _, name := range list {
p, ok := mapping[name] p, ok := mapping[name]
if !ok { if !ok {
return nil, fmt.Errorf("'%s' not found", name) return nil, fmt.Errorf("'%s' not found", name)
} }
if p.VehicleType() == provider.Compatible { if p.VehicleType() == types.Compatible {
return nil, fmt.Errorf("proxy group %s can't contains in `use`", name) return nil, fmt.Errorf("proxy group %s can't contains in `use`", name)
} }
ps = append(ps, p) ps = append(ps, p)

View File

@ -3,14 +3,13 @@ package outboundgroup
import ( import (
"context" "context"
"encoding/json" "encoding/json"
"errors"
"fmt" "fmt"
"github.com/Dreamacro/clash/adapters/outbound" "github.com/Dreamacro/clash/adapter/outbound"
"github.com/Dreamacro/clash/adapters/provider"
"github.com/Dreamacro/clash/common/singledo" "github.com/Dreamacro/clash/common/singledo"
"github.com/Dreamacro/clash/component/dialer" "github.com/Dreamacro/clash/component/dialer"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/constant/provider"
) )
type Relay struct { type Relay struct {
@ -19,11 +18,22 @@ type Relay struct {
providers []provider.ProxyProvider providers []provider.ProxyProvider
} }
// DialContext implements C.ProxyAdapter
func (r *Relay) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) { func (r *Relay) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
proxies := r.proxies(metadata, true) var proxies []C.Proxy
if len(proxies) == 0 { for _, proxy := range r.proxies(metadata, true) {
return nil, errors.New("proxy does not exist") if proxy.Type() != C.Direct {
proxies = append(proxies, proxy)
}
} }
switch len(proxies) {
case 0:
return outbound.NewDirect().DialContext(ctx, metadata)
case 1:
return proxies[0].DialContext(ctx, metadata)
}
first := proxies[0] first := proxies[0]
last := proxies[len(proxies)-1] last := proxies[len(proxies)-1]
@ -56,6 +66,7 @@ func (r *Relay) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn,
return outbound.NewConn(c, r), nil return outbound.NewConn(c, r), nil
} }
// MarshalJSON implements C.ProxyAdapter
func (r *Relay) MarshalJSON() ([]byte, error) { func (r *Relay) MarshalJSON() ([]byte, error) {
var all []string var all []string
for _, proxy := range r.rawProxies(false) { for _, proxy := range r.rawProxies(false) {

View File

@ -5,10 +5,10 @@ import (
"encoding/json" "encoding/json"
"errors" "errors"
"github.com/Dreamacro/clash/adapters/outbound" "github.com/Dreamacro/clash/adapter/outbound"
"github.com/Dreamacro/clash/adapters/provider"
"github.com/Dreamacro/clash/common/singledo" "github.com/Dreamacro/clash/common/singledo"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/constant/provider"
) )
type Selector struct { type Selector struct {
@ -19,6 +19,7 @@ type Selector struct {
providers []provider.ProxyProvider providers []provider.ProxyProvider
} }
// DialContext implements C.ProxyAdapter
func (s *Selector) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) { func (s *Selector) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
c, err := s.selectedProxy(true).DialContext(ctx, metadata) c, err := s.selectedProxy(true).DialContext(ctx, metadata)
if err == nil { if err == nil {
@ -27,6 +28,7 @@ func (s *Selector) DialContext(ctx context.Context, metadata *C.Metadata) (C.Con
return c, err return c, err
} }
// DialUDP implements C.ProxyAdapter
func (s *Selector) DialUDP(metadata *C.Metadata) (C.PacketConn, error) { func (s *Selector) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
pc, err := s.selectedProxy(true).DialUDP(metadata) pc, err := s.selectedProxy(true).DialUDP(metadata)
if err == nil { if err == nil {
@ -35,6 +37,7 @@ func (s *Selector) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
return pc, err return pc, err
} }
// SupportUDP implements C.ProxyAdapter
func (s *Selector) SupportUDP() bool { func (s *Selector) SupportUDP() bool {
if s.disableUDP { if s.disableUDP {
return false return false
@ -43,6 +46,7 @@ func (s *Selector) SupportUDP() bool {
return s.selectedProxy(false).SupportUDP() return s.selectedProxy(false).SupportUDP()
} }
// MarshalJSON implements C.ProxyAdapter
func (s *Selector) MarshalJSON() ([]byte, error) { func (s *Selector) MarshalJSON() ([]byte, error) {
var all []string var all []string
for _, proxy := range getProvidersProxies(s.providers, false) { for _, proxy := range getProvidersProxies(s.providers, false) {
@ -72,6 +76,7 @@ func (s *Selector) Set(name string) error {
return errors.New("proxy not exist") return errors.New("proxy not exist")
} }
// Unwrap implements C.ProxyAdapter
func (s *Selector) Unwrap(metadata *C.Metadata) C.Proxy { func (s *Selector) Unwrap(metadata *C.Metadata) C.Proxy {
return s.selectedProxy(true) return s.selectedProxy(true)
} }

View File

@ -5,10 +5,10 @@ import (
"encoding/json" "encoding/json"
"time" "time"
"github.com/Dreamacro/clash/adapters/outbound" "github.com/Dreamacro/clash/adapter/outbound"
"github.com/Dreamacro/clash/adapters/provider"
"github.com/Dreamacro/clash/common/singledo" "github.com/Dreamacro/clash/common/singledo"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/constant/provider"
) )
type urlTestOption func(*URLTest) type urlTestOption func(*URLTest)
@ -33,6 +33,7 @@ func (u *URLTest) Now() string {
return u.fast(false).Name() return u.fast(false).Name()
} }
// DialContext implements C.ProxyAdapter
func (u *URLTest) DialContext(ctx context.Context, metadata *C.Metadata) (c C.Conn, err error) { func (u *URLTest) DialContext(ctx context.Context, metadata *C.Metadata) (c C.Conn, err error) {
c, err = u.fast(true).DialContext(ctx, metadata) c, err = u.fast(true).DialContext(ctx, metadata)
if err == nil { if err == nil {
@ -41,6 +42,7 @@ func (u *URLTest) DialContext(ctx context.Context, metadata *C.Metadata) (c C.Co
return c, err return c, err
} }
// DialUDP implements C.ProxyAdapter
func (u *URLTest) DialUDP(metadata *C.Metadata) (C.PacketConn, error) { func (u *URLTest) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
pc, err := u.fast(true).DialUDP(metadata) pc, err := u.fast(true).DialUDP(metadata)
if err == nil { if err == nil {
@ -49,6 +51,7 @@ func (u *URLTest) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
return pc, err return pc, err
} }
// Unwrap implements C.ProxyAdapter
func (u *URLTest) Unwrap(metadata *C.Metadata) C.Proxy { func (u *URLTest) Unwrap(metadata *C.Metadata) C.Proxy {
return u.fast(true) return u.fast(true)
} }
@ -66,7 +69,13 @@ func (u *URLTest) fast(touch bool) C.Proxy {
proxies := u.proxies(touch) proxies := u.proxies(touch)
fast := proxies[0] fast := proxies[0]
min := fast.LastDelay() min := fast.LastDelay()
fastNotExist := true
for _, proxy := range proxies[1:] { for _, proxy := range proxies[1:] {
if u.fastNode != nil && proxy.Name() == u.fastNode.Name() {
fastNotExist = false
}
if !proxy.Alive() { if !proxy.Alive() {
continue continue
} }
@ -79,7 +88,7 @@ func (u *URLTest) fast(touch bool) C.Proxy {
} }
// tolerance // tolerance
if u.fastNode == nil || u.fastNode.LastDelay() > fast.LastDelay()+u.tolerance { if u.fastNode == nil || fastNotExist || !u.fastNode.Alive() || u.fastNode.LastDelay() > fast.LastDelay()+u.tolerance {
u.fastNode = fast u.fastNode = fast
} }
@ -89,6 +98,7 @@ func (u *URLTest) fast(touch bool) C.Proxy {
return elm.(C.Proxy) return elm.(C.Proxy)
} }
// SupportUDP implements C.ProxyAdapter
func (u *URLTest) SupportUDP() bool { func (u *URLTest) SupportUDP() bool {
if u.disableUDP { if u.disableUDP {
return false return false
@ -97,6 +107,7 @@ func (u *URLTest) SupportUDP() bool {
return u.fast(false).SupportUDP() return u.fast(false).SupportUDP()
} }
// MarshalJSON implements C.ProxyAdapter
func (u *URLTest) MarshalJSON() ([]byte, error) { func (u *URLTest) MarshalJSON() ([]byte, error) {
var all []string var all []string
for _, proxy := range u.proxies(false) { for _, proxy := range u.proxies(false) {

View File

@ -16,25 +16,7 @@ func addrToMetadata(rawAddress string) (addr *C.Metadata, err error) {
} }
ip := net.ParseIP(host) ip := net.ParseIP(host)
if ip != nil { if ip == nil {
if ip.To4() != nil {
addr = &C.Metadata{
AddrType: C.AtypIPv4,
Host: "",
DstIP: ip,
DstPort: port,
}
return
} else {
addr = &C.Metadata{
AddrType: C.AtypIPv6,
Host: "",
DstIP: ip,
DstPort: port,
}
return
}
} else {
addr = &C.Metadata{ addr = &C.Metadata{
AddrType: C.AtypDomainName, AddrType: C.AtypDomainName,
Host: host, Host: host,
@ -42,7 +24,23 @@ func addrToMetadata(rawAddress string) (addr *C.Metadata, err error) {
DstPort: port, DstPort: port,
} }
return return
} else if ip4 := ip.To4(); ip4 != nil {
addr = &C.Metadata{
AddrType: C.AtypIPv4,
Host: "",
DstIP: ip4,
DstPort: port,
}
return
} }
addr = &C.Metadata{
AddrType: C.AtypIPv6,
Host: "",
DstIP: ip,
DstPort: port,
}
return
} }
func tcpKeepAlive(c net.Conn) { func tcpKeepAlive(c net.Conn) {

View File

@ -1,8 +1,9 @@
package outbound package adapter
import ( import (
"fmt" "fmt"
"github.com/Dreamacro/clash/adapter/outbound"
"github.com/Dreamacro/clash/common/structure" "github.com/Dreamacro/clash/common/structure"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
) )
@ -20,36 +21,36 @@ func ParseProxy(mapping map[string]interface{}) (C.Proxy, error) {
) )
switch proxyType { switch proxyType {
case "ss": case "ss":
ssOption := &ShadowSocksOption{} ssOption := &outbound.ShadowSocksOption{}
err = decoder.Decode(mapping, ssOption) err = decoder.Decode(mapping, ssOption)
if err != nil { if err != nil {
break break
} }
proxy, err = NewShadowSocks(*ssOption) proxy, err = outbound.NewShadowSocks(*ssOption)
case "ssr": case "ssr":
ssrOption := &ShadowSocksROption{} ssrOption := &outbound.ShadowSocksROption{}
err = decoder.Decode(mapping, ssrOption) err = decoder.Decode(mapping, ssrOption)
if err != nil { if err != nil {
break break
} }
proxy, err = NewShadowSocksR(*ssrOption) proxy, err = outbound.NewShadowSocksR(*ssrOption)
case "socks5": case "socks5":
socksOption := &Socks5Option{} socksOption := &outbound.Socks5Option{}
err = decoder.Decode(mapping, socksOption) err = decoder.Decode(mapping, socksOption)
if err != nil { if err != nil {
break break
} }
proxy = NewSocks5(*socksOption) proxy = outbound.NewSocks5(*socksOption)
case "http": case "http":
httpOption := &HttpOption{} httpOption := &outbound.HttpOption{}
err = decoder.Decode(mapping, httpOption) err = decoder.Decode(mapping, httpOption)
if err != nil { if err != nil {
break break
} }
proxy = NewHttp(*httpOption) proxy = outbound.NewHttp(*httpOption)
case "vmess": case "vmess":
vmessOption := &VmessOption{ vmessOption := &outbound.VmessOption{
HTTPOpts: HTTPOptions{ HTTPOpts: outbound.HTTPOptions{
Method: "GET", Method: "GET",
Path: []string{"/"}, Path: []string{"/"},
}, },
@ -58,21 +59,21 @@ func ParseProxy(mapping map[string]interface{}) (C.Proxy, error) {
if err != nil { if err != nil {
break break
} }
proxy, err = NewVmess(*vmessOption) proxy, err = outbound.NewVmess(*vmessOption)
case "snell": case "snell":
snellOption := &SnellOption{} snellOption := &outbound.SnellOption{}
err = decoder.Decode(mapping, snellOption) err = decoder.Decode(mapping, snellOption)
if err != nil { if err != nil {
break break
} }
proxy, err = NewSnell(*snellOption) proxy, err = outbound.NewSnell(*snellOption)
case "trojan": case "trojan":
trojanOption := &TrojanOption{} trojanOption := &outbound.TrojanOption{}
err = decoder.Decode(mapping, trojanOption) err = decoder.Decode(mapping, trojanOption)
if err != nil { if err != nil {
break break
} }
proxy, err = NewTrojan(*trojanOption) proxy, err = outbound.NewTrojan(*trojanOption)
default: default:
return nil, fmt.Errorf("unsupport proxy type: %s", proxyType) return nil, fmt.Errorf("unsupport proxy type: %s", proxyType)
} }

View File

@ -8,6 +8,7 @@ import (
"path/filepath" "path/filepath"
"time" "time"
types "github.com/Dreamacro/clash/constant/provider"
"github.com/Dreamacro/clash/log" "github.com/Dreamacro/clash/log"
) )
@ -20,7 +21,7 @@ type parser = func([]byte) (interface{}, error)
type fetcher struct { type fetcher struct {
name string name string
vehicle Vehicle vehicle types.Vehicle
updatedAt *time.Time updatedAt *time.Time
ticker *time.Ticker ticker *time.Ticker
done chan struct{} done chan struct{}
@ -33,7 +34,7 @@ func (f *fetcher) Name() string {
return f.name return f.name
} }
func (f *fetcher) VehicleType() VehicleType { func (f *fetcher) VehicleType() types.VehicleType {
return f.vehicle.Type() return f.vehicle.Type()
} }
@ -72,9 +73,11 @@ func (f *fetcher) Initial() (interface{}, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
isLocal = false
} }
if f.vehicle.Type() != File && !isLocal { if f.vehicle.Type() != types.File && !isLocal {
if err := safeWrite(f.vehicle.Path(), buf); err != nil { if err := safeWrite(f.vehicle.Path(), buf); err != nil {
return nil, err return nil, err
} }
@ -108,7 +111,7 @@ func (f *fetcher) Update() (interface{}, bool, error) {
return nil, false, err return nil, false, err
} }
if f.vehicle.Type() != File { if f.vehicle.Type() != types.File {
if err := safeWrite(f.vehicle.Path(), buf); err != nil { if err := safeWrite(f.vehicle.Path(), buf); err != nil {
return nil, false, err return nil, false, err
} }
@ -165,7 +168,7 @@ func safeWrite(path string, buf []byte) error {
return ioutil.WriteFile(path, buf, fileMode) return ioutil.WriteFile(path, buf, fileMode)
} }
func newFetcher(name string, interval time.Duration, vehicle Vehicle, parser parser, onUpdate func(interface{})) *fetcher { func newFetcher(name string, interval time.Duration, vehicle types.Vehicle, parser parser, onUpdate func(interface{})) *fetcher {
var ticker *time.Ticker var ticker *time.Ticker
if interval != 0 { if interval != 0 {
ticker = time.NewTicker(interval) ticker = time.NewTicker(interval)

View File

@ -2,9 +2,9 @@ package provider
import ( import (
"context" "context"
"sync"
"time" "time"
"github.com/Dreamacro/clash/common/batch"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
"go.uber.org/atomic" "go.uber.org/atomic"
@ -59,20 +59,17 @@ func (hc *HealthCheck) touch() {
} }
func (hc *HealthCheck) check() { func (hc *HealthCheck) check() {
ctx, cancel := context.WithTimeout(context.Background(), defaultURLTestTimeout) b, _ := batch.New(context.Background(), batch.WithConcurrencyNum(10))
wg := &sync.WaitGroup{}
for _, proxy := range hc.proxies { for _, proxy := range hc.proxies {
wg.Add(1) p := proxy
b.Go(p.Name(), func() (interface{}, error) {
go func(p C.Proxy) { ctx, cancel := context.WithTimeout(context.Background(), defaultURLTestTimeout)
defer cancel()
p.URLTest(ctx, hc.url) p.URLTest(ctx, hc.url)
wg.Done() return nil, nil
}(proxy) })
} }
b.Wait()
wg.Wait()
cancel()
} }
func (hc *HealthCheck) close() { func (hc *HealthCheck) close() {

View File

@ -7,6 +7,7 @@ import (
"github.com/Dreamacro/clash/common/structure" "github.com/Dreamacro/clash/common/structure"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
types "github.com/Dreamacro/clash/constant/provider"
) )
var ( var (
@ -28,7 +29,7 @@ type proxyProviderSchema struct {
HealthCheck healthCheckSchema `provider:"health-check,omitempty"` HealthCheck healthCheckSchema `provider:"health-check,omitempty"`
} }
func ParseProxyProvider(name string, mapping map[string]interface{}) (ProxyProvider, error) { func ParseProxyProvider(name string, mapping map[string]interface{}) (types.ProxyProvider, error) {
decoder := structure.NewDecoder(structure.Option{TagName: "provider", WeaklyTypedInput: true}) decoder := structure.NewDecoder(structure.Option{TagName: "provider", WeaklyTypedInput: true})
schema := &proxyProviderSchema{ schema := &proxyProviderSchema{
@ -40,7 +41,7 @@ func ParseProxyProvider(name string, mapping map[string]interface{}) (ProxyProvi
return nil, err return nil, err
} }
var hcInterval uint = 0 var hcInterval uint
if schema.HealthCheck.Enable { if schema.HealthCheck.Enable {
hcInterval = uint(schema.HealthCheck.Interval) hcInterval = uint(schema.HealthCheck.Interval)
} }
@ -48,7 +49,7 @@ func ParseProxyProvider(name string, mapping map[string]interface{}) (ProxyProvi
path := C.Path.Resolve(schema.Path) path := C.Path.Resolve(schema.Path)
var vehicle Vehicle var vehicle types.Vehicle
switch schema.Type { switch schema.Type {
case "file": case "file":
vehicle = NewFileVehicle(path) vehicle = NewFileVehicle(path)

View File

@ -7,8 +7,9 @@ import (
"runtime" "runtime"
"time" "time"
"github.com/Dreamacro/clash/adapters/outbound" "github.com/Dreamacro/clash/adapter"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
types "github.com/Dreamacro/clash/constant/provider"
"gopkg.in/yaml.v2" "gopkg.in/yaml.v2"
) )
@ -17,45 +18,6 @@ const (
ReservedName = "default" ReservedName = "default"
) )
// Provider Type
const (
Proxy ProviderType = iota
Rule
)
// ProviderType defined
type ProviderType int
func (pt ProviderType) String() string {
switch pt {
case Proxy:
return "Proxy"
case Rule:
return "Rule"
default:
return "Unknown"
}
}
// Provider interface
type Provider interface {
Name() string
VehicleType() VehicleType
Type() ProviderType
Initial() error
Update() error
}
// ProxyProvider interface
type ProxyProvider interface {
Provider
Proxies() []C.Proxy
// ProxiesWithTouch is used to inform the provider that the proxy is actually being used while getting the list of proxies.
// Commonly used in Dial and DialUDP
ProxiesWithTouch() []C.Proxy
HealthCheck()
}
type ProxySchema struct { type ProxySchema struct {
Proxies []map[string]interface{} `yaml:"proxies"` Proxies []map[string]interface{} `yaml:"proxies"`
} }
@ -107,8 +69,8 @@ func (pp *proxySetProvider) Initial() error {
return nil return nil
} }
func (pp *proxySetProvider) Type() ProviderType { func (pp *proxySetProvider) Type() types.ProviderType {
return Proxy return types.Proxy
} }
func (pp *proxySetProvider) Proxies() []C.Proxy { func (pp *proxySetProvider) Proxies() []C.Proxy {
@ -133,7 +95,7 @@ func proxiesParse(buf []byte) (interface{}, error) {
proxies := []C.Proxy{} proxies := []C.Proxy{}
for idx, mapping := range schema.Proxies { for idx, mapping := range schema.Proxies {
proxy, err := outbound.ParseProxy(mapping) proxy, err := adapter.ParseProxy(mapping)
if err != nil { if err != nil {
return nil, fmt.Errorf("proxy %d error: %w", idx, err) return nil, fmt.Errorf("proxy %d error: %w", idx, err)
} }
@ -160,7 +122,7 @@ func stopProxyProvider(pd *ProxySetProvider) {
pd.fetcher.Destroy() pd.fetcher.Destroy()
} }
func NewProxySetProvider(name string, interval time.Duration, vehicle Vehicle, hc *HealthCheck) *ProxySetProvider { func NewProxySetProvider(name string, interval time.Duration, vehicle types.Vehicle, hc *HealthCheck) *ProxySetProvider {
if hc.auto() { if hc.auto() {
go hc.process() go hc.process()
} }
@ -219,12 +181,12 @@ func (cp *compatibleProvider) Initial() error {
return nil return nil
} }
func (cp *compatibleProvider) VehicleType() VehicleType { func (cp *compatibleProvider) VehicleType() types.VehicleType {
return Compatible return types.Compatible
} }
func (cp *compatibleProvider) Type() ProviderType { func (cp *compatibleProvider) Type() types.ProviderType {
return Proxy return types.Proxy
} }
func (cp *compatibleProvider) Proxies() []C.Proxy { func (cp *compatibleProvider) Proxies() []C.Proxy {
@ -242,7 +204,7 @@ func stopCompatibleProvider(pd *CompatibleProvider) {
func NewCompatibleProvider(name string, proxies []C.Proxy, hc *HealthCheck) (*CompatibleProvider, error) { func NewCompatibleProvider(name string, proxies []C.Proxy, hc *HealthCheck) (*CompatibleProvider, error) {
if len(proxies) == 0 { if len(proxies) == 0 {
return nil, errors.New("Provider need one proxy at least") return nil, errors.New("provider need one proxy at least")
} }
if hc.auto() { if hc.auto() {

View File

@ -3,48 +3,21 @@ package provider
import ( import (
"context" "context"
"io/ioutil" "io/ioutil"
"net"
"net/http" "net/http"
"net/url" "net/url"
"time" "time"
"github.com/Dreamacro/clash/component/dialer" "github.com/Dreamacro/clash/component/dialer"
types "github.com/Dreamacro/clash/constant/provider"
) )
// Vehicle Type
const (
File VehicleType = iota
HTTP
Compatible
)
// VehicleType defined
type VehicleType int
func (v VehicleType) String() string {
switch v {
case File:
return "File"
case HTTP:
return "HTTP"
case Compatible:
return "Compatible"
default:
return "Unknown"
}
}
type Vehicle interface {
Read() ([]byte, error)
Path() string
Type() VehicleType
}
type FileVehicle struct { type FileVehicle struct {
path string path string
} }
func (f *FileVehicle) Type() VehicleType { func (f *FileVehicle) Type() types.VehicleType {
return File return types.File
} }
func (f *FileVehicle) Path() string { func (f *FileVehicle) Path() string {
@ -64,8 +37,8 @@ type HTTPVehicle struct {
path string path string
} }
func (h *HTTPVehicle) Type() VehicleType { func (h *HTTPVehicle) Type() types.VehicleType {
return HTTP return types.HTTP
} }
func (h *HTTPVehicle) Path() string { func (h *HTTPVehicle) Path() string {
@ -99,7 +72,9 @@ func (h *HTTPVehicle) Read() ([]byte, error) {
IdleConnTimeout: 90 * time.Second, IdleConnTimeout: 90 * time.Second,
TLSHandshakeTimeout: 10 * time.Second, TLSHandshakeTimeout: 10 * time.Second,
ExpectContinueTimeout: 1 * time.Second, ExpectContinueTimeout: 1 * time.Second,
DialContext: dialer.DialContext, DialContext: func(ctx context.Context, network, address string) (net.Conn, error) {
return dialer.DialContext(ctx, network, address)
},
} }
client := http.Client{Transport: transport} client := http.Client{Transport: transport}

View File

@ -1,61 +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))
}
}
// RemoveExtraHTTPHostPort remove extra host port (example.com:80 --> example.com)
// It resolves the behavior of some HTTP servers that do not handle host:80 (e.g. baidu.com)
func RemoveExtraHTTPHostPort(req *http.Request) {
host := req.Host
if host == "" {
host = req.URL.Host
}
if pHost, port, err := net.SplitHostPort(host); err == nil && port == "80" {
host = pHost
}
req.Host = host
req.URL.Host = host
}

View File

@ -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
View 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
}

View 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)
}

View File

@ -1,4 +1,4 @@
package mixed package net
import ( import (
"bufio" "bufio"

View File

@ -8,11 +8,7 @@ import (
"sync" "sync"
) )
var defaultAllocator *Allocator var defaultAllocator = NewAllocator()
func init() {
defaultAllocator = NewAllocator()
}
// Allocator for incoming frames, optimized to prevent overwriting after zeroing // Allocator for incoming frames, optimized to prevent overwriting after zeroing
type Allocator struct { type Allocator struct {

View File

@ -1,3 +1,4 @@
//go:build !linux
// +build !linux // +build !linux
package sockopt package sockopt

18
component/dhcp/conn.go Normal file
View 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
View 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
}

View File

@ -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
}

View File

@ -3,51 +3,57 @@ package dialer
import ( import (
"net" "net"
"syscall" "syscall"
"golang.org/x/sys/unix"
"github.com/Dreamacro/clash/component/iface"
) )
type controlFn = func(network, address string, c syscall.RawConn) error type controlFn = func(network, address string, c syscall.RawConn) error
func bindControl(ifaceIdx int) controlFn { func bindControl(ifaceIdx int, chain controlFn) controlFn {
return func(network, address string, c syscall.RawConn) error { return func(network, address string, c syscall.RawConn) (err error) {
defer func() {
if err == nil && chain != nil {
err = chain(network, address, c)
}
}()
ipStr, _, err := net.SplitHostPort(address) ipStr, _, err := net.SplitHostPort(address)
if err == nil { if err == nil {
ip := net.ParseIP(ipStr) ip := net.ParseIP(ipStr)
if ip != nil && !ip.IsGlobalUnicast() { if ip != nil && !ip.IsGlobalUnicast() {
return nil return
} }
} }
return c.Control(func(fd uintptr) { return c.Control(func(fd uintptr) {
switch network { switch network {
case "tcp4", "udp4": case "tcp4", "udp4":
syscall.SetsockoptInt(int(fd), syscall.IPPROTO_IP, syscall.IP_BOUND_IF, ifaceIdx) unix.SetsockoptInt(int(fd), unix.IPPROTO_IP, unix.IP_BOUND_IF, ifaceIdx)
case "tcp6", "udp6": case "tcp6", "udp6":
syscall.SetsockoptInt(int(fd), syscall.IPPROTO_IPV6, syscall.IPV6_BOUND_IF, ifaceIdx) unix.SetsockoptInt(int(fd), unix.IPPROTO_IPV6, unix.IPV6_BOUND_IF, ifaceIdx)
} }
}) })
} }
} }
func bindIfaceToDialer(dialer *net.Dialer, ifaceName string) error { func bindIfaceToDialer(ifaceName string, dialer *net.Dialer, _ string, _ net.IP) error {
iface, err, _ := ifaceSingle.Do(func() (interface{}, error) { ifaceObj, err := iface.ResolveInterface(ifaceName)
return net.InterfaceByName(ifaceName)
})
if err != nil { if err != nil {
return err return err
} }
dialer.Control = bindControl(iface.(*net.Interface).Index) dialer.Control = bindControl(ifaceObj.Index, dialer.Control)
return nil return nil
} }
func bindIfaceToListenConfig(lc *net.ListenConfig, ifaceName string) error { func bindIfaceToListenConfig(ifaceName string, lc *net.ListenConfig, _, address string) (string, error) {
iface, err, _ := ifaceSingle.Do(func() (interface{}, error) { ifaceObj, err := iface.ResolveInterface(ifaceName)
return net.InterfaceByName(ifaceName)
})
if err != nil { if err != nil {
return err return "", err
} }
lc.Control = bindControl(iface.(*net.Interface).Index) lc.Control = bindControl(ifaceObj.Index, lc.Control)
return nil return address, nil
} }

View File

@ -3,34 +3,42 @@ package dialer
import ( import (
"net" "net"
"syscall" "syscall"
"golang.org/x/sys/unix"
) )
type controlFn = func(network, address string, c syscall.RawConn) error type controlFn = func(network, address string, c syscall.RawConn) error
func bindControl(ifaceName string) controlFn { func bindControl(ifaceName string, chain controlFn) controlFn {
return func(network, address string, c syscall.RawConn) error { return func(network, address string, c syscall.RawConn) (err error) {
defer func() {
if err == nil && chain != nil {
err = chain(network, address, c)
}
}()
ipStr, _, err := net.SplitHostPort(address) ipStr, _, err := net.SplitHostPort(address)
if err == nil { if err == nil {
ip := net.ParseIP(ipStr) ip := net.ParseIP(ipStr)
if ip != nil && !ip.IsGlobalUnicast() { if ip != nil && !ip.IsGlobalUnicast() {
return nil return
} }
} }
return c.Control(func(fd uintptr) { return c.Control(func(fd uintptr) {
syscall.BindToDevice(int(fd), ifaceName) unix.BindToDevice(int(fd), ifaceName)
}) })
} }
} }
func bindIfaceToDialer(dialer *net.Dialer, ifaceName string) error { func bindIfaceToDialer(ifaceName string, dialer *net.Dialer, _ string, _ net.IP) error {
dialer.Control = bindControl(ifaceName) dialer.Control = bindControl(ifaceName, dialer.Control)
return nil return nil
} }
func bindIfaceToListenConfig(lc *net.ListenConfig, ifaceName string) error { func bindIfaceToListenConfig(ifaceName string, lc *net.ListenConfig, _, address string) (string, error) {
lc.Control = bindControl(ifaceName) lc.Control = bindControl(ifaceName, lc.Control)
return nil return address, nil
} }

View File

@ -1,13 +1,93 @@
//go:build !linux && !darwin
// +build !linux,!darwin // +build !linux,!darwin
package dialer package dialer
import "net" import (
"net"
"strconv"
"strings"
func bindIfaceToDialer(dialer *net.Dialer, ifaceName string) error { "github.com/Dreamacro/clash/component/iface"
return errPlatformNotSupport )
func lookupLocalAddr(ifaceName string, network string, destination net.IP, port int) (net.Addr, error) {
ifaceObj, err := iface.ResolveInterface(ifaceName)
if err != nil {
return nil, err
}
var addr *net.IPNet
switch network {
case "udp4", "tcp4":
addr, err = ifaceObj.PickIPv4Addr(destination)
case "tcp6", "udp6":
addr, err = ifaceObj.PickIPv6Addr(destination)
default:
if destination != nil {
if destination.To4() != nil {
addr, err = ifaceObj.PickIPv4Addr(destination)
} else {
addr, err = ifaceObj.PickIPv6Addr(destination)
}
} else {
addr, err = ifaceObj.PickIPv4Addr(destination)
}
}
if err != nil {
return nil, err
}
if strings.HasPrefix(network, "tcp") {
return &net.TCPAddr{
IP: addr.IP,
Port: port,
}, nil
} else if strings.HasPrefix(network, "udp") {
return &net.UDPAddr{
IP: addr.IP,
Port: port,
}, nil
}
return nil, iface.ErrAddrNotFound
} }
func bindIfaceToListenConfig(lc *net.ListenConfig, ifaceName string) error { func bindIfaceToDialer(ifaceName string, dialer *net.Dialer, network string, destination net.IP) error {
return errPlatformNotSupport if !destination.IsGlobalUnicast() {
return nil
}
local := 0
if dialer.LocalAddr != nil {
_, port, err := net.SplitHostPort(dialer.LocalAddr.String())
if err == nil {
local, _ = strconv.Atoi(port)
}
}
addr, err := lookupLocalAddr(ifaceName, network, destination, local)
if err != nil {
return err
}
dialer.LocalAddr = addr
return nil
}
func bindIfaceToListenConfig(ifaceName string, _ *net.ListenConfig, network, address string) (string, error) {
_, port, err := net.SplitHostPort(address)
if err != nil {
port = "0"
}
local, _ := strconv.Atoi(port)
addr, err := lookupLocalAddr(ifaceName, network, nil, local)
if err != nil {
return "", err
}
return addr.String(), nil
} }

View File

@ -8,22 +8,7 @@ import (
"github.com/Dreamacro/clash/component/resolver" "github.com/Dreamacro/clash/component/resolver"
) )
func Dialer() (*net.Dialer, error) { func DialContext(ctx context.Context, network, address string, options ...Option) (net.Conn, error) {
dialer := &net.Dialer{}
if DialerHook != nil {
if err := DialerHook(dialer); err != nil {
return nil, err
}
}
return dialer, nil
}
func Dial(network, address string) (net.Conn, error) {
return DialContext(context.Background(), network, address)
}
func DialContext(ctx context.Context, network, address string) (net.Conn, error) {
switch network { switch network {
case "tcp4", "tcp6", "udp4", "udp6": case "tcp4", "tcp6", "udp4", "udp6":
host, port, err := net.SplitHostPort(address) host, port, err := net.SplitHostPort(address)
@ -31,11 +16,6 @@ func DialContext(ctx context.Context, network, address string) (net.Conn, error)
return nil, err return nil, err
} }
dialer, err := Dialer()
if err != nil {
return nil, err
}
var ip net.IP var ip net.IP
switch network { switch network {
case "tcp4", "udp4": case "tcp4", "udp4":
@ -43,38 +23,70 @@ func DialContext(ctx context.Context, network, address string) (net.Conn, error)
default: default:
ip, err = resolver.ResolveIPv6(host) ip, err = resolver.ResolveIPv6(host)
} }
if err != nil { if err != nil {
return nil, err return nil, err
} }
if DialHook != nil { return dialContext(ctx, network, ip, port, options)
if err := DialHook(dialer, network, ip); err != nil {
return nil, err
}
}
return dialer.DialContext(ctx, network, net.JoinHostPort(ip.String(), port))
case "tcp", "udp": case "tcp", "udp":
return dualStackDialContext(ctx, network, address) return dualStackDialContext(ctx, network, address, options)
default: default:
return nil, errors.New("network invalid") return nil, errors.New("network invalid")
} }
} }
func ListenPacket(network, address string) (net.PacketConn, error) { func ListenPacket(ctx context.Context, network, address string, options ...Option) (net.PacketConn, error) {
cfg := &net.ListenConfig{} cfg := &config{}
if ListenPacketHook != nil {
var err error if !cfg.skipDefault {
address, err = ListenPacketHook(cfg, address) for _, o := range DefaultOptions {
o(cfg)
}
}
for _, o := range options {
o(cfg)
}
lc := &net.ListenConfig{}
if cfg.interfaceName != "" {
addr, err := bindIfaceToListenConfig(cfg.interfaceName, lc, network, address)
if err != nil { if err != nil {
return nil, err return nil, err
} }
address = addr
}
if cfg.addrReuse {
addrReuseToListenConfig(lc)
}
return lc.ListenPacket(ctx, network, address)
}
func dialContext(ctx context.Context, network string, destination net.IP, port string, options []Option) (net.Conn, error) {
opt := &config{}
if !opt.skipDefault {
for _, o := range DefaultOptions {
o(opt)
}
}
for _, o := range options {
o(opt)
}
dialer := &net.Dialer{}
if opt.interfaceName != "" {
if err := bindIfaceToDialer(opt.interfaceName, dialer, network, destination); err != nil {
return nil, err
}
} }
return cfg.ListenPacket(context.Background(), network, address) return dialer.DialContext(ctx, network, net.JoinHostPort(destination.String(), port))
} }
func dualStackDialContext(ctx context.Context, network, address string) (net.Conn, error) { func dualStackDialContext(ctx context.Context, network, address string, options []Option) (net.Conn, error) {
host, port, err := net.SplitHostPort(address) host, port, err := net.SplitHostPort(address)
if err != nil { if err != nil {
return nil, err return nil, err
@ -105,12 +117,6 @@ func dualStackDialContext(ctx context.Context, network, address string) (net.Con
} }
}() }()
dialer, err := Dialer()
if err != nil {
result.error = err
return
}
var ip net.IP var ip net.IP
if ipv6 { if ipv6 {
ip, result.error = resolver.ResolveIPv6(host) ip, result.error = resolver.ResolveIPv6(host)
@ -122,12 +128,7 @@ func dualStackDialContext(ctx context.Context, network, address string) (net.Con
} }
result.resolved = true result.resolved = true
if DialHook != nil { result.Conn, result.error = dialContext(ctx, network, ip, port, options)
if result.error = DialHook(dialer, network, ip); result.error != nil {
return
}
}
result.Conn, result.error = dialer.DialContext(ctx, network, net.JoinHostPort(ip.String(), port))
} }
go startRacer(ctx, network+"4", host, false) go startRacer(ctx, network+"4", host, false)

View File

@ -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
}
}

View 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
}
}

View 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) {}

View 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)
})
}
}

View 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
View 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
}

View File

@ -1,12 +1,13 @@
package process package process
import ( import (
"bytes"
"encoding/binary" "encoding/binary"
"net" "net"
"path/filepath" "path/filepath"
"syscall" "syscall"
"unsafe" "unsafe"
"golang.org/x/sys/unix"
) )
const ( const (
@ -94,12 +95,8 @@ func getExecPathFromPID(pid uint32) (string, error) {
if errno != 0 { if errno != 0 {
return "", errno return "", errno
} }
firstZero := bytes.IndexByte(buf, 0)
if firstZero <= 0 {
return "", nil
}
return filepath.Base(string(buf[:firstZero])), nil return filepath.Base(unix.ByteSliceToString(buf)), nil
} }
func readNativeUint32(b []byte) uint32 { func readNativeUint32(b []byte) uint32 {

View File

@ -30,6 +30,10 @@ func findProcessName(network string, ip net.IP, srcPort int) (string, error) {
} }
}) })
if defaultSearcher == nil {
return "", ErrPlatformNotSupport
}
var spath string var spath string
isTCP := network == TCP isTCP := network == TCP
switch network { switch network {
@ -173,7 +177,7 @@ func (s *searcher) searchSocketPid(socket uint64) (uint32, error) {
} }
func newSearcher(major int) *searcher { func newSearcher(major int) *searcher {
var s *searcher = nil var s *searcher
switch major { switch major {
case 11: case 11:
s = &searcher{ s = &searcher{
@ -190,6 +194,8 @@ func newSearcher(major int) *searcher {
udpInpOffset: 8, udpInpOffset: 8,
} }
case 12: case 12:
fallthrough
case 13:
s = &searcher{ s = &searcher{
headSize: 64, headSize: 64,
tcpItemSize: 744, tcpItemSize: 744,

View File

@ -16,14 +16,14 @@ import (
) )
// from https://github.com/vishvananda/netlink/blob/bca67dfc8220b44ef582c9da4e9172bf1c9ec973/nl/nl_linux.go#L52-L62 // from https://github.com/vishvananda/netlink/blob/bca67dfc8220b44ef582c9da4e9172bf1c9ec973/nl/nl_linux.go#L52-L62
func init() { var nativeEndian = func() binary.ByteOrder {
var x uint32 = 0x01020304 var x uint32 = 0x01020304
if *(*byte)(unsafe.Pointer(&x)) == 0x01 { if *(*byte)(unsafe.Pointer(&x)) == 0x01 {
nativeEndian = binary.BigEndian return binary.BigEndian
} else {
nativeEndian = binary.LittleEndian
} }
}
return binary.LittleEndian
}()
type SocketResolver func(network string, ip net.IP, srcPort int) (inode, uid int, err error) type SocketResolver func(network string, ip net.IP, srcPort int) (inode, uid int, err error)
type ProcessNameResolver func(inode, uid int) (name string, err error) type ProcessNameResolver func(inode, uid int) (name string, err error)
@ -40,8 +40,6 @@ const (
pathProc = "/proc" pathProc = "/proc"
) )
var nativeEndian binary.ByteOrder = binary.LittleEndian
func findProcessName(network string, ip net.IP, srcPort int) (string, error) { func findProcessName(network string, ip net.IP, srcPort int) (string, error) {
inode, uid, err := DefaultSocketResolver(network, ip, srcPort) inode, uid, err := DefaultSocketResolver(network, ip, srcPort)
if err != nil { if err != nil {

View File

@ -1,4 +1,7 @@
// +build !darwin,!linux,!windows //go:build !darwin && !linux && !windows && (!freebsd || !amd64)
// +build !darwin
// +build !linux
// +build !windows
// +build !freebsd !amd64 // +build !freebsd !amd64
package process package process

View File

@ -22,8 +22,8 @@ const (
) )
var ( var (
getExTcpTable uintptr getExTCPTable uintptr
getExUdpTable uintptr getExUDPTable uintptr
queryProcName uintptr queryProcName uintptr
once sync.Once once sync.Once
@ -35,12 +35,12 @@ func initWin32API() error {
return fmt.Errorf("LoadLibrary iphlpapi.dll failed: %s", err.Error()) return fmt.Errorf("LoadLibrary iphlpapi.dll failed: %s", err.Error())
} }
getExTcpTable, err = windows.GetProcAddress(h, tcpTableFunc) getExTCPTable, err = windows.GetProcAddress(h, tcpTableFunc)
if err != nil { if err != nil {
return fmt.Errorf("GetProcAddress of %s failed: %s", tcpTableFunc, err.Error()) return fmt.Errorf("GetProcAddress of %s failed: %s", tcpTableFunc, err.Error())
} }
getExUdpTable, err = windows.GetProcAddress(h, udpTableFunc) getExUDPTable, err = windows.GetProcAddress(h, udpTableFunc)
if err != nil { if err != nil {
return fmt.Errorf("GetProcAddress of %s failed: %s", udpTableFunc, err.Error()) return fmt.Errorf("GetProcAddress of %s failed: %s", udpTableFunc, err.Error())
} }
@ -76,10 +76,10 @@ func findProcessName(network string, ip net.IP, srcPort int) (string, error) {
var fn uintptr var fn uintptr
switch network { switch network {
case TCP: case TCP:
fn = getExTcpTable fn = getExTCPTable
class = tcpTablePidConn class = tcpTablePidConn
case UDP: case UDP:
fn = getExUdpTable fn = getExUDPTable
class = udpTablePid class = udpTablePid
default: default:
return "", ErrInvalidNetwork return "", ErrInvalidNetwork

View File

@ -1,9 +1,12 @@
package resolver package resolver
import ( import (
"context"
"errors" "errors"
"math/rand"
"net" "net"
"strings" "strings"
"time"
"github.com/Dreamacro/clash/component/trie" "github.com/Dreamacro/clash/component/trie"
) )
@ -18,6 +21,9 @@ var (
// DefaultHosts aim to resolve hosts // DefaultHosts aim to resolve hosts
DefaultHosts = trie.New() DefaultHosts = trie.New()
// DefaultDNSTimeout defined the default dns request timeout
DefaultDNSTimeout = time.Second * 5
) )
var ( var (
@ -52,18 +58,16 @@ func ResolveIPv4(host string) (net.IP, error) {
return DefaultResolver.ResolveIPv4(host) return DefaultResolver.ResolveIPv4(host)
} }
ipAddrs, err := net.LookupIP(host) ctx, cancel := context.WithTimeout(context.Background(), DefaultDNSTimeout)
defer cancel()
ipAddrs, err := net.DefaultResolver.LookupIP(ctx, "ip4", host)
if err != nil { if err != nil {
return nil, err return nil, err
} else if len(ipAddrs) == 0 {
return nil, ErrIPNotFound
} }
for _, ip := range ipAddrs { return ipAddrs[rand.Intn(len(ipAddrs))], nil
if ip4 := ip.To4(); ip4 != nil {
return ip4, nil
}
}
return nil, ErrIPNotFound
} }
// ResolveIPv6 with a host, return ipv6 // ResolveIPv6 with a host, return ipv6
@ -90,31 +94,29 @@ func ResolveIPv6(host string) (net.IP, error) {
return DefaultResolver.ResolveIPv6(host) return DefaultResolver.ResolveIPv6(host)
} }
ipAddrs, err := net.LookupIP(host) ctx, cancel := context.WithTimeout(context.Background(), DefaultDNSTimeout)
defer cancel()
ipAddrs, err := net.DefaultResolver.LookupIP(ctx, "ip6", host)
if err != nil { if err != nil {
return nil, err return nil, err
} else if len(ipAddrs) == 0 {
return nil, ErrIPNotFound
} }
for _, ip := range ipAddrs { return ipAddrs[rand.Intn(len(ipAddrs))], nil
if ip.To4() == nil {
return ip, nil
}
}
return nil, ErrIPNotFound
} }
// ResolveIP with a host, return ip // ResolveIPWithResolver same as ResolveIP, but with a resolver
func ResolveIP(host string) (net.IP, error) { func ResolveIPWithResolver(host string, r Resolver) (net.IP, error) {
if node := DefaultHosts.Search(host); node != nil { if node := DefaultHosts.Search(host); node != nil {
return node.Data.(net.IP), nil return node.Data.(net.IP), nil
} }
if DefaultResolver != nil { if r != nil {
if DisableIPv6 { if DisableIPv6 {
return DefaultResolver.ResolveIPv4(host) return r.ResolveIPv4(host)
} }
return DefaultResolver.ResolveIP(host) return r.ResolveIP(host)
} else if DisableIPv6 { } else if DisableIPv6 {
return ResolveIPv4(host) return ResolveIPv4(host)
} }
@ -131,3 +133,8 @@ func ResolveIP(host string) (net.IP, error) {
return ipAddr.IP, nil return ipAddr.IP, nil
} }
// ResolveIP with a host, return ip
func ResolveIP(host string) (net.IP, error) {
return ResolveIPWithResolver(host, DefaultResolver)
}

View File

@ -23,7 +23,7 @@ type DomainTrie struct {
root *Node root *Node
} }
func validAndSplitDomain(domain string) ([]string, bool) { func ValidAndSplitDomain(domain string) ([]string, bool) {
if domain != "" && domain[len(domain)-1] == '.' { if domain != "" && domain[len(domain)-1] == '.' {
return nil, false return nil, false
} }
@ -54,7 +54,7 @@ func validAndSplitDomain(domain string) ([]string, bool) {
// 4. .example.com // 4. .example.com
// 5. +.example.com // 5. +.example.com
func (t *DomainTrie) Insert(domain string, data interface{}) error { func (t *DomainTrie) Insert(domain string, data interface{}) error {
parts, valid := validAndSplitDomain(domain) parts, valid := ValidAndSplitDomain(domain)
if !valid { if !valid {
return ErrInvalidDomain return ErrInvalidDomain
} }
@ -91,7 +91,7 @@ func (t *DomainTrie) insert(parts []string, data interface{}) {
// 2. wildcard domain // 2. wildcard domain
// 2. dot wildcard domain // 2. dot wildcard domain
func (t *DomainTrie) Search(domain string) *Node { func (t *DomainTrie) Search(domain string) *Node {
parts, valid := validAndSplitDomain(domain) parts, valid := ValidAndSplitDomain(domain)
if !valid || parts[0] == "" { if !valid || parts[0] == "" {
return nil return nil
} }
@ -122,11 +122,7 @@ func (t *DomainTrie) search(node *Node, parts []string) *Node {
} }
} }
if c := node.getChild(dotWildcard); c != nil { return node.getChild(dotWildcard)
return c
}
return nil
} }
// New returns a new, empty Trie. // New returns a new, empty Trie.

View File

@ -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
}

View File

@ -8,19 +8,21 @@ import (
"os" "os"
"strings" "strings"
"github.com/Dreamacro/clash/adapters/outbound" "github.com/Dreamacro/clash/adapter"
"github.com/Dreamacro/clash/adapters/outboundgroup" "github.com/Dreamacro/clash/adapter/outbound"
"github.com/Dreamacro/clash/adapters/provider" "github.com/Dreamacro/clash/adapter/outboundgroup"
"github.com/Dreamacro/clash/adapter/provider"
"github.com/Dreamacro/clash/component/auth" "github.com/Dreamacro/clash/component/auth"
"github.com/Dreamacro/clash/component/fakeip" "github.com/Dreamacro/clash/component/fakeip"
"github.com/Dreamacro/clash/component/trie" "github.com/Dreamacro/clash/component/trie"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
providerTypes "github.com/Dreamacro/clash/constant/provider"
"github.com/Dreamacro/clash/dns" "github.com/Dreamacro/clash/dns"
"github.com/Dreamacro/clash/log" "github.com/Dreamacro/clash/log"
R "github.com/Dreamacro/clash/rules" R "github.com/Dreamacro/clash/rule"
T "github.com/Dreamacro/clash/tunnel" T "github.com/Dreamacro/clash/tunnel"
yaml "gopkg.in/yaml.v2" "gopkg.in/yaml.v2"
) )
// General config // General config
@ -64,13 +66,15 @@ type DNS struct {
DefaultNameserver []dns.NameServer `yaml:"default-nameserver"` DefaultNameserver []dns.NameServer `yaml:"default-nameserver"`
FakeIPRange *fakeip.Pool FakeIPRange *fakeip.Pool
Hosts *trie.DomainTrie Hosts *trie.DomainTrie
NameServerPolicy map[string]dns.NameServer
} }
// FallbackFilter config // FallbackFilter config
type FallbackFilter struct { type FallbackFilter struct {
GeoIP bool `yaml:"geoip"` GeoIP bool `yaml:"geoip"`
IPCIDR []*net.IPNet `yaml:"ipcidr"` GeoIPCode string `yaml:"geoip-code"`
Domain []string `yaml:"domain"` IPCIDR []*net.IPNet `yaml:"ipcidr"`
Domain []string `yaml:"domain"`
} }
// Profile config // Profile config
@ -91,7 +95,7 @@ type Config struct {
Rules []C.Rule Rules []C.Rule
Users []auth.AuthUser Users []auth.AuthUser
Proxies map[string]C.Proxy Proxies map[string]C.Proxy
Providers map[string]provider.ProxyProvider Providers map[string]providerTypes.ProxyProvider
} }
type RawDNS struct { type RawDNS struct {
@ -106,12 +110,14 @@ type RawDNS struct {
FakeIPRange string `yaml:"fake-ip-range"` FakeIPRange string `yaml:"fake-ip-range"`
FakeIPFilter []string `yaml:"fake-ip-filter"` FakeIPFilter []string `yaml:"fake-ip-filter"`
DefaultNameserver []string `yaml:"default-nameserver"` DefaultNameserver []string `yaml:"default-nameserver"`
NameServerPolicy map[string]string `yaml:"nameserver-policy"`
} }
type RawFallbackFilter struct { type RawFallbackFilter struct {
GeoIP bool `yaml:"geoip"` GeoIP bool `yaml:"geoip"`
IPCIDR []string `yaml:"ipcidr"` GeoIPCode string `yaml:"geoip-code"`
Domain []string `yaml:"domain"` IPCIDR []string `yaml:"ipcidr"`
Domain []string `yaml:"domain"`
} }
type RawConfig struct { type RawConfig struct {
@ -168,8 +174,9 @@ func UnmarshalRawConfig(buf []byte) (*RawConfig, error) {
UseHosts: true, UseHosts: true,
FakeIPRange: "198.18.0.1/16", FakeIPRange: "198.18.0.1/16",
FallbackFilter: RawFallbackFilter{ FallbackFilter: RawFallbackFilter{
GeoIP: true, GeoIP: true,
IPCIDR: []string{}, GeoIPCode: "CN",
IPCIDR: []string{},
}, },
DefaultNameserver: []string{ DefaultNameserver: []string{
"114.114.114.114", "114.114.114.114",
@ -181,7 +188,7 @@ func UnmarshalRawConfig(buf []byte) (*RawConfig, error) {
}, },
} }
if err := yaml.Unmarshal(buf, &rawCfg); err != nil { if err := yaml.Unmarshal(buf, rawCfg); err != nil {
return nil, err return nil, err
} }
@ -264,21 +271,21 @@ func parseGeneral(cfg *RawConfig) (*General, error) {
}, nil }, nil
} }
func parseProxies(cfg *RawConfig) (proxies map[string]C.Proxy, providersMap map[string]provider.ProxyProvider, err error) { func parseProxies(cfg *RawConfig) (proxies map[string]C.Proxy, providersMap map[string]providerTypes.ProxyProvider, err error) {
proxies = make(map[string]C.Proxy) proxies = make(map[string]C.Proxy)
providersMap = make(map[string]provider.ProxyProvider) providersMap = make(map[string]providerTypes.ProxyProvider)
proxyList := []string{} proxyList := []string{}
proxiesConfig := cfg.Proxy proxiesConfig := cfg.Proxy
groupsConfig := cfg.ProxyGroup groupsConfig := cfg.ProxyGroup
providersConfig := cfg.ProxyProvider providersConfig := cfg.ProxyProvider
proxies["DIRECT"] = outbound.NewProxy(outbound.NewDirect()) proxies["DIRECT"] = adapter.NewProxy(outbound.NewDirect())
proxies["REJECT"] = outbound.NewProxy(outbound.NewReject()) proxies["REJECT"] = adapter.NewProxy(outbound.NewReject())
proxyList = append(proxyList, "DIRECT", "REJECT") proxyList = append(proxyList, "DIRECT", "REJECT")
// parse proxy // parse proxy
for idx, mapping := range proxiesConfig { for idx, mapping := range proxiesConfig {
proxy, err := outbound.ParseProxy(mapping) proxy, err := adapter.ParseProxy(mapping)
if err != nil { if err != nil {
return nil, nil, fmt.Errorf("proxy %d: %w", idx, err) return nil, nil, fmt.Errorf("proxy %d: %w", idx, err)
} }
@ -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) return nil, nil, fmt.Errorf("proxy group %s: the duplicate name", groupName)
} }
proxies[groupName] = outbound.NewProxy(group) proxies[groupName] = adapter.NewProxy(group)
} }
// initial compatible provider // initial compatible provider
for _, pd := range providersMap { for _, pd := range providersMap {
if pd.VehicleType() != provider.Compatible { if pd.VehicleType() != providerTypes.Compatible {
continue continue
} }
@ -364,9 +371,9 @@ func parseProxies(cfg *RawConfig) (proxies map[string]C.Proxy, providersMap map[
&outboundgroup.GroupCommonOption{ &outboundgroup.GroupCommonOption{
Name: "GLOBAL", Name: "GLOBAL",
}, },
[]provider.ProxyProvider{pd}, []providerTypes.ProxyProvider{pd},
) )
proxies["GLOBAL"] = outbound.NewProxy(global) proxies["GLOBAL"] = adapter.NewProxy(global)
return proxies, providersMap, nil return proxies, providersMap, nil
} }
@ -481,6 +488,9 @@ func parseNameServer(servers []string) ([]dns.NameServer, error) {
clearURL := url.URL{Scheme: "https", Host: u.Host, Path: u.Path} clearURL := url.URL{Scheme: "https", Host: u.Host, Path: u.Path}
addr = clearURL.String() addr = clearURL.String()
dnsNetType = "https" // DNS over HTTPS dnsNetType = "https" // DNS over HTTPS
case "dhcp":
addr = u.Host
dnsNetType = "dhcp" // UDP from DHCP
default: default:
return nil, fmt.Errorf("DNS NameServer[%d] unsupport scheme: %s", idx, u.Scheme) return nil, fmt.Errorf("DNS NameServer[%d] unsupport scheme: %s", idx, u.Scheme)
} }
@ -500,6 +510,23 @@ func parseNameServer(servers []string) ([]dns.NameServer, error) {
return nameservers, nil return nameservers, nil
} }
func parseNameServerPolicy(nsPolicy map[string]string) (map[string]dns.NameServer, error) {
policy := map[string]dns.NameServer{}
for domain, server := range nsPolicy {
nameservers, err := parseNameServer([]string{server})
if err != nil {
return nil, err
}
if _, valid := trie.ValidAndSplitDomain(domain); !valid {
return nil, fmt.Errorf("DNS ResoverRule invalid domain: %s", domain)
}
policy[domain] = nameservers[0]
}
return policy, nil
}
func parseFallbackIPCIDR(ips []string) ([]*net.IPNet, error) { func parseFallbackIPCIDR(ips []string) ([]*net.IPNet, error) {
ipNets := []*net.IPNet{} ipNets := []*net.IPNet{}
@ -537,6 +564,10 @@ func parseDNS(cfg RawDNS, hosts *trie.DomainTrie) (*DNS, error) {
return nil, err return nil, err
} }
if dnsCfg.NameServerPolicy, err = parseNameServerPolicy(cfg.NameServerPolicy); err != nil {
return nil, err
}
if len(cfg.DefaultNameserver) == 0 { if len(cfg.DefaultNameserver) == 0 {
return nil, errors.New("default nameserver should have at least one nameserver") return nil, errors.New("default nameserver should have at least one nameserver")
} }
@ -575,6 +606,7 @@ func parseDNS(cfg RawDNS, hosts *trie.DomainTrie) (*DNS, error) {
} }
dnsCfg.FallbackFilter.GeoIP = cfg.FallbackFilter.GeoIP dnsCfg.FallbackFilter.GeoIP = cfg.FallbackFilter.GeoIP
dnsCfg.FallbackFilter.GeoIPCode = cfg.FallbackFilter.GeoIPCode
if fallbackip, err := parseFallbackIPCIDR(cfg.FallbackFilter.IPCIDR); err == nil { if fallbackip, err := parseFallbackIPCIDR(cfg.FallbackFilter.IPCIDR); err == nil {
dnsCfg.FallbackFilter.IPCIDR = fallbackip dnsCfg.FallbackFilter.IPCIDR = fallbackip
} }

View File

@ -66,7 +66,7 @@ func Init(dir string) error {
if err != nil { if err != nil {
return fmt.Errorf("can't create file %s: %s", C.Path.Config(), err.Error()) return fmt.Errorf("can't create file %s: %s", C.Path.Config(), err.Error())
} }
f.Write([]byte(`port: 7890`)) f.Write([]byte(`mixed-port: 7890`))
f.Close() f.Close()
} }

View File

@ -4,7 +4,7 @@ import (
"fmt" "fmt"
"strings" "strings"
"github.com/Dreamacro/clash/adapters/outboundgroup" "github.com/Dreamacro/clash/adapter/outboundgroup"
"github.com/Dreamacro/clash/common/structure" "github.com/Dreamacro/clash/common/structure"
) )

View File

@ -27,6 +27,10 @@ const (
LoadBalance LoadBalance
) )
const (
DefaultTCPTimeout = 5 * time.Second
)
type Connection interface { type Connection interface {
Chains() Chain Chains() Chain
AppendToChains(adapter ProxyAdapter) AppendToChains(adapter ProxyAdapter)
@ -69,8 +73,21 @@ type PacketConn interface {
type ProxyAdapter interface { type ProxyAdapter interface {
Name() string Name() string
Type() AdapterType Type() AdapterType
// StreamConn wraps a protocol around net.Conn with Metadata.
//
// Examples:
// conn, _ := net.Dial("tcp", "host:port")
// conn, _ = adapter.StreamConn(conn, metadata)
//
// It returns a C.Conn with protocol which start with
// a new session (if any)
StreamConn(c net.Conn, metadata *Metadata) (net.Conn, error) StreamConn(c net.Conn, metadata *Metadata) (net.Conn, error)
// DialContext return a C.Conn with protocol which
// contains multiplexing-related reuse logic (if any)
DialContext(ctx context.Context, metadata *Metadata) (Conn, error) DialContext(ctx context.Context, metadata *Metadata) (Conn, error)
DialUDP(metadata *Metadata) (PacketConn, error) DialUDP(metadata *Metadata) (PacketConn, error)
SupportUDP() bool SupportUDP() bool
MarshalJSON() ([]byte, error) MarshalJSON() ([]byte, error)

7
constant/listener.go Normal file
View File

@ -0,0 +1,7 @@
package constant
type Listener interface {
RawAddress() string
Address() string
Close() error
}

View File

@ -17,7 +17,8 @@ const (
HTTP Type = iota HTTP Type = iota
HTTPCONNECT HTTPCONNECT
SOCKS SOCKS4
SOCKS5
REDIR REDIR
TPROXY TPROXY
) )
@ -43,7 +44,9 @@ func (t Type) String() string {
return "HTTP" return "HTTP"
case HTTPCONNECT: case HTTPCONNECT:
return "HTTP Connect" return "HTTP Connect"
case SOCKS: case SOCKS4:
return "Socks4"
case SOCKS5:
return "Socks5" return "Socks5"
case REDIR: case REDIR:
return "Redir" return "Redir"

View File

@ -9,21 +9,19 @@ import (
const Name = "clash" const Name = "clash"
// Path is used to get the configuration path // Path is used to get the configuration path
var Path *path var Path = func() *path {
type path struct {
homeDir string
configFile string
}
func init() {
homeDir, err := os.UserHomeDir() homeDir, err := os.UserHomeDir()
if err != nil { if err != nil {
homeDir, _ = os.Getwd() homeDir, _ = os.Getwd()
} }
homeDir = P.Join(homeDir, ".config", Name) homeDir = P.Join(homeDir, ".config", Name)
Path = &path{homeDir: homeDir, configFile: "config.yaml"} return &path{homeDir: homeDir, configFile: "config.yaml"}
}()
type path struct {
homeDir string
configFile string
} }
// SetHomeDir is used to set the configuration path // SetHomeDir is used to set the configuration path

View 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
}

View File

@ -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
}

View File

@ -2,11 +2,13 @@ package dns
import ( import (
"context" "context"
"crypto/tls"
"fmt" "fmt"
"net" "net"
"strings" "strings"
"github.com/Dreamacro/clash/component/dialer" "github.com/Dreamacro/clash/component/dialer"
"github.com/Dreamacro/clash/component/resolver"
D "github.com/miekg/dns" D "github.com/miekg/dns"
) )
@ -18,38 +20,36 @@ type client struct {
host string host string
} }
func (c *client) Exchange(m *D.Msg) (msg *D.Msg, err error) { func (c *client) Exchange(m *D.Msg) (*D.Msg, error) {
return c.ExchangeContext(context.Background(), m) return c.ExchangeContext(context.Background(), m)
} }
func (c *client) ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg, err error) { func (c *client) ExchangeContext(ctx context.Context, m *D.Msg) (*D.Msg, error) {
var ip net.IP var (
ip net.IP
err error
)
if c.r == nil { if c.r == nil {
// a default ip dns // a default ip dns
ip = net.ParseIP(c.host) if ip = net.ParseIP(c.host); ip == nil {
return nil, fmt.Errorf("dns %s not a valid ip", c.host)
}
} else { } else {
var err error if ip, err = resolver.ResolveIPWithResolver(c.host, c.r); err != nil {
if ip, err = c.r.ResolveIP(c.host); err != nil {
return nil, fmt.Errorf("use default dns resolve failed: %w", err) return nil, fmt.Errorf("use default dns resolve failed: %w", err)
} }
} }
d, err := dialer.Dialer() network := "udp"
if strings.HasPrefix(c.Client.Net, "tcp") {
network = "tcp"
}
conn, err := dialer.DialContext(ctx, network, net.JoinHostPort(ip.String(), c.port))
if err != nil { if err != nil {
return nil, err return nil, err
} }
defer conn.Close()
if ip != nil && ip.IsGlobalUnicast() && dialer.DialHook != nil {
network := "udp"
if strings.HasPrefix(c.Client.Net, "tcp") {
network = "tcp"
}
if err := dialer.DialHook(d, network, ip); err != nil {
return nil, err
}
}
c.Client.Dialer = d
// miekg/dns ExchangeContext doesn't respond to context cancel. // miekg/dns ExchangeContext doesn't respond to context cancel.
// this is a workaround // this is a workaround
@ -59,7 +59,17 @@ func (c *client) ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg, err
} }
ch := make(chan result, 1) ch := make(chan result, 1)
go func() { go func() {
msg, _, err := c.Client.Exchange(m, net.JoinHostPort(ip.String(), c.port)) if strings.HasSuffix(c.Client.Net, "tls") {
conn = tls.Client(conn, c.Client.TLSConfig)
}
msg, _, err := c.Client.ExchangeWithConn(m, &D.Conn{
Conn: conn,
UDPSize: c.Client.UDPSize,
TsigSecret: c.Client.TsigSecret,
TsigProvider: c.Client.TsigProvider,
})
ch <- result{msg, err} ch <- result{msg, err}
}() }()

144
dns/dhcp.go Normal file
View 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}
}

View File

@ -3,12 +3,12 @@ package dns
import ( import (
"bytes" "bytes"
"context" "context"
"crypto/tls"
"io/ioutil" "io/ioutil"
"net" "net"
"net/http" "net/http"
"github.com/Dreamacro/clash/component/dialer" "github.com/Dreamacro/clash/component/dialer"
"github.com/Dreamacro/clash/component/resolver"
D "github.com/miekg/dns" D "github.com/miekg/dns"
) )
@ -75,7 +75,6 @@ func newDoHClient(url string, r *Resolver) *dohClient {
return &dohClient{ return &dohClient{
url: url, url: url,
transport: &http.Transport{ transport: &http.Transport{
TLSClientConfig: &tls.Config{ClientSessionCache: globalSessionCache},
ForceAttemptHTTP2: true, ForceAttemptHTTP2: true,
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) { DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
host, port, err := net.SplitHostPort(addr) host, port, err := net.SplitHostPort(addr)
@ -83,12 +82,12 @@ func newDoHClient(url string, r *Resolver) *dohClient {
return nil, err return nil, err
} }
ip, err := r.ResolveIPv4(host) ip, err := resolver.ResolveIPWithResolver(host, r)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return dialer.DialContext(ctx, "tcp4", net.JoinHostPort(ip.String(), port)) return dialer.DialContext(ctx, "tcp", net.JoinHostPort(ip.String(), port))
}, },
}, },
} }

View File

@ -2,6 +2,7 @@ package dns
import ( import (
"net" "net"
"strings"
"github.com/Dreamacro/clash/component/mmdb" "github.com/Dreamacro/clash/component/mmdb"
"github.com/Dreamacro/clash/component/trie" "github.com/Dreamacro/clash/component/trie"
@ -11,11 +12,13 @@ type fallbackIPFilter interface {
Match(net.IP) bool Match(net.IP) bool
} }
type geoipFilter struct{} type geoipFilter struct {
code string
}
func (gf *geoipFilter) Match(ip net.IP) bool { func (gf *geoipFilter) Match(ip net.IP) bool {
record, _ := mmdb.Instance().Country(ip) record, _ := mmdb.Instance().Country(ip)
return record.Country.IsoCode != "CN" && record.Country.IsoCode != "" return !strings.EqualFold(record.Country.IsoCode, gf.code) && !ip.IsPrivate()
} }
type ipnetFilter struct { type ipnetFilter struct {

View File

@ -2,7 +2,6 @@ package dns
import ( import (
"context" "context"
"crypto/tls"
"errors" "errors"
"fmt" "fmt"
"math/rand" "math/rand"
@ -20,10 +19,6 @@ import (
"golang.org/x/sync/singleflight" "golang.org/x/sync/singleflight"
) )
var (
globalSessionCache = tls.NewLRUClientSessionCache(64)
)
type dnsClient interface { type dnsClient interface {
Exchange(m *D.Msg) (msg *D.Msg, err error) Exchange(m *D.Msg) (msg *D.Msg, err error)
ExchangeContext(ctx context.Context, 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 fallbackIPFilters []fallbackIPFilter
group singleflight.Group group singleflight.Group
lruCache *cache.LruCache lruCache *cache.LruCache
policy *trie.DomainTrie
} }
// ResolveIP request with TypeA and TypeAAAA, priority return TypeA // 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 // Exchange a batch of dns request, and it use cache
func (r *Resolver) Exchange(m *D.Msg) (msg *D.Msg, err error) { func (r *Resolver) Exchange(m *D.Msg) (msg *D.Msg, err error) {
return r.ExchangeContext(context.Background(), m)
}
// ExchangeContext a batch of dns request with context.Context, and it use cache
func (r *Resolver) ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg, err error) {
if len(m.Question) == 0 { if len(m.Question) == 0 {
return nil, errors.New("should have one question at least") return nil, errors.New("should have one question at least")
} }
@ -102,17 +103,17 @@ func (r *Resolver) Exchange(m *D.Msg) (msg *D.Msg, err error) {
msg = cache.(*D.Msg).Copy() msg = cache.(*D.Msg).Copy()
if expireTime.Before(now) { if expireTime.Before(now) {
setMsgTTL(msg, uint32(1)) // Continue fetch setMsgTTL(msg, uint32(1)) // Continue fetch
go r.exchangeWithoutCache(m) go r.exchangeWithoutCache(ctx, m)
} else { } else {
setMsgTTL(msg, uint32(time.Until(expireTime).Seconds())) setMsgTTL(msg, uint32(time.Until(expireTime).Seconds()))
} }
return return
} }
return r.exchangeWithoutCache(m) return r.exchangeWithoutCache(ctx, m)
} }
// ExchangeWithoutCache a batch of dns request, and it do NOT GET from cache // ExchangeWithoutCache a batch of dns request, and it do NOT GET from cache
func (r *Resolver) exchangeWithoutCache(m *D.Msg) (msg *D.Msg, err error) { func (r *Resolver) exchangeWithoutCache(ctx context.Context, m *D.Msg) (msg *D.Msg, err error) {
q := m.Question[0] q := m.Question[0]
ret, err, shared := r.group.Do(q.String(), func() (result interface{}, err error) { ret, err, shared := r.group.Do(q.String(), func() (result interface{}, err error) {
@ -128,10 +129,13 @@ func (r *Resolver) exchangeWithoutCache(m *D.Msg) (msg *D.Msg, err error) {
isIPReq := isIPRequest(q) isIPReq := isIPRequest(q)
if isIPReq { if isIPReq {
return r.ipExchange(m) return r.ipExchange(ctx, m)
} }
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 { if err == nil {
@ -144,8 +148,8 @@ func (r *Resolver) exchangeWithoutCache(m *D.Msg) (msg *D.Msg, err error) {
return return
} }
func (r *Resolver) batchExchange(clients []dnsClient, m *D.Msg) (msg *D.Msg, err error) { func (r *Resolver) batchExchange(ctx context.Context, clients []dnsClient, m *D.Msg) (msg *D.Msg, err error) {
fast, ctx := picker.WithTimeout(context.Background(), time.Second*5) fast, ctx := picker.WithTimeout(ctx, resolver.DefaultDNSTimeout)
for _, client := range clients { for _, client := range clients {
r := client r := client
fast.Go(func() (interface{}, error) { fast.Go(func() (interface{}, error) {
@ -172,6 +176,24 @@ func (r *Resolver) batchExchange(clients []dnsClient, m *D.Msg) (msg *D.Msg, err
return 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 { func (r *Resolver) shouldOnlyQueryFallback(m *D.Msg) bool {
if r.fallback == nil || len(r.fallbackDomainFilters) == 0 { if r.fallback == nil || len(r.fallbackDomainFilters) == 0 {
return false return false
@ -192,16 +214,21 @@ func (r *Resolver) shouldOnlyQueryFallback(m *D.Msg) bool {
return false return false
} }
func (r *Resolver) ipExchange(m *D.Msg) (msg *D.Msg, err error) { func (r *Resolver) ipExchange(ctx context.Context, m *D.Msg) (msg *D.Msg, err error) {
if matched := r.matchPolicy(m); len(matched) != 0 {
res := <-r.asyncExchange(ctx, matched, m)
return res.Msg, res.Error
}
onlyFallback := r.shouldOnlyQueryFallback(m) onlyFallback := r.shouldOnlyQueryFallback(m)
if onlyFallback { if onlyFallback {
res := <-r.asyncExchange(r.fallback, m) res := <-r.asyncExchange(ctx, r.fallback, m)
return res.Msg, res.Error return res.Msg, res.Error
} }
msgCh := r.asyncExchange(r.main, m) msgCh := r.asyncExchange(ctx, r.main, m)
if r.fallback == nil { // directly return if no fallback servers are available if r.fallback == nil { // directly return if no fallback servers are available
res := <-msgCh res := <-msgCh
@ -209,7 +236,7 @@ func (r *Resolver) ipExchange(m *D.Msg) (msg *D.Msg, err error) {
return return
} }
fallbackMsg := r.asyncExchange(r.fallback, m) fallbackMsg := r.asyncExchange(ctx, r.fallback, m)
res := <-msgCh res := <-msgCh
if res.Error == nil { if res.Error == nil {
if ips := msgToIP(res.Msg); len(ips) != 0 { if ips := msgToIP(res.Msg); len(ips) != 0 {
@ -265,10 +292,10 @@ func (r *Resolver) msgToDomain(msg *D.Msg) string {
return "" return ""
} }
func (r *Resolver) asyncExchange(client []dnsClient, msg *D.Msg) <-chan *result { func (r *Resolver) asyncExchange(ctx context.Context, client []dnsClient, msg *D.Msg) <-chan *result {
ch := make(chan *result, 1) ch := make(chan *result, 1)
go func() { go func() {
res, err := r.batchExchange(client, msg) res, err := r.batchExchange(ctx, client, msg)
ch <- &result{Msg: res, Error: err} ch <- &result{Msg: res, Error: err}
}() }()
return ch return ch
@ -280,9 +307,10 @@ type NameServer struct {
} }
type FallbackFilter struct { type FallbackFilter struct {
GeoIP bool GeoIP bool
IPCIDR []*net.IPNet GeoIPCode string
Domain []string IPCIDR []*net.IPNet
Domain []string
} }
type Config struct { type Config struct {
@ -293,6 +321,7 @@ type Config struct {
FallbackFilter FallbackFilter FallbackFilter FallbackFilter
Pool *fakeip.Pool Pool *fakeip.Pool
Hosts *trie.DomainTrie Hosts *trie.DomainTrie
Policy map[string]NameServer
} }
func NewResolver(config Config) *Resolver { func NewResolver(config Config) *Resolver {
@ -312,9 +341,18 @@ func NewResolver(config Config) *Resolver {
r.fallback = transform(config.Fallback, defaultResolver) 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{} fallbackIPFilters := []fallbackIPFilter{}
if config.FallbackFilter.GeoIP { if config.FallbackFilter.GeoIP {
fallbackIPFilters = append(fallbackIPFilters, &geoipFilter{}) fallbackIPFilters = append(fallbackIPFilters, &geoipFilter{
code: config.FallbackFilter.GeoIPCode,
})
} }
for _, ipnet := range config.FallbackFilter.IPCIDR { for _, ipnet := range config.FallbackFilter.IPCIDR {
fallbackIPFilters = append(fallbackIPFilters, &ipnetFilter{ipnet: ipnet}) fallbackIPFilters = append(fallbackIPFilters, &ipnetFilter{ipnet: ipnet})

View File

@ -30,6 +30,7 @@ func (s *Server) ServeDNS(w D.ResponseWriter, r *D.Msg) {
D.HandleFailed(w, r) D.HandleFailed(w, r)
return return
} }
msg.Compress = true
w.WriteMsg(msg) w.WriteMsg(msg)
} }

View File

@ -117,9 +117,13 @@ func isIPRequest(q D.Question) bool {
func transform(servers []NameServer, resolver *Resolver) []dnsClient { func transform(servers []NameServer, resolver *Resolver) []dnsClient {
ret := []dnsClient{} ret := []dnsClient{}
for _, s := range servers { for _, s := range servers {
if s.Net == "https" { switch s.Net {
case "https":
ret = append(ret, newDoHClient(s.Addr, resolver)) ret = append(ret, newDoHClient(s.Addr, resolver))
continue continue
case "dhcp":
ret = append(ret, newDHCPClient(s.Addr))
continue
} }
host, port, _ := net.SplitHostPort(s.Addr) host, port, _ := net.SplitHostPort(s.Addr)
@ -127,7 +131,6 @@ func transform(servers []NameServer, resolver *Resolver) []dnsClient {
Client: &D.Client{ Client: &D.Client{
Net: s.Net, Net: s.Net,
TLSConfig: &tls.Config{ TLSConfig: &tls.Config{
ClientSessionCache: globalSessionCache,
// alpn identifier, see https://tools.ietf.org/html/draft-hoffman-dprive-dns-tls-alpn-00#page-6 // alpn identifier, see https://tools.ietf.org/html/draft-hoffman-dprive-dns-tls-alpn-00#page-6
NextProtos: []string{"dns"}, NextProtos: []string{"dns"},
ServerName: host, ServerName: host,

28
go.mod
View File

@ -1,22 +1,32 @@
module github.com/Dreamacro/clash module github.com/Dreamacro/clash
go 1.16 go 1.17
require ( require (
github.com/Dreamacro/go-shadowsocks2 v0.1.6 github.com/Dreamacro/go-shadowsocks2 v0.1.7
github.com/go-chi/chi v4.1.2+incompatible github.com/go-chi/chi/v5 v5.0.4
github.com/go-chi/cors v1.1.1 github.com/go-chi/cors v1.2.0
github.com/go-chi/render v1.0.1 github.com/go-chi/render v1.0.1
github.com/gofrs/uuid v4.0.0+incompatible github.com/gofrs/uuid v4.0.0+incompatible
github.com/gorilla/websocket v1.4.2 github.com/gorilla/websocket v1.4.2
github.com/miekg/dns v1.1.40 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/oschwald/geoip2-golang v1.5.0
github.com/sirupsen/logrus v1.8.1 github.com/sirupsen/logrus v1.8.1
github.com/stretchr/testify v1.7.0 github.com/stretchr/testify v1.7.0
go.uber.org/atomic v1.7.0 go.uber.org/atomic v1.9.0
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83 golang.org/x/crypto v0.0.0-20210817164053-32db794688a5
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 golang.org/x/net v0.0.0-20210903162142-ad29c8ab022f
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
golang.org/x/sys v0.0.0-20210309074719-68d13333faf2 golang.org/x/sys v0.0.0-20210906170528-6f6e22806c34
gopkg.in/yaml.v2 v2.4.0 gopkg.in/yaml.v2 v2.4.0
) )
require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/oschwald/maxminddb-golang v1.8.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/u-root/uio v0.0.0-20210528114334-82958018845c // indirect
golang.org/x/text v0.3.6 // indirect
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect
)

96
go.sum
View File

@ -1,20 +1,42 @@
github.com/Dreamacro/go-shadowsocks2 v0.1.6 h1:PysSf9sLT3Qn8jhlin5v7Rk68gOQG4K5BZFY1nxLGxI= github.com/Dreamacro/go-shadowsocks2 v0.1.7 h1:8CtbE1HoPPMfrQZGXmlluq6dO2lL31W6WRRE8fabc4Q=
github.com/Dreamacro/go-shadowsocks2 v0.1.6/go.mod h1:LSXCjyHesPY3pLjhwff1mQX72ItcBT/N2xNC685cYeU= 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.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/go-chi/chi v4.1.2+incompatible h1:fGFk2Gmi/YKXk0OmGfBh0WgmN3XB8lVnEyNz34tQRec= github.com/fanliao/go-promise v0.0.0-20141029170127-1890db352a72/go.mod h1:PjfxuH4FZdUyfMdtBio2lsRr1AKEaVPwelzuHuh8Lqc=
github.com/go-chi/chi v4.1.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ= github.com/go-chi/chi/v5 v5.0.4 h1:5e494iHzsYBiyXQAHHuI4tyJS9M3V84OuX3ufIIGHFo=
github.com/go-chi/cors v1.1.1 h1:eHuqxsIw89iXcWnWUN8R72JMibABJTN/4IOYI5WERvw= github.com/go-chi/chi/v5 v5.0.4/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
github.com/go-chi/cors v1.1.1/go.mod h1:K2Yje0VW/SJzxiyMYu6iPQYa7hMjQX2i/F491VChg1I= 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 h1:4/5tis2cKaNdnv9zFLfXzcquC9HbeZgCnxGnKrltBS8=
github.com/go-chi/render v1.0.1/go.mod h1:pq4Rr7HbnsdaeHagklXub+p6Wd16Af5l9koip1OvJns= github.com/go-chi/render v1.0.1/go.mod h1:pq4Rr7HbnsdaeHagklXub+p6Wd16Af5l9koip1OvJns=
github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw= github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw=
github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/miekg/dns v1.1.40 h1:pyyPFfGMnciYUk/mXpKkVmeMQjfXqt3FAJ2hy7tPiLA= github.com/hugelgupf/socketpair v0.0.0-20190730060125-05d35a94e714/go.mod h1:2Goc3h8EklBH5mspfHFxBnEoURQCGzQQH1ga9Myjvis=
github.com/miekg/dns v1.1.40/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM= 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 h1:igg2yQIrrcRccB1ytFXqBfOHCjXWIoMv85lVJ1ONZzw=
github.com/oschwald/geoip2-golang v1.5.0/go.mod h1:xdvYt5xQzB8ORWFqPnqMwZpCpgNagttWdoZLlJQzg7s= 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 h1:Uh/DSnGoxsyp/KYbY1AuP0tYEwfs0sCph9p/UMXK/Hk=
@ -23,44 +45,66 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= github.com/u-root/uio v0.0.0-20210528114334-82958018845c h1:BFvcl34IGnw8yvJi8hlqLFo9EshRInwWBs2M5fGWzQA=
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= github.com/u-root/uio v0.0.0-20210528114334-82958018845c/go.mod h1:LpEX5FO/cB+WF4TYGY1V5qktpaZLkKkSegbr0V4eYXA=
go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210317152858-513c2a44f670/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83 h1:/ZScEX8SfEmUGRHs0gxpqteO5nfNW6axyZbBdw9A12g= golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 h1:HWj/xjIHfjYU5nVXpTM0s39J9CbLn7Cc5a7IC5rwsMQ=
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= 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-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-20190419010253-1f3472d942ba/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 h1:qWPm9rbaAMKs8Bq/9LRpbMqxWRVUAQwMI9fVrssnTfw= golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191007182048-72f939374954/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 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 h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-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-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-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191224085550-c709ea063b76/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191224085550-c709ea063b76/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201009025420-dfb3f7c4e634/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201101102859-da207088b7d1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210309074719-68d13333faf2 h1:46ULzRKLh1CwgRq2dC5SlBzEqqNCi8rreOZnNrbqcIY= golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210309074719-68d13333faf2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/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-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/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.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.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-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/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=

View File

@ -6,21 +6,22 @@ import (
"os" "os"
"sync" "sync"
"github.com/Dreamacro/clash/adapters/outbound" "github.com/Dreamacro/clash/adapter"
"github.com/Dreamacro/clash/adapters/outboundgroup" "github.com/Dreamacro/clash/adapter/outboundgroup"
"github.com/Dreamacro/clash/adapters/provider"
"github.com/Dreamacro/clash/component/auth" "github.com/Dreamacro/clash/component/auth"
"github.com/Dreamacro/clash/component/dialer" "github.com/Dreamacro/clash/component/dialer"
"github.com/Dreamacro/clash/component/iface"
"github.com/Dreamacro/clash/component/profile" "github.com/Dreamacro/clash/component/profile"
"github.com/Dreamacro/clash/component/profile/cachefile" "github.com/Dreamacro/clash/component/profile/cachefile"
"github.com/Dreamacro/clash/component/resolver" "github.com/Dreamacro/clash/component/resolver"
"github.com/Dreamacro/clash/component/trie" "github.com/Dreamacro/clash/component/trie"
"github.com/Dreamacro/clash/config" "github.com/Dreamacro/clash/config"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/constant/provider"
"github.com/Dreamacro/clash/dns" "github.com/Dreamacro/clash/dns"
P "github.com/Dreamacro/clash/listener"
authStore "github.com/Dreamacro/clash/listener/auth"
"github.com/Dreamacro/clash/log" "github.com/Dreamacro/clash/log"
P "github.com/Dreamacro/clash/proxy"
authStore "github.com/Dreamacro/clash/proxy/auth"
"github.com/Dreamacro/clash/tunnel" "github.com/Dreamacro/clash/tunnel"
) )
@ -70,13 +71,13 @@ func ApplyConfig(cfg *config.Config, force bool) {
defer mux.Unlock() defer mux.Unlock()
updateUsers(cfg.Users) updateUsers(cfg.Users)
updateGeneral(cfg.General, force)
updateProxies(cfg.Proxies, cfg.Providers) updateProxies(cfg.Proxies, cfg.Providers)
updateRules(cfg.Rules) updateRules(cfg.Rules)
updateDNS(cfg.DNS)
updateHosts(cfg.Hosts) updateHosts(cfg.Hosts)
updateExperimental(cfg)
updateProfile(cfg) updateProfile(cfg)
updateGeneral(cfg.General, force)
updateDNS(cfg.DNS)
updateExperimental(cfg)
} }
func GetGeneral() *config.General { func GetGeneral() *config.General {
@ -123,11 +124,13 @@ func updateDNS(c *config.DNS) {
Pool: c.FakeIPRange, Pool: c.FakeIPRange,
Hosts: c.Hosts, Hosts: c.Hosts,
FallbackFilter: dns.FallbackFilter{ FallbackFilter: dns.FallbackFilter{
GeoIP: c.FallbackFilter.GeoIP, GeoIP: c.FallbackFilter.GeoIP,
IPCIDR: c.FallbackFilter.IPCIDR, GeoIPCode: c.FallbackFilter.GeoIPCode,
Domain: c.FallbackFilter.Domain, IPCIDR: c.FallbackFilter.IPCIDR,
Domain: c.FallbackFilter.Domain,
}, },
Default: c.DefaultNameserver, Default: c.DefaultNameserver,
Policy: c.NameServerPolicy,
} }
r := dns.NewResolver(cfg) r := dns.NewResolver(cfg)
@ -169,13 +172,13 @@ func updateGeneral(general *config.General, force bool) {
resolver.DisableIPv6 = !general.IPv6 resolver.DisableIPv6 = !general.IPv6
if general.Interface != "" { if general.Interface != "" {
dialer.DialHook = dialer.DialerWithInterface(general.Interface) dialer.DefaultOptions = []dialer.Option{dialer.WithInterface(general.Interface)}
dialer.ListenPacketHook = dialer.ListenPacketWithInterface(general.Interface)
} else { } else {
dialer.DialHook = nil dialer.DefaultOptions = nil
dialer.ListenPacketHook = nil
} }
iface.FlushCache()
if !force { if !force {
return return
} }
@ -186,24 +189,27 @@ func updateGeneral(general *config.General, force bool) {
bindAddress := general.BindAddress bindAddress := general.BindAddress
P.SetBindAddress(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()) log.Errorln("Start HTTP server error: %s", err.Error())
} }
if err := P.ReCreateSocks(general.SocksPort); err != nil { if err := P.ReCreateSocks(general.SocksPort, tcpIn, udpIn); err != nil {
log.Errorln("Start SOCKS5 server error: %s", err.Error()) log.Errorln("Start SOCKS server error: %s", err.Error())
} }
if err := P.ReCreateRedir(general.RedirPort); err != nil { if err := P.ReCreateRedir(general.RedirPort, tcpIn, udpIn); err != nil {
log.Errorln("Start Redir server error: %s", err.Error()) 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()) log.Errorln("Start TProxy server error: %s", err.Error())
} }
if err := P.ReCreateMixed(general.MixedPort); err != nil { if err := P.ReCreateMixed(general.MixedPort, tcpIn, udpIn); err != nil {
log.Errorln("Start Mixed(http and socks5) server error: %s", err.Error()) log.Errorln("Start Mixed(http and socks) server error: %s", err.Error())
} }
} }
@ -231,7 +237,7 @@ func patchSelectGroup(proxies map[string]C.Proxy) {
} }
for name, proxy := range proxies { for name, proxy := range proxies {
outbound, ok := proxy.(*outbound.Proxy) outbound, ok := proxy.(*adapter.Proxy)
if !ok { if !ok {
continue continue
} }

View File

@ -4,7 +4,7 @@ import (
"net/http" "net/http"
"net/url" "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 // When name is composed of a partial escape string, Golang does not unescape it

View File

@ -6,12 +6,13 @@ import (
"github.com/Dreamacro/clash/component/resolver" "github.com/Dreamacro/clash/component/resolver"
"github.com/Dreamacro/clash/config" "github.com/Dreamacro/clash/config"
"github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/hub/executor" "github.com/Dreamacro/clash/hub/executor"
P "github.com/Dreamacro/clash/listener"
"github.com/Dreamacro/clash/log" "github.com/Dreamacro/clash/log"
P "github.com/Dreamacro/clash/proxy"
"github.com/Dreamacro/clash/tunnel" "github.com/Dreamacro/clash/tunnel"
"github.com/go-chi/chi" "github.com/go-chi/chi/v5"
"github.com/go-chi/render" "github.com/go-chi/render"
) )
@ -66,11 +67,15 @@ func patchConfigs(w http.ResponseWriter, r *http.Request) {
} }
ports := P.GetPorts() ports := P.GetPorts()
P.ReCreateHTTP(pointerOrDefault(general.Port, ports.Port))
P.ReCreateSocks(pointerOrDefault(general.SocksPort, ports.SocksPort)) tcpIn := tunnel.TCPIn()
P.ReCreateRedir(pointerOrDefault(general.RedirPort, ports.RedirPort)) udpIn := tunnel.UDPIn()
P.ReCreateTProxy(pointerOrDefault(general.TProxyPort, ports.TProxyPort))
P.ReCreateMixed(pointerOrDefault(general.MixedPort, ports.MixedPort)) 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 { if general.Mode != nil {
tunnel.SetMode(*general.Mode) tunnel.SetMode(*general.Mode)
@ -112,6 +117,9 @@ func updateConfigs(w http.ResponseWriter, r *http.Request) {
return return
} }
} else { } else {
if req.Path == "" {
req.Path = constant.Path.Config()
}
if !filepath.IsAbs(req.Path) { if !filepath.IsAbs(req.Path) {
render.Status(r, http.StatusBadRequest) render.Status(r, http.StatusBadRequest)
render.JSON(w, r, newError("path is not a absolute path")) render.JSON(w, r, newError("path is not a absolute path"))

View File

@ -8,10 +8,10 @@ import (
"time" "time"
"github.com/Dreamacro/clash/tunnel/statistic" "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/go-chi/render"
"github.com/gorilla/websocket"
) )
func connectionRouter() http.Handler { func connectionRouter() http.Handler {

View File

@ -4,10 +4,10 @@ import (
"context" "context"
"net/http" "net/http"
"github.com/Dreamacro/clash/adapters/provider" "github.com/Dreamacro/clash/constant/provider"
"github.com/Dreamacro/clash/tunnel" "github.com/Dreamacro/clash/tunnel"
"github.com/go-chi/chi" "github.com/go-chi/chi/v5"
"github.com/go-chi/render" "github.com/go-chi/render"
) )

View File

@ -7,13 +7,13 @@ import (
"strconv" "strconv"
"time" "time"
"github.com/Dreamacro/clash/adapters/outbound" "github.com/Dreamacro/clash/adapter"
"github.com/Dreamacro/clash/adapters/outboundgroup" "github.com/Dreamacro/clash/adapter/outboundgroup"
"github.com/Dreamacro/clash/component/profile/cachefile" "github.com/Dreamacro/clash/component/profile/cachefile"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/tunnel" "github.com/Dreamacro/clash/tunnel"
"github.com/go-chi/chi" "github.com/go-chi/chi/v5"
"github.com/go-chi/render" "github.com/go-chi/render"
) )
@ -78,7 +78,7 @@ func updateProxy(w http.ResponseWriter, r *http.Request) {
return return
} }
proxy := r.Context().Value(CtxKeyProxy).(*outbound.Proxy) proxy := r.Context().Value(CtxKeyProxy).(*adapter.Proxy)
selector, ok := proxy.ProxyAdapter.(*outboundgroup.Selector) selector, ok := proxy.ProxyAdapter.(*outboundgroup.Selector)
if !ok { if !ok {
render.Status(r, http.StatusBadRequest) render.Status(r, http.StatusBadRequest)

View File

@ -5,7 +5,7 @@ import (
"github.com/Dreamacro/clash/tunnel" "github.com/Dreamacro/clash/tunnel"
"github.com/go-chi/chi" "github.com/go-chi/chi/v5"
"github.com/go-chi/render" "github.com/go-chi/render"
) )

View File

@ -3,6 +3,7 @@ package route
import ( import (
"bytes" "bytes"
"encoding/json" "encoding/json"
"net"
"net/http" "net/http"
"strings" "strings"
"time" "time"
@ -11,7 +12,7 @@ import (
"github.com/Dreamacro/clash/log" "github.com/Dreamacro/clash/log"
"github.com/Dreamacro/clash/tunnel/statistic" "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/cors"
"github.com/go-chi/render" "github.com/go-chi/render"
"github.com/gorilla/websocket" "github.com/gorilla/websocket"
@ -81,10 +82,15 @@ func Start(addr string, secret string) {
}) })
} }
log.Infoln("RESTful API listening at: %s", addr) l, err := net.Listen("tcp", addr)
err := http.ListenAndServe(addr, r)
if err != nil { if err != nil {
log.Errorln("External controller error: %s", err.Error()) log.Errorln("External controller listen error: %s", err)
return
}
serverAddr = l.Addr().String()
log.Infoln("RESTful API listening at: %s", serverAddr)
if err = http.Serve(l, r); err != nil {
log.Errorln("External controller serve error: %s", err)
} }
} }

45
listener/http/client.go Normal file
View File

@ -0,0 +1,45 @@
package http
import (
"context"
"errors"
"net"
"net/http"
"time"
"github.com/Dreamacro/clash/adapter/inbound"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/transport/socks5"
)
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)
}
dstAddr := socks5.ParseAddr(address)
if dstAddr == nil {
return nil, socks5.ErrAddressNotSupported
}
left, right := net.Pipe()
in <- inbound.NewHTTP(dstAddr, source, right)
return left, nil
},
},
CheckRedirect: func(req *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse
},
}
}

10
listener/http/hack.go Normal file
View 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
View 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