Compare commits

..

130 Commits

Author SHA1 Message Date
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
6c83ff3496 Chore: update dependencies 2021-03-10 21:13:23 +08:00
f7f97ef625 Fix: some HTTP proxy request broken 2021-03-10 16:23:55 +08:00
5acdd72a1d Fix: remove host if host is ip string 2021-03-10 12:49:30 +08:00
f53686103d Chore: reset udp timeout after sending each packet (#1260) 2021-02-26 10:40:55 +08:00
f63c9eb22f Chore: update staticcheck command on actions 2021-02-21 19:37:37 +08:00
a37243cf30 Fix: store cache correctly 2021-02-21 01:07:22 +08:00
b3c1b4a840 Chore: update dependencies 2021-02-19 20:35:10 +08:00
14bbf6eedc Feature: support store group selected node to cache (enable by default) 2021-02-18 23:41:50 +08:00
aa81193d5b Feature: add darwin arm64 to Makefile (Apple Silicon) (#1234) 2021-02-18 18:15:09 +08:00
9eb98e399d Improve: refactor ssr and fix #995 (#1189)
Co-authored-by: goomada <madao@DESKTOP-IOEBS0C.localdomain>
2021-02-15 14:32:03 +08:00
d48cfecf60 Chore: API support patch ipv6 config (#1217) 2021-02-05 16:43:42 +08:00
6036fb63ba Chore: avoid provider unnecessary write file operations (#1210) 2021-02-02 17:52:46 +08:00
cd48f69b1f Fix: wrap net.Conn to avoid using *net.TCPConn.(ReadFrom) (#1209) 2021-02-01 20:06:45 +08:00
fcc594ae26 Chore: use jsdelivr CDN for Country.mmdb (#1057) 2021-01-30 00:40:35 +08:00
f4de055aa1 Refactor: make inbound request contextual 2021-01-23 14:58:09 +08:00
35925cb3da Chore: standardized Dockerfile label (#1191)
Signed-off-by: Junjie Yuan <yuan@junjie.pro>
2021-01-20 16:08:24 +08:00
ff430df845 Fix: connectivity of ssr auth_chain_(ab) protocol (#1180) 2021-01-13 23:35:41 +08:00
e4cdea2111 chore: use singleDo to get interface info 2021-01-13 17:30:54 +08:00
b6ee47a541 Fix: get general should return correct result (#1172) 2021-01-07 13:59:39 +08:00
b25009cde7 Fix: unnecessary write operation on provider (#1170) 2021-01-06 14:20:15 +08:00
6fedd7ec84 Fix: dns client should not bind local address 2021-01-04 00:51:53 +08:00
9619c3fb20 Fix: support unspecified UDP bind address (#1159) 2020-12-31 18:58:03 +08:00
02d029dd2d Fix: close http Response body on provider (#1154) 2020-12-29 11:28:22 +08:00
09c28e0355 Fix: fallback bind fn should not bind global unicast 2020-12-28 22:24:58 +08:00
3600077f3b Chore: update dependencies 2020-12-27 18:59:59 +08:00
de7656a787 Chore: update premium README 2020-12-27 00:14:24 +08:00
5dfe7f8561 Fix: handle keep alive on http connect proxy 2020-12-24 14:55:11 +08:00
ed27898a33 Fix: snell should support the config without obfs 2020-12-24 13:47:56 +08:00
532396d25c Fix: PROCESS-NAME rule for UDP sessions on Windows (#1140) 2020-12-22 15:13:44 +08:00
4b1b494164 Chore: move find process name to a single part 2020-12-17 22:17:27 +08:00
0d33dc3eb9 Chore: health checks return immediately if completed (#1097) 2020-11-24 22:52:23 +08:00
994cbff215 Fix: should not log rule when rule = nil 2020-11-22 23:38:12 +08:00
bea2ee8bf2 Chore: log rule msg on dial error 2020-11-22 19:12:36 +08:00
244 changed files with 10370 additions and 4822 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.15.x go-version: 1.17
- name: Check out code into the Go module directory - name: Check out code into the Go module directory
uses: actions/checkout@v2 uses: actions/checkout@v2
@ -26,7 +26,7 @@ jobs:
run: | run: |
go test ./... go test ./...
go vet ./... go vet ./...
go get -u honnef.co/go/tools/cmd/staticcheck go install honnef.co/go/tools/cmd/staticcheck@latest
staticcheck -- $(go list ./...) staticcheck -- $(go list ./...)
- name: Build - name: Build
@ -39,9 +39,6 @@ jobs:
- name: Upload Release - name: Upload Release
uses: softprops/action-gh-release@v1 uses: softprops/action-gh-release@v1
if: startsWith(github.ref, 'refs/tags/') if: startsWith(github.ref, 'refs/tags/')
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with: with:
files: bin/* files: bin/*
draft: true draft: true
prerelease: true

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

@ -10,7 +10,7 @@ RUN go mod download && \
mv ./bin/clash-docker /clash mv ./bin/clash-docker /clash
FROM alpine:latest FROM alpine:latest
LABEL org.opencontainers.image.source https://github.com/Dreamacro/clash LABEL org.opencontainers.image.source="https://github.com/Dreamacro/clash"
RUN apk add --no-cache ca-certificates RUN apk add --no-cache ca-certificates
COPY --from=builder /Country.mmdb /root/.config/clash/ COPY --from=builder /Country.mmdb /root/.config/clash/

View File

@ -8,6 +8,7 @@ GOBUILD=CGO_ENABLED=0 go build -trimpath -ldflags '-X "github.com/Dreamacro/clas
PLATFORM_LIST = \ PLATFORM_LIST = \
darwin-amd64 \ darwin-amd64 \
darwin-arm64 \
linux-386 \ linux-386 \
linux-amd64 \ linux-amd64 \
linux-armv5 \ linux-armv5 \
@ -21,11 +22,13 @@ PLATFORM_LIST = \
linux-mips64 \ linux-mips64 \
linux-mips64le \ linux-mips64le \
freebsd-386 \ freebsd-386 \
freebsd-amd64 freebsd-amd64 \
freebsd-arm64
WINDOWS_ARCH_LIST = \ WINDOWS_ARCH_LIST = \
windows-386 \ windows-386 \
windows-amd64 \ windows-amd64 \
windows-arm64 \
windows-arm32v7 windows-arm32v7
all: linux-amd64 darwin-amd64 windows-amd64 # Most used all: linux-amd64 darwin-amd64 windows-amd64 # Most used
@ -36,6 +39,9 @@ docker:
darwin-amd64: darwin-amd64:
GOARCH=amd64 GOOS=darwin $(GOBUILD) -o $(BINDIR)/$(NAME)-$@ GOARCH=amd64 GOOS=darwin $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
darwin-arm64:
GOARCH=arm64 GOOS=darwin $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
linux-386: linux-386:
GOARCH=386 GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@ GOARCH=386 GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
@ -78,12 +84,18 @@ freebsd-386:
freebsd-amd64: freebsd-amd64:
GOARCH=amd64 GOOS=freebsd $(GOBUILD) -o $(BINDIR)/$(NAME)-$@ GOARCH=amd64 GOOS=freebsd $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
freebsd-arm64:
GOARCH=arm64 GOOS=freebsd $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
windows-386: windows-386:
GOARCH=386 GOOS=windows $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.exe GOARCH=386 GOOS=windows $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.exe
windows-amd64: windows-amd64:
GOARCH=amd64 GOOS=windows $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.exe GOARCH=amd64 GOOS=windows $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.exe
windows-arm64:
GOARCH=arm64 GOOS=windows $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.exe
windows-arm32v7: windows-arm32v7:
GOARCH=arm GOOS=windows GOARM=7 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.exe GOARCH=arm GOOS=windows GOARM=7 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.exe

View File

@ -28,9 +28,21 @@
- Netfilter TCP redirecting. Deploy Clash on your Internet gateway with `iptables`. - Netfilter TCP redirecting. Deploy Clash on your Internet gateway with `iptables`.
- Comprehensive HTTP RESTful API controller - Comprehensive HTTP RESTful API controller
## Premium Features
- TUN mode on macOS, Linux and Windows. [Doc](https://github.com/Dreamacro/clash/wiki/premium-core-features#tun-device)
- Match your tunnel by [Script](https://github.com/Dreamacro/clash/wiki/premium-core-features#script)
- [Rule Provider](https://github.com/Dreamacro/clash/wiki/premium-core-features#rule-providers)
## Getting Started ## Getting Started
Documentations are now moved to [GitHub Wiki](https://github.com/Dreamacro/clash/wiki). Documentations are now moved to [GitHub Wiki](https://github.com/Dreamacro/clash/wiki).
## Premium Release
[Release](https://github.com/Dreamacro/clash/releases/tag/premium)
## Development
If you want to build an application that uses clash as a library, check out the the [GitHub Wiki](https://github.com/Dreamacro/clash/wiki/use-clash-as-a-library)
## Credits ## Credits
* [riobard/go-shadowsocks2](https://github.com/riobard/go-shadowsocks2) * [riobard/go-shadowsocks2](https://github.com/riobard/go-shadowsocks2)
@ -48,4 +60,3 @@ This software is released under the GPL-3.0 license.
- [x] Redir proxy - [x] Redir proxy
- [x] UDP support - [x] UDP support
- [x] Connection manager - [x] Connection manager
- [ ] ~~Event API~~

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 string, source net.Addr, conn net.Conn) *context.ConnContext {
metadata := parseSocksAddr(socks5.ParseAddr(target))
metadata.NetWork = C.TCP
metadata.Type = C.HTTP
if ip, port, err := parseAddr(source.String()); err == nil {
metadata.SrcIP = ip
metadata.SrcPort = port
}
return context.NewConnContext(conn, metadata)
}

View File

@ -5,18 +5,16 @@ import (
"net/http" "net/http"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/context"
) )
// NewHTTPS is HTTPAdapter generator // NewHTTPS receive CONNECT request and return ConnContext
func NewHTTPS(request *http.Request, conn net.Conn) *SocketAdapter { func NewHTTPS(request *http.Request, conn net.Conn) *context.ConnContext {
metadata := parseHTTPAddr(request) metadata := parseHTTPAddr(request)
metadata.Type = C.HTTPCONNECT metadata.Type = C.HTTPCONNECT
if ip, port, err := parseAddr(conn.RemoteAddr().String()); err == nil { if ip, port, err := parseAddr(conn.RemoteAddr().String()); err == nil {
metadata.SrcIP = ip metadata.SrcIP = ip
metadata.SrcPort = port metadata.SrcPort = port
} }
return &SocketAdapter{ return context.NewConnContext(conn, metadata)
metadata: metadata,
Conn: conn,
}
} }

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

22
adapter/inbound/socket.go Normal file
View File

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

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"
) )
@ -40,7 +39,7 @@ type ShadowSocksOption struct {
} }
type simpleObfsOption struct { type simpleObfsOption struct {
Mode string `obfs:"mode"` Mode string `obfs:"mode,omitempty"`
Host string `obfs:"host,omitempty"` Host string `obfs:"host,omitempty"`
} }
@ -54,6 +53,7 @@ type v2rayObfsOption struct {
Mux bool `obfs:"mux,omitempty"` Mux bool `obfs:"mux,omitempty"`
} }
// StreamConn implements C.ProxyAdapter
func (ss *ShadowSocks) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) { func (ss *ShadowSocks) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
switch ss.obfsMode { switch ss.obfsMode {
case "tls": case "tls":
@ -73,25 +73,30 @@ func (ss *ShadowSocks) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, e
return c, err return c, err
} }
func (ss *ShadowSocks) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) { // DialContext implements C.ProxyAdapter
func (ss *ShadowSocks) DialContext(ctx context.Context, metadata *C.Metadata) (_ C.Conn, err error) {
c, err := dialer.DialContext(ctx, "tcp", ss.addr) c, err := dialer.DialContext(ctx, "tcp", ss.addr)
if err != nil { if err != nil {
return nil, fmt.Errorf("%s connect error: %w", ss.addr, err) return nil, fmt.Errorf("%s connect error: %w", ss.addr, err)
} }
tcpKeepAlive(c) tcpKeepAlive(c)
defer safeConnClose(c, err)
c, err = ss.StreamConn(c, metadata) c, err = ss.StreamConn(c, metadata)
return NewConn(c, ss), err return NewConn(c, ss), err
} }
// DialUDP implements C.ProxyAdapter
func (ss *ShadowSocks) DialUDP(metadata *C.Metadata) (C.PacketConn, error) { func (ss *ShadowSocks) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
pc, err := dialer.ListenPacket("udp", "") pc, err := dialer.ListenPacket(context.Background(), "udp", "")
if err != nil { if err != nil {
return nil, err return nil, err
} }
addr, err := resolveUDPAddr("udp", ss.addr) addr, err := resolveUDPAddr("udp", ss.addr)
if err != nil { if err != nil {
pc.Close()
return nil, err return nil, err
} }
@ -99,12 +104,6 @@ func (ss *ShadowSocks) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
return newPacketConn(&ssPacketConn{PacketConn: pc, rAddr: addr}, ss), nil return newPacketConn(&ssPacketConn{PacketConn: pc, rAddr: addr}, ss), nil
} }
func (ss *ShadowSocks) MarshalJSON() ([]byte, error) {
return json.Marshal(map[string]string{
"type": ss.Type().String(),
})
}
func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) { func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) {
addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port)) addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port))
cipher := option.Cipher cipher := option.Cipher
@ -150,7 +149,6 @@ func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) {
if opts.TLS { if opts.TLS {
v2rayOption.TLS = true v2rayOption.TLS = true
v2rayOption.SkipCertVerify = opts.SkipCertVerify v2rayOption.SkipCertVerify = opts.SkipCertVerify
v2rayOption.SessionCache = getClientSessionCache()
} }
} }

View File

@ -2,22 +2,23 @@ package outbound
import ( import (
"context" "context"
"encoding/json"
"fmt" "fmt"
"net" "net"
"strconv" "strconv"
"github.com/Dreamacro/clash/component/dialer" "github.com/Dreamacro/clash/component/dialer"
"github.com/Dreamacro/clash/component/ssr/obfs"
"github.com/Dreamacro/clash/component/ssr/protocol"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/transport/ssr/obfs"
"github.com/Dreamacro/clash/transport/ssr/protocol"
"github.com/Dreamacro/go-shadowsocks2/core" "github.com/Dreamacro/go-shadowsocks2/core"
"github.com/Dreamacro/go-shadowsocks2/shadowaead"
"github.com/Dreamacro/go-shadowsocks2/shadowstream" "github.com/Dreamacro/go-shadowsocks2/shadowstream"
) )
type ShadowSocksR struct { type ShadowSocksR struct {
*Base *Base
cipher *core.StreamCipher cipher core.Cipher
obfs obfs.Obfs obfs obfs.Obfs
protocol protocol.Protocol protocol protocol.Protocol
} }
@ -35,90 +36,103 @@ type ShadowSocksROption struct {
UDP bool `proxy:"udp,omitempty"` UDP bool `proxy:"udp,omitempty"`
} }
// StreamConn implements C.ProxyAdapter
func (ssr *ShadowSocksR) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) { func (ssr *ShadowSocksR) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
c = obfs.NewConn(c, ssr.obfs) c = ssr.obfs.StreamConn(c)
c = ssr.cipher.StreamConn(c) c = ssr.cipher.StreamConn(c)
conn, ok := c.(*shadowstream.Conn) var (
if !ok { iv []byte
err error
)
switch conn := c.(type) {
case *shadowstream.Conn:
iv, err = conn.ObtainWriteIV()
if err != nil {
return nil, err
}
case *shadowaead.Conn:
return nil, fmt.Errorf("invalid connection type") return nil, fmt.Errorf("invalid connection type")
} }
iv, err := conn.ObtainWriteIV() c = ssr.protocol.StreamConn(c, iv)
if err != nil {
return nil, err
}
c = protocol.NewConn(c, ssr.protocol, iv)
_, err = c.Write(serializesSocksAddr(metadata)) _, err = c.Write(serializesSocksAddr(metadata))
return c, err return c, err
} }
func (ssr *ShadowSocksR) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) { // DialContext implements C.ProxyAdapter
func (ssr *ShadowSocksR) DialContext(ctx context.Context, metadata *C.Metadata) (_ C.Conn, err error) {
c, err := dialer.DialContext(ctx, "tcp", ssr.addr) c, err := dialer.DialContext(ctx, "tcp", ssr.addr)
if err != nil { if err != nil {
return nil, fmt.Errorf("%s connect error: %w", ssr.addr, err) return nil, fmt.Errorf("%s connect error: %w", ssr.addr, err)
} }
tcpKeepAlive(c) tcpKeepAlive(c)
defer safeConnClose(c, err)
c, err = ssr.StreamConn(c, metadata) c, err = ssr.StreamConn(c, metadata)
return NewConn(c, ssr), err return NewConn(c, ssr), err
} }
// DialUDP implements C.ProxyAdapter
func (ssr *ShadowSocksR) DialUDP(metadata *C.Metadata) (C.PacketConn, error) { func (ssr *ShadowSocksR) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
pc, err := dialer.ListenPacket("udp", "") pc, err := dialer.ListenPacket(context.Background(), "udp", "")
if err != nil { if err != nil {
return nil, err return nil, err
} }
addr, err := resolveUDPAddr("udp", ssr.addr) addr, err := resolveUDPAddr("udp", ssr.addr)
if err != nil { if err != nil {
pc.Close()
return nil, err return nil, err
} }
pc = ssr.cipher.PacketConn(pc) pc = ssr.cipher.PacketConn(pc)
pc = protocol.NewPacketConn(pc, ssr.protocol) pc = ssr.protocol.PacketConn(pc)
return newPacketConn(&ssPacketConn{PacketConn: pc, rAddr: addr}, ssr), nil return newPacketConn(&ssPacketConn{PacketConn: pc, rAddr: addr}, ssr), nil
} }
func (ssr *ShadowSocksR) MarshalJSON() ([]byte, error) {
return json.Marshal(map[string]string{
"type": ssr.Type().String(),
})
}
func NewShadowSocksR(option ShadowSocksROption) (*ShadowSocksR, error) { func NewShadowSocksR(option ShadowSocksROption) (*ShadowSocksR, error) {
addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port)) addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port))
cipher := option.Cipher cipher := option.Cipher
password := option.Password password := option.Password
coreCiph, err := core.PickCipher(cipher, nil, password) coreCiph, err := core.PickCipher(cipher, nil, password)
if err != nil { if err != nil {
return nil, fmt.Errorf("ssr %s initialize cipher error: %w", addr, err) return nil, fmt.Errorf("ssr %s initialize error: %w", addr, err)
} }
ciph, ok := coreCiph.(*core.StreamCipher) var (
if !ok { ivSize int
return nil, fmt.Errorf("%s is not a supported stream cipher in ssr", cipher) key []byte
)
if option.Cipher == "dummy" {
ivSize = 0
key = core.Kdf(option.Password, 16)
} else {
ciph, ok := coreCiph.(*core.StreamCipher)
if !ok {
return nil, fmt.Errorf("%s is not dummy or a supported stream cipher in ssr", cipher)
}
ivSize = ciph.IVSize()
key = ciph.Key
} }
obfs, err := obfs.PickObfs(option.Obfs, &obfs.Base{ obfs, obfsOverhead, err := obfs.PickObfs(option.Obfs, &obfs.Base{
IVSize: ciph.IVSize(), Host: option.Server,
Key: ciph.Key, Port: option.Port,
HeadLen: 30, Key: key,
Host: option.Server, IVSize: ivSize,
Port: option.Port, Param: option.ObfsParam,
Param: option.ObfsParam,
}) })
if err != nil { if err != nil {
return nil, fmt.Errorf("ssr %s initialize obfs error: %w", addr, err) return nil, fmt.Errorf("ssr %s initialize obfs error: %w", addr, err)
} }
protocol, err := protocol.PickProtocol(option.Protocol, &protocol.Base{ protocol, err := protocol.PickProtocol(option.Protocol, &protocol.Base{
IV: nil, Key: key,
Key: ciph.Key, Overhead: obfsOverhead,
TCPMss: 1460, Param: option.ProtocolParam,
Param: option.ProtocolParam,
}) })
if err != nil { if err != nil {
return nil, fmt.Errorf("ssr %s initialize protocol error: %w", addr, err) return nil, fmt.Errorf("ssr %s initialize protocol error: %w", addr, err)
} }
protocol.SetOverhead(obfs.GetObfsOverhead() + protocol.GetProtocolOverhead())
return &ShadowSocksR{ return &ShadowSocksR{
Base: &Base{ Base: &Base{
@ -127,7 +141,7 @@ func NewShadowSocksR(option ShadowSocksROption) (*ShadowSocksR, error) {
tp: C.ShadowsocksR, tp: C.ShadowsocksR,
udp: option.UDP, udp: option.UDP,
}, },
cipher: ciph, cipher: coreCiph,
obfs: obfs, obfs: obfs,
protocol: protocol, protocol: protocol,
}, nil }, nil

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
} }
@ -87,7 +94,10 @@ func NewSnell(option SnellOption) (*Snell, error) {
return nil, fmt.Errorf("snell %s initialize obfs error: %w", addr, err) return nil, fmt.Errorf("snell %s initialize obfs error: %w", addr, err)
} }
if obfsOption.Mode != "tls" && obfsOption.Mode != "http" { switch obfsOption.Mode {
case "tls", "http", "":
break
default:
return nil, fmt.Errorf("snell %s obfs mode error: %s", addr, obfsOption.Mode) return nil, fmt.Errorf("snell %s obfs mode error: %s", addr, obfsOption.Mode)
} }

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
} }
@ -122,7 +123,21 @@ func (ss *Socks5) DialUDP(metadata *C.Metadata) (_ C.PacketConn, err error) {
pc.Close() pc.Close()
}() }()
return newPacketConn(&socksPacketConn{PacketConn: pc, rAddr: bindAddr.UDPAddr(), tcpConn: c}, ss), nil // Support unspecified UDP bind address.
bindUDPAddr := bindAddr.UDPAddr()
if bindUDPAddr == nil {
err = errors.New("invalid UDP bind address")
return
} else if bindUDPAddr.IP.IsUnspecified() {
serverAddr, err := resolveUDPAddr("udp", ss.Addr())
if err != nil {
return nil, err
}
bindUDPAddr.IP = serverAddr.IP
}
return newPacketConn(&socksPacketConn{PacketConn: pc, rAddr: bindUDPAddr, tcpConn: c}, ss), nil
} }
func NewSocks5(option Socks5Option) *Socks5 { func NewSocks5(option Socks5Option) *Socks5 {
@ -130,7 +145,6 @@ func NewSocks5(option Socks5Option) *Socks5 {
if option.TLS { if option.TLS {
tlsConfig = &tls.Config{ tlsConfig = &tls.Config{
InsecureSkipVerify: option.SkipCertVerify, InsecureSkipVerify: option.SkipCertVerify,
ClientSessionCache: getClientSessionCache(),
ServerName: option.Server, ServerName: option.Server,
} }
} }

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

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 { if f.vehicle.Type() != types.File && !isLocal {
if err := safeWrite(f.vehicle.Path(), buf); err != nil { if err := safeWrite(f.vehicle.Path(), buf); err != nil {
return nil, err return nil, err
} }
@ -108,8 +111,10 @@ func (f *fetcher) Update() (interface{}, bool, error) {
return nil, false, err return nil, false, err
} }
if err := safeWrite(f.vehicle.Path(), buf); err != nil { if f.vehicle.Type() != types.File {
return nil, false, err if err := safeWrite(f.vehicle.Path(), buf); err != nil {
return nil, false, err
}
} }
f.updatedAt = &now f.updatedAt = &now
@ -163,7 +168,7 @@ func safeWrite(path string, buf []byte) error {
return ioutil.WriteFile(path, buf, fileMode) return ioutil.WriteFile(path, buf, fileMode)
} }
func newFetcher(name string, interval time.Duration, vehicle Vehicle, parser parser, onUpdate func(interface{})) *fetcher { func newFetcher(name string, interval time.Duration, vehicle types.Vehicle, parser parser, onUpdate func(interface{})) *fetcher {
var ticker *time.Ticker var ticker *time.Ticker
if interval != 0 { if interval != 0 {
ticker = time.NewTicker(interval) ticker = time.NewTicker(interval)

View File

@ -4,6 +4,7 @@ import (
"context" "context"
"time" "time"
"github.com/Dreamacro/clash/common/batch"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
"go.uber.org/atomic" "go.uber.org/atomic"
@ -58,13 +59,17 @@ func (hc *HealthCheck) touch() {
} }
func (hc *HealthCheck) check() { func (hc *HealthCheck) check() {
ctx, cancel := context.WithTimeout(context.Background(), defaultURLTestTimeout) b, _ := batch.New(context.Background(), batch.WithConcurrencyNum(10))
for _, proxy := range hc.proxies { for _, proxy := range hc.proxies {
go proxy.URLTest(ctx, hc.url) p := proxy
b.Go(p.Name(), func() (interface{}, error) {
ctx, cancel := context.WithTimeout(context.Background(), defaultURLTestTimeout)
defer cancel()
p.URLTest(ctx, hc.url)
return nil, nil
})
} }
b.Wait()
<-ctx.Done()
cancel()
} }
func (hc *HealthCheck) close() { func (hc *HealthCheck) close() {

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}
@ -107,6 +82,7 @@ func (h *HTTPVehicle) Read() ([]byte, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
defer resp.Body.Close()
buf, err := ioutil.ReadAll(resp.Body) buf, err := ioutil.ReadAll(resp.Body)
if err != nil { if err != nil {

View File

@ -1,60 +0,0 @@
package inbound
import (
"net"
"net/http"
"strings"
C "github.com/Dreamacro/clash/constant"
)
// HTTPAdapter is a adapter for HTTP connection
type HTTPAdapter struct {
net.Conn
metadata *C.Metadata
R *http.Request
}
// Metadata return destination metadata
func (h *HTTPAdapter) Metadata() *C.Metadata {
return h.metadata
}
// NewHTTP is HTTPAdapter generator
func NewHTTP(request *http.Request, conn net.Conn) *HTTPAdapter {
metadata := parseHTTPAddr(request)
metadata.Type = C.HTTP
if ip, port, err := parseAddr(conn.RemoteAddr().String()); err == nil {
metadata.SrcIP = ip
metadata.SrcPort = port
}
return &HTTPAdapter{
metadata: metadata,
R: request,
Conn: conn,
}
}
// RemoveHopByHopHeaders remove hop-by-hop header
func RemoveHopByHopHeaders(header http.Header) {
// Strip hop-by-hop header based on RFC:
// http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.5.1
// https://www.mnot.net/blog/2011/07/11/what_proxies_must_do
header.Del("Proxy-Connection")
header.Del("Proxy-Authenticate")
header.Del("Proxy-Authorization")
header.Del("TE")
header.Del("Trailers")
header.Del("Transfer-Encoding")
header.Del("Upgrade")
connections := header.Get("Connection")
header.Del("Connection")
if len(connections) == 0 {
return
}
for _, h := range strings.Split(connections, ",") {
header.Del(strings.TrimSpace(h))
}
}

View File

@ -1,35 +0,0 @@
package inbound
import (
"net"
"github.com/Dreamacro/clash/component/socks5"
C "github.com/Dreamacro/clash/constant"
)
// SocketAdapter is a adapter for socks and redir connection
type SocketAdapter struct {
net.Conn
metadata *C.Metadata
}
// Metadata return destination metadata
func (s *SocketAdapter) Metadata() *C.Metadata {
return s.metadata
}
// NewSocket is SocketAdapter generator
func NewSocket(target socks5.Addr, conn net.Conn, source C.Type) *SocketAdapter {
metadata := parseSocksAddr(target)
metadata.NetWork = C.TCP
metadata.Type = source
if ip, port, err := parseAddr(conn.RemoteAddr().String()); err == nil {
metadata.SrcIP = ip
metadata.SrcPort = port
}
return &SocketAdapter{
Conn: conn,
metadata: metadata,
}
}

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"

11
common/net/io.go Normal file
View File

@ -0,0 +1,11 @@
package net
import "io"
type ReadOnlyReader struct {
io.Reader
}
type WriteOnlyWriter struct {
io.Writer
}

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,104 +0,0 @@
package dialer
import (
"errors"
"net"
)
var (
errPlatformNotSupport = errors.New("unsupport platform")
)
func lookupTCPAddr(ip net.IP, addrs []net.Addr) (*net.TCPAddr, error) {
ipv4 := ip.To4() != nil
for _, elm := range addrs {
addr, ok := elm.(*net.IPNet)
if !ok {
continue
}
addrV4 := addr.IP.To4() != nil
if addrV4 && ipv4 {
return &net.TCPAddr{IP: addr.IP, Port: 0}, nil
} else if !addrV4 && !ipv4 {
return &net.TCPAddr{IP: addr.IP, Port: 0}, nil
}
}
return nil, ErrAddrNotFound
}
func lookupUDPAddr(ip net.IP, addrs []net.Addr) (*net.UDPAddr, error) {
ipv4 := ip.To4() != nil
for _, elm := range addrs {
addr, ok := elm.(*net.IPNet)
if !ok {
continue
}
addrV4 := addr.IP.To4() != nil
if addrV4 && ipv4 {
return &net.UDPAddr{IP: addr.IP, Port: 0}, nil
} else if !addrV4 && !ipv4 {
return &net.UDPAddr{IP: addr.IP, Port: 0}, nil
}
}
return nil, ErrAddrNotFound
}
func fallbackBindToDialer(dialer *net.Dialer, network string, ip net.IP, name string) error {
iface, err := net.InterfaceByName(name)
if err != nil {
return err
}
addrs, err := iface.Addrs()
if err != nil {
return err
}
switch network {
case "tcp", "tcp4", "tcp6":
if addr, err := lookupTCPAddr(ip, addrs); err == nil {
dialer.LocalAddr = addr
} else {
return err
}
case "udp", "udp4", "udp6":
if addr, err := lookupUDPAddr(ip, addrs); err == nil {
dialer.LocalAddr = addr
} else {
return err
}
}
return nil
}
func fallbackBindToListenConfig(name string) (string, error) {
iface, err := net.InterfaceByName(name)
if err != nil {
return "", err
}
addrs, err := iface.Addrs()
if err != nil {
return "", err
}
for _, elm := range addrs {
addr, ok := elm.(*net.IPNet)
if !ok || addr.IP.To4() == nil {
continue
}
return net.JoinHostPort(addr.IP.String(), "0"), nil
}
return "", ErrAddrNotFound
}

View File

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

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

@ -0,0 +1,21 @@
package process
import (
"errors"
"net"
)
var (
ErrInvalidNetwork = errors.New("invalid network")
ErrPlatformNotSupport = errors.New("not support on this platform")
ErrNotFound = errors.New("process not found")
)
const (
TCP = "tcp"
UDP = "udp"
)
func FindProcessName(network string, srcIP net.IP, srcPort int) (string, error) {
return findProcessName(network, srcIP, srcPort)
}

View File

@ -1,109 +1,27 @@
package rules package process
import ( import (
"bytes"
"encoding/binary" "encoding/binary"
"errors"
"fmt"
"net" "net"
"path/filepath" "path/filepath"
"strconv"
"strings"
"syscall" "syscall"
"unsafe" "unsafe"
"github.com/Dreamacro/clash/common/cache" "golang.org/x/sys/unix"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/log"
) )
// store process name for when dealing with multiple PROCESS-NAME rules
var processCache = cache.NewLRUCache(cache.WithAge(2), cache.WithSize(64))
type Process struct {
adapter string
process string
}
func (ps *Process) RuleType() C.RuleType {
return C.Process
}
func (ps *Process) Match(metadata *C.Metadata) bool {
key := fmt.Sprintf("%s:%s:%s", metadata.NetWork.String(), metadata.SrcIP.String(), metadata.SrcPort)
cached, hit := processCache.Get(key)
if !hit {
name, err := getExecPathFromAddress(metadata)
if err != nil {
log.Debugln("[%s] getExecPathFromAddress error: %s", C.Process.String(), err.Error())
}
processCache.Set(key, name)
cached = name
}
return strings.EqualFold(cached.(string), ps.process)
}
func (p *Process) Adapter() string {
return p.adapter
}
func (p *Process) Payload() string {
return p.process
}
func (p *Process) ShouldResolveIP() bool {
return false
}
func NewProcess(process string, adapter string) (*Process, error) {
return &Process{
adapter: adapter,
process: process,
}, nil
}
const ( const (
procpidpathinfo = 0xb procpidpathinfo = 0xb
procpidpathinfosize = 1024 procpidpathinfosize = 1024
proccallnumpidinfo = 0x2 proccallnumpidinfo = 0x2
) )
func getExecPathFromPID(pid uint32) (string, error) { func findProcessName(network string, ip net.IP, port int) (string, error) {
buf := make([]byte, procpidpathinfosize)
_, _, errno := syscall.Syscall6(
syscall.SYS_PROC_INFO,
proccallnumpidinfo,
uintptr(pid),
procpidpathinfo,
0,
uintptr(unsafe.Pointer(&buf[0])),
procpidpathinfosize)
if errno != 0 {
return "", errno
}
firstZero := bytes.IndexByte(buf, 0)
if firstZero <= 0 {
return "", nil
}
return filepath.Base(string(buf[:firstZero])), nil
}
func getExecPathFromAddress(metadata *C.Metadata) (string, error) {
ip := metadata.SrcIP
port, err := strconv.Atoi(metadata.SrcPort)
if err != nil {
return "", err
}
var spath string var spath string
switch metadata.NetWork { switch network {
case C.TCP: case TCP:
spath = "net.inet.tcp.pcblist_n" spath = "net.inet.tcp.pcblist_n"
case C.UDP: case UDP:
spath = "net.inet.udp.pcblist_n" spath = "net.inet.udp.pcblist_n"
default: default:
return "", ErrInvalidNetwork return "", ErrInvalidNetwork
@ -123,7 +41,7 @@ func getExecPathFromAddress(metadata *C.Metadata) (string, error) {
// rup8(sizeof(xinpcb_n)) + rup8(sizeof(xsocket_n)) + // rup8(sizeof(xinpcb_n)) + rup8(sizeof(xsocket_n)) +
// 2 * rup8(sizeof(xsockbuf_n)) + rup8(sizeof(xsockstat_n)) // 2 * rup8(sizeof(xsockbuf_n)) + rup8(sizeof(xsockstat_n))
itemSize := 384 itemSize := 384
if metadata.NetWork == C.TCP { if network == TCP {
// rup8(sizeof(xtcpcb_n)) // rup8(sizeof(xtcpcb_n))
itemSize += 208 itemSize += 208
} }
@ -161,7 +79,24 @@ func getExecPathFromAddress(metadata *C.Metadata) (string, error) {
return getExecPathFromPID(pid) return getExecPathFromPID(pid)
} }
return "", errors.New("process not found") return "", ErrNotFound
}
func getExecPathFromPID(pid uint32) (string, error) {
buf := make([]byte, procpidpathinfosize)
_, _, errno := syscall.Syscall6(
syscall.SYS_PROC_INFO,
proccallnumpidinfo,
uintptr(pid),
procpidpathinfo,
0,
uintptr(unsafe.Pointer(&buf[0])),
procpidpathinfosize)
if errno != 0 {
return "", errno
}
return filepath.Base(unix.ByteSliceToString(buf)), nil
} }
func readNativeUint32(b []byte) uint32 { func readNativeUint32(b []byte) uint32 {

View File

@ -1,8 +1,7 @@
package rules package process
import ( import (
"encoding/binary" "encoding/binary"
"errors"
"fmt" "fmt"
"net" "net"
"path/filepath" "path/filepath"
@ -12,78 +11,52 @@ import (
"syscall" "syscall"
"unsafe" "unsafe"
"github.com/Dreamacro/clash/common/cache"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/log" "github.com/Dreamacro/clash/log"
) )
// store process name for when dealing with multiple PROCESS-NAME rules // store process name for when dealing with multiple PROCESS-NAME rules
var ( var (
processCache = cache.NewLRUCache(cache.WithAge(2), cache.WithSize(64))
errNotFound = errors.New("process not found")
matchMeta = func(p *Process, m *C.Metadata) bool { return false }
defaultSearcher *searcher defaultSearcher *searcher
once sync.Once once sync.Once
) )
type Process struct { func findProcessName(network string, ip net.IP, srcPort int) (string, error) {
adapter string
process string
}
func (ps *Process) RuleType() C.RuleType {
return C.Process
}
func match(ps *Process, metadata *C.Metadata) bool {
key := fmt.Sprintf("%s:%s:%s", metadata.NetWork.String(), metadata.SrcIP.String(), metadata.SrcPort)
cached, hit := processCache.Get(key)
if !hit {
name, err := getExecPathFromAddress(metadata)
if err != nil {
log.Debugln("[%s] getExecPathFromAddress error: %s", C.Process.String(), err.Error())
}
processCache.Set(key, name)
cached = name
}
return strings.EqualFold(cached.(string), ps.process)
}
func (ps *Process) Match(metadata *C.Metadata) bool {
return matchMeta(ps, metadata)
}
func (p *Process) Adapter() string {
return p.adapter
}
func (p *Process) Payload() string {
return p.process
}
func (p *Process) ShouldResolveIP() bool {
return false
}
func NewProcess(process string, adapter string) (*Process, error) {
once.Do(func() { once.Do(func() {
err := initSearcher() if err := initSearcher(); err != nil {
if err != nil {
log.Errorln("Initialize PROCESS-NAME failed: %s", err.Error()) log.Errorln("Initialize PROCESS-NAME failed: %s", err.Error())
log.Warnln("All PROCESS-NAME rules will be skipped") log.Warnln("All PROCESS-NAME rules will be skipped")
return return
} }
matchMeta = match
}) })
return &Process{
adapter: adapter, if defaultSearcher == nil {
process: process, return "", ErrPlatformNotSupport
}, nil }
var spath string
isTCP := network == TCP
switch network {
case TCP:
spath = "net.inet.tcp.pcblist"
case UDP:
spath = "net.inet.udp.pcblist"
default:
return "", ErrInvalidNetwork
}
value, err := syscall.Sysctl(spath)
if err != nil {
return "", err
}
buf := []byte(value)
pid, err := defaultSearcher.Search(buf, ip, uint16(srcPort), isTCP)
if err != nil {
return "", err
}
return getExecPathFromPID(pid)
} }
func getExecPathFromPID(pid uint32) (string, error) { func getExecPathFromPID(pid uint32) (string, error) {
@ -107,41 +80,6 @@ func getExecPathFromPID(pid uint32) (string, error) {
return filepath.Base(string(buf[:size-1])), nil return filepath.Base(string(buf[:size-1])), nil
} }
func getExecPathFromAddress(metadata *C.Metadata) (string, error) {
ip := metadata.SrcIP
port, err := strconv.Atoi(metadata.SrcPort)
if err != nil {
return "", err
}
var spath string
var isTCP bool
switch metadata.NetWork {
case C.TCP:
spath = "net.inet.tcp.pcblist"
isTCP = true
case C.UDP:
spath = "net.inet.udp.pcblist"
isTCP = false
default:
return "", ErrInvalidNetwork
}
value, err := syscall.Sysctl(spath)
if err != nil {
return "", err
}
buf := []byte(value)
pid, err := defaultSearcher.Search(buf, ip, uint16(port), isTCP)
if err != nil {
return "", err
}
return getExecPathFromPID(pid)
}
func readNativeUint32(b []byte) uint32 { func readNativeUint32(b []byte) uint32 {
return *(*uint32)(unsafe.Pointer(&b[0])) return *(*uint32)(unsafe.Pointer(&b[0]))
} }
@ -213,7 +151,7 @@ func (s *searcher) Search(buf []byte, ip net.IP, port uint16, isTCP bool) (uint3
socket := binary.BigEndian.Uint64(buf[inp+s.socket : inp+s.socket+8]) socket := binary.BigEndian.Uint64(buf[inp+s.socket : inp+s.socket+8])
return s.searchSocketPid(socket) return s.searchSocketPid(socket)
} }
return 0, errNotFound return 0, ErrNotFound
} }
func (s *searcher) searchSocketPid(socket uint64) (uint32, error) { func (s *searcher) searchSocketPid(socket uint64) (uint32, error) {
@ -235,11 +173,11 @@ func (s *searcher) searchSocketPid(socket uint64) (uint32, error) {
return pid, nil return pid, nil
} }
} }
return 0, errNotFound return 0, ErrNotFound
} }
func newSearcher(major int) *searcher { func newSearcher(major int) *searcher {
var s *searcher = nil var s *searcher
switch major { switch major {
case 11: case 11:
s = &searcher{ s = &searcher{
@ -256,6 +194,8 @@ func newSearcher(major int) *searcher {
udpInpOffset: 8, udpInpOffset: 8,
} }
case 12: case 12:
fallthrough
case 13:
s = &searcher{ s = &searcher{
headSize: 64, headSize: 64,
tcpItemSize: 744, tcpItemSize: 744,

View File

@ -1,4 +1,4 @@
package rules package process
import ( import (
"bytes" "bytes"
@ -9,28 +9,23 @@ import (
"net" "net"
"path" "path"
"path/filepath" "path/filepath"
"strconv"
"strings"
"syscall" "syscall"
"unsafe" "unsafe"
"github.com/Dreamacro/clash/common/cache"
"github.com/Dreamacro/clash/common/pool" "github.com/Dreamacro/clash/common/pool"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/log"
) )
// from https://github.com/vishvananda/netlink/blob/bca67dfc8220b44ef582c9da4e9172bf1c9ec973/nl/nl_linux.go#L52-L62 // from https://github.com/vishvananda/netlink/blob/bca67dfc8220b44ef582c9da4e9172bf1c9ec973/nl/nl_linux.go#L52-L62
func init() { var nativeEndian = func() binary.ByteOrder {
var x uint32 = 0x01020304 var x uint32 = 0x01020304
if *(*byte)(unsafe.Pointer(&x)) == 0x01 { if *(*byte)(unsafe.Pointer(&x)) == 0x01 {
nativeEndian = binary.BigEndian return binary.BigEndian
} else {
nativeEndian = binary.LittleEndian
} }
}
type SocketResolver func(metadata *C.Metadata) (inode, uid int, err error) return binary.LittleEndian
}()
type SocketResolver func(network string, ip net.IP, srcPort int) (inode, uid int, err error)
type ProcessNameResolver func(inode, uid int) (name string, err error) type ProcessNameResolver func(inode, uid int) (name string, err error)
// export for android // export for android
@ -39,63 +34,14 @@ var (
DefaultProcessNameResolver ProcessNameResolver = resolveProcessNameByProcSearch DefaultProcessNameResolver ProcessNameResolver = resolveProcessNameByProcSearch
) )
type Process struct {
adapter string
process string
}
func (p *Process) RuleType() C.RuleType {
return C.Process
}
func (p *Process) Match(metadata *C.Metadata) bool {
key := fmt.Sprintf("%s:%s:%s", metadata.NetWork.String(), metadata.SrcIP.String(), metadata.SrcPort)
cached, hit := processCache.Get(key)
if !hit {
processName, err := resolveProcessName(metadata)
if err != nil {
log.Debugln("[%s] Resolve process of %s failure: %s", C.Process.String(), key, err.Error())
}
processCache.Set(key, processName)
cached = processName
}
return strings.EqualFold(cached.(string), p.process)
}
func (p *Process) Adapter() string {
return p.adapter
}
func (p *Process) Payload() string {
return p.process
}
func (p *Process) ShouldResolveIP() bool {
return false
}
func NewProcess(process string, adapter string) (*Process, error) {
return &Process{
adapter: adapter,
process: process,
}, nil
}
const ( const (
sizeOfSocketDiagRequest = syscall.SizeofNlMsghdr + 8 + 48 sizeOfSocketDiagRequest = syscall.SizeofNlMsghdr + 8 + 48
socketDiagByFamily = 20 socketDiagByFamily = 20
pathProc = "/proc" pathProc = "/proc"
) )
var nativeEndian binary.ByteOrder = binary.LittleEndian func findProcessName(network string, ip net.IP, srcPort int) (string, error) {
inode, uid, err := DefaultSocketResolver(network, ip, srcPort)
var processCache = cache.NewLRUCache(cache.WithAge(2), cache.WithSize(64))
func resolveProcessName(metadata *C.Metadata) (string, error) {
inode, uid, err := DefaultSocketResolver(metadata)
if err != nil { if err != nil {
return "", err return "", err
} }
@ -103,31 +49,26 @@ func resolveProcessName(metadata *C.Metadata) (string, error) {
return DefaultProcessNameResolver(inode, uid) return DefaultProcessNameResolver(inode, uid)
} }
func resolveSocketByNetlink(metadata *C.Metadata) (int, int, error) { func resolveSocketByNetlink(network string, ip net.IP, srcPort int) (int, int, error) {
var family byte var family byte
var protocol byte var protocol byte
switch metadata.NetWork { switch network {
case C.TCP: case TCP:
protocol = syscall.IPPROTO_TCP protocol = syscall.IPPROTO_TCP
case C.UDP: case UDP:
protocol = syscall.IPPROTO_UDP protocol = syscall.IPPROTO_UDP
default: default:
return 0, 0, ErrInvalidNetwork return 0, 0, ErrInvalidNetwork
} }
if metadata.SrcIP.To4() != nil { if ip.To4() != nil {
family = syscall.AF_INET family = syscall.AF_INET
} else { } else {
family = syscall.AF_INET6 family = syscall.AF_INET6
} }
srcPort, err := strconv.Atoi(metadata.SrcPort) req := packSocketDiagRequest(family, protocol, ip, uint16(srcPort))
if err != nil {
return 0, 0, err
}
req := packSocketDiagRequest(family, protocol, metadata.SrcIP, uint16(srcPort))
socket, err := syscall.Socket(syscall.AF_NETLINK, syscall.SOCK_DGRAM, syscall.NETLINK_INET_DIAG) socket, err := syscall.Socket(syscall.AF_NETLINK, syscall.SOCK_DGRAM, syscall.NETLINK_INET_DIAG)
if err != nil { if err != nil {

View File

@ -0,0 +1,13 @@
//go:build !darwin && !linux && !windows && (!freebsd || !amd64)
// +build !darwin
// +build !linux
// +build !windows
// +build !freebsd !amd64
package process
import "net"
func findProcessName(network string, ip net.IP, srcPort int) (string, error) {
return "", ErrPlatformNotSupport
}

View File

@ -1,18 +1,13 @@
package rules package process
import ( import (
"errors"
"fmt" "fmt"
"net" "net"
"path/filepath" "path/filepath"
"strconv"
"strings"
"sync" "sync"
"syscall" "syscall"
"unsafe" "unsafe"
"github.com/Dreamacro/clash/common/cache"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/log" "github.com/Dreamacro/clash/log"
"golang.org/x/sys/windows" "golang.org/x/sys/windows"
@ -27,12 +22,8 @@ const (
) )
var ( var (
processCache = cache.NewLRUCache(cache.WithAge(2), cache.WithSize(64)) getExTCPTable uintptr
errNotFound = errors.New("process not found") getExUDPTable uintptr
matchMeta = func(p *Process, m *C.Metadata) bool { return false }
getExTcpTable uintptr
getExUdpTable uintptr
queryProcName uintptr queryProcName uintptr
once sync.Once once sync.Once
@ -44,12 +35,12 @@ func initWin32API() error {
return fmt.Errorf("LoadLibrary iphlpapi.dll failed: %s", err.Error()) return fmt.Errorf("LoadLibrary iphlpapi.dll failed: %s", err.Error())
} }
getExTcpTable, err = windows.GetProcAddress(h, tcpTableFunc) getExTCPTable, err = windows.GetProcAddress(h, tcpTableFunc)
if err != nil { if err != nil {
return fmt.Errorf("GetProcAddress of %s failed: %s", tcpTableFunc, err.Error()) return fmt.Errorf("GetProcAddress of %s failed: %s", tcpTableFunc, err.Error())
} }
getExUdpTable, err = windows.GetProcAddress(h, udpTableFunc) getExUDPTable, err = windows.GetProcAddress(h, udpTableFunc)
if err != nil { if err != nil {
return fmt.Errorf("GetProcAddress of %s failed: %s", udpTableFunc, err.Error()) return fmt.Errorf("GetProcAddress of %s failed: %s", udpTableFunc, err.Error())
} }
@ -67,47 +58,7 @@ func initWin32API() error {
return nil return nil
} }
type Process struct { func findProcessName(network string, ip net.IP, srcPort int) (string, error) {
adapter string
process string
}
func (p *Process) RuleType() C.RuleType {
return C.Process
}
func (p *Process) Adapter() string {
return p.adapter
}
func (p *Process) Payload() string {
return p.process
}
func (p *Process) ShouldResolveIP() bool {
return false
}
func match(p *Process, metadata *C.Metadata) bool {
key := fmt.Sprintf("%s:%s:%s", metadata.NetWork.String(), metadata.SrcIP.String(), metadata.SrcPort)
cached, hit := processCache.Get(key)
if !hit {
processName, err := resolveProcessName(metadata)
if err != nil {
log.Debugln("[%s] Resolve process of %s failed: %s", C.Process.String(), key, err.Error())
}
processCache.Set(key, processName)
cached = processName
}
return strings.EqualFold(cached.(string), p.process)
}
func (p *Process) Match(metadata *C.Metadata) bool {
return matchMeta(p, metadata)
}
func NewProcess(process string, adapter string) (*Process, error) {
once.Do(func() { once.Do(func() {
err := initWin32API() err := initWin32API()
if err != nil { if err != nil {
@ -115,16 +66,7 @@ func NewProcess(process string, adapter string) (*Process, error) {
log.Warnln("All PROCESS-NAMES rules will be skiped") log.Warnln("All PROCESS-NAMES rules will be skiped")
return return
} }
matchMeta = match
}) })
return &Process{
adapter: adapter,
process: process,
}, nil
}
func resolveProcessName(metadata *C.Metadata) (string, error) {
ip := metadata.SrcIP
family := windows.AF_INET family := windows.AF_INET
if ip.To4() == nil { if ip.To4() == nil {
family = windows.AF_INET6 family = windows.AF_INET6
@ -132,28 +74,23 @@ func resolveProcessName(metadata *C.Metadata) (string, error) {
var class int var class int
var fn uintptr var fn uintptr
switch metadata.NetWork { switch network {
case C.TCP: case TCP:
fn = getExTcpTable fn = getExTCPTable
class = tcpTablePidConn class = tcpTablePidConn
case C.UDP: case UDP:
fn = getExUdpTable fn = getExUDPTable
class = udpTablePid class = udpTablePid
default: default:
return "", ErrInvalidNetwork return "", ErrInvalidNetwork
} }
srcPort, err := strconv.Atoi(metadata.SrcPort)
if err != nil {
return "", err
}
buf, err := getTransportTable(fn, family, class) buf, err := getTransportTable(fn, family, class)
if err != nil { if err != nil {
return "", err return "", err
} }
s := newSearcher(family == windows.AF_INET, metadata.NetWork == C.TCP) s := newSearcher(family == windows.AF_INET, network == TCP)
pid, err := s.Search(buf, ip, uint16(srcPort)) pid, err := s.Search(buf, ip, uint16(srcPort))
if err != nil { if err != nil {
@ -196,14 +133,15 @@ func (s *searcher) Search(b []byte, ip net.IP, port uint16) (uint32, error) {
} }
srcIP := net.IP(row[s.ip : s.ip+s.ipSize]) srcIP := net.IP(row[s.ip : s.ip+s.ipSize])
if !ip.Equal(srcIP) { // windows binds an unbound udp socket to 0.0.0.0/[::] while first sendto
if !ip.Equal(srcIP) && (!srcIP.IsUnspecified() || s.tcpState != -1) {
continue continue
} }
pid := readNativeUint32(row[s.pid : s.pid+4]) pid := readNativeUint32(row[s.pid : s.pid+4])
return pid, nil return pid, nil
} }
return 0, errNotFound return 0, ErrNotFound
} }
func newSearcher(isV4, isTCP bool) *searcher { func newSearcher(isV4, isTCP bool) *searcher {

View File

@ -0,0 +1,101 @@
package cachefile
import (
"bytes"
"encoding/gob"
"io/ioutil"
"os"
"sync"
"github.com/Dreamacro/clash/component/profile"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/log"
)
var (
initOnce sync.Once
fileMode os.FileMode = 0666
defaultCache *CacheFile
)
type cache struct {
Selected map[string]string
}
// CacheFile store and update the cache file
type CacheFile struct {
path string
model *cache
buf *bytes.Buffer
mux sync.Mutex
}
func (c *CacheFile) SetSelected(group, selected string) {
if !profile.StoreSelected.Load() {
return
}
c.mux.Lock()
defer c.mux.Unlock()
model := c.element()
model.Selected[group] = selected
c.buf.Reset()
if err := gob.NewEncoder(c.buf).Encode(model); err != nil {
log.Warnln("[CacheFile] encode gob failed: %s", err.Error())
return
}
if err := ioutil.WriteFile(c.path, c.buf.Bytes(), fileMode); err != nil {
log.Warnln("[CacheFile] write cache to %s failed: %s", c.path, err.Error())
return
}
}
func (c *CacheFile) SelectedMap() map[string]string {
if !profile.StoreSelected.Load() {
return nil
}
c.mux.Lock()
defer c.mux.Unlock()
model := c.element()
mapping := map[string]string{}
for k, v := range model.Selected {
mapping[k] = v
}
return mapping
}
func (c *CacheFile) element() *cache {
if c.model != nil {
return c.model
}
model := &cache{
Selected: map[string]string{},
}
if buf, err := ioutil.ReadFile(c.path); err == nil {
bufReader := bytes.NewBuffer(buf)
gob.NewDecoder(bufReader).Decode(model)
}
c.model = model
return c.model
}
// Cache return singleton of CacheFile
func Cache() *CacheFile {
initOnce.Do(func() {
defaultCache = &CacheFile{
path: C.Path.Cache(),
buf: &bytes.Buffer{},
}
})
return defaultCache
}

View File

@ -0,0 +1,10 @@
package profile
import (
"go.uber.org/atomic"
)
var (
// StoreSelected is a global switch for storing selected proxy to cache
StoreSelected = atomic.NewBool(true)
)

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

@ -1,11 +0,0 @@
package obfs
// Base information for obfs
type Base struct {
IVSize int
Key []byte
HeadLen int
Host string
Port int
Param string
}

View File

@ -1,402 +0,0 @@
package obfs
import (
"bytes"
"encoding/hex"
"fmt"
"io"
"math/rand"
"strings"
)
type httpObfs struct {
*Base
firstRequest bool
firstResponse bool
post bool
}
func init() {
register("http_simple", newHTTPSimple)
}
func newHTTPSimple(b *Base) Obfs {
return &httpObfs{Base: b}
}
func (h *httpObfs) initForConn() Obfs {
return &httpObfs{
Base: h.Base,
firstRequest: true,
firstResponse: true,
post: h.post,
}
}
func (h *httpObfs) GetObfsOverhead() int {
return 0
}
func (h *httpObfs) Decode(b []byte) ([]byte, bool, error) {
if h.firstResponse {
idx := bytes.Index(b, []byte("\r\n\r\n"))
if idx == -1 {
return nil, false, io.EOF
}
h.firstResponse = false
return b[idx+4:], false, nil
}
return b, false, nil
}
func (h *httpObfs) Encode(b []byte) ([]byte, error) {
if h.firstRequest {
bSize := len(b)
var headData []byte
if headSize := h.IVSize + h.HeadLen; bSize-headSize > 64 {
headData = make([]byte, headSize+rand.Intn(64))
} else {
headData = make([]byte, bSize)
}
copy(headData, b[:len(headData)])
host := h.Host
var customHead string
if len(h.Param) > 0 {
customHeads := strings.Split(h.Param, "#")
if len(customHeads) > 2 {
customHeads = customHeads[:2]
}
customHosts := h.Param
if len(customHeads) > 1 {
customHosts = customHeads[0]
customHead = customHeads[1]
}
hosts := strings.Split(customHosts, ",")
if len(hosts) > 0 {
host = strings.TrimSpace(hosts[rand.Intn(len(hosts))])
}
}
method := "GET /"
if h.post {
method = "POST /"
}
requestPathIndex := rand.Intn(len(requestPath)/2) * 2
httpBuf := fmt.Sprintf("%s%s%s%s HTTP/1.1\r\nHost: %s:%d\r\n",
method,
requestPath[requestPathIndex],
data2URLEncode(headData),
requestPath[requestPathIndex+1],
host, h.Port)
if len(customHead) > 0 {
httpBuf = httpBuf + strings.Replace(customHead, "\\n", "\r\n", -1) + "\r\n\r\n"
} else {
var contentType string
if h.post {
contentType = "Content-Type: multipart/form-data; boundary=" + boundary() + "\r\n"
}
httpBuf = httpBuf + "User-agent: " + requestUserAgent[rand.Intn(len(requestUserAgent))] + "\r\n" +
"Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n" +
"Accept-Language: en-US,en;q=0.8\r\n" +
"Accept-Encoding: gzip, deflate\r\n" +
contentType +
"DNT: 1\r\n" +
"Connection: keep-alive\r\n" +
"\r\n"
}
var encoded []byte
if len(headData) < bSize {
encoded = make([]byte, len(httpBuf)+(bSize-len(headData)))
copy(encoded, []byte(httpBuf))
copy(encoded[len(httpBuf):], b[len(headData):])
} else {
encoded = []byte(httpBuf)
}
h.firstRequest = false
return encoded, nil
}
return b, nil
}
func data2URLEncode(data []byte) (ret string) {
for i := 0; i < len(data); i++ {
ret = fmt.Sprintf("%s%%%s", ret, hex.EncodeToString([]byte{data[i]}))
}
return
}
func boundary() (ret string) {
set := "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
for i := 0; i < 32; i++ {
ret = fmt.Sprintf("%s%c", ret, set[rand.Intn(len(set))])
}
return
}
var (
requestPath = []string{
"", "",
"login.php?redir=", "",
"register.php?code=", "",
"?keyword=", "",
"search?src=typd&q=", "&lang=en",
"s?ie=utf-8&f=8&rsv_bp=1&rsv_idx=1&ch=&bar=&wd=", "&rn=",
"post.php?id=", "&goto=view.php",
}
requestUserAgent = []string{
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.162 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.85 Safari/537.36",
"Mozilla/5.0 (Linux; Android 7.0; Moto C Build/NRD90M.059) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36",
"Mozilla/5.0 (Linux; Android 6.0.1; SM-G532M Build/MMB29T; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/55.0.2883.91 Mobile Safari/537.36",
"Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.80 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.101 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.2; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/40.0.2214.111 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.94 Safari/537.36",
"Mozilla/5.0 (Linux; Android 5.1.1; SM-J120M Build/LMY47X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36",
"Mozilla/5.0 (Windows NT 6.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.80 Safari/537.36",
"Mozilla/5.0 (Linux; Android 7.0; Moto G (5) Build/NPPS25.137-93-14) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36",
"Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.106 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36",
"Mozilla/5.0 (Linux; Android 7.0; SM-G570M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.80 Mobile Safari/537.36",
"Mozilla/5.0 (Windows NT 5.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.80 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.112 Safari/537.36",
"Mozilla/5.0 (Linux; Android 6.0; CAM-L03 Build/HUAWEICAM-L03) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36",
"Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.76 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.117 Safari/537.36",
"Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/534.3 (KHTML, like Gecko) Chrome/6.0.472.63 Safari/534.3",
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.106 Safari/537.36",
"Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/534.7 (KHTML, like Gecko) Chrome/7.0.517.44 Safari/534.7",
"Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.75 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.71 Safari/537.36",
"Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/534.3 (KHTML, like Gecko) Chrome/6.0.472.63 Safari/534.3",
"Mozilla/5.0 (Linux; Android 8.0.0; FIG-LX3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.80 Mobile Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/40.0.2214.115 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36",
"Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/534.10 (KHTML, like Gecko) Chrome/8.0.552.237 Safari/534.10",
"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36",
"Mozilla/5.0 (X11; U; Linux x86_64; en-US) AppleWebKit/533.2 (KHTML, like Gecko) Chrome/5.0.342.1 Safari/533.2",
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.110 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.89 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.81 Safari/537.36",
"Mozilla/5.0 (X11; Datanyze; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.71 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.1916.153 Safari/537.36",
"Mozilla/5.0 (Linux; Android 5.1.1; SM-J111M Build/LMY47V) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.106 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/37.0.2062.120 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.84 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.99 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/32.0.1700.107 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36",
"Mozilla/5.0 (Linux; Android 6.0.1; SM-J700M Build/MMB29K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36",
"Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.63 Safari/537.36",
"Mozilla/5.0 (X11; Linux i686) AppleWebKit/534.30 (KHTML, like Gecko) Slackware/Chrome/12.0.742.100 Safari/534.30",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.86 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.167 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/48.0.2564.116 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.102 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.143 Safari/537.36",
"Mozilla/5.0 (X11; Linux i686) AppleWebKit/534.30 (KHTML, like Gecko) Chrome/12.0.742.100 Safari/534.30",
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36",
"Mozilla/5.0 (Linux; Android 8.0.0; WAS-LX3 Build/HUAWEIWAS-LX3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.99 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.87 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.102 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.57 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.101 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.1805 Safari/537.36 MVisionPlayer/1.0.0.0",
"Mozilla/5.0 (Linux; Android 7.0; TRT-LX3 Build/HUAWEITRT-LX3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.89 Safari/537.36",
"Mozilla/5.0 (Linux; Android 6.0; vivo 1610 Build/MMB29M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.124 Mobile Safari/537.36",
"Mozilla/5.0 (Windows NT 6.2; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.87 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.111 Safari/537.36",
"Mozilla/5.0 (Linux; Android 4.4.2; de-de; SAMSUNG GT-I9195 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Version/1.5 Chrome/28.0.1500.94 Mobile Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.95 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.90 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.110 Safari/537.36",
"Mozilla/5.0 (Linux; Android 8.0.0; ANE-LX3 Build/HUAWEIANE-LX3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.112 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.87 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.143 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36",
"Mozilla/5.0 (X11; U; Linux i586; en-US) AppleWebKit/533.2 (KHTML, like Gecko) Chrome/5.0.342.1 Safari/533.2",
"Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/43.0.2357.65 Safari/537.36",
"Mozilla/5.0 (Linux; Android 7.0; SM-G610M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.80 Mobile Safari/537.36",
"Mozilla/5.0 (Linux; Android 6.0.1; SM-J500M Build/MMB29M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36",
"Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/534.7 (KHTML, like Gecko) Chrome/7.0.517.44 Safari/534.7",
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.104 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36",
"Mozilla/5.0 (Linux; Android 6.0; vivo 1606 Build/MMB29M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.124 Mobile Safari/537.36",
"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36",
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.135 Safari/537.36",
"Mozilla/5.0 (Linux; Android 7.0; SM-G610M Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36",
"Mozilla/5.0 (Linux; Android 7.1; vivo 1716 Build/N2G47H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.98 Mobile Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.93 Safari/537.36",
"Mozilla/5.0 (Linux; Android 7.0; SM-G570M Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36",
"Mozilla/5.0 (Windows NT 6.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36",
"Mozilla/5.0 (Linux; Android 6.0; MYA-L22 Build/HUAWEIMYA-L22) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.84 Mobile Safari/537.36",
"Mozilla/5.0 (Linux; Android 5.1; A1601 Build/LMY47I) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.98 Mobile Safari/537.36",
"Mozilla/5.0 (Linux; Android 7.0; TRT-LX2 Build/HUAWEITRT-LX2; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/59.0.3071.125 Mobile Safari/537.36",
"Mozilla/5.0 (Windows NT 6.2; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36",
"Mozilla/5.0 (Windows NT 5.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36",
"Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/534.17 (KHTML, like Gecko) Chrome/10.0.649.0 Safari/534.17",
"Mozilla/5.0 (Linux; Android 6.0; CAM-L21 Build/HUAWEICAM-L21; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/62.0.3202.84 Mobile Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.94 Safari/537.36",
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/534.24 (KHTML, like Gecko) Chrome/11.0.696.3 Safari/534.24",
"Mozilla/5.0 (Linux; Android 7.1.2; Redmi 4X Build/N2G47H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.111 Mobile Safari/537.36",
"Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.106 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.181 Safari/537.36",
"Mozilla/5.0 (Linux; Android 4.4.2; SM-G7102 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.84 Mobile Safari/537.36",
"Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/48.0.2564.109 Safari/537.36",
"Mozilla/5.0 (Linux; Android 5.1; HUAWEI CUN-L22 Build/HUAWEICUN-L22; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/62.0.3202.84 Mobile Safari/537.36",
"Mozilla/5.0 (Linux; Android 5.1.1; A37fw Build/LMY47V) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.84 Mobile Safari/537.36",
"Mozilla/5.0 (Linux; Android 7.0; SM-J730GM Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.111 Mobile Safari/537.36",
"Mozilla/5.0 (Linux; Android 7.0; SM-G610F Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.111 Mobile Safari/537.36",
"Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.101 Safari/537.36",
"Mozilla/5.0 (Linux; Android 7.1.2; Redmi Note 5A Build/N2G47H; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/63.0.3239.111 Mobile Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36",
"Mozilla/5.0 (Linux; Android 7.0; Redmi Note 4 Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.111 Mobile Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.106 Safari/537.36",
"Mozilla/5.0 (Unknown; Linux) AppleWebKit/538.1 (KHTML, like Gecko) Chrome/v1.0.0 Safari/538.1",
"Mozilla/5.0 (Linux; Android 7.0; BLL-L22 Build/HUAWEIBLL-L22) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.91 Mobile Safari/537.36",
"Mozilla/5.0 (Linux; Android 7.0; SM-J710F Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.84 Mobile Safari/537.36",
"Mozilla/5.0 (Linux; Android 6.0.1; SM-G532M Build/MMB29T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.91 Mobile Safari/537.36",
"Mozilla/5.0 (Linux; Android 7.1.1; CPH1723 Build/N6F26Q) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.98 Mobile Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.118 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.79 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.101 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.94 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.139 Safari/537.36",
"Mozilla/5.0 (Linux; Android 8.0.0; FIG-LX3 Build/HUAWEIFIG-LX3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36",
"Mozilla/5.0 (Windows; U; Windows NT 6.1; de-DE) AppleWebKit/534.17 (KHTML, like Gecko) Chrome/10.0.649.0 Safari/534.17",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.63 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2228.0 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.67 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.94 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.99 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.67 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.67 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.67 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/43.0.2357.65 Safari/537.36",
"Mozilla/5.0 (Linux; Android 7.1; Mi A1 Build/N2G47H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.83 Mobile Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.117 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36",
"Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/533.4 (KHTML, like Gecko) Chrome/5.0.375.99 Safari/533.4",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/36.0.1985.125 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.89 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.111 Safari/537.36 MVisionPlayer/1.0.0.0",
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.103 Safari/537.36",
"Mozilla/5.0 (Linux; Android 5.1; A37f Build/LMY47V) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/43.0.2357.93 Mobile Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.2; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.100 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.86 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.76 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36",
"Mozilla/5.0 (Windows NT 5.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.80 Safari/537.36",
"Mozilla/5.0 (Linux; Android 6.0.1; CPH1607 Build/MMB29M; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/63.0.3239.111 Mobile Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.99 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.95 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.100 Safari/537.36",
"Mozilla/5.0 (Linux; Android 6.0.1; vivo 1603 Build/MMB29M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.83 Mobile Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.186 Safari/537.36",
"Mozilla/5.0 (Linux; Android 6.0.1; SM-G532M Build/MMB29T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.87 Safari/537.36",
"Mozilla/5.0 (Linux; Android 6.0.1; Redmi 4A Build/MMB29M; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/60.0.3112.116 Mobile Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.103 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.112 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36",
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/44.0.2403.157 Safari/537.36",
"Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.71 Safari/537.36",
"Mozilla/5.0 (Windows NT 5.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.2; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.90 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.100 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.31 (KHTML, like Gecko) Chrome/26.0.1410.64 Safari/537.31",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.84 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.186 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.80 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/36.0.1985.143 Safari/537.36",
"Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.112 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.67 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.181 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.106 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36",
"Mozilla/5.0 (Linux; Android 6.0.1; SM-G532G Build/MMB29T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.83 Mobile Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/48.0.2564.109 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.117 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.139 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.67 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/34.0.1847.131 Safari/537.36",
"Mozilla/5.0 (Linux; Android 6.0; vivo 1713 Build/MRA58K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.124 Mobile Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.89 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.80 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.101 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.67 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36",
}
)

View File

@ -1,37 +0,0 @@
package obfs
import (
"errors"
"fmt"
"strings"
)
var (
errTLS12TicketAuthIncorrectMagicNumber = errors.New("tls1.2_ticket_auth incorrect magic number")
errTLS12TicketAuthTooShortData = errors.New("tls1.2_ticket_auth too short data")
errTLS12TicketAuthHMACError = errors.New("tls1.2_ticket_auth hmac verifying failed")
)
// Obfs provides methods for decoding and encoding
type Obfs interface {
initForConn() Obfs
GetObfsOverhead() int
Decode(b []byte) ([]byte, bool, error)
Encode(b []byte) ([]byte, error)
}
type obfsCreator func(b *Base) Obfs
var obfsList = make(map[string]obfsCreator)
func register(name string, c obfsCreator) {
obfsList[name] = c
}
// PickObfs returns an obfs of the given name
func PickObfs(name string, b *Base) (Obfs, error) {
if obfsCreator, ok := obfsList[strings.ToLower(name)]; ok {
return obfsCreator(b), nil
}
return nil, fmt.Errorf("Obfs %s not supported", name)
}

View File

@ -1,25 +0,0 @@
package obfs
type plain struct{}
func init() {
register("plain", newPlain)
}
func newPlain(b *Base) Obfs {
return &plain{}
}
func (p *plain) initForConn() Obfs { return &plain{} }
func (p *plain) GetObfsOverhead() int {
return 0
}
func (p *plain) Encode(b []byte) ([]byte, error) {
return b, nil
}
func (p *plain) Decode(b []byte) ([]byte, bool, error) {
return b, false, nil
}

View File

@ -1,75 +0,0 @@
package obfs
import (
"encoding/binary"
"hash/crc32"
"math/rand"
)
type randomHead struct {
*Base
firstRequest bool
firstResponse bool
headerSent bool
buffer []byte
}
func init() {
register("random_head", newRandomHead)
}
func newRandomHead(b *Base) Obfs {
return &randomHead{Base: b}
}
func (r *randomHead) initForConn() Obfs {
return &randomHead{
Base: r.Base,
firstRequest: true,
firstResponse: true,
}
}
func (r *randomHead) GetObfsOverhead() int {
return 0
}
func (r *randomHead) Encode(b []byte) (encoded []byte, err error) {
if !r.firstRequest {
return b, nil
}
bSize := len(b)
if r.headerSent {
if bSize > 0 {
d := make([]byte, len(r.buffer)+bSize)
copy(d, r.buffer)
copy(d[len(r.buffer):], b)
r.buffer = d
} else {
encoded = r.buffer
r.buffer = nil
r.firstRequest = false
}
} else {
size := rand.Intn(96) + 8
encoded = make([]byte, size)
rand.Read(encoded)
crc := (0xFFFFFFFF - crc32.ChecksumIEEE(encoded[:size-4])) & 0xFFFFFFFF
binary.LittleEndian.PutUint32(encoded[size-4:], crc)
d := make([]byte, bSize)
copy(d, b)
r.buffer = d
}
r.headerSent = true
return encoded, nil
}
func (r *randomHead) Decode(b []byte) ([]byte, bool, error) {
if r.firstResponse {
r.firstResponse = false
return b, true, nil
}
return b, false, nil
}

View File

@ -1,72 +0,0 @@
package obfs
import (
"net"
"github.com/Dreamacro/clash/common/pool"
)
// NewConn wraps a stream-oriented net.Conn with obfs decoding/encoding
func NewConn(c net.Conn, o Obfs) net.Conn {
return &Conn{Conn: c, Obfs: o.initForConn()}
}
// Conn represents an obfs connection
type Conn struct {
net.Conn
Obfs
buf []byte
offset int
}
func (c *Conn) Read(b []byte) (int, error) {
if c.buf != nil {
n := copy(b, c.buf[c.offset:])
c.offset += n
if c.offset == len(c.buf) {
pool.Put(c.buf)
c.buf = nil
}
return n, nil
}
buf := pool.Get(pool.RelayBufferSize)
defer pool.Put(buf)
n, err := c.Conn.Read(buf)
if err != nil {
return 0, err
}
decoded, sendback, err := c.Decode(buf[:n])
// decoded may be part of buf
decodedData := pool.Get(len(decoded))
copy(decodedData, decoded)
if err != nil {
pool.Put(decodedData)
return 0, err
}
if sendback {
c.Write(nil)
pool.Put(decodedData)
return 0, nil
}
n = copy(b, decodedData)
if len(decodedData) > len(b) {
c.buf = decodedData
c.offset = n
} else {
pool.Put(decodedData)
}
return n, err
}
func (c *Conn) Write(b []byte) (int, error) {
encoded, err := c.Encode(b)
if err != nil {
return 0, err
}
_, err = c.Conn.Write(encoded)
if err != nil {
return 0, err
}
return len(b), nil
}

View File

@ -1,290 +0,0 @@
package obfs
import (
"bytes"
"crypto/hmac"
"encoding/binary"
"fmt"
"io"
"math/rand"
"strings"
"time"
"github.com/Dreamacro/clash/common/pool"
"github.com/Dreamacro/clash/component/ssr/tools"
"github.com/Dreamacro/clash/log"
)
type tlsAuthData struct {
localClientID [32]byte
}
type tls12Ticket struct {
*Base
*tlsAuthData
handshakeStatus int
sendSaver bytes.Buffer
recvBuffer bytes.Buffer
buffer bytes.Buffer
}
func init() {
register("tls1.2_ticket_auth", newTLS12Ticket)
register("tls1.2_ticket_fastauth", newTLS12Ticket)
}
func newTLS12Ticket(b *Base) Obfs {
return &tls12Ticket{
Base: b,
}
}
func (t *tls12Ticket) initForConn() Obfs {
r := &tls12Ticket{
Base: t.Base,
tlsAuthData: &tlsAuthData{},
}
rand.Read(r.localClientID[:])
return r
}
func (t *tls12Ticket) GetObfsOverhead() int {
return 5
}
func (t *tls12Ticket) Decode(b []byte) ([]byte, bool, error) {
if t.handshakeStatus == -1 {
return b, false, nil
}
t.buffer.Reset()
if t.handshakeStatus == 8 {
t.recvBuffer.Write(b)
for t.recvBuffer.Len() > 5 {
var h [5]byte
t.recvBuffer.Read(h[:])
if !bytes.Equal(h[:3], []byte{0x17, 0x3, 0x3}) {
log.Warnln("incorrect magic number %x, 0x170303 is expected", h[:3])
return nil, false, errTLS12TicketAuthIncorrectMagicNumber
}
size := int(binary.BigEndian.Uint16(h[3:5]))
if t.recvBuffer.Len() < size {
// 不够读,下回再读吧
unread := t.recvBuffer.Bytes()
t.recvBuffer.Reset()
t.recvBuffer.Write(h[:])
t.recvBuffer.Write(unread)
break
}
d := pool.Get(size)
t.recvBuffer.Read(d)
t.buffer.Write(d)
pool.Put(d)
}
return t.buffer.Bytes(), false, nil
}
if len(b) < 11+32+1+32 {
return nil, false, errTLS12TicketAuthTooShortData
}
hash := t.hmacSHA1(b[11 : 11+22])
if !hmac.Equal(b[33:33+tools.HmacSHA1Len], hash) {
return nil, false, errTLS12TicketAuthHMACError
}
return nil, true, nil
}
func (t *tls12Ticket) Encode(b []byte) ([]byte, error) {
t.buffer.Reset()
switch t.handshakeStatus {
case 8:
if len(b) < 1024 {
d := []byte{0x17, 0x3, 0x3, 0, 0}
binary.BigEndian.PutUint16(d[3:5], uint16(len(b)&0xFFFF))
t.buffer.Write(d)
t.buffer.Write(b)
return t.buffer.Bytes(), nil
}
start := 0
var l int
for len(b)-start > 2048 {
l = rand.Intn(4096) + 100
if l > len(b)-start {
l = len(b) - start
}
packData(&t.buffer, b[start:start+l])
start += l
}
if len(b)-start > 0 {
l = len(b) - start
packData(&t.buffer, b[start:start+l])
}
return t.buffer.Bytes(), nil
case 1:
if len(b) > 0 {
if len(b) < 1024 {
packData(&t.sendSaver, b)
} else {
start := 0
var l int
for len(b)-start > 2048 {
l = rand.Intn(4096) + 100
if l > len(b)-start {
l = len(b) - start
}
packData(&t.buffer, b[start:start+l])
start += l
}
if len(b)-start > 0 {
l = len(b) - start
packData(&t.buffer, b[start:start+l])
}
io.Copy(&t.sendSaver, &t.buffer)
}
return []byte{}, nil
}
hmacData := make([]byte, 43)
handshakeFinish := []byte("\x14\x03\x03\x00\x01\x01\x16\x03\x03\x00\x20")
copy(hmacData, handshakeFinish)
rand.Read(hmacData[11:33])
h := t.hmacSHA1(hmacData[:33])
copy(hmacData[33:], h)
t.buffer.Write(hmacData)
io.Copy(&t.buffer, &t.sendSaver)
t.handshakeStatus = 8
return t.buffer.Bytes(), nil
case 0:
tlsData0 := []byte("\x00\x1c\xc0\x2b\xc0\x2f\xcc\xa9\xcc\xa8\xcc\x14\xcc\x13\xc0\x0a\xc0\x14\xc0\x09\xc0\x13\x00\x9c\x00\x35\x00\x2f\x00\x0a\x01\x00")
tlsData1 := []byte("\xff\x01\x00\x01\x00")
tlsData2 := []byte("\x00\x17\x00\x00\x00\x23\x00\xd0")
// tlsData3 := []byte("\x00\x0d\x00\x16\x00\x14\x06\x01\x06\x03\x05\x01\x05\x03\x04\x01\x04\x03\x03\x01\x03\x03\x02\x01\x02\x03\x00\x05\x00\x05\x01\x00\x00\x00\x00\x00\x12\x00\x00\x75\x50\x00\x00\x00\x0b\x00\x02\x01\x00\x00\x0a\x00\x06\x00\x04\x00\x17\x00\x18\x00\x15\x00\x66\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00")
tlsData3 := []byte("\x00\x0d\x00\x16\x00\x14\x06\x01\x06\x03\x05\x01\x05\x03\x04\x01\x04\x03\x03\x01\x03\x03\x02\x01\x02\x03\x00\x05\x00\x05\x01\x00\x00\x00\x00\x00\x12\x00\x00\x75\x50\x00\x00\x00\x0b\x00\x02\x01\x00\x00\x0a\x00\x06\x00\x04\x00\x17\x00\x18")
var tlsData [2048]byte
tlsDataLen := 0
copy(tlsData[0:], tlsData1)
tlsDataLen += len(tlsData1)
sni := t.sni(t.getHost())
copy(tlsData[tlsDataLen:], sni)
tlsDataLen += len(sni)
copy(tlsData[tlsDataLen:], tlsData2)
tlsDataLen += len(tlsData2)
ticketLen := rand.Intn(164)*2 + 64
tlsData[tlsDataLen-1] = uint8(ticketLen & 0xff)
tlsData[tlsDataLen-2] = uint8(ticketLen >> 8)
//ticketLen := 208
rand.Read(tlsData[tlsDataLen : tlsDataLen+ticketLen])
tlsDataLen += ticketLen
copy(tlsData[tlsDataLen:], tlsData3)
tlsDataLen += len(tlsData3)
length := 11 + 32 + 1 + 32 + len(tlsData0) + 2 + tlsDataLen
encodedData := make([]byte, length)
pdata := length - tlsDataLen
l := tlsDataLen
copy(encodedData[pdata:], tlsData[:tlsDataLen])
encodedData[pdata-1] = uint8(tlsDataLen)
encodedData[pdata-2] = uint8(tlsDataLen >> 8)
pdata -= 2
l += 2
copy(encodedData[pdata-len(tlsData0):], tlsData0)
pdata -= len(tlsData0)
l += len(tlsData0)
copy(encodedData[pdata-32:], t.localClientID[:])
pdata -= 32
l += 32
encodedData[pdata-1] = 0x20
pdata--
l++
copy(encodedData[pdata-32:], t.packAuthData())
pdata -= 32
l += 32
encodedData[pdata-1] = 0x3
encodedData[pdata-2] = 0x3 // tls version
pdata -= 2
l += 2
encodedData[pdata-1] = uint8(l)
encodedData[pdata-2] = uint8(l >> 8)
encodedData[pdata-3] = 0
encodedData[pdata-4] = 1
pdata -= 4
l += 4
encodedData[pdata-1] = uint8(l)
encodedData[pdata-2] = uint8(l >> 8)
pdata -= 2
l += 2
encodedData[pdata-1] = 0x1
encodedData[pdata-2] = 0x3 // tls version
pdata -= 2
l += 2
encodedData[pdata-1] = 0x16 // tls handshake
pdata--
l++
packData(&t.sendSaver, b)
t.handshakeStatus = 1
return encodedData, nil
default:
return nil, fmt.Errorf("unexpected handshake status: %d", t.handshakeStatus)
}
}
func (t *tls12Ticket) hmacSHA1(data []byte) []byte {
key := make([]byte, len(t.Key)+32)
copy(key, t.Key)
copy(key[len(t.Key):], t.localClientID[:])
sha1Data := tools.HmacSHA1(key, data)
return sha1Data[:tools.HmacSHA1Len]
}
func (t *tls12Ticket) sni(u string) []byte {
bURL := []byte(u)
length := len(bURL)
ret := make([]byte, length+9)
copy(ret[9:9+length], bURL)
binary.BigEndian.PutUint16(ret[7:], uint16(length&0xFFFF))
length += 3
binary.BigEndian.PutUint16(ret[4:], uint16(length&0xFFFF))
length += 2
binary.BigEndian.PutUint16(ret[2:], uint16(length&0xFFFF))
return ret
}
func (t *tls12Ticket) getHost() string {
host := t.Host
if len(t.Param) > 0 {
hosts := strings.Split(t.Param, ",")
if len(hosts) > 0 {
host = hosts[rand.Intn(len(hosts))]
host = strings.TrimSpace(host)
}
}
if len(host) > 0 && host[len(host)-1] >= byte('0') && host[len(host)-1] <= byte('9') && len(t.Param) == 0 {
host = ""
}
return host
}
func (t *tls12Ticket) packAuthData() (ret []byte) {
retSize := 32
ret = make([]byte, retSize)
now := time.Now().Unix()
binary.BigEndian.PutUint32(ret[:4], uint32(now))
rand.Read(ret[4 : 4+18])
hash := t.hmacSHA1(ret[:retSize-tools.HmacSHA1Len])
copy(ret[retSize-tools.HmacSHA1Len:], hash)
return
}
func packData(buffer *bytes.Buffer, suffix []byte) {
d := []byte{0x17, 0x3, 0x3, 0, 0}
binary.BigEndian.PutUint16(d[3:5], uint16(len(suffix)&0xFFFF))
buffer.Write(d)
buffer.Write(suffix)
}

View File

@ -1,310 +0,0 @@
package protocol
import (
"bytes"
"crypto/aes"
"crypto/cipher"
"encoding/base64"
"encoding/binary"
"math/rand"
"strconv"
"strings"
"time"
"github.com/Dreamacro/clash/common/pool"
"github.com/Dreamacro/clash/component/ssr/tools"
"github.com/Dreamacro/go-shadowsocks2/core"
)
type authAES128 struct {
*Base
*recvInfo
*authData
hasSentHeader bool
packID uint32
userKey []byte
uid [4]byte
salt string
hmac hmacMethod
hashDigest hashDigestMethod
}
func init() {
register("auth_aes128_md5", newAuthAES128MD5)
}
func newAuthAES128MD5(b *Base) Protocol {
return &authAES128{
Base: b,
authData: &authData{},
salt: "auth_aes128_md5",
hmac: tools.HmacMD5,
hashDigest: tools.MD5Sum,
}
}
func (a *authAES128) initForConn(iv []byte) Protocol {
return &authAES128{
Base: &Base{
IV: iv,
Key: a.Key,
TCPMss: a.TCPMss,
Overhead: a.Overhead,
Param: a.Param,
},
recvInfo: &recvInfo{recvID: 1, buffer: new(bytes.Buffer)},
authData: a.authData,
packID: 1,
salt: a.salt,
hmac: a.hmac,
hashDigest: a.hashDigest,
}
}
func (a *authAES128) GetProtocolOverhead() int {
return 9
}
func (a *authAES128) SetOverhead(overhead int) {
a.Overhead = overhead
}
func (a *authAES128) Decode(b []byte) ([]byte, int, error) {
a.buffer.Reset()
bSize := len(b)
readSize := 0
key := pool.Get(len(a.userKey) + 4)
defer pool.Put(key)
copy(key, a.userKey)
for bSize > 4 {
binary.LittleEndian.PutUint32(key[len(key)-4:], a.recvID)
h := a.hmac(key, b[:2])
if !bytes.Equal(h[:2], b[2:4]) {
return nil, 0, errAuthAES128IncorrectMAC
}
length := int(binary.LittleEndian.Uint16(b[:2]))
if length >= 8192 || length < 8 {
return nil, 0, errAuthAES128DataLengthError
}
if length > bSize {
break
}
h = a.hmac(key, b[:length-4])
if !bytes.Equal(h[:4], b[length-4:length]) {
return nil, 0, errAuthAES128IncorrectChecksum
}
a.recvID++
pos := int(b[4])
if pos < 255 {
pos += 4
} else {
pos = int(binary.LittleEndian.Uint16(b[5:7])) + 4
}
if pos > length-4 {
return nil, 0, errAuthAES128PositionTooLarge
}
a.buffer.Write(b[pos : length-4])
b = b[length:]
bSize -= length
readSize += length
}
return a.buffer.Bytes(), readSize, nil
}
func (a *authAES128) Encode(b []byte) ([]byte, error) {
a.buffer.Reset()
bSize := len(b)
offset := 0
if bSize > 0 && !a.hasSentHeader {
authSize := bSize
if authSize > 1200 {
authSize = 1200
}
a.hasSentHeader = true
a.buffer.Write(a.packAuthData(b[:authSize]))
bSize -= authSize
offset += authSize
}
const blockSize = 4096
for bSize > blockSize {
packSize, randSize := a.packedDataSize(b[offset : offset+blockSize])
pack := pool.Get(packSize)
a.packData(b[offset:offset+blockSize], pack, randSize)
a.buffer.Write(pack)
pool.Put(pack)
bSize -= blockSize
offset += blockSize
}
if bSize > 0 {
packSize, randSize := a.packedDataSize(b[offset:])
pack := pool.Get(packSize)
a.packData(b[offset:], pack, randSize)
a.buffer.Write(pack)
pool.Put(pack)
}
return a.buffer.Bytes(), nil
}
func (a *authAES128) DecodePacket(b []byte) ([]byte, int, error) {
bSize := len(b)
h := a.hmac(a.Key, b[:bSize-4])
if !bytes.Equal(h[:4], b[bSize-4:]) {
return nil, 0, errAuthAES128IncorrectMAC
}
return b[:bSize-4], bSize - 4, nil
}
func (a *authAES128) EncodePacket(b []byte) ([]byte, error) {
a.initUserKeyAndID()
var buf bytes.Buffer
buf.Write(b)
buf.Write(a.uid[:])
h := a.hmac(a.userKey, buf.Bytes())
buf.Write(h[:4])
return buf.Bytes(), nil
}
func (a *authAES128) initUserKeyAndID() {
if a.userKey == nil {
params := strings.Split(a.Param, ":")
if len(params) >= 2 {
if userID, err := strconv.ParseUint(params[0], 10, 32); err == nil {
binary.LittleEndian.PutUint32(a.uid[:], uint32(userID))
a.userKey = a.hashDigest([]byte(params[1]))
}
}
if a.userKey == nil {
rand.Read(a.uid[:])
a.userKey = make([]byte, len(a.Key))
copy(a.userKey, a.Key)
}
}
}
func (a *authAES128) packedDataSize(data []byte) (packSize, randSize int) {
dataSize := len(data)
randSize = 1
if dataSize <= 1200 {
if a.packID > 4 {
randSize += rand.Intn(32)
} else {
if dataSize > 900 {
randSize += rand.Intn(128)
} else {
randSize += rand.Intn(512)
}
}
}
packSize = randSize + dataSize + 8
return
}
func (a *authAES128) packData(data, ret []byte, randSize int) {
dataSize := len(data)
retSize := len(ret)
// 0~1, ret_size
binary.LittleEndian.PutUint16(ret[0:], uint16(retSize&0xFFFF))
// 2~3, hmac
key := pool.Get(len(a.userKey) + 4)
defer pool.Put(key)
copy(key, a.userKey)
binary.LittleEndian.PutUint32(key[len(key)-4:], a.packID)
h := a.hmac(key, ret[:2])
copy(ret[2:4], h[:2])
// 4~rand_size+4, rand number
rand.Read(ret[4 : 4+randSize])
// 4, rand_size
if randSize < 128 {
ret[4] = byte(randSize & 0xFF)
} else {
// 4, magic number 0xFF
ret[4] = 0xFF
// 5~6, rand_size
binary.LittleEndian.PutUint16(ret[5:], uint16(randSize&0xFFFF))
}
// rand_size+4~ret_size-4, data
if dataSize > 0 {
copy(ret[randSize+4:], data)
}
a.packID++
h = a.hmac(key, ret[:retSize-4])
copy(ret[retSize-4:], h[:4])
}
func (a *authAES128) packAuthData(data []byte) (ret []byte) {
dataSize := len(data)
var randSize int
if dataSize > 400 {
randSize = rand.Intn(512)
} else {
randSize = rand.Intn(1024)
}
dataOffset := randSize + 16 + 4 + 4 + 7
retSize := dataOffset + dataSize + 4
ret = make([]byte, retSize)
encrypt := make([]byte, 24)
key := make([]byte, len(a.IV)+len(a.Key))
copy(key, a.IV)
copy(key[len(a.IV):], a.Key)
rand.Read(ret[dataOffset-randSize:])
a.mutex.Lock()
defer a.mutex.Unlock()
a.connectionID++
if a.connectionID > 0xFF000000 {
a.clientID = nil
}
if len(a.clientID) == 0 {
a.clientID = make([]byte, 8)
rand.Read(a.clientID)
b := make([]byte, 4)
rand.Read(b)
a.connectionID = binary.LittleEndian.Uint32(b) & 0xFFFFFF
}
copy(encrypt[4:], a.clientID)
binary.LittleEndian.PutUint32(encrypt[8:], a.connectionID)
now := time.Now().Unix()
binary.LittleEndian.PutUint32(encrypt[:4], uint32(now))
binary.LittleEndian.PutUint16(encrypt[12:], uint16(retSize&0xFFFF))
binary.LittleEndian.PutUint16(encrypt[14:], uint16(randSize&0xFFFF))
a.initUserKeyAndID()
aesCipherKey := core.Kdf(base64.StdEncoding.EncodeToString(a.userKey)+a.salt, 16)
block, err := aes.NewCipher(aesCipherKey)
if err != nil {
return nil
}
encryptData := make([]byte, 16)
iv := make([]byte, aes.BlockSize)
cbc := cipher.NewCBCEncrypter(block, iv)
cbc.CryptBlocks(encryptData, encrypt[:16])
copy(encrypt[:4], a.uid[:])
copy(encrypt[4:4+16], encryptData)
h := a.hmac(key, encrypt[:20])
copy(encrypt[20:], h[:4])
rand.Read(ret[:1])
h = a.hmac(key, ret[:1])
copy(ret[1:], h[:7-1])
copy(ret[7:], encrypt)
copy(ret[dataOffset:], data)
h = a.hmac(a.userKey, ret[:retSize-4])
copy(ret[retSize-4:], h[:4])
return
}

View File

@ -1,22 +0,0 @@
package protocol
import (
"bytes"
"github.com/Dreamacro/clash/component/ssr/tools"
)
func init() {
register("auth_aes128_sha1", newAuthAES128SHA1)
}
func newAuthAES128SHA1(b *Base) Protocol {
return &authAES128{
Base: b,
recvInfo: &recvInfo{buffer: new(bytes.Buffer)},
authData: &authData{},
salt: "auth_aes128_sha1",
hmac: tools.HmacSHA1,
hashDigest: tools.SHA1Sum,
}
}

View File

@ -1,427 +0,0 @@
package protocol
import (
"bytes"
"crypto/aes"
"crypto/cipher"
"crypto/rc4"
"encoding/base64"
"encoding/binary"
"math/rand"
"strconv"
"strings"
"time"
"github.com/Dreamacro/clash/common/pool"
"github.com/Dreamacro/clash/component/ssr/tools"
"github.com/Dreamacro/go-shadowsocks2/core"
)
type authChain struct {
*Base
*recvInfo
*authData
randomClient shift128PlusContext
randomServer shift128PlusContext
enc cipher.Stream
dec cipher.Stream
headerSent bool
lastClientHash []byte
lastServerHash []byte
userKey []byte
uid [4]byte
salt string
hmac hmacMethod
hashDigest hashDigestMethod
rnd rndMethod
dataSizeList []int
dataSizeList2 []int
chunkID uint32
}
func init() {
register("auth_chain_a", newAuthChainA)
}
func newAuthChainA(b *Base) Protocol {
return &authChain{
Base: b,
authData: &authData{},
salt: "auth_chain_a",
hmac: tools.HmacMD5,
hashDigest: tools.SHA1Sum,
rnd: authChainAGetRandLen,
}
}
func (a *authChain) initForConn(iv []byte) Protocol {
r := &authChain{
Base: &Base{
IV: iv,
Key: a.Key,
TCPMss: a.TCPMss,
Overhead: a.Overhead,
Param: a.Param,
},
recvInfo: &recvInfo{recvID: 1, buffer: new(bytes.Buffer)},
authData: a.authData,
salt: a.salt,
hmac: a.hmac,
hashDigest: a.hashDigest,
rnd: a.rnd,
}
if r.salt == "auth_chain_b" {
initDataSize(r)
}
return r
}
func (a *authChain) GetProtocolOverhead() int {
return 4
}
func (a *authChain) SetOverhead(overhead int) {
a.Overhead = overhead
}
func (a *authChain) Decode(b []byte) ([]byte, int, error) {
a.buffer.Reset()
key := pool.Get(len(a.userKey) + 4)
defer pool.Put(key)
readSize := 0
copy(key, a.userKey)
for len(b) > 4 {
binary.LittleEndian.PutUint32(key[len(a.userKey):], a.recvID)
dataLen := (int)((uint(b[1]^a.lastServerHash[15]) << 8) + uint(b[0]^a.lastServerHash[14]))
randLen := a.getServerRandLen(dataLen, a.Overhead)
length := randLen + dataLen
if length >= 4096 {
return nil, 0, errAuthChainDataLengthError
}
length += 4
if length > len(b) {
break
}
hash := a.hmac(key, b[:length-2])
if !bytes.Equal(hash[:2], b[length-2:length]) {
return nil, 0, errAuthChainHMACError
}
var dataPos int
if dataLen > 0 && randLen > 0 {
dataPos = 2 + getRandStartPos(&a.randomServer, randLen)
} else {
dataPos = 2
}
d := pool.Get(dataLen)
a.dec.XORKeyStream(d, b[dataPos:dataPos+dataLen])
a.buffer.Write(d)
pool.Put(d)
if a.recvID == 1 {
a.TCPMss = int(binary.LittleEndian.Uint16(a.buffer.Next(2)))
}
a.lastServerHash = hash
a.recvID++
b = b[length:]
readSize += length
}
return a.buffer.Bytes(), readSize, nil
}
func (a *authChain) Encode(b []byte) ([]byte, error) {
a.buffer.Reset()
bSize := len(b)
offset := 0
if bSize > 0 && !a.headerSent {
headSize := 1200
if headSize > bSize {
headSize = bSize
}
a.buffer.Write(a.packAuthData(b[:headSize]))
offset += headSize
bSize -= headSize
a.headerSent = true
}
var unitSize = a.TCPMss - a.Overhead
for bSize > unitSize {
dataLen, randLength := a.packedDataLen(b[offset : offset+unitSize])
d := pool.Get(dataLen)
a.packData(d, b[offset:offset+unitSize], randLength)
a.buffer.Write(d)
pool.Put(d)
bSize -= unitSize
offset += unitSize
}
if bSize > 0 {
dataLen, randLength := a.packedDataLen(b[offset:])
d := pool.Get(dataLen)
a.packData(d, b[offset:], randLength)
a.buffer.Write(d)
pool.Put(d)
}
return a.buffer.Bytes(), nil
}
func (a *authChain) DecodePacket(b []byte) ([]byte, int, error) {
bSize := len(b)
if bSize < 9 {
return nil, 0, errAuthChainDataLengthError
}
h := a.hmac(a.userKey, b[:bSize-1])
if h[0] != b[bSize-1] {
return nil, 0, errAuthChainHMACError
}
hash := a.hmac(a.Key, b[bSize-8:bSize-1])
cipherKey := a.getRC4CipherKey(hash)
dec, _ := rc4.NewCipher(cipherKey)
randLength := udpGetRandLen(&a.randomServer, hash)
bSize -= 8 + randLength
dec.XORKeyStream(b, b[:bSize])
return b, bSize, nil
}
func (a *authChain) EncodePacket(b []byte) ([]byte, error) {
a.initUserKeyAndID()
authData := pool.Get(3)
defer pool.Put(authData)
rand.Read(authData)
hash := a.hmac(a.Key, authData)
uid := pool.Get(4)
defer pool.Put(uid)
for i := 0; i < 4; i++ {
uid[i] = a.uid[i] ^ hash[i]
}
cipherKey := a.getRC4CipherKey(hash)
enc, _ := rc4.NewCipher(cipherKey)
var buf bytes.Buffer
enc.XORKeyStream(b, b)
buf.Write(b)
randLength := udpGetRandLen(&a.randomClient, hash)
randBytes := pool.Get(randLength)
defer pool.Put(randBytes)
buf.Write(randBytes)
buf.Write(authData)
buf.Write(uid)
h := a.hmac(a.userKey, buf.Bytes())
buf.Write(h[:1])
return buf.Bytes(), nil
}
func (a *authChain) getRC4CipherKey(hash []byte) []byte {
base64UserKey := base64.StdEncoding.EncodeToString(a.userKey)
return a.calcRC4CipherKey(hash, base64UserKey)
}
func (a *authChain) calcRC4CipherKey(hash []byte, base64UserKey string) []byte {
password := pool.Get(len(base64UserKey) + base64.StdEncoding.EncodedLen(16))
defer pool.Put(password)
copy(password, base64UserKey)
base64.StdEncoding.Encode(password[len(base64UserKey):], hash[:16])
return core.Kdf(string(password), 16)
}
func (a *authChain) initUserKeyAndID() {
if a.userKey == nil {
params := strings.Split(a.Param, ":")
if len(params) >= 2 {
if userID, err := strconv.ParseUint(params[0], 10, 32); err == nil {
binary.LittleEndian.PutUint32(a.uid[:], uint32(userID))
a.userKey = []byte(params[1])[:len(a.userKey)]
}
}
if a.userKey == nil {
rand.Read(a.uid[:])
a.userKey = make([]byte, len(a.Key))
copy(a.userKey, a.Key)
}
}
}
func (a *authChain) getClientRandLen(dataLength int, overhead int) int {
return a.rnd(dataLength, &a.randomClient, a.lastClientHash, a.dataSizeList, a.dataSizeList2, overhead)
}
func (a *authChain) getServerRandLen(dataLength int, overhead int) int {
return a.rnd(dataLength, &a.randomServer, a.lastServerHash, a.dataSizeList, a.dataSizeList2, overhead)
}
func (a *authChain) packedDataLen(data []byte) (chunkLength, randLength int) {
dataLength := len(data)
randLength = a.getClientRandLen(dataLength, a.Overhead)
chunkLength = randLength + dataLength + 2 + 2
return
}
func (a *authChain) packData(outData []byte, data []byte, randLength int) {
dataLength := len(data)
outLength := randLength + dataLength + 2
outData[0] = byte(dataLength) ^ a.lastClientHash[14]
outData[1] = byte(dataLength>>8) ^ a.lastClientHash[15]
{
if dataLength > 0 {
randPart1Length := getRandStartPos(&a.randomClient, randLength)
rand.Read(outData[2 : 2+randPart1Length])
a.enc.XORKeyStream(outData[2+randPart1Length:], data)
rand.Read(outData[2+randPart1Length+dataLength : outLength])
} else {
rand.Read(outData[2 : 2+randLength])
}
}
userKeyLen := uint8(len(a.userKey))
key := pool.Get(int(userKeyLen + 4))
defer pool.Put(key)
copy(key, a.userKey)
a.chunkID++
binary.LittleEndian.PutUint32(key[userKeyLen:], a.chunkID)
a.lastClientHash = a.hmac(key, outData[:outLength])
copy(outData[outLength:], a.lastClientHash[:2])
}
const authHeadLength = 4 + 8 + 4 + 16 + 4
func (a *authChain) packAuthData(data []byte) (outData []byte) {
outData = make([]byte, authHeadLength, authHeadLength+1500)
a.mutex.Lock()
defer a.mutex.Unlock()
a.connectionID++
if a.connectionID > 0xFF000000 {
rand.Read(a.clientID)
b := make([]byte, 4)
rand.Read(b)
a.connectionID = binary.LittleEndian.Uint32(b) & 0xFFFFFF
}
var key = make([]byte, len(a.IV)+len(a.Key))
copy(key, a.IV)
copy(key[len(a.IV):], a.Key)
encrypt := make([]byte, 20)
t := time.Now().Unix()
binary.LittleEndian.PutUint32(encrypt[:4], uint32(t))
copy(encrypt[4:8], a.clientID)
binary.LittleEndian.PutUint32(encrypt[8:], a.connectionID)
binary.LittleEndian.PutUint16(encrypt[12:], uint16(a.Overhead))
binary.LittleEndian.PutUint16(encrypt[14:], 0)
// first 12 bytes
{
rand.Read(outData[:4])
a.lastClientHash = a.hmac(key, outData[:4])
copy(outData[4:], a.lastClientHash[:8])
}
var base64UserKey string
// uid & 16 bytes auth data
{
a.initUserKeyAndID()
uid := make([]byte, 4)
for i := 0; i < 4; i++ {
uid[i] = a.uid[i] ^ a.lastClientHash[8+i]
}
base64UserKey = base64.StdEncoding.EncodeToString(a.userKey)
aesCipherKey := core.Kdf(base64UserKey+a.salt, 16)
block, err := aes.NewCipher(aesCipherKey)
if err != nil {
return
}
encryptData := make([]byte, 16)
iv := make([]byte, aes.BlockSize)
cbc := cipher.NewCBCEncrypter(block, iv)
cbc.CryptBlocks(encryptData, encrypt[:16])
copy(encrypt[:4], uid[:])
copy(encrypt[4:4+16], encryptData)
}
// final HMAC
{
a.lastServerHash = a.hmac(a.userKey, encrypt[:20])
copy(outData[12:], encrypt)
copy(outData[12+20:], a.lastServerHash[:4])
}
// init cipher
cipherKey := a.calcRC4CipherKey(a.lastClientHash, base64UserKey)
a.enc, _ = rc4.NewCipher(cipherKey)
a.dec, _ = rc4.NewCipher(cipherKey)
// data
chunkLength, randLength := a.packedDataLen(data)
if chunkLength <= 1500 {
outData = outData[:authHeadLength+chunkLength]
} else {
newOutData := make([]byte, authHeadLength+chunkLength)
copy(newOutData, outData[:authHeadLength])
outData = newOutData
}
a.packData(outData[authHeadLength:], data, randLength)
return
}
func getRandStartPos(random *shift128PlusContext, randLength int) int {
if randLength > 0 {
return int(random.Next() % 8589934609 % uint64(randLength))
}
return 0
}
func authChainAGetRandLen(dataLength int, random *shift128PlusContext, lastHash []byte, dataSizeList, dataSizeList2 []int, overhead int) int {
if dataLength > 1440 {
return 0
}
random.InitFromBinDatalen(lastHash[:16], dataLength)
if dataLength > 1300 {
return int(random.Next() % 31)
}
if dataLength > 900 {
return int(random.Next() % 127)
}
if dataLength > 400 {
return int(random.Next() % 521)
}
return int(random.Next() % 1021)
}
func udpGetRandLen(random *shift128PlusContext, lastHash []byte) int {
random.InitFromBin(lastHash[:16])
return int(random.Next() % 127)
}
type shift128PlusContext struct {
v [2]uint64
}
func (ctx *shift128PlusContext) InitFromBin(bin []byte) {
var fillBin [16]byte
copy(fillBin[:], bin)
ctx.v[0] = binary.LittleEndian.Uint64(fillBin[:8])
ctx.v[1] = binary.LittleEndian.Uint64(fillBin[8:])
}
func (ctx *shift128PlusContext) InitFromBinDatalen(bin []byte, datalen int) {
var fillBin [16]byte
copy(fillBin[:], bin)
binary.LittleEndian.PutUint16(fillBin[:2], uint16(datalen))
ctx.v[0] = binary.LittleEndian.Uint64(fillBin[:8])
ctx.v[1] = binary.LittleEndian.Uint64(fillBin[8:])
for i := 0; i < 4; i++ {
ctx.Next()
}
}
func (ctx *shift128PlusContext) Next() uint64 {
x := ctx.v[0]
y := ctx.v[1]
ctx.v[0] = y
x ^= x << 23
x ^= y ^ (x >> 17) ^ (y >> 26)
ctx.v[1] = x
return x + y
}

View File

@ -1,72 +0,0 @@
package protocol
import (
"sort"
"github.com/Dreamacro/clash/component/ssr/tools"
)
func init() {
register("auth_chain_b", newAuthChainB)
}
func newAuthChainB(b *Base) Protocol {
return &authChain{
Base: b,
authData: &authData{},
salt: "auth_chain_b",
hmac: tools.HmacMD5,
hashDigest: tools.SHA1Sum,
rnd: authChainBGetRandLen,
}
}
func initDataSize(r *authChain) {
random := &r.randomServer
random.InitFromBin(r.Key)
len := random.Next()%8 + 4
r.dataSizeList = make([]int, len)
for i := 0; i < int(len); i++ {
r.dataSizeList[i] = int(random.Next() % 2340 % 2040 % 1440)
}
sort.Ints(r.dataSizeList)
len = random.Next()%16 + 8
r.dataSizeList2 = make([]int, len)
for i := 0; i < int(len); i++ {
r.dataSizeList2[i] = int(random.Next() % 2340 % 2040 % 1440)
}
sort.Ints(r.dataSizeList2)
}
func authChainBGetRandLen(dataLength int, random *shift128PlusContext, lastHash []byte, dataSizeList, dataSizeList2 []int, overhead int) int {
if dataLength > 1440 {
return 0
}
random.InitFromBinDatalen(lastHash[:16], dataLength)
pos := sort.Search(len(dataSizeList), func(i int) bool { return dataSizeList[i] > dataLength+overhead })
finalPos := uint64(pos) + random.Next()%uint64(len(dataSizeList))
if finalPos < uint64(len(dataSizeList)) {
return dataSizeList[finalPos] - dataLength - overhead
}
pos = sort.Search(len(dataSizeList2), func(i int) bool { return dataSizeList2[i] > dataLength+overhead })
finalPos = uint64(pos) + random.Next()%uint64(len(dataSizeList2))
if finalPos < uint64(len(dataSizeList2)) {
return dataSizeList2[finalPos] - dataLength - overhead
}
if finalPos < uint64(pos+len(dataSizeList2)-1) {
return 0
}
if dataLength > 1300 {
return int(random.Next() % 31)
}
if dataLength > 900 {
return int(random.Next() % 127)
}
if dataLength > 400 {
return int(random.Next() % 521)
}
return int(random.Next() % 1021)
}

View File

@ -1,253 +0,0 @@
package protocol
import (
"bytes"
"encoding/binary"
"hash/adler32"
"hash/crc32"
"math/rand"
"time"
"github.com/Dreamacro/clash/common/pool"
"github.com/Dreamacro/clash/component/ssr/tools"
)
type authSHA1V4 struct {
*Base
*authData
headerSent bool
buffer bytes.Buffer
}
func init() {
register("auth_sha1_v4", newAuthSHA1V4)
}
func newAuthSHA1V4(b *Base) Protocol {
return &authSHA1V4{Base: b, authData: &authData{}}
}
func (a *authSHA1V4) initForConn(iv []byte) Protocol {
return &authSHA1V4{
Base: &Base{
IV: iv,
Key: a.Key,
TCPMss: a.TCPMss,
Overhead: a.Overhead,
Param: a.Param,
},
authData: a.authData,
}
}
func (a *authSHA1V4) GetProtocolOverhead() int {
return 7
}
func (a *authSHA1V4) SetOverhead(overhead int) {
a.Overhead = overhead
}
func (a *authSHA1V4) Decode(b []byte) ([]byte, int, error) {
a.buffer.Reset()
bSize := len(b)
originalSize := bSize
for bSize > 4 {
crc := crc32.ChecksumIEEE(b[:2]) & 0xFFFF
if binary.LittleEndian.Uint16(b[2:4]) != uint16(crc) {
return nil, 0, errAuthSHA1v4CRC32Error
}
length := int(binary.BigEndian.Uint16(b[:2]))
if length >= 8192 || length < 8 {
return nil, 0, errAuthSHA1v4DataLengthError
}
if length > bSize {
break
}
if adler32.Checksum(b[:length-4]) == binary.LittleEndian.Uint32(b[length-4:]) {
pos := int(b[4])
if pos != 0xFF {
pos += 4
} else {
pos = int(binary.BigEndian.Uint16(b[5:5+2])) + 4
}
retSize := length - pos - 4
a.buffer.Write(b[pos : pos+retSize])
bSize -= length
b = b[length:]
} else {
return nil, 0, errAuthSHA1v4IncorrectChecksum
}
}
return a.buffer.Bytes(), originalSize - bSize, nil
}
func (a *authSHA1V4) Encode(b []byte) ([]byte, error) {
a.buffer.Reset()
bSize := len(b)
offset := 0
if !a.headerSent && bSize > 0 {
headSize := getHeadSize(b, 30)
if headSize > bSize {
headSize = bSize
}
a.buffer.Write(a.packAuthData(b[:headSize]))
offset += headSize
bSize -= headSize
a.headerSent = true
}
const blockSize = 4096
for bSize > blockSize {
packSize, randSize := a.packedDataSize(b[offset : offset+blockSize])
pack := pool.Get(packSize)
a.packData(b[offset:offset+blockSize], pack, randSize)
a.buffer.Write(pack)
pool.Put(pack)
offset += blockSize
bSize -= blockSize
}
if bSize > 0 {
packSize, randSize := a.packedDataSize(b[offset:])
pack := pool.Get(packSize)
a.packData(b[offset:], pack, randSize)
a.buffer.Write(pack)
pool.Put(pack)
}
return a.buffer.Bytes(), nil
}
func (a *authSHA1V4) DecodePacket(b []byte) ([]byte, int, error) {
return b, len(b), nil
}
func (a *authSHA1V4) EncodePacket(b []byte) ([]byte, error) {
return b, nil
}
func (a *authSHA1V4) packedDataSize(data []byte) (packSize, randSize int) {
dataSize := len(data)
randSize = 1
if dataSize <= 1300 {
if dataSize > 400 {
randSize += rand.Intn(128)
} else {
randSize += rand.Intn(1024)
}
}
packSize = randSize + dataSize + 8
return
}
func (a *authSHA1V4) packData(data, ret []byte, randSize int) {
dataSize := len(data)
retSize := len(ret)
// 0~1, ret size
binary.BigEndian.PutUint16(ret[:2], uint16(retSize&0xFFFF))
// 2~3, crc of ret size
crc := crc32.ChecksumIEEE(ret[:2]) & 0xFFFF
binary.LittleEndian.PutUint16(ret[2:4], uint16(crc))
// 4, rand size
if randSize < 128 {
ret[4] = uint8(randSize & 0xFF)
} else {
ret[4] = uint8(0xFF)
binary.BigEndian.PutUint16(ret[5:7], uint16(randSize&0xFFFF))
}
// (rand size+4)~(ret size-4), data
if dataSize > 0 {
copy(ret[randSize+4:], data)
}
// (ret size-4)~end, adler32 of full data
adler := adler32.Checksum(ret[:retSize-4])
binary.LittleEndian.PutUint32(ret[retSize-4:], adler)
}
func (a *authSHA1V4) packAuthData(data []byte) (ret []byte) {
dataSize := len(data)
randSize := 1
if dataSize <= 1300 {
if dataSize > 400 {
randSize += rand.Intn(128)
} else {
randSize += rand.Intn(1024)
}
}
dataOffset := randSize + 4 + 2
retSize := dataOffset + dataSize + 12 + tools.HmacSHA1Len
ret = make([]byte, retSize)
a.mutex.Lock()
defer a.mutex.Unlock()
a.connectionID++
if a.connectionID > 0xFF000000 {
a.clientID = nil
}
if len(a.clientID) == 0 {
a.clientID = make([]byte, 8)
rand.Read(a.clientID)
b := make([]byte, 4)
rand.Read(b)
a.connectionID = binary.LittleEndian.Uint32(b) & 0xFFFFFF
}
// 0~1, ret size
binary.BigEndian.PutUint16(ret[:2], uint16(retSize&0xFFFF))
// 2~6, crc of (ret size+salt+key)
salt := []byte("auth_sha1_v4")
crcData := make([]byte, len(salt)+len(a.Key)+2)
copy(crcData[:2], ret[:2])
copy(crcData[2:], salt)
copy(crcData[2+len(salt):], a.Key)
crc := crc32.ChecksumIEEE(crcData) & 0xFFFFFFFF
// 2~6, crc of (ret size+salt+key)
binary.LittleEndian.PutUint32(ret[2:], crc)
// 6~(rand size+6), rand numbers
rand.Read(ret[dataOffset-randSize : dataOffset])
// 6, rand size
if randSize < 128 {
ret[6] = byte(randSize & 0xFF)
} else {
// 6, magic number 0xFF
ret[6] = 0xFF
// 7~8, rand size
binary.BigEndian.PutUint16(ret[7:9], uint16(randSize&0xFFFF))
}
// rand size+6~(rand size+10), time stamp
now := time.Now().Unix()
binary.LittleEndian.PutUint32(ret[dataOffset:dataOffset+4], uint32(now))
// rand size+10~(rand size+14), client ID
copy(ret[dataOffset+4:dataOffset+4+4], a.clientID[:4])
// rand size+14~(rand size+18), connection ID
binary.LittleEndian.PutUint32(ret[dataOffset+8:dataOffset+8+4], a.connectionID)
// rand size+18~(rand size+18)+data length, data
copy(ret[dataOffset+12:], data)
key := make([]byte, len(a.IV)+len(a.Key))
copy(key, a.IV)
copy(key[len(a.IV):], a.Key)
h := tools.HmacSHA1(key, ret[:retSize-tools.HmacSHA1Len])
// (ret size-10)~(ret size)/(rand size)+18+data length~end, hmac
copy(ret[retSize-tools.HmacSHA1Len:], h[:tools.HmacSHA1Len])
return ret
}
func getHeadSize(data []byte, defaultValue int) int {
if data == nil || len(data) < 2 {
return defaultValue
}
headType := data[0] & 0x07
switch headType {
case 1:
// IPv4 1+4+2
return 7
case 4:
// IPv6 1+16+2
return 19
case 3:
// domain name, variant length
return 4 + int(data[1])
}
return defaultValue
}

View File

@ -1,10 +0,0 @@
package protocol
// Base information for protocol
type Base struct {
IV []byte
Key []byte
TCPMss int
Overhead int
Param string
}

View File

@ -1,36 +0,0 @@
package protocol
type origin struct{ *Base }
func init() {
register("origin", newOrigin)
}
func newOrigin(b *Base) Protocol {
return &origin{}
}
func (o *origin) initForConn(iv []byte) Protocol { return &origin{} }
func (o *origin) GetProtocolOverhead() int {
return 0
}
func (o *origin) SetOverhead(overhead int) {
}
func (o *origin) Decode(b []byte) ([]byte, int, error) {
return b, len(b), nil
}
func (o *origin) Encode(b []byte) ([]byte, error) {
return b, nil
}
func (o *origin) DecodePacket(b []byte) ([]byte, int, error) {
return b, len(b), nil
}
func (o *origin) EncodePacket(b []byte) ([]byte, error) {
return b, nil
}

View File

@ -1,42 +0,0 @@
package protocol
import (
"net"
"github.com/Dreamacro/clash/common/pool"
)
// NewPacketConn returns a net.NewPacketConn with protocol decoding/encoding
func NewPacketConn(pc net.PacketConn, p Protocol) net.PacketConn {
return &PacketConn{PacketConn: pc, Protocol: p.initForConn(nil)}
}
// PacketConn represents a protocol packet connection
type PacketConn struct {
net.PacketConn
Protocol
}
func (c *PacketConn) WriteTo(b []byte, addr net.Addr) (int, error) {
buf := pool.Get(pool.RelayBufferSize)
defer pool.Put(buf)
buf, err := c.EncodePacket(b)
if err != nil {
return 0, err
}
_, err = c.PacketConn.WriteTo(buf, addr)
return len(b), err
}
func (c *PacketConn) ReadFrom(b []byte) (int, net.Addr, error) {
n, addr, err := c.PacketConn.ReadFrom(b)
if err != nil {
return n, addr, err
}
bb, length, err := c.DecodePacket(b[:n])
if err != nil {
return n, addr, err
}
copy(b, bb)
return length, addr, err
}

View File

@ -1,63 +0,0 @@
package protocol
import (
"bytes"
"errors"
"fmt"
"strings"
"sync"
)
var (
errAuthAES128IncorrectMAC = errors.New("auth_aes128_* post decrypt incorrect mac")
errAuthAES128DataLengthError = errors.New("auth_aes128_* post decrypt length mismatch")
errAuthAES128IncorrectChecksum = errors.New("auth_aes128_* post decrypt incorrect checksum")
errAuthAES128PositionTooLarge = errors.New("auth_aes128_* post decrypt position is too large")
errAuthSHA1v4CRC32Error = errors.New("auth_sha1_v4 post decrypt data crc32 error")
errAuthSHA1v4DataLengthError = errors.New("auth_sha1_v4 post decrypt data length error")
errAuthSHA1v4IncorrectChecksum = errors.New("auth_sha1_v4 post decrypt incorrect checksum")
errAuthChainDataLengthError = errors.New("auth_chain_* post decrypt length mismatch")
errAuthChainHMACError = errors.New("auth_chain_* post decrypt hmac error")
)
type authData struct {
clientID []byte
connectionID uint32
mutex sync.Mutex
}
type recvInfo struct {
recvID uint32
buffer *bytes.Buffer
}
type hmacMethod func(key []byte, data []byte) []byte
type hashDigestMethod func(data []byte) []byte
type rndMethod func(dataSize int, random *shift128PlusContext, lastHash []byte, dataSizeList, dataSizeList2 []int, overhead int) int
// Protocol provides methods for decoding, encoding and iv setting
type Protocol interface {
initForConn(iv []byte) Protocol
GetProtocolOverhead() int
SetOverhead(int)
Decode([]byte) ([]byte, int, error)
Encode([]byte) ([]byte, error)
DecodePacket([]byte) ([]byte, int, error)
EncodePacket([]byte) ([]byte, error)
}
type protocolCreator func(b *Base) Protocol
var protocolList = make(map[string]protocolCreator)
func register(name string, c protocolCreator) {
protocolList[name] = c
}
// PickProtocol returns a protocol of the given name
func PickProtocol(name string, b *Base) (Protocol, error) {
if protocolCreator, ok := protocolList[strings.ToLower(name)]; ok {
return protocolCreator(b), nil
}
return nil, fmt.Errorf("Protocol %s not supported", name)
}

View File

@ -1,68 +0,0 @@
package protocol
import (
"bytes"
"net"
"github.com/Dreamacro/clash/common/pool"
)
// NewConn wraps a stream-oriented net.Conn with protocol decoding/encoding
func NewConn(c net.Conn, p Protocol, iv []byte) net.Conn {
return &Conn{Conn: c, Protocol: p.initForConn(iv)}
}
// Conn represents a protocol connection
type Conn struct {
net.Conn
Protocol
buf []byte
offset int
underDecoded bytes.Buffer
}
func (c *Conn) Read(b []byte) (int, error) {
if c.buf != nil {
n := copy(b, c.buf[c.offset:])
c.offset += n
if c.offset == len(c.buf) {
c.buf = nil
}
return n, nil
}
buf := pool.Get(pool.RelayBufferSize)
defer pool.Put(buf)
n, err := c.Conn.Read(buf)
if err != nil {
return 0, err
}
c.underDecoded.Write(buf[:n])
underDecoded := c.underDecoded.Bytes()
decoded, length, err := c.Decode(underDecoded)
if err != nil {
c.underDecoded.Reset()
return 0, nil
}
if length == 0 {
return 0, nil
}
c.underDecoded.Next(length)
n = copy(b, decoded)
if len(decoded) > len(b) {
c.buf = decoded
c.offset = n
}
return n, nil
}
func (c *Conn) Write(b []byte) (int, error) {
encoded, err := c.Encode(b)
if err != nil {
return 0, err
}
_, err = c.Conn.Write(encoded)
if err != nil {
return 0, err
}
return len(b), nil
}

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,16 +8,18 @@ import (
"os" "os"
"strings" "strings"
"github.com/Dreamacro/clash/adapters/outbound" "github.com/Dreamacro/clash/adapter"
"github.com/Dreamacro/clash/adapters/outboundgroup" "github.com/Dreamacro/clash/adapter/outbound"
"github.com/Dreamacro/clash/adapters/provider" "github.com/Dreamacro/clash/adapter/outboundgroup"
"github.com/Dreamacro/clash/adapter/provider"
"github.com/Dreamacro/clash/component/auth" "github.com/Dreamacro/clash/component/auth"
"github.com/Dreamacro/clash/component/fakeip" "github.com/Dreamacro/clash/component/fakeip"
"github.com/Dreamacro/clash/component/trie" "github.com/Dreamacro/clash/component/trie"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
providerTypes "github.com/Dreamacro/clash/constant/provider"
"github.com/Dreamacro/clash/dns" "github.com/Dreamacro/clash/dns"
"github.com/Dreamacro/clash/log" "github.com/Dreamacro/clash/log"
R "github.com/Dreamacro/clash/rules" R "github.com/Dreamacro/clash/rule"
T "github.com/Dreamacro/clash/tunnel" T "github.com/Dreamacro/clash/tunnel"
yaml "gopkg.in/yaml.v2" yaml "gopkg.in/yaml.v2"
@ -30,7 +32,7 @@ type General struct {
Mode T.TunnelMode `json:"mode"` Mode T.TunnelMode `json:"mode"`
LogLevel log.LogLevel `json:"log-level"` LogLevel log.LogLevel `json:"log-level"`
IPv6 bool `json:"ipv6"` IPv6 bool `json:"ipv6"`
Interface string `json:"interface-name"` Interface string `json:"-"`
} }
// Inbound // Inbound
@ -64,13 +66,20 @@ type DNS struct {
DefaultNameserver []dns.NameServer `yaml:"default-nameserver"` DefaultNameserver []dns.NameServer `yaml:"default-nameserver"`
FakeIPRange *fakeip.Pool FakeIPRange *fakeip.Pool
Hosts *trie.DomainTrie Hosts *trie.DomainTrie
NameServerPolicy map[string]dns.NameServer
} }
// FallbackFilter config // FallbackFilter config
type FallbackFilter struct { type FallbackFilter struct {
GeoIP bool `yaml:"geoip"` GeoIP bool `yaml:"geoip"`
IPCIDR []*net.IPNet `yaml:"ipcidr"` GeoIPCode string `yaml:"geoip-code"`
Domain []string `yaml:"domain"` IPCIDR []*net.IPNet `yaml:"ipcidr"`
Domain []string `yaml:"domain"`
}
// Profile config
type Profile struct {
StoreSelected bool `yaml:"store-selected"`
} }
// Experimental config // Experimental config
@ -82,10 +91,11 @@ type Config struct {
DNS *DNS DNS *DNS
Experimental *Experimental Experimental *Experimental
Hosts *trie.DomainTrie Hosts *trie.DomainTrie
Profile *Profile
Rules []C.Rule Rules []C.Rule
Users []auth.AuthUser Users []auth.AuthUser
Proxies map[string]C.Proxy Proxies map[string]C.Proxy
Providers map[string]provider.ProxyProvider Providers map[string]providerTypes.ProxyProvider
} }
type RawDNS struct { type RawDNS struct {
@ -100,12 +110,14 @@ type RawDNS struct {
FakeIPRange string `yaml:"fake-ip-range"` FakeIPRange string `yaml:"fake-ip-range"`
FakeIPFilter []string `yaml:"fake-ip-filter"` FakeIPFilter []string `yaml:"fake-ip-filter"`
DefaultNameserver []string `yaml:"default-nameserver"` DefaultNameserver []string `yaml:"default-nameserver"`
NameServerPolicy map[string]string `yaml:"nameserver-policy"`
} }
type RawFallbackFilter struct { type RawFallbackFilter struct {
GeoIP bool `yaml:"geoip"` GeoIP bool `yaml:"geoip"`
IPCIDR []string `yaml:"ipcidr"` GeoIPCode string `yaml:"geoip-code"`
Domain []string `yaml:"domain"` IPCIDR []string `yaml:"ipcidr"`
Domain []string `yaml:"domain"`
} }
type RawConfig struct { type RawConfig struct {
@ -129,6 +141,7 @@ type RawConfig struct {
Hosts map[string]string `yaml:"hosts"` Hosts map[string]string `yaml:"hosts"`
DNS RawDNS `yaml:"dns"` DNS RawDNS `yaml:"dns"`
Experimental Experimental `yaml:"experimental"` Experimental Experimental `yaml:"experimental"`
Profile Profile `yaml:"profile"`
Proxy []map[string]interface{} `yaml:"proxies"` Proxy []map[string]interface{} `yaml:"proxies"`
ProxyGroup []map[string]interface{} `yaml:"proxy-groups"` ProxyGroup []map[string]interface{} `yaml:"proxy-groups"`
Rule []string `yaml:"rules"` Rule []string `yaml:"rules"`
@ -145,7 +158,7 @@ func Parse(buf []byte) (*Config, error) {
} }
func UnmarshalRawConfig(buf []byte) (*RawConfig, error) { func UnmarshalRawConfig(buf []byte) (*RawConfig, error) {
// config with some default value // config with default value
rawCfg := &RawConfig{ rawCfg := &RawConfig{
AllowLan: false, AllowLan: false,
BindAddress: "*", BindAddress: "*",
@ -161,14 +174,18 @@ func UnmarshalRawConfig(buf []byte) (*RawConfig, error) {
UseHosts: true, UseHosts: true,
FakeIPRange: "198.18.0.1/16", FakeIPRange: "198.18.0.1/16",
FallbackFilter: RawFallbackFilter{ FallbackFilter: RawFallbackFilter{
GeoIP: true, GeoIP: true,
IPCIDR: []string{}, GeoIPCode: "CN",
IPCIDR: []string{},
}, },
DefaultNameserver: []string{ DefaultNameserver: []string{
"114.114.114.114", "114.114.114.114",
"8.8.8.8", "8.8.8.8",
}, },
}, },
Profile: Profile{
StoreSelected: true,
},
} }
if err := yaml.Unmarshal(buf, &rawCfg); err != nil { if err := yaml.Unmarshal(buf, &rawCfg); err != nil {
@ -182,6 +199,7 @@ func ParseRawConfig(rawCfg *RawConfig) (*Config, error) {
config := &Config{} config := &Config{}
config.Experimental = &rawCfg.Experimental config.Experimental = &rawCfg.Experimental
config.Profile = &rawCfg.Profile
general, err := parseGeneral(rawCfg) general, err := parseGeneral(rawCfg)
if err != nil { if err != nil {
@ -253,21 +271,21 @@ func parseGeneral(cfg *RawConfig) (*General, error) {
}, nil }, nil
} }
func parseProxies(cfg *RawConfig) (proxies map[string]C.Proxy, providersMap map[string]provider.ProxyProvider, err error) { func parseProxies(cfg *RawConfig) (proxies map[string]C.Proxy, providersMap map[string]providerTypes.ProxyProvider, err error) {
proxies = make(map[string]C.Proxy) proxies = make(map[string]C.Proxy)
providersMap = make(map[string]provider.ProxyProvider) providersMap = make(map[string]providerTypes.ProxyProvider)
proxyList := []string{} proxyList := []string{}
proxiesConfig := cfg.Proxy proxiesConfig := cfg.Proxy
groupsConfig := cfg.ProxyGroup groupsConfig := cfg.ProxyGroup
providersConfig := cfg.ProxyProvider providersConfig := cfg.ProxyProvider
proxies["DIRECT"] = outbound.NewProxy(outbound.NewDirect()) proxies["DIRECT"] = adapter.NewProxy(outbound.NewDirect())
proxies["REJECT"] = outbound.NewProxy(outbound.NewReject()) proxies["REJECT"] = adapter.NewProxy(outbound.NewReject())
proxyList = append(proxyList, "DIRECT", "REJECT") proxyList = append(proxyList, "DIRECT", "REJECT")
// parse proxy // parse proxy
for idx, mapping := range proxiesConfig { for idx, mapping := range proxiesConfig {
proxy, err := outbound.ParseProxy(mapping) proxy, err := adapter.ParseProxy(mapping)
if err != nil { if err != nil {
return nil, nil, fmt.Errorf("proxy %d: %w", idx, err) return nil, nil, fmt.Errorf("proxy %d: %w", idx, err)
} }
@ -326,12 +344,12 @@ func parseProxies(cfg *RawConfig) (proxies map[string]C.Proxy, providersMap map[
return nil, nil, fmt.Errorf("proxy group %s: the duplicate name", groupName) return nil, nil, fmt.Errorf("proxy group %s: the duplicate name", groupName)
} }
proxies[groupName] = outbound.NewProxy(group) proxies[groupName] = adapter.NewProxy(group)
} }
// initial compatible provider // initial compatible provider
for _, pd := range providersMap { for _, pd := range providersMap {
if pd.VehicleType() != provider.Compatible { if pd.VehicleType() != providerTypes.Compatible {
continue continue
} }
@ -353,9 +371,9 @@ func parseProxies(cfg *RawConfig) (proxies map[string]C.Proxy, providersMap map[
&outboundgroup.GroupCommonOption{ &outboundgroup.GroupCommonOption{
Name: "GLOBAL", Name: "GLOBAL",
}, },
[]provider.ProxyProvider{pd}, []providerTypes.ProxyProvider{pd},
) )
proxies["GLOBAL"] = outbound.NewProxy(global) proxies["GLOBAL"] = adapter.NewProxy(global)
return proxies, providersMap, nil return proxies, providersMap, nil
} }
@ -395,10 +413,6 @@ func parseRules(cfg *RawConfig, proxies map[string]C.Proxy) ([]C.Rule, error) {
parsed, parseErr := R.ParseRule(rule[0], payload, target, params) parsed, parseErr := R.ParseRule(rule[0], payload, target, params)
if parseErr != nil { if parseErr != nil {
if parseErr == R.ErrPlatformNotSupport {
log.Warnln("Rules[%d] [%s] don't support current OS, skip", idx, line)
continue
}
return nil, fmt.Errorf("rules[%d] [%s] error: %s", idx, line, parseErr.Error()) return nil, fmt.Errorf("rules[%d] [%s] error: %s", idx, line, parseErr.Error())
} }
@ -474,6 +488,9 @@ func parseNameServer(servers []string) ([]dns.NameServer, error) {
clearURL := url.URL{Scheme: "https", Host: u.Host, Path: u.Path} clearURL := url.URL{Scheme: "https", Host: u.Host, Path: u.Path}
addr = clearURL.String() addr = clearURL.String()
dnsNetType = "https" // DNS over HTTPS dnsNetType = "https" // DNS over HTTPS
case "dhcp":
addr = u.Host
dnsNetType = "dhcp" // UDP from DHCP
default: default:
return nil, fmt.Errorf("DNS NameServer[%d] unsupport scheme: %s", idx, u.Scheme) return nil, fmt.Errorf("DNS NameServer[%d] unsupport scheme: %s", idx, u.Scheme)
} }
@ -493,6 +510,23 @@ func parseNameServer(servers []string) ([]dns.NameServer, error) {
return nameservers, nil return nameservers, nil
} }
func parseNameServerPolicy(nsPolicy map[string]string) (map[string]dns.NameServer, error) {
policy := map[string]dns.NameServer{}
for domain, server := range nsPolicy {
nameservers, err := parseNameServer([]string{server})
if err != nil {
return nil, err
}
if _, valid := trie.ValidAndSplitDomain(domain); !valid {
return nil, fmt.Errorf("DNS ResoverRule invalid domain: %s", domain)
}
policy[domain] = nameservers[0]
}
return policy, nil
}
func parseFallbackIPCIDR(ips []string) ([]*net.IPNet, error) { func parseFallbackIPCIDR(ips []string) ([]*net.IPNet, error) {
ipNets := []*net.IPNet{} ipNets := []*net.IPNet{}
@ -530,6 +564,10 @@ func parseDNS(cfg RawDNS, hosts *trie.DomainTrie) (*DNS, error) {
return nil, err return nil, err
} }
if dnsCfg.NameServerPolicy, err = parseNameServerPolicy(cfg.NameServerPolicy); err != nil {
return nil, err
}
if len(cfg.DefaultNameserver) == 0 { if len(cfg.DefaultNameserver) == 0 {
return nil, errors.New("default nameserver should have at least one nameserver") return nil, errors.New("default nameserver should have at least one nameserver")
} }
@ -568,6 +606,7 @@ func parseDNS(cfg RawDNS, hosts *trie.DomainTrie) (*DNS, error) {
} }
dnsCfg.FallbackFilter.GeoIP = cfg.FallbackFilter.GeoIP dnsCfg.FallbackFilter.GeoIP = cfg.FallbackFilter.GeoIP
dnsCfg.FallbackFilter.GeoIPCode = cfg.FallbackFilter.GeoIPCode
if fallbackip, err := parseFallbackIPCIDR(cfg.FallbackFilter.IPCIDR); err == nil { if fallbackip, err := parseFallbackIPCIDR(cfg.FallbackFilter.IPCIDR); err == nil {
dnsCfg.FallbackFilter.IPCIDR = fallbackip dnsCfg.FallbackFilter.IPCIDR = fallbackip
} }

View File

@ -12,7 +12,7 @@ import (
) )
func downloadMMDB(path string) (err error) { func downloadMMDB(path string) (err error) {
resp, err := http.Get("https://github.com/Dreamacro/maxmind-geoip/releases/latest/download/Country.mmdb") resp, err := http.Get("https://cdn.jsdelivr.net/gh/Dreamacro/maxmind-geoip@release/Country.mmdb")
if err != nil { if err != nil {
return return
} }
@ -66,7 +66,7 @@ func Init(dir string) error {
if err != nil { if err != nil {
return fmt.Errorf("can't create file %s: %s", C.Path.Config(), err.Error()) return fmt.Errorf("can't create file %s: %s", C.Path.Config(), err.Error())
} }
f.Write([]byte(`port: 7890`)) f.Write([]byte(`mixed-port: 7890`))
f.Close() f.Close()
} }

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,10 +27,9 @@ const (
LoadBalance LoadBalance
) )
type ServerAdapter interface { const (
net.Conn DefaultTCPTimeout = 5 * time.Second
Metadata() *Metadata )
}
type Connection interface { type Connection interface {
Chains() Chain Chains() Chain
@ -50,6 +49,15 @@ func (c Chain) String() string {
} }
} }
func (c Chain) Last() string {
switch len(c) {
case 0:
return ""
default:
return c[0]
}
}
type Conn interface { type Conn interface {
net.Conn net.Conn
Connection Connection
@ -65,8 +73,21 @@ type PacketConn interface {
type ProxyAdapter interface { type ProxyAdapter interface {
Name() string Name() string
Type() AdapterType Type() AdapterType
// StreamConn wraps a protocol around net.Conn with Metadata.
//
// Examples:
// conn, _ := net.Dial("tcp", "host:port")
// conn, _ = adapter.StreamConn(conn, metadata)
//
// It returns a C.Conn with protocol which start with
// a new session (if any)
StreamConn(c net.Conn, metadata *Metadata) (net.Conn, error) StreamConn(c net.Conn, metadata *Metadata) (net.Conn, error)
// DialContext return a C.Conn with protocol which
// contains multiplexing-related reuse logic (if any)
DialContext(ctx context.Context, metadata *Metadata) (Conn, error) DialContext(ctx context.Context, metadata *Metadata) (Conn, error)
DialUDP(metadata *Metadata) (PacketConn, error) DialUDP(metadata *Metadata) (PacketConn, error)
SupportUDP() bool SupportUDP() bool
MarshalJSON() ([]byte, error) MarshalJSON() ([]byte, error)

23
constant/context.go Normal file
View File

@ -0,0 +1,23 @@
package constant
import (
"net"
"github.com/gofrs/uuid"
)
type PlainContext interface {
ID() uuid.UUID
}
type ConnContext interface {
PlainContext
Metadata() *Metadata
Conn() net.Conn
}
type PacketConnContext interface {
PlainContext
Metadata() *Metadata
PacketConn() net.PacketConn
}

7
constant/listener.go Normal file
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"

Some files were not shown because too many files have changed in this diff Show More