Compare commits

..

176 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
1e5593f1a9 Chore: update dependencies 2020-11-20 20:36:20 +08:00
34febc4579 Chore: more detailed error when dial failed 2020-11-20 00:27:37 +08:00
97581148b5 Fix: static check 2020-11-19 00:56:36 +08:00
0402878daa Feature: add lazy for proxy group and provider 2020-11-19 00:53:22 +08:00
4735f61fd1 Feature: add disable-udp option for all proxy group 2020-11-13 21:48:52 +08:00
16ae107e70 Chore: push image to github docker registry 2020-11-10 15:19:12 +08:00
83efe2ae57 Feature: add TCP TPROXY support (#1049) 2020-11-09 10:46:10 +08:00
87e4d94290 Fix: tunnel manager & tracker race condition (#1048) 2020-10-29 17:51:14 +08:00
b98e9ea202 Improve: #1038 and #1041 2020-10-29 00:32:31 +08:00
9a62b1081d Feature: support round-robin strategy for load-balance group (#1044) 2020-10-28 22:35:02 +08:00
2cd1b890ce Fix: tunnel UDP race condition (#1043) 2020-10-28 21:26:50 +08:00
ba060bd0ee Fix: should not bind interface on local address 2020-10-25 20:31:01 +08:00
b1795b1e3d Fix: stale typo 2020-10-25 11:53:03 +08:00
76c9820065 Fix: undefined variable 2020-10-23 17:49:34 +08:00
2db4ce57ef Chore: make stale time into 60 days 2020-10-23 00:30:17 +08:00
50b3d497f6 Feature: use native syscall to bind interface on Linux and macOS 2020-10-22 22:32:03 +08:00
2321e9139d Chore: deprecated eapache/channels 2020-10-20 17:44:39 +08:00
baabf21340 Chore: update github workflow 2020-10-17 13:46:05 +08:00
d3bb4c65a8 Fix: missing fake-ip record should return error 2020-10-17 12:52:43 +08:00
8c3e2a7559 Chore: fix typo (#1017) 2020-10-14 19:56:02 +08:00
bc52f8e4fd Chore: return empty record in SVCB/HTTPSSVC on fake-ip mode 2020-10-13 00:15:49 +08:00
d3b14c325f Fix: the priority of fake-ip-filter 2020-10-09 00:04:24 +08:00
4859b158b4 Chore: make builds reproducible (#1006) 2020-10-08 17:54:38 +08:00
d65b51c62b Feature: http support custom sni 2020-10-02 11:34:40 +08:00
a6444bb449 Feature: support domain in fallback filter (#964) 2020-09-28 22:17:10 +08:00
e09931dcf7 Chore: remove broken test temporarily 2020-09-26 20:36:52 +08:00
5bd189f2d0 Feature: support VMess HTTP/2 transport (#903) 2020-09-26 20:33:57 +08:00
8766287e72 Chore: sync necessary changes from premium 2020-09-21 22:22:07 +08:00
10f9571c9e Fix: pool gc test 2020-09-21 00:44:47 +08:00
96a8259c42 Feature: support snell v2 (#952)
Co-authored-by: Dreamacro <8615343+Dreamacro@users.noreply.github.com>
2020-09-21 00:33:13 +08:00
68dd0622b8 Chore: code style 2020-09-20 15:53:27 +08:00
558ac6b965 Chore: split enhanced mode instance (#936)
Co-authored-by: Dreamacro <305009791@qq.com>
2020-09-17 10:48:42 +08:00
e773f95f21 Fix: PROCESS-NAME on FreeBSD 11.x (#947) 2020-09-07 17:43:34 +08:00
314ce1c249 Feature: vmess network http support TLS (https) 2020-09-04 21:27:19 +08:00
13275b1aa6 Chore: use only one goroutine to handle statistic (#940) 2020-09-03 10:30:18 +08:00
02d9169b5d Fix: potential PCB buffer overflow on bsd systems (#941) 2020-09-03 10:27:20 +08:00
7631bcc99e Improve: use atomic for connection statistic (#938) 2020-09-02 16:34:12 +08:00
a32ee13fc9 Feature: reuse dns resolver cache when hot reload 2020-08-31 00:32:18 +08:00
b8ed738238 Chore: update actions version 2020-08-30 23:06:21 +08:00
687c2a21cf Fix: vmess UDP option should be effect 2020-08-30 22:49:55 +08:00
ad18064e6b Chore: code style (#933) 2020-08-30 19:53:00 +08:00
c9735ef75b Fix: static check 2020-08-25 22:36:38 +08:00
b70882f01a Chore: add static check 2020-08-25 22:32:23 +08:00
5805334ccd Chore: pass staticcheck 2020-08-25 22:19:59 +08:00
c1b4382fe8 Feature: add Windows ARM32 build (#902)
Co-authored-by: MarksonHon <50002150+MarksonHon@users.noreply.github.com>
2020-08-16 13:50:56 +08:00
008743f20b Chore: update dependencies 2020-08-16 11:32:51 +08:00
266 changed files with 12802 additions and 5950 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

@ -17,37 +17,60 @@ jobs:
with:
fetch-depth: 0
- name: Set up QEMU
uses: docker/setup-qemu-action@v1
with:
platforms: all
- name: Set up docker buildx
id: buildx
uses: crazy-max/ghaction-docker-buildx@v2
uses: docker/setup-buildx-action@v1
with:
buildx-version: latest
skip-cache: false
qemu-version: latest
version: latest
- name: Docker login
env:
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
run: |
echo "${DOCKER_PASSWORD}" | docker login --username "${DOCKER_USERNAME}" --password-stdin
- name: Login to DockerHub
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Docker buildx image and push on dev branch
if: github.ref == 'refs/heads/dev'
run: |
docker buildx build --output "type=image,push=true" --platform=linux/amd64,linux/arm/v7,linux/arm64 --tag dreamacro/clash:dev .
- name: Login to Github Package
uses: docker/login-action@v1
with:
registry: ghcr.io
username: Dreamacro
password: ${{ secrets.PACKAGE_TOKEN }}
- name: Replace tag without `v`
- name: Build dev branch and push
if: github.ref == 'refs/heads/dev'
uses: docker/build-push-action@v2
with:
context: .
platforms: linux/amd64,linux/arm/v7,linux/arm64
push: true
tags: 'dreamacro/clash:dev,ghcr.io/dreamacro/clash:dev'
- name: Get all docker tags
if: startsWith(github.ref, 'refs/tags/')
uses: actions/github-script@v1
id: version
uses: actions/github-script@v4
id: tags
with:
script: |
return context.payload.ref.replace(/\/?refs\/tags\/v/, '')
const ref = `${context.payload.ref.replace(/\/?refs\/tags\//, '')}`
const tags = [
'dreamacro/clash:latest',
`dreamacro/clash:${ref}`,
'ghcr.io/dreamacro/clash:latest',
`ghcr.io/dreamacro/clash:${ref}`
]
return tags.join(',')
result-encoding: string
- name: Docker buildx image and push on release
- name: Build release and push
if: startsWith(github.ref, 'refs/tags/')
run: |
docker buildx build --output "type=image,push=true" --platform=linux/amd64,linux/arm/v7,linux/arm64 --tag dreamacro/clash:${{steps.version.outputs.result}} .
docker buildx build --output "type=image,push=true" --platform=linux/amd64,linux/arm/v7,linux/arm64 --tag dreamacro/clash:latest .
uses: docker/build-push-action@v2
with:
context: .
platforms: linux/amd64,linux/arm/v7,linux/arm64
push: true
tags: ${{steps.tags.outputs.result}}

View File

@ -9,7 +9,7 @@ jobs:
- name: Setup Go
uses: actions/setup-go@v2
with:
go-version: 1.15.x
go-version: 1.17
- name: Check out code into the Go module directory
uses: actions/checkout@v2
@ -22,9 +22,12 @@ jobs:
restore-keys: |
${{ runner.os }}-go-
- name: Get dependencies and run test
- name: Get dependencies, run test and static check
run: |
go test ./...
go vet ./...
go install honnef.co/go/tools/cmd/staticcheck@latest
staticcheck -- $(go list ./...)
- name: Build
if: startsWith(github.ref, 'refs/tags/')
@ -36,9 +39,6 @@ jobs:
- name: Upload Release
uses: softprops/action-gh-release@v1
if: startsWith(github.ref, 'refs/tags/')
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
files: bin/*
draft: true
prerelease: true

View File

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

View File

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

View File

@ -4,10 +4,11 @@ VERSION=$(shell git describe --tags || echo "unknown version")
BUILDTIME=$(shell date -u)
GOBUILD=CGO_ENABLED=0 go build -trimpath -ldflags '-X "github.com/Dreamacro/clash/constant.Version=$(VERSION)" \
-X "github.com/Dreamacro/clash/constant.BuildTime=$(BUILDTIME)" \
-w -s'
-w -s -buildid='
PLATFORM_LIST = \
darwin-amd64 \
darwin-arm64 \
linux-386 \
linux-amd64 \
linux-armv5 \
@ -21,11 +22,14 @@ PLATFORM_LIST = \
linux-mips64 \
linux-mips64le \
freebsd-386 \
freebsd-amd64
freebsd-amd64 \
freebsd-arm64
WINDOWS_ARCH_LIST = \
windows-386 \
windows-amd64
windows-amd64 \
windows-arm64 \
windows-arm32v7
all: linux-amd64 darwin-amd64 windows-amd64 # Most used
@ -35,6 +39,9 @@ docker:
darwin-amd64:
GOARCH=amd64 GOOS=darwin $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
darwin-arm64:
GOARCH=arm64 GOOS=darwin $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
linux-386:
GOARCH=386 GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
@ -77,12 +84,21 @@ freebsd-386:
freebsd-amd64:
GOARCH=amd64 GOOS=freebsd $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
freebsd-arm64:
GOARCH=arm64 GOOS=freebsd $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
windows-386:
GOARCH=386 GOOS=windows $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.exe
windows-amd64:
GOARCH=amd64 GOOS=windows $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.exe
windows-arm64:
GOARCH=arm64 GOOS=windows $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.exe
windows-arm32v7:
GOARCH=arm GOOS=windows GOARM=7 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.exe
gz_releases=$(addsuffix .gz, $(PLATFORM_LIST))
zip_releases=$(addsuffix .zip, $(WINDOWS_ARCH_LIST))

View File

@ -28,9 +28,21 @@
- Netfilter TCP redirecting. Deploy Clash on your Internet gateway with `iptables`.
- 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
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
* [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] UDP support
- [x] Connection manager
- [ ] ~~Event API~~

View File

@ -1,125 +1,48 @@
package outbound
package adapter
import (
"context"
"encoding/json"
"errors"
"fmt"
"net"
"net/http"
"sync/atomic"
"net/url"
"time"
"github.com/Dreamacro/clash/common/queue"
C "github.com/Dreamacro/clash/constant"
"go.uber.org/atomic"
)
var (
defaultURLTestTimeout = time.Second * 5
)
type Base struct {
name string
addr string
tp C.AdapterType
udp bool
}
func (b *Base) Name() string {
return b.name
}
func (b *Base) Type() C.AdapterType {
return b.tp
}
func (b *Base) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
return c, errors.New("no support")
}
func (b *Base) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
return nil, errors.New("no support")
}
func (b *Base) SupportUDP() bool {
return b.udp
}
func (b *Base) MarshalJSON() ([]byte, error) {
return json.Marshal(map[string]string{
"type": b.Type().String(),
})
}
func (b *Base) Addr() string {
return b.addr
}
func (b *Base) Unwrap(metadata *C.Metadata) C.Proxy {
return nil
}
func NewBase(name string, addr string, tp C.AdapterType, udp bool) *Base {
return &Base{name, addr, tp, udp}
}
type conn struct {
net.Conn
chain C.Chain
}
func (c *conn) Chains() C.Chain {
return c.chain
}
func (c *conn) AppendToChains(a C.ProxyAdapter) {
c.chain = append(c.chain, a.Name())
}
func NewConn(c net.Conn, a C.ProxyAdapter) C.Conn {
return &conn{c, []string{a.Name()}}
}
type packetConn struct {
net.PacketConn
chain C.Chain
}
func (c *packetConn) Chains() C.Chain {
return c.chain
}
func (c *packetConn) AppendToChains(a C.ProxyAdapter) {
c.chain = append(c.chain, a.Name())
}
func newPacketConn(pc net.PacketConn, a C.ProxyAdapter) C.PacketConn {
return &packetConn{pc, []string{a.Name()}}
}
type Proxy struct {
C.ProxyAdapter
history *queue.Queue
alive uint32
alive *atomic.Bool
}
// Alive implements C.Proxy
func (p *Proxy) Alive() bool {
return atomic.LoadUint32(&p.alive) > 0
return p.alive.Load()
}
// Dial implements C.Proxy
func (p *Proxy) Dial(metadata *C.Metadata) (C.Conn, error) {
ctx, cancel := context.WithTimeout(context.Background(), tcpTimeout)
ctx, cancel := context.WithTimeout(context.Background(), C.DefaultTCPTimeout)
defer cancel()
return p.DialContext(ctx, metadata)
}
// DialContext implements C.ProxyAdapter
func (p *Proxy) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
conn, err := p.ProxyAdapter.DialContext(ctx, metadata)
if err != nil {
atomic.StoreUint32(&p.alive, 0)
p.alive.Store(false)
}
return conn, err
}
// DelayHistory implements C.Proxy
func (p *Proxy) DelayHistory() []C.DelayHistory {
queue := p.history.Copy()
histories := []C.DelayHistory{}
@ -130,9 +53,10 @@ func (p *Proxy) DelayHistory() []C.DelayHistory {
}
// LastDelay return last history record. if proxy is not alive, return the max value of uint16.
// implements C.Proxy
func (p *Proxy) LastDelay() (delay uint16) {
var max uint16 = 0xffff
if atomic.LoadUint32(&p.alive) == 0 {
if !p.alive.Load() {
return max
}
@ -147,6 +71,7 @@ func (p *Proxy) LastDelay() (delay uint16) {
return history.Delay
}
// MarshalJSON implements C.ProxyAdapter
func (p *Proxy) MarshalJSON() ([]byte, error) {
inner, err := p.ProxyAdapter.MarshalJSON()
if err != nil {
@ -157,17 +82,15 @@ func (p *Proxy) MarshalJSON() ([]byte, error) {
json.Unmarshal(inner, &mapping)
mapping["history"] = p.DelayHistory()
mapping["name"] = p.Name()
mapping["udp"] = p.SupportUDP()
return json.Marshal(mapping)
}
// URLTest get the delay for the specified URL
// implements C.Proxy
func (p *Proxy) URLTest(ctx context.Context, url string) (t uint16, err error) {
defer func() {
if err == nil {
atomic.StoreUint32(&p.alive, 1)
} else {
atomic.StoreUint32(&p.alive, 0)
}
p.alive.Store(err == nil)
record := C.DelayHistory{Time: time.Now()}
if err == nil {
record.Delay = t
@ -213,6 +136,8 @@ func (p *Proxy) URLTest(ctx context.Context, url string) (t uint16, err error) {
return http.ErrUseLastResponse
},
}
defer client.CloseIdleConnections()
resp, err := client.Do(req)
if err != nil {
return
@ -223,5 +148,33 @@ func (p *Proxy) URLTest(ctx context.Context, url string) (t uint16, err error) {
}
func NewProxy(adapter C.ProxyAdapter) *Proxy {
return &Proxy{adapter, queue.New(10), 1}
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"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/context"
)
// NewHTTPS is HTTPAdapter generator
func NewHTTPS(request *http.Request, conn net.Conn) *SocketAdapter {
// NewHTTPS receive CONNECT request and return ConnContext
func NewHTTPS(request *http.Request, conn net.Conn) *context.ConnContext {
metadata := parseHTTPAddr(request)
metadata.Type = C.HTTPCONNECT
if ip, port, err := parseAddr(conn.RemoteAddr().String()); err == nil {
metadata.SrcIP = ip
metadata.SrcPort = port
}
return &SocketAdapter{
metadata: metadata,
Conn: conn,
}
return context.NewConnContext(conn, metadata)
}

View File

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

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"
"strings"
"github.com/Dreamacro/clash/component/socks5"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/transport/socks5"
)
func parseSocksAddr(target socks5.Addr) *C.Metadata {

100
adapter/outbound/base.go Normal file
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
}
// DialContext implements C.ProxyAdapter
func (d *Direct) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
address := net.JoinHostPort(metadata.String(), metadata.DstPort)
c, err := dialer.DialContext(ctx, "tcp", address)
c, err := dialer.DialContext(ctx, "tcp", metadata.RemoteAddress())
if err != nil {
return nil, err
}
@ -23,8 +22,9 @@ func (d *Direct) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn,
return NewConn(c, d), nil
}
// DialUDP implements C.ProxyAdapter
func (d *Direct) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
pc, err := dialer.ListenPacket("udp", "")
pc, err := dialer.ListenPacket(context.Background(), "udp", "")
if err != nil {
return nil, err
}

View File

@ -31,9 +31,11 @@ type HttpOption struct {
UserName string `proxy:"username,omitempty"`
Password string `proxy:"password,omitempty"`
TLS bool `proxy:"tls,omitempty"`
SNI string `proxy:"sni,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) {
if h.tlsConfig != nil {
cc := tls.Client(c, h.tlsConfig)
@ -50,13 +52,16 @@ func (h *Http) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
return c, nil
}
func (h *Http) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
// DialContext implements C.ProxyAdapter
func (h *Http) DialContext(ctx context.Context, metadata *C.Metadata) (_ C.Conn, err error) {
c, err := dialer.DialContext(ctx, "tcp", h.addr)
if err != nil {
return nil, fmt.Errorf("%s connect error: %w", h.addr, err)
}
tcpKeepAlive(c)
defer safeConnClose(c, err)
c, err = h.StreamConn(c, metadata)
if err != nil {
return nil, err
@ -114,10 +119,13 @@ func (h *Http) shakeHand(metadata *C.Metadata, rw io.ReadWriter) error {
func NewHttp(option HttpOption) *Http {
var tlsConfig *tls.Config
if option.TLS {
sni := option.Server
if option.SNI != "" {
sni = option.SNI
}
tlsConfig = &tls.Config{
InsecureSkipVerify: option.SkipCertVerify,
ClientSessionCache: getClientSessionCache(),
ServerName: option.Server,
ServerName: sni,
}
}

View File

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

View File

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

View File

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

135
adapter/outbound/snell.go Normal file
View File

@ -0,0 +1,135 @@
package outbound
import (
"context"
"fmt"
"net"
"strconv"
"github.com/Dreamacro/clash/common/structure"
"github.com/Dreamacro/clash/component/dialer"
C "github.com/Dreamacro/clash/constant"
obfs "github.com/Dreamacro/clash/transport/simple-obfs"
"github.com/Dreamacro/clash/transport/snell"
)
type Snell struct {
*Base
psk []byte
pool *snell.Pool
obfsOption *simpleObfsOption
version int
}
type SnellOption struct {
Name string `proxy:"name"`
Server string `proxy:"server"`
Port int `proxy:"port"`
Psk string `proxy:"psk"`
Version int `proxy:"version,omitempty"`
ObfsOpts map[string]interface{} `proxy:"obfs-opts,omitempty"`
}
type streamOption struct {
psk []byte
version int
addr string
obfsOption *simpleObfsOption
}
func streamConn(c net.Conn, option streamOption) *snell.Snell {
switch option.obfsOption.Mode {
case "tls":
c = obfs.NewTLSObfs(c, option.obfsOption.Host)
case "http":
_, port, _ := net.SplitHostPort(option.addr)
c = obfs.NewHTTPObfs(c, option.obfsOption.Host, port)
}
return snell.StreamConn(c, option.psk, option.version)
}
// StreamConn implements C.ProxyAdapter
func (s *Snell) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
c = streamConn(c, streamOption{s.psk, s.version, s.addr, s.obfsOption})
port, _ := strconv.Atoi(metadata.DstPort)
err := snell.WriteHeader(c, metadata.String(), uint(port), s.version)
return c, err
}
// DialContext implements C.ProxyAdapter
func (s *Snell) DialContext(ctx context.Context, metadata *C.Metadata) (_ C.Conn, err error) {
if s.version == snell.Version2 {
c, err := s.pool.Get()
if err != nil {
return nil, err
}
port, _ := strconv.Atoi(metadata.DstPort)
if err = snell.WriteHeader(c, metadata.String(), uint(port), s.version); err != nil {
c.Close()
return nil, err
}
return NewConn(c, s), err
}
c, err := dialer.DialContext(ctx, "tcp", s.addr)
if err != nil {
return nil, fmt.Errorf("%s connect error: %w", s.addr, err)
}
tcpKeepAlive(c)
defer safeConnClose(c, err)
c, err = s.StreamConn(c, metadata)
return NewConn(c, s), err
}
func NewSnell(option SnellOption) (*Snell, error) {
addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port))
psk := []byte(option.Psk)
decoder := structure.NewDecoder(structure.Option{TagName: "obfs", WeaklyTypedInput: true})
obfsOption := &simpleObfsOption{Host: "bing.com"}
if err := decoder.Decode(option.ObfsOpts, obfsOption); err != nil {
return nil, fmt.Errorf("snell %s initialize obfs error: %w", addr, err)
}
switch obfsOption.Mode {
case "tls", "http", "":
break
default:
return nil, fmt.Errorf("snell %s obfs mode error: %s", addr, obfsOption.Mode)
}
// backward compatible
if option.Version == 0 {
option.Version = snell.DefaultSnellVersion
}
if option.Version != snell.Version1 && option.Version != snell.Version2 {
return nil, fmt.Errorf("snell version error: %d", option.Version)
}
s := &Snell{
Base: &Base{
name: option.Name,
addr: addr,
tp: C.Snell,
},
psk: psk,
obfsOption: obfsOption,
version: option.Version,
}
if option.Version == snell.Version2 {
s.pool = snell.NewPool(func(ctx context.Context) (*snell.Snell, error) {
c, err := dialer.DialContext(ctx, "tcp", addr)
if err != nil {
return nil, err
}
tcpKeepAlive(c)
return streamConn(c, streamOption{psk, option.Version, addr, obfsOption}), nil
})
}
return s, nil
}

View File

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

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

367
adapter/outbound/vmess.go Normal file
View File

@ -0,0 +1,367 @@
package outbound
import (
"context"
"crypto/tls"
"errors"
"fmt"
"net"
"net/http"
"strconv"
"strings"
"github.com/Dreamacro/clash/component/dialer"
"github.com/Dreamacro/clash/component/resolver"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/transport/gun"
"github.com/Dreamacro/clash/transport/vmess"
"golang.org/x/net/http2"
)
type Vmess struct {
*Base
client *vmess.Client
option *VmessOption
// for gun mux
gunTLSConfig *tls.Config
gunConfig *gun.Config
transport *http2.Transport
}
type VmessOption struct {
Name string `proxy:"name"`
Server string `proxy:"server"`
Port int `proxy:"port"`
UUID string `proxy:"uuid"`
AlterID int `proxy:"alterId"`
Cipher string `proxy:"cipher"`
TLS bool `proxy:"tls,omitempty"`
UDP bool `proxy:"udp,omitempty"`
Network string `proxy:"network,omitempty"`
HTTPOpts HTTPOptions `proxy:"http-opts,omitempty"`
HTTP2Opts HTTP2Options `proxy:"h2-opts,omitempty"`
GrpcOpts GrpcOptions `proxy:"grpc-opts,omitempty"`
WSOpts WSOptions `proxy:"ws-opts,omitempty"`
WSPath string `proxy:"ws-path,omitempty"`
WSHeaders map[string]string `proxy:"ws-headers,omitempty"`
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
ServerName string `proxy:"servername,omitempty"`
}
type HTTPOptions struct {
Method string `proxy:"method,omitempty"`
Path []string `proxy:"path,omitempty"`
Headers map[string][]string `proxy:"headers,omitempty"`
}
type HTTP2Options struct {
Host []string `proxy:"host,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) {
var err error
switch v.option.Network {
case "ws":
if v.option.WSOpts.Path == "" {
v.option.WSOpts.Path = v.option.WSPath
}
if len(v.option.WSOpts.Headers) == 0 {
v.option.WSOpts.Headers = v.option.WSHeaders
}
host, port, _ := net.SplitHostPort(v.addr)
wsOpts := &vmess.WebsocketConfig{
Host: host,
Port: port,
Path: v.option.WSOpts.Path,
MaxEarlyData: v.option.WSOpts.MaxEarlyData,
EarlyDataHeaderName: v.option.WSOpts.EarlyDataHeaderName,
}
if len(v.option.WSOpts.Headers) != 0 {
header := http.Header{}
for key, value := range v.option.WSHeaders {
header.Add(key, value)
}
wsOpts.Headers = header
}
if v.option.TLS {
wsOpts.TLS = true
wsOpts.SkipCertVerify = v.option.SkipCertVerify
wsOpts.ServerName = v.option.ServerName
}
c, err = vmess.StreamWebsocketConn(c, wsOpts)
case "http":
// readability first, so just copy default TLS logic
if v.option.TLS {
host, _, _ := net.SplitHostPort(v.addr)
tlsOpts := &vmess.TLSConfig{
Host: host,
SkipCertVerify: v.option.SkipCertVerify,
}
if v.option.ServerName != "" {
tlsOpts.Host = v.option.ServerName
}
c, err = vmess.StreamTLSConn(c, tlsOpts)
if err != nil {
return nil, err
}
}
host, _, _ := net.SplitHostPort(v.addr)
httpOpts := &vmess.HTTPConfig{
Host: host,
Method: v.option.HTTPOpts.Method,
Path: v.option.HTTPOpts.Path,
Headers: v.option.HTTPOpts.Headers,
}
c = vmess.StreamHTTPConn(c, httpOpts)
case "h2":
host, _, _ := net.SplitHostPort(v.addr)
tlsOpts := vmess.TLSConfig{
Host: host,
SkipCertVerify: v.option.SkipCertVerify,
NextProtos: []string{"h2"},
}
if v.option.ServerName != "" {
tlsOpts.Host = v.option.ServerName
}
c, err = vmess.StreamTLSConn(c, &tlsOpts)
if err != nil {
return nil, err
}
h2Opts := &vmess.H2Config{
Hosts: v.option.HTTP2Opts.Host,
Path: v.option.HTTP2Opts.Path,
}
c, err = vmess.StreamH2Conn(c, h2Opts)
case "grpc":
c, err = gun.StreamGunWithConn(c, v.gunTLSConfig, v.gunConfig)
default:
// handle TLS
if v.option.TLS {
host, _, _ := net.SplitHostPort(v.addr)
tlsOpts := &vmess.TLSConfig{
Host: host,
SkipCertVerify: v.option.SkipCertVerify,
}
if v.option.ServerName != "" {
tlsOpts.Host = v.option.ServerName
}
c, err = vmess.StreamTLSConn(c, tlsOpts)
}
}
if err != nil {
return nil, err
}
return v.client.StreamConn(c, parseVmessAddr(metadata))
}
// DialContext implements C.ProxyAdapter
func (v *Vmess) DialContext(ctx context.Context, metadata *C.Metadata) (_ C.Conn, err error) {
// gun transport
if v.transport != nil {
c, err := gun.StreamGunWithTransport(v.transport, v.gunConfig)
if err != nil {
return nil, err
}
defer safeConnClose(c, err)
c, err = v.client.StreamConn(c, parseVmessAddr(metadata))
if err != nil {
return nil, err
}
return NewConn(c, v), nil
}
c, err := dialer.DialContext(ctx, "tcp", v.addr)
if err != nil {
return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error())
}
tcpKeepAlive(c)
defer safeConnClose(c, err)
c, err = v.StreamConn(c, metadata)
return NewConn(c, v), err
}
// DialUDP implements C.ProxyAdapter
func (v *Vmess) DialUDP(metadata *C.Metadata) (_ C.PacketConn, err error) {
// vmess use stream-oriented udp with a special address, so we needs a net.UDPAddr
if !metadata.Resolved() {
ip, err := resolver.ResolveIP(metadata.Host)
if err != nil {
return nil, errors.New("can't resolve ip")
}
metadata.DstIP = ip
}
var c net.Conn
// gun transport
if v.transport != nil {
c, err = gun.StreamGunWithTransport(v.transport, v.gunConfig)
if err != nil {
return nil, err
}
defer safeConnClose(c, err)
c, err = v.client.StreamConn(c, parseVmessAddr(metadata))
} else {
ctx, cancel := context.WithTimeout(context.Background(), C.DefaultTCPTimeout)
defer cancel()
c, err = dialer.DialContext(ctx, "tcp", v.addr)
if err != nil {
return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error())
}
tcpKeepAlive(c)
defer safeConnClose(c, err)
c, err = v.StreamConn(c, metadata)
}
if err != nil {
return nil, fmt.Errorf("new vmess client error: %v", err)
}
return newPacketConn(&vmessPacketConn{Conn: c, rAddr: metadata.UDPAddr()}, v), nil
}
func NewVmess(option VmessOption) (*Vmess, error) {
security := strings.ToLower(option.Cipher)
client, err := vmess.NewClient(vmess.Config{
UUID: option.UUID,
AlterID: uint16(option.AlterID),
Security: security,
HostName: option.Server,
Port: strconv.Itoa(option.Port),
IsAead: option.AlterID == 0,
})
if err != nil {
return nil, err
}
switch option.Network {
case "h2", "grpc":
if !option.TLS {
return nil, fmt.Errorf("TLS must be true with h2/grpc network")
}
}
v := &Vmess{
Base: &Base{
name: option.Name,
addr: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)),
tp: C.Vmess,
udp: option.UDP,
},
client: client,
option: &option,
}
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 {
var addrType byte
var addr []byte
switch metadata.AddrType {
case C.AtypIPv4:
addrType = byte(vmess.AtypIPv4)
addr = make([]byte, net.IPv4len)
copy(addr[:], metadata.DstIP.To4())
case C.AtypIPv6:
addrType = byte(vmess.AtypIPv6)
addr = make([]byte, net.IPv6len)
copy(addr[:], metadata.DstIP.To16())
case C.AtypDomainName:
addrType = byte(vmess.AtypDomainName)
addr = make([]byte, len(metadata.Host)+1)
addr[0] = byte(len(metadata.Host))
copy(addr[1:], []byte(metadata.Host))
}
port, _ := strconv.Atoi(metadata.DstPort)
return &vmess.DstAddr{
UDP: metadata.NetWork == C.UDP,
AddrType: addrType,
Addr: addr,
Port: uint(port),
}
}
type vmessPacketConn struct {
net.Conn
rAddr net.Addr
}
func (uc *vmessPacketConn) WriteTo(b []byte, addr net.Addr) (int, error) {
return uc.Conn.Write(b)
}
func (uc *vmessPacketConn) ReadFrom(b []byte) (int, net.Addr, error) {
n, err := uc.Conn.Read(b)
return n, uc.rAddr, err
}

View File

@ -0,0 +1,24 @@
package outboundgroup
import (
"time"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/constant/provider"
)
const (
defaultGetProxiesDuration = time.Second * 5
)
func getProvidersProxies(providers []provider.ProxyProvider, touch bool) []C.Proxy {
proxies := []C.Proxy{}
for _, provider := range providers {
if touch {
proxies = append(proxies, provider.ProxiesWithTouch()...)
} else {
proxies = append(proxies, provider.Proxies()...)
}
}
return proxies
}

View File

@ -4,25 +4,27 @@ import (
"context"
"encoding/json"
"github.com/Dreamacro/clash/adapters/outbound"
"github.com/Dreamacro/clash/adapters/provider"
"github.com/Dreamacro/clash/adapter/outbound"
"github.com/Dreamacro/clash/common/singledo"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/constant/provider"
)
type Fallback struct {
*outbound.Base
single *singledo.Single
providers []provider.ProxyProvider
disableUDP bool
single *singledo.Single
providers []provider.ProxyProvider
}
func (f *Fallback) Now() string {
proxy := f.findAliveProxy()
proxy := f.findAliveProxy(false)
return proxy.Name()
}
// DialContext implements C.ProxyAdapter
func (f *Fallback) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
proxy := f.findAliveProxy()
proxy := f.findAliveProxy(true)
c, err := proxy.DialContext(ctx, metadata)
if err == nil {
c.AppendToChains(f)
@ -30,8 +32,9 @@ func (f *Fallback) DialContext(ctx context.Context, metadata *C.Metadata) (C.Con
return c, err
}
// DialUDP implements C.ProxyAdapter
func (f *Fallback) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
proxy := f.findAliveProxy()
proxy := f.findAliveProxy(true)
pc, err := proxy.DialUDP(metadata)
if err == nil {
pc.AppendToChains(f)
@ -39,14 +42,20 @@ func (f *Fallback) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
return pc, err
}
// SupportUDP implements C.ProxyAdapter
func (f *Fallback) SupportUDP() bool {
proxy := f.findAliveProxy()
if f.disableUDP {
return false
}
proxy := f.findAliveProxy(false)
return proxy.SupportUDP()
}
// MarshalJSON implements C.ProxyAdapter
func (f *Fallback) MarshalJSON() ([]byte, error) {
var all []string
for _, proxy := range f.proxies() {
for _, proxy := range f.proxies(false) {
all = append(all, proxy.Name())
}
return json.Marshal(map[string]interface{}{
@ -56,34 +65,36 @@ func (f *Fallback) MarshalJSON() ([]byte, error) {
})
}
// Unwrap implements C.ProxyAdapter
func (f *Fallback) Unwrap(metadata *C.Metadata) C.Proxy {
proxy := f.findAliveProxy()
proxy := f.findAliveProxy(true)
return proxy
}
func (f *Fallback) proxies() []C.Proxy {
func (f *Fallback) proxies(touch bool) []C.Proxy {
elm, _, _ := f.single.Do(func() (interface{}, error) {
return getProvidersProxies(f.providers), nil
return getProvidersProxies(f.providers, touch), nil
})
return elm.([]C.Proxy)
}
func (f *Fallback) findAliveProxy() C.Proxy {
proxies := f.proxies()
func (f *Fallback) findAliveProxy(touch bool) C.Proxy {
proxies := f.proxies(touch)
for _, proxy := range proxies {
if proxy.Alive() {
return proxy
}
}
return f.proxies()[0]
return proxies[0]
}
func NewFallback(name string, providers []provider.ProxyProvider) *Fallback {
func NewFallback(options *GroupCommonOption, providers []provider.ProxyProvider) *Fallback {
return &Fallback{
Base: outbound.NewBase(name, "", C.Fallback, false),
single: singledo.NewSingle(defaultGetProxiesDuration),
providers: providers,
Base: outbound.NewBase(options.Name, "", C.Fallback, false),
single: singledo.NewSingle(defaultGetProxiesDuration),
providers: providers,
disableUDP: options.DisableUDP,
}
}

View File

@ -0,0 +1,179 @@
package outboundgroup
import (
"context"
"encoding/json"
"errors"
"fmt"
"net"
"github.com/Dreamacro/clash/adapter/outbound"
"github.com/Dreamacro/clash/common/murmur3"
"github.com/Dreamacro/clash/common/singledo"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/constant/provider"
"golang.org/x/net/publicsuffix"
)
type strategyFn = func(proxies []C.Proxy, metadata *C.Metadata) C.Proxy
type LoadBalance struct {
*outbound.Base
disableUDP bool
single *singledo.Single
providers []provider.ProxyProvider
strategyFn strategyFn
}
var errStrategy = errors.New("unsupported strategy")
func parseStrategy(config map[string]interface{}) string {
if elm, ok := config["strategy"]; ok {
if strategy, ok := elm.(string); ok {
return strategy
}
}
return "consistent-hashing"
}
func getKey(metadata *C.Metadata) string {
if metadata.Host != "" {
// ip host
if ip := net.ParseIP(metadata.Host); ip != nil {
return metadata.Host
}
if etld, err := publicsuffix.EffectiveTLDPlusOne(metadata.Host); err == nil {
return etld
}
}
if metadata.DstIP == nil {
return ""
}
return metadata.DstIP.String()
}
func jumpHash(key uint64, buckets int32) int32 {
var b, j int64
for j < int64(buckets) {
b = j
key = key*2862933555777941757 + 1
j = int64(float64(b+1) * (float64(int64(1)<<31) / float64((key>>33)+1)))
}
return int32(b)
}
// DialContext implements C.ProxyAdapter
func (lb *LoadBalance) DialContext(ctx context.Context, metadata *C.Metadata) (c C.Conn, err error) {
defer func() {
if err == nil {
c.AppendToChains(lb)
}
}()
proxy := lb.Unwrap(metadata)
c, err = proxy.DialContext(ctx, metadata)
return
}
// DialUDP implements C.ProxyAdapter
func (lb *LoadBalance) DialUDP(metadata *C.Metadata) (pc C.PacketConn, err error) {
defer func() {
if err == nil {
pc.AppendToChains(lb)
}
}()
proxy := lb.Unwrap(metadata)
return proxy.DialUDP(metadata)
}
// SupportUDP implements C.ProxyAdapter
func (lb *LoadBalance) SupportUDP() bool {
return !lb.disableUDP
}
func strategyRoundRobin() strategyFn {
idx := 0
return func(proxies []C.Proxy, metadata *C.Metadata) C.Proxy {
length := len(proxies)
for i := 0; i < length; i++ {
idx = (idx + 1) % length
proxy := proxies[idx]
if proxy.Alive() {
return proxy
}
}
return proxies[0]
}
}
func strategyConsistentHashing() strategyFn {
maxRetry := 5
return func(proxies []C.Proxy, metadata *C.Metadata) C.Proxy {
key := uint64(murmur3.Sum32([]byte(getKey(metadata))))
buckets := int32(len(proxies))
for i := 0; i < maxRetry; i, key = i+1, key+1 {
idx := jumpHash(key, buckets)
proxy := proxies[idx]
if proxy.Alive() {
return proxy
}
}
return proxies[0]
}
}
// Unwrap implements C.ProxyAdapter
func (lb *LoadBalance) Unwrap(metadata *C.Metadata) C.Proxy {
proxies := lb.proxies(true)
return lb.strategyFn(proxies, metadata)
}
func (lb *LoadBalance) proxies(touch bool) []C.Proxy {
elm, _, _ := lb.single.Do(func() (interface{}, error) {
return getProvidersProxies(lb.providers, touch), nil
})
return elm.([]C.Proxy)
}
// MarshalJSON implements C.ProxyAdapter
func (lb *LoadBalance) MarshalJSON() ([]byte, error) {
var all []string
for _, proxy := range lb.proxies(false) {
all = append(all, proxy.Name())
}
return json.Marshal(map[string]interface{}{
"type": lb.Type().String(),
"all": all,
})
}
func NewLoadBalance(options *GroupCommonOption, providers []provider.ProxyProvider, strategy string) (lb *LoadBalance, err error) {
var strategyFn strategyFn
switch strategy {
case "consistent-hashing":
strategyFn = strategyConsistentHashing()
case "round-robin":
strategyFn = strategyRoundRobin()
default:
return nil, fmt.Errorf("%w: %s", errStrategy, strategy)
}
return &LoadBalance{
Base: outbound.NewBase(options.Name, "", C.LoadBalance, false),
single: singledo.NewSingle(defaultGetProxiesDuration),
providers: providers,
strategyFn: strategyFn,
disableUDP: options.DisableUDP,
}, nil
}

View File

@ -4,33 +4,37 @@ import (
"errors"
"fmt"
"github.com/Dreamacro/clash/adapters/provider"
"github.com/Dreamacro/clash/adapter/provider"
"github.com/Dreamacro/clash/common/structure"
C "github.com/Dreamacro/clash/constant"
types "github.com/Dreamacro/clash/constant/provider"
)
var (
errFormat = errors.New("format error")
errType = errors.New("unsupport type")
errMissUse = errors.New("`use` field should not be empty")
errMissProxy = errors.New("`use` or `proxies` missing")
errMissHealthCheck = errors.New("`url` or `interval` missing")
errDuplicateProvider = errors.New("`duplicate provider name")
)
type GroupCommonOption struct {
Name string `group:"name"`
Type string `group:"type"`
Proxies []string `group:"proxies,omitempty"`
Use []string `group:"use,omitempty"`
URL string `group:"url,omitempty"`
Interval int `group:"interval,omitempty"`
Name string `group:"name"`
Type string `group:"type"`
Proxies []string `group:"proxies,omitempty"`
Use []string `group:"use,omitempty"`
URL string `group:"url,omitempty"`
Interval int `group:"interval,omitempty"`
Lazy bool `group:"lazy,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})
groupOption := &GroupCommonOption{}
groupOption := &GroupCommonOption{
Lazy: true,
}
if err := decoder.Decode(config, groupOption); err != nil {
return nil, errFormat
}
@ -41,7 +45,7 @@ func ParseProxyGroup(config map[string]interface{}, proxyMap map[string]C.Proxy,
groupName := groupOption.Name
providers := []provider.ProxyProvider{}
providers := []types.ProxyProvider{}
if len(groupOption.Proxies) == 0 && len(groupOption.Use) == 0 {
return nil, errMissProxy
@ -55,7 +59,7 @@ func ParseProxyGroup(config map[string]interface{}, proxyMap map[string]C.Proxy,
// if Use not empty, drop health check options
if len(groupOption.Use) != 0 {
hc := provider.NewHealthCheck(ps, "", 0)
hc := provider.NewHealthCheck(ps, "", 0, true)
pd, err := provider.NewCompatibleProvider(groupName, ps, hc)
if err != nil {
return nil, err
@ -63,9 +67,13 @@ func ParseProxyGroup(config map[string]interface{}, proxyMap map[string]C.Proxy,
providers = append(providers, pd)
} else {
if _, ok := providersMap[groupName]; ok {
return nil, errDuplicateProvider
}
// select don't need health check
if groupOption.Type == "select" || groupOption.Type == "relay" {
hc := provider.NewHealthCheck(ps, "", 0)
hc := provider.NewHealthCheck(ps, "", 0, true)
pd, err := provider.NewCompatibleProvider(groupName, ps, hc)
if err != nil {
return nil, err
@ -78,7 +86,7 @@ func ParseProxyGroup(config map[string]interface{}, proxyMap map[string]C.Proxy,
return nil, errMissHealthCheck
}
hc := provider.NewHealthCheck(ps, groupOption.URL, uint(groupOption.Interval))
hc := provider.NewHealthCheck(ps, groupOption.URL, uint(groupOption.Interval), groupOption.Lazy)
pd, err := provider.NewCompatibleProvider(groupName, ps, hc)
if err != nil {
return nil, err
@ -102,15 +110,16 @@ func ParseProxyGroup(config map[string]interface{}, proxyMap map[string]C.Proxy,
switch groupOption.Type {
case "url-test":
opts := parseURLTestOption(config)
group = NewURLTest(groupName, providers, opts...)
group = NewURLTest(groupOption, providers, opts...)
case "select":
group = NewSelector(groupName, providers)
group = NewSelector(groupOption, providers)
case "fallback":
group = NewFallback(groupName, providers)
group = NewFallback(groupOption, providers)
case "load-balance":
group = NewLoadBalance(groupName, providers)
strategy := parseStrategy(config)
return NewLoadBalance(groupOption, providers, strategy)
case "relay":
group = NewRelay(groupName, providers)
group = NewRelay(groupOption, providers)
default:
return nil, fmt.Errorf("%w: %s", errType, groupOption.Type)
}
@ -130,15 +139,15 @@ func getProxies(mapping map[string]C.Proxy, list []string) ([]C.Proxy, error) {
return ps, nil
}
func getProviders(mapping map[string]provider.ProxyProvider, list []string) ([]provider.ProxyProvider, error) {
var ps []provider.ProxyProvider
func getProviders(mapping map[string]types.ProxyProvider, list []string) ([]types.ProxyProvider, error) {
var ps []types.ProxyProvider
for _, name := range list {
p, ok := mapping[name]
if !ok {
return nil, fmt.Errorf("'%s' not found", name)
}
if p.VehicleType() == provider.Compatible {
if p.VehicleType() == types.Compatible {
return nil, fmt.Errorf("proxy group %s can't contains in `use`", name)
}
ps = append(ps, p)

View File

@ -3,14 +3,13 @@ package outboundgroup
import (
"context"
"encoding/json"
"errors"
"fmt"
"github.com/Dreamacro/clash/adapters/outbound"
"github.com/Dreamacro/clash/adapters/provider"
"github.com/Dreamacro/clash/adapter/outbound"
"github.com/Dreamacro/clash/common/singledo"
"github.com/Dreamacro/clash/component/dialer"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/constant/provider"
)
type Relay struct {
@ -19,11 +18,22 @@ type Relay struct {
providers []provider.ProxyProvider
}
// DialContext implements C.ProxyAdapter
func (r *Relay) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
proxies := r.proxies(metadata)
if len(proxies) == 0 {
return nil, errors.New("Proxy does not exist")
var proxies []C.Proxy
for _, proxy := range r.proxies(metadata, true) {
if proxy.Type() != C.Direct {
proxies = append(proxies, proxy)
}
}
switch len(proxies) {
case 0:
return outbound.NewDirect().DialContext(ctx, metadata)
case 1:
return proxies[0].DialContext(ctx, metadata)
}
first := proxies[0]
last := proxies[len(proxies)-1]
@ -56,9 +66,10 @@ func (r *Relay) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn,
return outbound.NewConn(c, r), nil
}
// MarshalJSON implements C.ProxyAdapter
func (r *Relay) MarshalJSON() ([]byte, error) {
var all []string
for _, proxy := range r.rawProxies() {
for _, proxy := range r.rawProxies(false) {
all = append(all, proxy.Name())
}
return json.Marshal(map[string]interface{}{
@ -67,16 +78,16 @@ func (r *Relay) MarshalJSON() ([]byte, error) {
})
}
func (r *Relay) rawProxies() []C.Proxy {
func (r *Relay) rawProxies(touch bool) []C.Proxy {
elm, _, _ := r.single.Do(func() (interface{}, error) {
return getProvidersProxies(r.providers), nil
return getProvidersProxies(r.providers, touch), nil
})
return elm.([]C.Proxy)
}
func (r *Relay) proxies(metadata *C.Metadata) []C.Proxy {
proxies := r.rawProxies()
func (r *Relay) proxies(metadata *C.Metadata, touch bool) []C.Proxy {
proxies := r.rawProxies(touch)
for n, proxy := range proxies {
subproxy := proxy.Unwrap(metadata)
@ -89,9 +100,9 @@ func (r *Relay) proxies(metadata *C.Metadata) []C.Proxy {
return proxies
}
func NewRelay(name string, providers []provider.ProxyProvider) *Relay {
func NewRelay(options *GroupCommonOption, providers []provider.ProxyProvider) *Relay {
return &Relay{
Base: outbound.NewBase(name, "", C.Relay, false),
Base: outbound.NewBase(options.Name, "", C.Relay, false),
single: singledo.NewSingle(defaultGetProxiesDuration),
providers: providers,
}

View File

@ -5,42 +5,51 @@ import (
"encoding/json"
"errors"
"github.com/Dreamacro/clash/adapters/outbound"
"github.com/Dreamacro/clash/adapters/provider"
"github.com/Dreamacro/clash/adapter/outbound"
"github.com/Dreamacro/clash/common/singledo"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/constant/provider"
)
type Selector struct {
*outbound.Base
single *singledo.Single
selected string
providers []provider.ProxyProvider
disableUDP bool
single *singledo.Single
selected string
providers []provider.ProxyProvider
}
// DialContext implements C.ProxyAdapter
func (s *Selector) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
c, err := s.selectedProxy().DialContext(ctx, metadata)
c, err := s.selectedProxy(true).DialContext(ctx, metadata)
if err == nil {
c.AppendToChains(s)
}
return c, err
}
// DialUDP implements C.ProxyAdapter
func (s *Selector) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
pc, err := s.selectedProxy().DialUDP(metadata)
pc, err := s.selectedProxy(true).DialUDP(metadata)
if err == nil {
pc.AppendToChains(s)
}
return pc, err
}
// SupportUDP implements C.ProxyAdapter
func (s *Selector) SupportUDP() bool {
return s.selectedProxy().SupportUDP()
if s.disableUDP {
return false
}
return s.selectedProxy(false).SupportUDP()
}
// MarshalJSON implements C.ProxyAdapter
func (s *Selector) MarshalJSON() ([]byte, error) {
var all []string
for _, proxy := range getProvidersProxies(s.providers) {
for _, proxy := range getProvidersProxies(s.providers, false) {
all = append(all, proxy.Name())
}
@ -52,11 +61,11 @@ func (s *Selector) MarshalJSON() ([]byte, error) {
}
func (s *Selector) Now() string {
return s.selectedProxy().Name()
return s.selectedProxy(false).Name()
}
func (s *Selector) Set(name string) error {
for _, proxy := range getProvidersProxies(s.providers) {
for _, proxy := range getProvidersProxies(s.providers, false) {
if proxy.Name() == name {
s.selected = name
s.single.Reset()
@ -64,16 +73,17 @@ func (s *Selector) Set(name string) error {
}
}
return errors.New("Proxy does not exist")
return errors.New("proxy not exist")
}
// Unwrap implements C.ProxyAdapter
func (s *Selector) Unwrap(metadata *C.Metadata) C.Proxy {
return s.selectedProxy()
return s.selectedProxy(true)
}
func (s *Selector) selectedProxy() C.Proxy {
func (s *Selector) selectedProxy(touch bool) C.Proxy {
elm, _, _ := s.single.Do(func() (interface{}, error) {
proxies := getProvidersProxies(s.providers)
proxies := getProvidersProxies(s.providers, touch)
for _, proxy := range proxies {
if proxy.Name() == s.selected {
return proxy, nil
@ -86,12 +96,13 @@ func (s *Selector) selectedProxy() C.Proxy {
return elm.(C.Proxy)
}
func NewSelector(name string, providers []provider.ProxyProvider) *Selector {
func NewSelector(options *GroupCommonOption, providers []provider.ProxyProvider) *Selector {
selected := providers[0].Proxies()[0].Name()
return &Selector{
Base: outbound.NewBase(name, "", C.Selector, false),
single: singledo.NewSingle(defaultGetProxiesDuration),
providers: providers,
selected: selected,
Base: outbound.NewBase(options.Name, "", C.Selector, false),
single: singledo.NewSingle(defaultGetProxiesDuration),
providers: providers,
selected: selected,
disableUDP: options.DisableUDP,
}
}

View File

@ -5,10 +5,10 @@ import (
"encoding/json"
"time"
"github.com/Dreamacro/clash/adapters/outbound"
"github.com/Dreamacro/clash/adapters/provider"
"github.com/Dreamacro/clash/adapter/outbound"
"github.com/Dreamacro/clash/common/singledo"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/constant/provider"
)
type urlTestOption func(*URLTest)
@ -22,6 +22,7 @@ func urlTestWithTolerance(tolerance uint16) urlTestOption {
type URLTest struct {
*outbound.Base
tolerance uint16
disableUDP bool
fastNode C.Proxy
single *singledo.Single
fastSingle *singledo.Single
@ -29,43 +30,52 @@ type URLTest struct {
}
func (u *URLTest) Now() string {
return u.fast().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) {
c, err = u.fast().DialContext(ctx, metadata)
c, err = u.fast(true).DialContext(ctx, metadata)
if err == nil {
c.AppendToChains(u)
}
return c, err
}
// DialUDP implements C.ProxyAdapter
func (u *URLTest) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
pc, err := u.fast().DialUDP(metadata)
pc, err := u.fast(true).DialUDP(metadata)
if err == nil {
pc.AppendToChains(u)
}
return pc, err
}
// Unwrap implements C.ProxyAdapter
func (u *URLTest) Unwrap(metadata *C.Metadata) C.Proxy {
return u.fast()
return u.fast(true)
}
func (u *URLTest) proxies() []C.Proxy {
func (u *URLTest) proxies(touch bool) []C.Proxy {
elm, _, _ := u.single.Do(func() (interface{}, error) {
return getProvidersProxies(u.providers), nil
return getProvidersProxies(u.providers, touch), nil
})
return elm.([]C.Proxy)
}
func (u *URLTest) fast() C.Proxy {
func (u *URLTest) fast(touch bool) C.Proxy {
elm, _, _ := u.fastSingle.Do(func() (interface{}, error) {
proxies := u.proxies()
proxies := u.proxies(touch)
fast := proxies[0]
min := fast.LastDelay()
fastNotExist := true
for _, proxy := range proxies[1:] {
if u.fastNode != nil && proxy.Name() == u.fastNode.Name() {
fastNotExist = false
}
if !proxy.Alive() {
continue
}
@ -78,7 +88,7 @@ func (u *URLTest) fast() C.Proxy {
}
// tolerance
if u.fastNode == nil || u.fastNode.LastDelay() > fast.LastDelay() + u.tolerance {
if u.fastNode == nil || fastNotExist || !u.fastNode.Alive() || u.fastNode.LastDelay() > fast.LastDelay()+u.tolerance {
u.fastNode = fast
}
@ -88,13 +98,19 @@ func (u *URLTest) fast() C.Proxy {
return elm.(C.Proxy)
}
// SupportUDP implements C.ProxyAdapter
func (u *URLTest) SupportUDP() bool {
return u.fast().SupportUDP()
if u.disableUDP {
return false
}
return u.fast(false).SupportUDP()
}
// MarshalJSON implements C.ProxyAdapter
func (u *URLTest) MarshalJSON() ([]byte, error) {
var all []string
for _, proxy := range u.proxies() {
for _, proxy := range u.proxies(false) {
all = append(all, proxy.Name())
}
return json.Marshal(map[string]interface{}{
@ -117,12 +133,13 @@ func parseURLTestOption(config map[string]interface{}) []urlTestOption {
return opts
}
func NewURLTest(name string, providers []provider.ProxyProvider, options ...urlTestOption) *URLTest {
func NewURLTest(commonOptions *GroupCommonOption, providers []provider.ProxyProvider, options ...urlTestOption) *URLTest {
urlTest := &URLTest{
Base: outbound.NewBase(name, "", C.URLTest, false),
Base: outbound.NewBase(commonOptions.Name, "", C.URLTest, false),
single: singledo.NewSingle(defaultGetProxiesDuration),
fastSingle: singledo.NewSingle(time.Second * 10),
providers: providers,
disableUDP: commonOptions.DisableUDP,
}
for _, option := range options {

View File

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

View File

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

View File

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

View File

@ -0,0 +1,88 @@
package provider
import (
"context"
"time"
"github.com/Dreamacro/clash/common/batch"
C "github.com/Dreamacro/clash/constant"
"go.uber.org/atomic"
)
const (
defaultURLTestTimeout = time.Second * 5
)
type HealthCheckOption struct {
URL string
Interval uint
}
type HealthCheck struct {
url string
proxies []C.Proxy
interval uint
lazy bool
lastTouch *atomic.Int64
done chan struct{}
}
func (hc *HealthCheck) process() {
ticker := time.NewTicker(time.Duration(hc.interval) * time.Second)
go hc.check()
for {
select {
case <-ticker.C:
now := time.Now().Unix()
if !hc.lazy || now-hc.lastTouch.Load() < int64(hc.interval) {
hc.check()
}
case <-hc.done:
ticker.Stop()
return
}
}
}
func (hc *HealthCheck) setProxy(proxies []C.Proxy) {
hc.proxies = proxies
}
func (hc *HealthCheck) auto() bool {
return hc.interval != 0
}
func (hc *HealthCheck) touch() {
hc.lastTouch.Store(time.Now().Unix())
}
func (hc *HealthCheck) check() {
b, _ := batch.New(context.Background(), batch.WithConcurrencyNum(10))
for _, proxy := range hc.proxies {
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()
}
func (hc *HealthCheck) close() {
hc.done <- struct{}{}
}
func NewHealthCheck(proxies []C.Proxy, url string, interval uint, lazy bool) *HealthCheck {
return &HealthCheck{
proxies: proxies,
url: url,
interval: interval,
lazy: lazy,
lastTouch: atomic.NewInt64(0),
done: make(chan struct{}, 1),
}
}

View File

@ -7,6 +7,7 @@ import (
"github.com/Dreamacro/clash/common/structure"
C "github.com/Dreamacro/clash/constant"
types "github.com/Dreamacro/clash/constant/provider"
)
var (
@ -17,6 +18,7 @@ type healthCheckSchema struct {
Enable bool `provider:"enable"`
URL string `provider:"url"`
Interval int `provider:"interval"`
Lazy bool `provider:"lazy,omitempty"`
}
type proxyProviderSchema struct {
@ -27,23 +29,27 @@ type proxyProviderSchema struct {
HealthCheck healthCheckSchema `provider:"health-check,omitempty"`
}
func ParseProxyProvider(name string, mapping map[string]interface{}) (ProxyProvider, error) {
func ParseProxyProvider(name string, mapping map[string]interface{}) (types.ProxyProvider, error) {
decoder := structure.NewDecoder(structure.Option{TagName: "provider", WeaklyTypedInput: true})
schema := &proxyProviderSchema{}
schema := &proxyProviderSchema{
HealthCheck: healthCheckSchema{
Lazy: true,
},
}
if err := decoder.Decode(mapping, schema); err != nil {
return nil, err
}
var hcInterval uint = 0
var hcInterval uint
if schema.HealthCheck.Enable {
hcInterval = uint(schema.HealthCheck.Interval)
}
hc := NewHealthCheck([]C.Proxy{}, schema.HealthCheck.URL, hcInterval)
hc := NewHealthCheck([]C.Proxy{}, schema.HealthCheck.URL, hcInterval, schema.HealthCheck.Lazy)
path := C.Path.Resolve(schema.Path)
var vehicle Vehicle
var vehicle types.Vehicle
switch schema.Type {
case "file":
vehicle = NewFileVehicle(path)

View File

@ -7,8 +7,9 @@ import (
"runtime"
"time"
"github.com/Dreamacro/clash/adapters/outbound"
"github.com/Dreamacro/clash/adapter"
C "github.com/Dreamacro/clash/constant"
types "github.com/Dreamacro/clash/constant/provider"
"gopkg.in/yaml.v2"
)
@ -17,42 +18,6 @@ const (
ReservedName = "default"
)
// Provider Type
const (
Proxy ProviderType = iota
Rule
)
// ProviderType defined
type ProviderType int
func (pt ProviderType) String() string {
switch pt {
case Proxy:
return "Proxy"
case Rule:
return "Rule"
default:
return "Unknown"
}
}
// Provider interface
type Provider interface {
Name() string
VehicleType() VehicleType
Type() ProviderType
Initial() error
Update() error
}
// ProxyProvider interface
type ProxyProvider interface {
Provider
Proxies() []C.Proxy
HealthCheck()
}
type ProxySchema struct {
Proxies []map[string]interface{} `yaml:"proxies"`
}
@ -104,14 +69,19 @@ func (pp *proxySetProvider) Initial() error {
return nil
}
func (pp *proxySetProvider) Type() ProviderType {
return Proxy
func (pp *proxySetProvider) Type() types.ProviderType {
return types.Proxy
}
func (pp *proxySetProvider) Proxies() []C.Proxy {
return pp.proxies
}
func (pp *proxySetProvider) ProxiesWithTouch() []C.Proxy {
pp.healthCheck.touch()
return pp.Proxies()
}
func proxiesParse(buf []byte) (interface{}, error) {
schema := &ProxySchema{}
@ -120,20 +90,20 @@ func proxiesParse(buf []byte) (interface{}, error) {
}
if schema.Proxies == nil {
return nil, errors.New("File must have a `proxies` field")
return nil, errors.New("file must have a `proxies` field")
}
proxies := []C.Proxy{}
for idx, mapping := range schema.Proxies {
proxy, err := outbound.ParseProxy(mapping)
proxy, err := adapter.ParseProxy(mapping)
if err != nil {
return nil, fmt.Errorf("Proxy %d error: %w", idx, err)
return nil, fmt.Errorf("proxy %d error: %w", idx, err)
}
proxies = append(proxies, proxy)
}
if len(proxies) == 0 {
return nil, errors.New("File doesn't have any valid proxy")
return nil, errors.New("file doesn't have any valid proxy")
}
return proxies, nil
@ -152,7 +122,7 @@ func stopProxyProvider(pd *ProxySetProvider) {
pd.fetcher.Destroy()
}
func NewProxySetProvider(name string, interval time.Duration, vehicle Vehicle, hc *HealthCheck) *ProxySetProvider {
func NewProxySetProvider(name string, interval time.Duration, vehicle types.Vehicle, hc *HealthCheck) *ProxySetProvider {
if hc.auto() {
go hc.process()
}
@ -211,25 +181,30 @@ func (cp *compatibleProvider) Initial() error {
return nil
}
func (cp *compatibleProvider) VehicleType() VehicleType {
return Compatible
func (cp *compatibleProvider) VehicleType() types.VehicleType {
return types.Compatible
}
func (cp *compatibleProvider) Type() ProviderType {
return Proxy
func (cp *compatibleProvider) Type() types.ProviderType {
return types.Proxy
}
func (cp *compatibleProvider) Proxies() []C.Proxy {
return cp.proxies
}
func (cp *compatibleProvider) ProxiesWithTouch() []C.Proxy {
cp.healthCheck.touch()
return cp.Proxies()
}
func stopCompatibleProvider(pd *CompatibleProvider) {
pd.healthCheck.close()
}
func NewCompatibleProvider(name string, proxies []C.Proxy, hc *HealthCheck) (*CompatibleProvider, error) {
if len(proxies) == 0 {
return nil, errors.New("Provider need one proxy at least")
return nil, errors.New("provider need one proxy at least")
}
if hc.auto() {

View File

@ -3,48 +3,21 @@ package provider
import (
"context"
"io/ioutil"
"net"
"net/http"
"net/url"
"time"
"github.com/Dreamacro/clash/component/dialer"
types "github.com/Dreamacro/clash/constant/provider"
)
// Vehicle Type
const (
File VehicleType = iota
HTTP
Compatible
)
// VehicleType defined
type VehicleType int
func (v VehicleType) String() string {
switch v {
case File:
return "File"
case HTTP:
return "HTTP"
case Compatible:
return "Compatible"
default:
return "Unknown"
}
}
type Vehicle interface {
Read() ([]byte, error)
Path() string
Type() VehicleType
}
type FileVehicle struct {
path string
}
func (f *FileVehicle) Type() VehicleType {
return File
func (f *FileVehicle) Type() types.VehicleType {
return types.File
}
func (f *FileVehicle) Path() string {
@ -64,8 +37,8 @@ type HTTPVehicle struct {
path string
}
func (h *HTTPVehicle) Type() VehicleType {
return HTTP
func (h *HTTPVehicle) Type() types.VehicleType {
return types.HTTP
}
func (h *HTTPVehicle) Path() string {
@ -99,7 +72,9 @@ func (h *HTTPVehicle) Read() ([]byte, error) {
IdleConnTimeout: 90 * time.Second,
TLSHandshakeTimeout: 10 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
DialContext: dialer.DialContext,
DialContext: func(ctx context.Context, network, address string) (net.Conn, error) {
return dialer.DialContext(ctx, network, address)
},
}
client := http.Client{Transport: transport}
@ -107,6 +82,7 @@ func (h *HTTPVehicle) Read() ([]byte, error) {
if err != nil {
return nil, err
}
defer resp.Body.Close()
buf, err := ioutil.ReadAll(resp.Body)
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,78 +0,0 @@
package outbound
import (
"context"
"fmt"
"net"
"strconv"
"github.com/Dreamacro/clash/common/structure"
"github.com/Dreamacro/clash/component/dialer"
obfs "github.com/Dreamacro/clash/component/simple-obfs"
"github.com/Dreamacro/clash/component/snell"
C "github.com/Dreamacro/clash/constant"
)
type Snell struct {
*Base
psk []byte
obfsOption *simpleObfsOption
}
type SnellOption struct {
Name string `proxy:"name"`
Server string `proxy:"server"`
Port int `proxy:"port"`
Psk string `proxy:"psk"`
ObfsOpts map[string]interface{} `proxy:"obfs-opts,omitempty"`
}
func (s *Snell) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
switch s.obfsOption.Mode {
case "tls":
c = obfs.NewTLSObfs(c, s.obfsOption.Host)
case "http":
_, port, _ := net.SplitHostPort(s.addr)
c = obfs.NewHTTPObfs(c, s.obfsOption.Host, port)
}
c = snell.StreamConn(c, s.psk)
port, _ := strconv.Atoi(metadata.DstPort)
err := snell.WriteHeader(c, metadata.String(), uint(port))
return c, err
}
func (s *Snell) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
c, err := dialer.DialContext(ctx, "tcp", s.addr)
if err != nil {
return nil, fmt.Errorf("%s connect error: %w", s.addr, err)
}
tcpKeepAlive(c)
c, err = s.StreamConn(c, metadata)
return NewConn(c, s), err
}
func NewSnell(option SnellOption) (*Snell, error) {
addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port))
psk := []byte(option.Psk)
decoder := structure.NewDecoder(structure.Option{TagName: "obfs", WeaklyTypedInput: true})
obfsOption := &simpleObfsOption{Host: "bing.com"}
if err := decoder.Decode(option.ObfsOpts, obfsOption); err != nil {
return nil, fmt.Errorf("snell %s initialize obfs error: %w", addr, err)
}
if obfsOption.Mode != "tls" && obfsOption.Mode != "http" {
return nil, fmt.Errorf("snell %s obfs mode error: %s", addr, obfsOption.Mode)
}
return &Snell{
Base: &Base{
name: option.Name,
addr: addr,
tp: C.Snell,
},
psk: psk,
obfsOption: obfsOption,
}, nil
}

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
}

View File

@ -1,208 +0,0 @@
package outbound
import (
"context"
"errors"
"fmt"
"net"
"net/http"
"strconv"
"strings"
"github.com/Dreamacro/clash/component/dialer"
"github.com/Dreamacro/clash/component/resolver"
"github.com/Dreamacro/clash/component/vmess"
C "github.com/Dreamacro/clash/constant"
)
type Vmess struct {
*Base
client *vmess.Client
option *VmessOption
}
type VmessOption struct {
Name string `proxy:"name"`
Server string `proxy:"server"`
Port int `proxy:"port"`
UUID string `proxy:"uuid"`
AlterID int `proxy:"alterId"`
Cipher string `proxy:"cipher"`
TLS bool `proxy:"tls,omitempty"`
UDP bool `proxy:"udp,omitempty"`
Network string `proxy:"network,omitempty"`
HTTPOpts HTTPOptions `proxy:"http-opts,omitempty"`
WSPath string `proxy:"ws-path,omitempty"`
WSHeaders map[string]string `proxy:"ws-headers,omitempty"`
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
ServerName string `proxy:"servername,omitempty"`
}
type HTTPOptions struct {
Method string `proxy:"method,omitempty"`
Path []string `proxy:"path,omitempty"`
Headers map[string][]string `proxy:"headers,omitempty"`
}
func (v *Vmess) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
var err error
switch v.option.Network {
case "ws":
host, port, _ := net.SplitHostPort(v.addr)
wsOpts := &vmess.WebsocketConfig{
Host: host,
Port: port,
Path: v.option.WSPath,
}
if len(v.option.WSHeaders) != 0 {
header := http.Header{}
for key, value := range v.option.WSHeaders {
header.Add(key, value)
}
wsOpts.Headers = header
}
if v.option.TLS {
wsOpts.TLS = true
wsOpts.SessionCache = getClientSessionCache()
wsOpts.SkipCertVerify = v.option.SkipCertVerify
wsOpts.ServerName = v.option.ServerName
}
c, err = vmess.StreamWebsocketConn(c, wsOpts)
case "http":
host, _, _ := net.SplitHostPort(v.addr)
httpOpts := &vmess.HTTPConfig{
Host: host,
Method: v.option.HTTPOpts.Method,
Path: v.option.HTTPOpts.Path,
Headers: v.option.HTTPOpts.Headers,
}
c = vmess.StreamHTTPConn(c, httpOpts)
default:
// handle TLS
if v.option.TLS {
host, _, _ := net.SplitHostPort(v.addr)
tlsOpts := &vmess.TLSConfig{
Host: host,
SkipCertVerify: v.option.SkipCertVerify,
SessionCache: getClientSessionCache(),
}
if v.option.ServerName != "" {
tlsOpts.Host = v.option.ServerName
}
c, err = vmess.StreamTLSConn(c, tlsOpts)
}
}
if err != nil {
return nil, err
}
return v.client.StreamConn(c, parseVmessAddr(metadata))
}
func (v *Vmess) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
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)
c, err = v.StreamConn(c, metadata)
return NewConn(c, v), err
}
func (v *Vmess) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
// vmess use stream-oriented udp, so clash needs a net.UDPAddr
if !metadata.Resolved() {
ip, err := resolver.ResolveIP(metadata.Host)
if err != nil {
return nil, errors.New("can't resolve ip")
}
metadata.DstIP = ip
}
ctx, cancel := context.WithTimeout(context.Background(), tcpTimeout)
defer cancel()
c, err := dialer.DialContext(ctx, "tcp", v.addr)
if err != nil {
return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error())
}
tcpKeepAlive(c)
c, err = v.StreamConn(c, metadata)
if err != nil {
return nil, fmt.Errorf("new vmess client error: %v", err)
}
return newPacketConn(&vmessPacketConn{Conn: c, rAddr: metadata.UDPAddr()}, v), nil
}
func NewVmess(option VmessOption) (*Vmess, error) {
security := strings.ToLower(option.Cipher)
client, err := vmess.NewClient(vmess.Config{
UUID: option.UUID,
AlterID: uint16(option.AlterID),
Security: security,
HostName: option.Server,
Port: strconv.Itoa(option.Port),
})
if err != nil {
return nil, err
}
return &Vmess{
Base: &Base{
name: option.Name,
addr: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)),
tp: C.Vmess,
udp: true,
},
client: client,
option: &option,
}, nil
}
func parseVmessAddr(metadata *C.Metadata) *vmess.DstAddr {
var addrType byte
var addr []byte
switch metadata.AddrType {
case C.AtypIPv4:
addrType = byte(vmess.AtypIPv4)
addr = make([]byte, net.IPv4len)
copy(addr[:], metadata.DstIP.To4())
case C.AtypIPv6:
addrType = byte(vmess.AtypIPv6)
addr = make([]byte, net.IPv6len)
copy(addr[:], metadata.DstIP.To16())
case C.AtypDomainName:
addrType = byte(vmess.AtypDomainName)
addr = make([]byte, len(metadata.Host)+1)
addr[0] = byte(len(metadata.Host))
copy(addr[1:], []byte(metadata.Host))
}
port, _ := strconv.Atoi(metadata.DstPort)
return &vmess.DstAddr{
UDP: metadata.NetWork == C.UDP,
AddrType: addrType,
Addr: addr,
Port: uint(port),
}
}
type vmessPacketConn struct {
net.Conn
rAddr net.Addr
}
func (uc *vmessPacketConn) WriteTo(b []byte, addr net.Addr) (int, error) {
return uc.Conn.Write(b)
}
func (uc *vmessPacketConn) ReadFrom(b []byte) (int, net.Addr, error) {
n, err := uc.Conn.Read(b)
return n, uc.rAddr, err
}

View File

@ -1,20 +0,0 @@
package outboundgroup
import (
"time"
"github.com/Dreamacro/clash/adapters/provider"
C "github.com/Dreamacro/clash/constant"
)
const (
defaultGetProxiesDuration = time.Second * 5
)
func getProvidersProxies(providers []provider.ProxyProvider) []C.Proxy {
proxies := []C.Proxy{}
for _, provider := range providers {
proxies = append(proxies, provider.Proxies()...)
}
return proxies
}

View File

@ -1,125 +0,0 @@
package outboundgroup
import (
"context"
"encoding/json"
"net"
"github.com/Dreamacro/clash/adapters/outbound"
"github.com/Dreamacro/clash/adapters/provider"
"github.com/Dreamacro/clash/common/murmur3"
"github.com/Dreamacro/clash/common/singledo"
C "github.com/Dreamacro/clash/constant"
"golang.org/x/net/publicsuffix"
)
type LoadBalance struct {
*outbound.Base
single *singledo.Single
maxRetry int
providers []provider.ProxyProvider
}
func getKey(metadata *C.Metadata) string {
if metadata.Host != "" {
// ip host
if ip := net.ParseIP(metadata.Host); ip != nil {
return metadata.Host
}
if etld, err := publicsuffix.EffectiveTLDPlusOne(metadata.Host); err == nil {
return etld
}
}
if metadata.DstIP == nil {
return ""
}
return metadata.DstIP.String()
}
func jumpHash(key uint64, buckets int32) int32 {
var b, j int64
for j < int64(buckets) {
b = j
key = key*2862933555777941757 + 1
j = int64(float64(b+1) * (float64(int64(1)<<31) / float64((key>>33)+1)))
}
return int32(b)
}
func (lb *LoadBalance) DialContext(ctx context.Context, metadata *C.Metadata) (c C.Conn, err error) {
defer func() {
if err == nil {
c.AppendToChains(lb)
}
}()
proxy := lb.Unwrap(metadata)
c, err = proxy.DialContext(ctx, metadata)
return
}
func (lb *LoadBalance) DialUDP(metadata *C.Metadata) (pc C.PacketConn, err error) {
defer func() {
if err == nil {
pc.AppendToChains(lb)
}
}()
proxy := lb.Unwrap(metadata)
return proxy.DialUDP(metadata)
}
func (lb *LoadBalance) SupportUDP() bool {
return true
}
func (lb *LoadBalance) Unwrap(metadata *C.Metadata) C.Proxy {
key := uint64(murmur3.Sum32([]byte(getKey(metadata))))
proxies := lb.proxies()
buckets := int32(len(proxies))
for i := 0; i < lb.maxRetry; i, key = i+1, key+1 {
idx := jumpHash(key, buckets)
proxy := proxies[idx]
if proxy.Alive() {
return proxy
}
}
return proxies[0]
}
func (lb *LoadBalance) proxies() []C.Proxy {
elm, _, _ := lb.single.Do(func() (interface{}, error) {
return getProvidersProxies(lb.providers), nil
})
return elm.([]C.Proxy)
}
func (lb *LoadBalance) MarshalJSON() ([]byte, error) {
var all []string
for _, proxy := range lb.proxies() {
all = append(all, proxy.Name())
}
return json.Marshal(map[string]interface{}{
"type": lb.Type().String(),
"all": all,
})
}
func NewLoadBalance(name string, providers []provider.ProxyProvider) *LoadBalance {
return &LoadBalance{
Base: outbound.NewBase(name, "", C.LoadBalance, false),
single: singledo.NewSingle(defaultGetProxiesDuration),
maxRetry: 3,
providers: providers,
}
}

View File

@ -1,70 +0,0 @@
package provider
import (
"context"
"time"
C "github.com/Dreamacro/clash/constant"
)
const (
defaultURLTestTimeout = time.Second * 5
)
type HealthCheckOption struct {
URL string
Interval uint
}
type HealthCheck struct {
url string
proxies []C.Proxy
interval uint
done chan struct{}
}
func (hc *HealthCheck) process() {
ticker := time.NewTicker(time.Duration(hc.interval) * time.Second)
go hc.check()
for {
select {
case <-ticker.C:
hc.check()
case <-hc.done:
ticker.Stop()
return
}
}
}
func (hc *HealthCheck) setProxy(proxies []C.Proxy) {
hc.proxies = proxies
}
func (hc *HealthCheck) auto() bool {
return hc.interval != 0
}
func (hc *HealthCheck) check() {
ctx, cancel := context.WithTimeout(context.Background(), defaultURLTestTimeout)
for _, proxy := range hc.proxies {
go proxy.URLTest(ctx, hc.url)
}
<-ctx.Done()
cancel()
}
func (hc *HealthCheck) close() {
hc.done <- struct{}{}
}
func NewHealthCheck(proxies []C.Proxy, url string, interval uint) *HealthCheck {
return &HealthCheck{
proxies: proxies,
url: url,
interval: interval,
done: make(chan struct{}, 1),
}
}

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

@ -121,7 +121,7 @@ func (c *LruCache) Set(key interface{}, value interface{}) {
c.SetWithExpire(key, value, time.Unix(expires, 0))
}
// SetWithExpire stores the interface{} representation of a response for a given key and given exires.
// SetWithExpire stores the interface{} representation of a response for a given key and given expires.
// The expires time will round to second.
func (c *LruCache) SetWithExpire(key interface{}, value interface{}, expires time.Time) {
c.mu.Lock()
@ -146,6 +146,23 @@ func (c *LruCache) SetWithExpire(key interface{}, value interface{}, expires tim
c.maybeDeleteOldest()
}
// CloneTo clone and overwrite elements to another LruCache
func (c *LruCache) CloneTo(n *LruCache) {
c.mu.Lock()
defer c.mu.Unlock()
n.mu.Lock()
defer n.mu.Unlock()
n.lru = list.New()
n.cache = make(map[interface{}]*list.Element)
for e := c.lru.Front(); e != nil; e = e.Next() {
elm := e.Value.(*entry)
n.cache[elm.key] = n.lru.PushBack(elm)
}
}
func (c *LruCache) get(key interface{}) *entry {
c.mu.Lock()
defer c.mu.Unlock()
@ -171,7 +188,7 @@ func (c *LruCache) get(key interface{}) *entry {
}
// Delete removes the value associated with a key.
func (c *LruCache) Delete(key string) {
func (c *LruCache) Delete(key interface{}) {
c.mu.Lock()
if le, ok := c.cache[key]; ok {

View File

@ -164,3 +164,21 @@ func TestStale(t *testing.T) {
assert.Equal(t, tenSecBefore, expires)
assert.Equal(t, true, exist)
}
func TestCloneTo(t *testing.T) {
o := NewLRUCache(WithSize(10))
o.Set("1", 1)
o.Set("2", 2)
n := NewLRUCache(WithSize(2))
n.Set("3", 3)
n.Set("4", 4)
o.CloneTo(n)
assert.False(t, n.Exist("3"))
assert.True(t, n.Exist("1"))
n.Set("5", 5)
assert.False(t, n.Exist("1"))
}

View File

@ -1,4 +1,4 @@
package mixed
package net
import (
"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

@ -2,11 +2,11 @@ package observable
import (
"sync"
"sync/atomic"
"testing"
"time"
"github.com/stretchr/testify/assert"
"go.uber.org/atomic"
)
func iterator(item []interface{}) chan interface{} {
@ -33,25 +33,25 @@ func TestObservable(t *testing.T) {
assert.Equal(t, count, 5)
}
func TestObservable_MutilSubscribe(t *testing.T) {
func TestObservable_MultiSubscribe(t *testing.T) {
iter := iterator([]interface{}{1, 2, 3, 4, 5})
src := NewObservable(iter)
ch1, _ := src.Subscribe()
ch2, _ := src.Subscribe()
var count int32
var count = atomic.NewInt32(0)
var wg sync.WaitGroup
wg.Add(2)
waitCh := func(ch <-chan interface{}) {
for range ch {
atomic.AddInt32(&count, 1)
count.Inc()
}
wg.Done()
}
go waitCh(ch1)
go waitCh(ch2)
wg.Wait()
assert.Equal(t, int32(10), count)
assert.Equal(t, int32(10), count.Load())
}
func TestObservable_UnSubscribe(t *testing.T) {
@ -113,3 +113,34 @@ func TestObservable_SubscribeGoroutineLeak(t *testing.T) {
_, more := <-list[0]
assert.False(t, more)
}
func Benchmark_Observable_1000(b *testing.B) {
ch := make(chan interface{})
o := NewObservable(ch)
num := 1000
subs := []Subscription{}
for i := 0; i < num; i++ {
sub, _ := o.Subscribe()
subs = append(subs, sub)
}
wg := sync.WaitGroup{}
wg.Add(num)
b.ResetTimer()
for _, sub := range subs {
go func(s Subscription) {
for range s {
}
wg.Done()
}(sub)
}
for i := 0; i < b.N; i++ {
ch <- i
}
close(ch)
wg.Wait()
}

View File

@ -2,34 +2,32 @@ package observable
import (
"sync"
"gopkg.in/eapache/channels.v1"
)
type Subscription <-chan interface{}
type Subscriber struct {
buffer *channels.InfiniteChannel
buffer chan interface{}
once sync.Once
}
func (s *Subscriber) Emit(item interface{}) {
s.buffer.In() <- item
s.buffer <- item
}
func (s *Subscriber) Out() Subscription {
return s.buffer.Out()
return s.buffer
}
func (s *Subscriber) Close() {
s.once.Do(func() {
s.buffer.Close()
close(s.buffer)
})
}
func newSubscriber() *Subscriber {
sub := &Subscriber{
buffer: channels.NewInfiniteChannel(),
buffer: make(chan interface{}, 200),
}
return sub
}

View File

@ -8,11 +8,7 @@ import (
"sync"
)
var defaultAllocator *Allocator
func init() {
defaultAllocator = NewAllocator()
}
var defaultAllocator = NewAllocator()
// Allocator for incoming frames, optimized to prevent overwriting after zeroing
type Allocator struct {
@ -55,11 +51,13 @@ func (alloc *Allocator) Put(buf []byte) error {
if cap(buf) == 0 || cap(buf) > 65536 || cap(buf) != 1<<bits {
return errors.New("allocator Put() incorrect buffer size")
}
//lint:ignore SA6002 ignore temporarily
alloc.buffers[bits].Put(buf)
return nil
}
// msb return the pos of most significiant bit
// msb return the pos of most significant bit
func msb(size int) uint16 {
return uint16(bits.Len32(uint32(size)) - 1)
}

View File

@ -25,11 +25,11 @@ func TestAllocGet(t *testing.T) {
func TestAllocPut(t *testing.T) {
alloc := NewAllocator()
assert.NotNil(t, alloc.Put(nil), "put nil misbehavior")
assert.NotNil(t, alloc.Put(make([]byte, 3, 3)), "put elem:3 []bytes misbehavior")
assert.Nil(t, alloc.Put(make([]byte, 4, 4)), "put elem:4 []bytes misbehavior")
assert.NotNil(t, alloc.Put(make([]byte, 3)), "put elem:3 []bytes misbehavior")
assert.Nil(t, alloc.Put(make([]byte, 4)), "put elem:4 []bytes misbehavior")
assert.Nil(t, alloc.Put(make([]byte, 1023, 1024)), "put elem:1024 []bytes misbehavior")
assert.Nil(t, alloc.Put(make([]byte, 65536, 65536)), "put elem:65536 []bytes misbehavior")
assert.NotNil(t, alloc.Put(make([]byte, 65537, 65537)), "put elem:65537 []bytes misbehavior")
assert.Nil(t, alloc.Put(make([]byte, 65536)), "put elem:65536 []bytes misbehavior")
assert.NotNil(t, alloc.Put(make([]byte, 65537)), "put elem:65537 []bytes misbehavior")
}
func TestAllocPutThenGet(t *testing.T) {

View File

@ -24,6 +24,8 @@ type Result struct {
Err error
}
// Do single.Do likes sync.singleFlight
//lint:ignore ST1008 it likes sync.singleFlight
func (s *Single) Do(fn func() (interface{}, error)) (v interface{}, err error, shared bool) {
s.mux.Lock()
now := time.Now()

View File

@ -2,17 +2,17 @@ package singledo
import (
"sync"
"sync/atomic"
"testing"
"time"
"github.com/stretchr/testify/assert"
"go.uber.org/atomic"
)
func TestBasic(t *testing.T) {
single := NewSingle(time.Millisecond * 30)
foo := 0
var shardCount int32 = 0
var shardCount = atomic.NewInt32(0)
call := func() (interface{}, error) {
foo++
time.Sleep(time.Millisecond * 5)
@ -26,7 +26,7 @@ func TestBasic(t *testing.T) {
go func() {
_, _, shard := single.Do(call)
if shard {
atomic.AddInt32(&shardCount, 1)
shardCount.Inc()
}
wg.Done()
}()
@ -34,7 +34,7 @@ func TestBasic(t *testing.T) {
wg.Wait()
assert.Equal(t, 1, foo)
assert.Equal(t, int32(4), shardCount)
assert.Equal(t, int32(4), shardCount.Load())
}
func TestTimer(t *testing.T) {

View File

@ -1,3 +1,4 @@
//go:build !linux
// +build !linux
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

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

View File

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

View File

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

View File

@ -8,33 +8,7 @@ import (
"github.com/Dreamacro/clash/component/resolver"
)
func Dialer() (*net.Dialer, error) {
dialer := &net.Dialer{}
if DialerHook != nil {
if err := DialerHook(dialer); err != nil {
return nil, err
}
}
return dialer, nil
}
func ListenConfig() (*net.ListenConfig, error) {
cfg := &net.ListenConfig{}
if ListenConfigHook != nil {
if err := ListenConfigHook(cfg); err != nil {
return nil, err
}
}
return cfg, nil
}
func Dial(network, address string) (net.Conn, error) {
return DialContext(context.Background(), network, address)
}
func DialContext(ctx context.Context, network, address string) (net.Conn, error) {
func DialContext(ctx context.Context, network, address string, options ...Option) (net.Conn, error) {
switch network {
case "tcp4", "tcp6", "udp4", "udp6":
host, port, err := net.SplitHostPort(address)
@ -42,11 +16,6 @@ func DialContext(ctx context.Context, network, address string) (net.Conn, error)
return nil, err
}
dialer, err := Dialer()
if err != nil {
return nil, err
}
var ip net.IP
switch network {
case "tcp4", "udp4":
@ -54,41 +23,70 @@ func DialContext(ctx context.Context, network, address string) (net.Conn, error)
default:
ip, err = resolver.ResolveIPv6(host)
}
if err != nil {
return nil, err
}
if DialHook != nil {
if err := DialHook(dialer, network, ip); err != nil {
return nil, err
}
}
return dialer.DialContext(ctx, network, net.JoinHostPort(ip.String(), port))
return dialContext(ctx, network, ip, port, options)
case "tcp", "udp":
return dualStackDialContext(ctx, network, address)
return dualStackDialContext(ctx, network, address, options)
default:
return nil, errors.New("network invalid")
}
}
func ListenPacket(network, address string) (net.PacketConn, error) {
lc, err := ListenConfig()
if err != nil {
return nil, err
func ListenPacket(ctx context.Context, network, address string, options ...Option) (net.PacketConn, error) {
cfg := &config{}
if !cfg.skipDefault {
for _, o := range DefaultOptions {
o(cfg)
}
}
if ListenPacketHook != nil && address == "" {
ip, err := ListenPacketHook()
for _, o := range options {
o(cfg)
}
lc := &net.ListenConfig{}
if cfg.interfaceName != "" {
addr, err := bindIfaceToListenConfig(cfg.interfaceName, lc, network, address)
if err != nil {
return nil, err
}
address = net.JoinHostPort(ip.String(), "0")
address = addr
}
return lc.ListenPacket(context.Background(), network, address)
if cfg.addrReuse {
addrReuseToListenConfig(lc)
}
return lc.ListenPacket(ctx, network, address)
}
func dualStackDialContext(ctx context.Context, network, address string) (net.Conn, error) {
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 dialer.DialContext(ctx, network, net.JoinHostPort(destination.String(), port))
}
func dualStackDialContext(ctx context.Context, network, address string, options []Option) (net.Conn, error) {
host, port, err := net.SplitHostPort(address)
if err != nil {
return nil, err
@ -119,12 +117,6 @@ func dualStackDialContext(ctx context.Context, network, address string) (net.Con
}
}()
dialer, err := Dialer()
if err != nil {
result.error = err
return
}
var ip net.IP
if ipv6 {
ip, result.error = resolver.ResolveIPv6(host)
@ -136,39 +128,33 @@ func dualStackDialContext(ctx context.Context, network, address string) (net.Con
}
result.resolved = true
if DialHook != nil {
if result.error = DialHook(dialer, network, ip); result.error != nil {
return
}
}
result.Conn, result.error = dialer.DialContext(ctx, network, net.JoinHostPort(ip.String(), port))
result.Conn, result.error = dialContext(ctx, network, ip, port, options)
}
go startRacer(ctx, network+"4", host, false)
go startRacer(ctx, network+"6", host, true)
for {
select {
case res := <-results:
if res.error == nil {
return res.Conn, nil
}
for res := range results {
if res.error == nil {
return res.Conn, nil
}
if !res.ipv6 {
primary = res
if !res.ipv6 {
primary = res
} else {
fallback = res
}
if primary.done && fallback.done {
if primary.resolved {
return nil, primary.error
} else if fallback.resolved {
return nil, fallback.error
} else {
fallback = res
}
if primary.done && fallback.done {
if primary.resolved {
return nil, primary.error
} else if fallback.resolved {
return nil, fallback.error
} else {
return nil, primary.error
}
return nil, primary.error
}
}
}
return nil, errors.New("never touched")
}

View File

@ -1,148 +0,0 @@
package dialer
import (
"errors"
"net"
"time"
"github.com/Dreamacro/clash/common/singledo"
)
type DialerHookFunc = func(dialer *net.Dialer) error
type DialHookFunc = func(dialer *net.Dialer, network string, ip net.IP) error
type ListenConfigHookFunc = func(*net.ListenConfig) error
type ListenPacketHookFunc = func() (net.IP, error)
var (
DialerHook DialerHookFunc
DialHook DialHookFunc
ListenConfigHook ListenConfigHookFunc
ListenPacketHook ListenPacketHookFunc
)
var (
ErrAddrNotFound = errors.New("addr not found")
ErrNetworkNotSupport = errors.New("network not support")
)
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 ListenPacketWithInterface(name string) ListenPacketHookFunc {
single := singledo.NewSingle(5 * time.Second)
return func() (net.IP, error) {
elm, err, _ := single.Do(func() (interface{}, error) {
iface, err := net.InterfaceByName(name)
if err != nil {
return nil, err
}
addrs, err := iface.Addrs()
if err != nil {
return nil, err
}
return addrs, nil
})
if err != nil {
return nil, err
}
addrs := elm.([]net.Addr)
for _, elm := range addrs {
addr, ok := elm.(*net.IPNet)
if !ok || addr.IP.To4() == nil {
continue
}
return addr.IP, nil
}
return nil, ErrAddrNotFound
}
}
func DialerWithInterface(name string) DialHookFunc {
single := singledo.NewSingle(5 * time.Second)
return func(dialer *net.Dialer, network string, ip net.IP) error {
elm, err, _ := single.Do(func() (interface{}, error) {
iface, err := net.InterfaceByName(name)
if err != nil {
return nil, err
}
addrs, err := iface.Addrs()
if err != nil {
return nil, err
}
return addrs, nil
})
if err != nil {
return err
}
addrs := elm.([]net.Addr)
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
}
}

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

View File

@ -17,6 +17,7 @@ type Pool struct {
offset uint32
mux sync.Mutex
host *trie.DomainTrie
ipnet *net.IPNet
cache *cache.LruCache
}
@ -89,6 +90,16 @@ func (p *Pool) Gateway() net.IP {
return uintToIP(p.gateway)
}
// IPNet return raw ipnet
func (p *Pool) IPNet() *net.IPNet {
return p.ipnet
}
// PatchFrom clone cache from old pool
func (p *Pool) PatchFrom(o *Pool) {
o.cache.CloneTo(p.cache)
}
func (p *Pool) get(host string) net.IP {
current := p.offset
for {
@ -116,7 +127,7 @@ func ipToUint(ip net.IP) uint32 {
}
func uintToIP(v uint32) net.IP {
return net.IPv4(byte(v>>24), byte(v>>16), byte(v>>8), byte(v))
return net.IP{byte(v >> 24), byte(v >> 16), byte(v >> 8), byte(v)}
}
// New return Pool instance
@ -136,6 +147,7 @@ func New(ipnet *net.IPNet, size int, host *trie.DomainTrie) (*Pool, error) {
max: max,
gateway: min - 1,
host: host,
ipnet: ipnet,
cache: cache.NewLRUCache(cache.WithSize(size * 2)),
}, nil
}

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

@ -22,9 +22,9 @@ func (t *Table) Get(key string) C.PacketConn {
return item.(C.PacketConn)
}
func (t *Table) GetOrCreateLock(key string) (*sync.WaitGroup, bool) {
item, loaded := t.mapping.LoadOrStore(key, &sync.WaitGroup{})
return item.(*sync.WaitGroup), loaded
func (t *Table) GetOrCreateLock(key string) (*sync.Cond, bool) {
item, loaded := t.mapping.LoadOrStore(key, sync.NewCond(&sync.Mutex{}))
return item.(*sync.Cond), loaded
}
func (t *Table) Delete(key string) {

114
component/pool/pool.go Normal file
View File

@ -0,0 +1,114 @@
package pool
import (
"context"
"runtime"
"time"
)
type Factory = func(context.Context) (interface{}, error)
type entry struct {
elm interface{}
time time.Time
}
type Option func(*pool)
// WithEvict set the evict callback
func WithEvict(cb func(interface{})) Option {
return func(p *pool) {
p.evict = cb
}
}
// WithAge defined element max age (millisecond)
func WithAge(maxAge int64) Option {
return func(p *pool) {
p.maxAge = maxAge
}
}
// WithSize defined max size of Pool
func WithSize(maxSize int) Option {
return func(p *pool) {
p.ch = make(chan interface{}, maxSize)
}
}
// Pool is for GC, see New for detail
type Pool struct {
*pool
}
type pool struct {
ch chan interface{}
factory Factory
evict func(interface{})
maxAge int64
}
func (p *pool) GetContext(ctx context.Context) (interface{}, error) {
now := time.Now()
for {
select {
case item := <-p.ch:
elm := item.(*entry)
if p.maxAge != 0 && now.Sub(item.(*entry).time).Milliseconds() > p.maxAge {
if p.evict != nil {
p.evict(elm.elm)
}
continue
}
return elm.elm, nil
default:
return p.factory(ctx)
}
}
}
func (p *pool) Get() (interface{}, error) {
return p.GetContext(context.Background())
}
func (p *pool) Put(item interface{}) {
e := &entry{
elm: item,
time: time.Now(),
}
select {
case p.ch <- e:
return
default:
// pool is full
if p.evict != nil {
p.evict(item)
}
return
}
}
func recycle(p *Pool) {
for item := range p.pool.ch {
if p.pool.evict != nil {
p.pool.evict(item.(*entry).elm)
}
}
}
func New(factory Factory, options ...Option) *Pool {
p := &pool{
ch: make(chan interface{}, 10),
factory: factory,
}
for _, option := range options {
option(p)
}
P := &Pool{p}
runtime.SetFinalizer(P, recycle)
return P
}

View File

@ -0,0 +1,73 @@
package pool
import (
"context"
"testing"
"time"
"github.com/stretchr/testify/assert"
)
func lg() Factory {
initial := -1
return func(context.Context) (interface{}, error) {
initial++
return initial, nil
}
}
func TestPool_Basic(t *testing.T) {
g := lg()
pool := New(g)
elm, _ := pool.Get()
assert.Equal(t, 0, elm.(int))
pool.Put(elm)
elm, _ = pool.Get()
assert.Equal(t, 0, elm.(int))
elm, _ = pool.Get()
assert.Equal(t, 1, elm.(int))
}
func TestPool_MaxSize(t *testing.T) {
g := lg()
size := 5
pool := New(g, WithSize(size))
items := []interface{}{}
for i := 0; i < size; i++ {
item, _ := pool.Get()
items = append(items, item)
}
extra, _ := pool.Get()
assert.Equal(t, size, extra.(int))
for _, item := range items {
pool.Put(item)
}
pool.Put(extra)
for _, item := range items {
elm, _ := pool.Get()
assert.Equal(t, item.(int), elm.(int))
}
}
func TestPool_MaxAge(t *testing.T) {
g := lg()
pool := New(g, WithAge(20))
elm, _ := pool.Get()
pool.Put(elm)
elm, _ = pool.Get()
assert.Equal(t, 0, elm.(int))
pool.Put(elm)
time.Sleep(time.Millisecond * 22)
elm, _ = pool.Get()
assert.Equal(t, 1, elm.(int))
}

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

@ -0,0 +1,104 @@
package process
import (
"encoding/binary"
"net"
"path/filepath"
"syscall"
"unsafe"
"golang.org/x/sys/unix"
)
const (
procpidpathinfo = 0xb
procpidpathinfosize = 1024
proccallnumpidinfo = 0x2
)
func findProcessName(network string, ip net.IP, port int) (string, error) {
var spath string
switch network {
case TCP:
spath = "net.inet.tcp.pcblist_n"
case UDP:
spath = "net.inet.udp.pcblist_n"
default:
return "", ErrInvalidNetwork
}
isIPv4 := ip.To4() != nil
value, err := syscall.Sysctl(spath)
if err != nil {
return "", err
}
buf := []byte(value)
// from darwin-xnu/bsd/netinet/in_pcblist.c:get_pcblist_n
// size/offset are round up (aligned) to 8 bytes in darwin
// rup8(sizeof(xinpcb_n)) + rup8(sizeof(xsocket_n)) +
// 2 * rup8(sizeof(xsockbuf_n)) + rup8(sizeof(xsockstat_n))
itemSize := 384
if network == TCP {
// rup8(sizeof(xtcpcb_n))
itemSize += 208
}
// skip the first xinpgen(24 bytes) block
for i := 24; i+itemSize <= len(buf); i += itemSize {
// offset of xinpcb_n and xsocket_n
inp, so := i, i+104
srcPort := binary.BigEndian.Uint16(buf[inp+18 : inp+20])
if uint16(port) != srcPort {
continue
}
// xinpcb_n.inp_vflag
flag := buf[inp+44]
var srcIP net.IP
switch {
case flag&0x1 > 0 && isIPv4:
// ipv4
srcIP = net.IP(buf[inp+76 : inp+80])
case flag&0x2 > 0 && !isIPv4:
// ipv6
srcIP = net.IP(buf[inp+64 : inp+80])
default:
continue
}
if !ip.Equal(srcIP) {
continue
}
// xsocket_n.so_last_pid
pid := readNativeUint32(buf[so+68 : so+72])
return getExecPathFromPID(pid)
}
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 {
return *(*uint32)(unsafe.Pointer(&b[0]))
}

View File

@ -0,0 +1,234 @@
package process
import (
"encoding/binary"
"fmt"
"net"
"path/filepath"
"strconv"
"strings"
"sync"
"syscall"
"unsafe"
"github.com/Dreamacro/clash/log"
)
// store process name for when dealing with multiple PROCESS-NAME rules
var (
defaultSearcher *searcher
once sync.Once
)
func findProcessName(network string, ip net.IP, srcPort int) (string, error) {
once.Do(func() {
if err := initSearcher(); err != nil {
log.Errorln("Initialize PROCESS-NAME failed: %s", err.Error())
log.Warnln("All PROCESS-NAME rules will be skipped")
return
}
})
if defaultSearcher == nil {
return "", ErrPlatformNotSupport
}
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) {
buf := make([]byte, 2048)
size := uint64(len(buf))
// CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, pid
mib := [4]uint32{1, 14, 12, pid}
_, _, errno := syscall.Syscall6(
syscall.SYS___SYSCTL,
uintptr(unsafe.Pointer(&mib[0])),
uintptr(len(mib)),
uintptr(unsafe.Pointer(&buf[0])),
uintptr(unsafe.Pointer(&size)),
0,
0)
if errno != 0 || size == 0 {
return "", errno
}
return filepath.Base(string(buf[:size-1])), nil
}
func readNativeUint32(b []byte) uint32 {
return *(*uint32)(unsafe.Pointer(&b[0]))
}
type searcher struct {
// sizeof(struct xinpgen)
headSize int
// sizeof(struct xtcpcb)
tcpItemSize int
// sizeof(struct xinpcb)
udpItemSize int
udpInpOffset int
port int
ip int
vflag int
socket int
// sizeof(struct xfile)
fileItemSize int
data int
pid int
}
func (s *searcher) Search(buf []byte, ip net.IP, port uint16, isTCP bool) (uint32, error) {
var itemSize int
var inpOffset int
if isTCP {
// struct xtcpcb
itemSize = s.tcpItemSize
inpOffset = 8
} else {
// struct xinpcb
itemSize = s.udpItemSize
inpOffset = s.udpInpOffset
}
isIPv4 := ip.To4() != nil
// skip the first xinpgen block
for i := s.headSize; i+itemSize <= len(buf); i += itemSize {
inp := i + inpOffset
srcPort := binary.BigEndian.Uint16(buf[inp+s.port : inp+s.port+2])
if port != srcPort {
continue
}
// xinpcb.inp_vflag
flag := buf[inp+s.vflag]
var srcIP net.IP
switch {
case flag&0x1 > 0 && isIPv4:
// ipv4
srcIP = net.IP(buf[inp+s.ip : inp+s.ip+4])
case flag&0x2 > 0 && !isIPv4:
// ipv6
srcIP = net.IP(buf[inp+s.ip-12 : inp+s.ip+4])
default:
continue
}
if !ip.Equal(srcIP) {
continue
}
// xsocket.xso_so, interpreted as big endian anyway since it's only used for comparison
socket := binary.BigEndian.Uint64(buf[inp+s.socket : inp+s.socket+8])
return s.searchSocketPid(socket)
}
return 0, ErrNotFound
}
func (s *searcher) searchSocketPid(socket uint64) (uint32, error) {
value, err := syscall.Sysctl("kern.file")
if err != nil {
return 0, err
}
buf := []byte(value)
// struct xfile
itemSize := s.fileItemSize
for i := 0; i+itemSize <= len(buf); i += itemSize {
// xfile.xf_data
data := binary.BigEndian.Uint64(buf[i+s.data : i+s.data+8])
if data == socket {
// xfile.xf_pid
pid := readNativeUint32(buf[i+s.pid : i+s.pid+4])
return pid, nil
}
}
return 0, ErrNotFound
}
func newSearcher(major int) *searcher {
var s *searcher
switch major {
case 11:
s = &searcher{
headSize: 32,
tcpItemSize: 1304,
udpItemSize: 632,
port: 198,
ip: 228,
vflag: 116,
socket: 88,
fileItemSize: 80,
data: 56,
pid: 8,
udpInpOffset: 8,
}
case 12:
fallthrough
case 13:
s = &searcher{
headSize: 64,
tcpItemSize: 744,
udpItemSize: 400,
port: 254,
ip: 284,
vflag: 392,
socket: 16,
fileItemSize: 128,
data: 56,
pid: 8,
}
}
return s
}
func initSearcher() error {
osRelease, err := syscall.Sysctl("kern.osrelease")
if err != nil {
return err
}
dot := strings.Index(osRelease, ".")
if dot != -1 {
osRelease = osRelease[:dot]
}
major, err := strconv.Atoi(osRelease)
if err != nil {
return err
}
defaultSearcher = newSearcher(major)
if defaultSearcher == nil {
return fmt.Errorf("unsupported freebsd version %d", major)
}
return nil
}

View File

@ -1,4 +1,4 @@
package rules
package process
import (
"bytes"
@ -9,28 +9,23 @@ import (
"net"
"path"
"path/filepath"
"strconv"
"strings"
"syscall"
"unsafe"
"github.com/Dreamacro/clash/common/cache"
"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
func init() {
var nativeEndian = func() binary.ByteOrder {
var x uint32 = 0x01020304
if *(*byte)(unsafe.Pointer(&x)) == 0x01 {
nativeEndian = binary.BigEndian
} else {
nativeEndian = binary.LittleEndian
return binary.BigEndian
}
}
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)
// export for android
@ -39,63 +34,14 @@ var (
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 (
sizeOfSocketDiagRequest = syscall.SizeofNlMsghdr + 8 + 48
socketDiagByFamily = 20
pathProc = "/proc"
)
var nativeEndian binary.ByteOrder = binary.LittleEndian
var processCache = cache.NewLRUCache(cache.WithAge(2), cache.WithSize(64))
func resolveProcessName(metadata *C.Metadata) (string, error) {
inode, uid, err := DefaultSocketResolver(metadata)
func findProcessName(network string, ip net.IP, srcPort int) (string, error) {
inode, uid, err := DefaultSocketResolver(network, ip, srcPort)
if err != nil {
return "", err
}
@ -103,31 +49,26 @@ func resolveProcessName(metadata *C.Metadata) (string, error) {
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 protocol byte
switch metadata.NetWork {
case C.TCP:
switch network {
case TCP:
protocol = syscall.IPPROTO_TCP
case C.UDP:
case UDP:
protocol = syscall.IPPROTO_UDP
default:
return 0, 0, ErrInvalidNetwork
}
if metadata.SrcIP.To4() != nil {
if ip.To4() != nil {
family = syscall.AF_INET
} else {
family = syscall.AF_INET6
}
srcPort, err := strconv.Atoi(metadata.SrcPort)
if err != nil {
return 0, 0, err
}
req := packSocketDiagRequest(family, protocol, metadata.SrcIP, uint16(srcPort))
req := packSocketDiagRequest(family, protocol, ip, uint16(srcPort))
socket, err := syscall.Socket(syscall.AF_NETLINK, syscall.SOCK_DGRAM, syscall.NETLINK_INET_DIAG)
if err != nil {
@ -257,7 +198,7 @@ func resolveProcessNameByProcSearch(inode, uid int) (string, error) {
continue
}
if bytes.Compare(buffer[:n], socket) == 0 {
if bytes.Equal(buffer[:n], socket) {
cmdline, err := ioutil.ReadFile(path.Join(processPath, "cmdline"))
if err != nil {
return "", err

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 (
"errors"
"fmt"
"net"
"path/filepath"
"strconv"
"strings"
"sync"
"syscall"
"unsafe"
"github.com/Dreamacro/clash/common/cache"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/log"
"golang.org/x/sys/windows"
@ -27,12 +22,8 @@ const (
)
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 }
getExTcpTable uintptr
getExUdpTable uintptr
getExTCPTable uintptr
getExUDPTable uintptr
queryProcName uintptr
once sync.Once
@ -44,12 +35,12 @@ func initWin32API() error {
return fmt.Errorf("LoadLibrary iphlpapi.dll failed: %s", err.Error())
}
getExTcpTable, err = windows.GetProcAddress(h, tcpTableFunc)
getExTCPTable, err = windows.GetProcAddress(h, tcpTableFunc)
if err != nil {
return fmt.Errorf("GetProcAddress of %s failed: %s", tcpTableFunc, err.Error())
}
getExUdpTable, err = windows.GetProcAddress(h, udpTableFunc)
getExUDPTable, err = windows.GetProcAddress(h, udpTableFunc)
if err != nil {
return fmt.Errorf("GetProcAddress of %s failed: %s", udpTableFunc, err.Error())
}
@ -67,47 +58,7 @@ func initWin32API() error {
return nil
}
type Process struct {
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) {
func findProcessName(network string, ip net.IP, srcPort int) (string, error) {
once.Do(func() {
err := initWin32API()
if err != nil {
@ -115,16 +66,7 @@ func NewProcess(process string, adapter string) (*Process, error) {
log.Warnln("All PROCESS-NAMES rules will be skiped")
return
}
matchMeta = match
})
return &Process{
adapter: adapter,
process: process,
}, nil
}
func resolveProcessName(metadata *C.Metadata) (string, error) {
ip := metadata.SrcIP
family := windows.AF_INET
if ip.To4() == nil {
family = windows.AF_INET6
@ -132,28 +74,23 @@ func resolveProcessName(metadata *C.Metadata) (string, error) {
var class int
var fn uintptr
switch metadata.NetWork {
case C.TCP:
fn = getExTcpTable
switch network {
case TCP:
fn = getExTCPTable
class = tcpTablePidConn
case C.UDP:
fn = getExUdpTable
case UDP:
fn = getExUDPTable
class = udpTablePid
default:
return "", ErrInvalidNetwork
}
srcPort, err := strconv.Atoi(metadata.SrcPort)
if err != nil {
return "", err
}
buf, err := getTransportTable(fn, family, class)
if err != nil {
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))
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])
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
}
pid := readNativeUint32(row[s.pid : s.pid+4])
return pid, nil
}
return 0, errNotFound
return 0, ErrNotFound
}
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

@ -0,0 +1,55 @@
package resolver
import (
"net"
)
var DefaultHostMapper Enhancer
type Enhancer interface {
FakeIPEnabled() bool
MappingEnabled() bool
IsFakeIP(net.IP) bool
IsExistFakeIP(net.IP) bool
FindHostByIP(net.IP) (string, bool)
}
func FakeIPEnabled() bool {
if mapper := DefaultHostMapper; mapper != nil {
return mapper.FakeIPEnabled()
}
return false
}
func MappingEnabled() bool {
if mapper := DefaultHostMapper; mapper != nil {
return mapper.MappingEnabled()
}
return false
}
func IsFakeIP(ip net.IP) bool {
if mapper := DefaultHostMapper; mapper != nil {
return mapper.IsFakeIP(ip)
}
return false
}
func IsExistFakeIP(ip net.IP) bool {
if mapper := DefaultHostMapper; mapper != nil {
return mapper.IsExistFakeIP(ip)
}
return false
}
func FindHostByIP(ip net.IP) (string, bool) {
if mapper := DefaultHostMapper; mapper != nil {
return mapper.FindHostByIP(ip)
}
return "", false
}

View File

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

View File

@ -1,21 +0,0 @@
package snell
import (
"crypto/cipher"
"golang.org/x/crypto/argon2"
)
type snellCipher struct {
psk []byte
makeAEAD func(key []byte) (cipher.AEAD, error)
}
func (sc *snellCipher) KeySize() int { return 32 }
func (sc *snellCipher) SaltSize() int { return 16 }
func (sc *snellCipher) Encrypter(salt []byte) (cipher.AEAD, error) {
return sc.makeAEAD(argon2.IDKey(sc.psk, salt, 3, 8, 1, uint32(sc.KeySize())))
}
func (sc *snellCipher) Decrypter(salt []byte) (cipher.AEAD, error) {
return sc.makeAEAD(argon2.IDKey(sc.psk, salt, 3, 8, 1, uint32(sc.KeySize())))
}

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,291 +0,0 @@
package obfs
import (
"bytes"
"crypto/hmac"
"encoding/binary"
"fmt"
"io"
"log"
"math/rand"
"strings"
"time"
"github.com/Dreamacro/clash/common/pool"
"github.com/Dreamacro/clash/component/ssr/tools"
)
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.Println("incorrect magic number", h[:3], ", 0x170303 is expected")
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)
return
}

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

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