Compare commits

...

1083 Commits

Author SHA1 Message Date
637a8b6ed5 refactor: udp 2022-06-09 21:16:42 +08:00
cd466f05d3 chore: reformat code 2022-06-09 21:08:46 +08:00
9a55213ddc chore: add more shadowsocks tests 2022-06-09 18:10:31 +08:00
5055542d61 chore: update dependencies 2022-06-09 18:05:45 +08:00
23063ae0b9 fix: make CodeQL happy Dreamacro 2022-06-09 17:59:17 +08:00
a7f9aa909a fix: upgrade to yaml v3 2022-06-09 17:59:17 +08:00
15ecc451f3 fix: benchmark read bytes 2022-06-09 17:58:15 +08:00
186a4cfdf3 fix: test broken on opensource repo 2022-06-09 17:58:10 +08:00
220ef9e2e2 chore: add benchmark r/w 2022-06-09 17:58:05 +08:00
7079116aa8 chore: cleanup test code 2022-06-09 17:57:59 +08:00
b9e6de45e6 chore: make linter happy 2022-06-09 17:57:53 +08:00
d3503ff940 fix: fix upgrade header detect (#2134) 2022-06-09 17:57:48 +08:00
c3f4e1ba2e fix: add length check for ssr auth_aes128_sha1 (#2129) 2022-06-09 17:57:41 +08:00
695fb64fa8 fix: vmess ws 2022-06-09 16:23:15 +08:00
d32ab9ce74 fix: 规则匹配默认策略组返回错误 2022-06-09 13:52:02 +08:00
94648989b8 fix: hysteria URI Scheme 解析问题 2022-06-09 00:22:47 +08:00
07522d3c2e chore: 修改test文件 2022-06-08 02:03:34 +08:00
c14c07d2e3 feat: 代理集支持 Hysteria 分享格式订阅解析 2022-06-08 01:50:14 +08:00
9511ccfe47 chore: refine code 2022-06-08 01:47:50 +08:00
ed17a1bf23 fix: group filter touch provider 2022-06-07 17:19:25 +08:00
2a4f2f3942 fix: hysteria dialer 2022-06-07 15:49:10 +08:00
3254eaf51c fix: hysteria parse auth 2022-06-07 15:24:46 +08:00
7941bae141 fix: hysteria parse 2022-06-07 14:53:00 +08:00
35a6666a84 feat: add hysteria 2022-06-07 13:46:54 +08:00
73d5042774 fix: some test 2022-06-07 10:45:32 +08:00
9126cbab91 fix: shadowsocks-2022 on 32-bit systems 2022-06-07 10:44:22 +08:00
f8366f6e42 fix: 代理集转换ws类型
feat: 新增grpc h2 http 等支持
2022-06-07 03:17:33 +08:00
100c9b94ba Merge branch 'dev' into Alpha 2022-06-06 23:15:33 +08:00
8343c3597e fix: doq maybe crash when use adapter 2022-06-06 21:45:08 +08:00
d31adafa11 Merge pull request #76 from nekohasekai/Alpha
feat: add support for shadowsocks 2022 ciphers
2022-06-06 20:21:01 +08:00
1acc6759e9 feat: add support for shadowsocks 2022 ciphers 2022-06-06 19:56:36 +08:00
e995003c27 Merge branch 'dev' into Alpha 2022-06-05 21:23:14 +08:00
9edee2435c chore: amd64v3 改名为amd64,amd64v2改为amd64-compatible, darwin删除v2 2022-06-05 21:23:06 +08:00
5d337b75f9 feat: proxy provider 支持V2ray格式订阅链接 2022-06-05 17:14:24 +08:00
2188fad9c1 Feature: add V2Ray subscription support to proxy provider 2022-06-05 15:59:47 +08:00
edc73f3f33 Merge remote-tracking branch 'Meta/Alpha' into Alpha
# Conflicts:
#	dns/client.go
#	dns/doh.go
#	dns/doq.go
2022-06-05 14:10:18 +08:00
ade424cbb4 chore: 调整dns interface与adapter部分 2022-06-05 13:37:00 +08:00
6e03773134 chore: 调整dns interface与adapter部分 2022-06-05 12:52:29 +08:00
4ad0294655 chore: adjust makefile 2022-06-04 21:21:42 +08:00
43d3a0c8ea chore: mix the proxy adapter and interface to dns client 2022-06-04 21:18:49 +08:00
c045a4f2a7 Chore: make hadowsocks2 lib embed 2022-06-04 20:29:33 +08:00
20611eb8dc Merge branch 'dev' into Alpha 2022-06-04 19:16:56 +08:00
8ff7e180a4 fix: 当初始化失败时,定时更新失效 2022-06-04 19:15:30 +08:00
3827e00b54 refactor: 抽离http请求方法 2022-06-04 19:14:39 +08:00
c52e689d0d fix: classical rule-set 更新未清理 2022-06-04 19:12:50 +08:00
cb517cb529 chore: 调整目录与包名一致 2022-06-04 03:25:33 +08:00
50bb620aa1 chore: 调整parseRule代码 2022-06-04 03:22:41 +08:00
c745ea63b2 chore: 优化GeoSite初始化代码 2022-06-04 02:58:14 +08:00
8e959bd245 chore: 当无tag时不输出无效日志 2022-06-03 21:00:45 +08:00
298ca42369 chore: 启动参数v,查看版本同时打印使用的tags 2022-06-03 20:23:53 +08:00
ed9b9ce3c5 refactor: 添加no_gvisor 编译tag, 剔除gvisor stack支持, 方便在arm设备上debug 2022-06-03 20:07:30 +08:00
1298d2f8b6 chore: 添加tag no_doq 编译不含doq版本, 仅减少1.5MB(macOS-arm64) 2022-06-03 18:12:06 +08:00
6e84f685ce chore: 更新geox时通过内存存储 2022-06-03 16:50:49 +08:00
1ad87cfec9 chore: 选择fallback时,当节点不可用时触发urltest 2022-06-03 13:32:11 +08:00
9e9f459c0e refactor: 优化proxy server nameserver, 当节点专用dns全部查询失败会回落到正常逻辑 2022-06-02 20:58:25 +08:00
fa3e0c726e chore: 调整解析逻辑 2022-06-02 17:03:08 +08:00
3b038310ab fix: 类型转换错误导致规则解析错误 2022-06-02 15:43:27 +08:00
6709936a8f refactor: 归类规则解析代码 2022-06-02 13:42:18 +08:00
04e5d02ab9 feat: IP-SUFFIX
eg. IP-SUFFIX,0.0.0.124/6,匹配ip二进制后四位(IP-CIDR的倒序),支持ipv6
2022-06-02 12:53:19 +08:00
1af39cb228 fix: OpenClash 回环 2022-06-01 12:32:45 +08:00
c95735f083 chore: 调整内置winTun.dll部分 2022-06-01 12:01:08 +08:00
fa2e6be05d fix: TUN file exists 2022-05-31 10:34:13 +08:00
4092a7c84b feat: proxies group URLTest api 2022-05-30 22:07:09 +08:00
58d299c737 chore: 调整geosite初始化位置 2022-05-30 21:26:41 +08:00
11ddac2b5f refactor: 逻辑规则显示效果 2022-05-30 13:58:37 +08:00
1f95c74f1e chore: 识别线程数 2022-05-29 21:04:47 +08:00
a197fbd4b5 Merge branch 'Alpha' into dev 2022-05-29 20:43:39 +08:00
708b8beadf fix: compile 2022-05-29 20:02:30 +08:00
0e1601e5b6 fix: 调整not规则判断子规则数量,逻辑规则返回payload采用解析后结果 2022-05-29 19:54:11 +08:00
c7355510a2 chore: 调整uid系统判断位置 2022-05-29 18:55:08 +08:00
1faa172944 chore: 调整uid系统判断位置 2022-05-29 18:12:43 +08:00
7a8c98cd90 refactor: 使用 netlink 获取默认网卡 2022-05-29 15:35:08 +08:00
13e907bbd0 refactor: 使用 netlink 配置 ip rule 2022-05-29 15:03:27 +08:00
39e7832676 fix: route on android 2022-05-28 23:29:22 +08:00
9b999e72ce fix: npe 2022-05-28 23:29:03 +08:00
e1a61503e4 Merge branch 'netlink' into Alpha 2022-05-28 21:59:04 +08:00
9272d02149 refactor: 合并部分android代码入linux && ip 使用netlink配置路由 2022-05-28 21:58:29 +08:00
067c02aba1 fix: 调整获取远程目的的位置 2022-05-28 20:01:27 +08:00
d0268bb9a2 chore: 降低并发查询时IPv6等待 2022-05-28 09:58:45 +08:00
fb4872ff7f fix: 关闭并发时双栈使用错误 2022-05-27 20:43:39 +08:00
2044458df9 fix: npe 2022-05-27 20:33:27 +08:00
d6df026550 chore: 更换GeoData下载地址
Signed-off-by: Meta <maze.y2b@gmail.com>
2022-05-27 18:32:23 +08:00
7858ca6cc5 fix: geox url setting 2022-05-27 12:32:59 +08:00
ac36473d13 refactor: 获取远程目的从tunnel中剔除,移至tracker 2022-05-27 09:00:48 +08:00
72fb153fe0 refactor: 优化UDP远程目标获取 2022-05-26 23:41:09 +08:00
527a602eba fix: 更新错误时未停止后续流程,日志修改 2022-05-26 23:13:36 +08:00
a71fd3b4df fix: 启动时检测provider文件是否过期,强制更新 2022-05-26 21:05:00 +08:00
2ebc0383b5 feat: RESTful API support set tcp-concurrent 2022-05-26 19:49:12 +08:00
7431001ed6 feat: RESTful API support update Geo file
and can set update url by user, eg.
geox-url:
  geoip: "http://xxxx/gepip.dat"
  mmdb: "http://xxxx/country.mmdb"
  geosite: "http://xxxx/geosite.dat"
2022-05-24 15:04:13 +08:00
149b4b5b43 feat: RESTful API support disable sniffer 2022-05-24 13:44:52 +08:00
c0eb9aac1c feat: fallback can be select by user 2022-05-24 10:17:44 +08:00
79469fc8d6 feat: uid rule support for logic and rule-set 2022-05-22 13:07:20 +08:00
948700eed6 fix: 并发dns查询,由于ipv6阻塞导致某些情况下的网络不通 2022-05-21 00:34:15 +08:00
3ab82849d4 feat: IN-TYPE rule support
eg. IN-TYPE,SOCKS/REDIR/INNER,Proxy
support list: HTTP HTTPS SOCKS SOCKS4 SOCKS5 REDIR TPROXY TUN INNER
2022-05-20 23:17:16 +08:00
0f43a19fdb refactor: new way to get interface change even for linux 2022-05-20 21:44:19 +08:00
cc1c1340a3 feat: 安卓恢复进程规则,可通过enable-process开关,默认true 2022-05-19 20:44:09 +08:00
fe25ae83df refactor: 修改sticky-session尝试逻辑 2022-05-19 20:27:26 +08:00
c787bbe0e5 fix: 热重载Tun配置 2022-05-19 19:19:19 +08:00
7aff9aac82 fix: sticky-sessions异常 2022-05-18 22:29:27 +08:00
8b09db5f7f fix: Rule-Set中不解析DNS
feat: RULE-SET支持no-resolve
2022-05-18 18:43:44 +08:00
b5623602f5 chore: Android auto-detect-interface plus 2022-05-18 12:00:57 +08:00
16b27b3a1f fix: doq过代理错误 2022-05-17 21:30:54 +08:00
8b00be9039 fix: 删除udp触发的错误逻辑 2022-05-17 21:23:28 +08:00
fa9e27c5e4 refactor: 重构失败主动健康检测 2022-05-17 21:15:14 +08:00
f4d9384603 chore: debug log print dns result 2022-05-17 18:21:18 +08:00
c4408612b3 chore: 暴露数据给前端 2022-05-17 16:47:21 +08:00
0742f7db26 refactor: 重构StickySessions 2022-05-17 13:28:54 +08:00
891c2fe899 fix: 当dns被禁用时,dns将根据general ipv6设置解析dns 2022-05-17 09:01:41 +08:00
b831eb178b chore: remove noisy log 2022-05-16 18:20:13 +08:00
962ceaa89e refactor: strategyStickySessions 2022-05-16 17:46:28 +08:00
d52b00bd34 refactor: remove useless code 2022-05-16 17:29:08 +08:00
aa0d174ccb fix: strategyStickySessions nil pointer 2022-05-16 17:06:44 +08:00
b8e9c3d55a fix: geoip ReverseMatch 2022-05-16 17:06:44 +08:00
0b4c498c93 refactor: new way to get interface for android 2022-05-16 17:06:44 +08:00
efc7c82cac feat: "!"(not) support for geosite
eg. GEOSITE,!CN,Proxy & dns.fallback-filter.geosite: ['!CN']
2022-05-15 13:16:45 +08:00
63917aa020 fix: uuid-map return failed error 2022-05-14 23:45:10 +08:00
5016f529af revert: yaml v2 2022-05-14 23:36:19 +08:00
5bd5f1bfda chore: remove Script mode residual code. 2022-05-14 13:00:33 +08:00
d4dcbce9cb chore: log show all ips when all ips shake hands failed 2022-05-13 21:43:42 +08:00
df8196a68c fix: print process path logic 2022-05-12 18:57:30 +08:00
c1631759fc feat: add strategy:sticky-sessions for LoadBalance
Signed-off-by: Meta <maze.y2b@gmail.com>
2022-05-09 18:56:36 +08:00
9e9c3c810f fixed: make log api unblocked 2022-05-09 18:54:00 +08:00
463101aec1 fix: limit load provider concurrent size 2022-05-08 22:52:46 +08:00
2072964701 revert: tls handshake timeout recovery 10s 2022-05-08 21:56:59 +08:00
aded1b78b5 chore: sniffer give the err to the caller 2022-05-08 09:09:39 +08:00
ca9c859059 Merge remote-tracking branch 'meta/Alpha' into Alpha 2022-05-08 07:59:17 +08:00
55811dae32 fix: Adjust the timing of loading proxy selection 2022-05-08 07:58:26 +08:00
7136d145f8 chore: update dependencies 2022-05-08 00:47:01 +08:00
2fbbf7519f fix: provider auto update 2022-05-08 00:04:16 +08:00
663bf4fbb0 fix: remove misjudgment 2022-05-07 12:53:13 +08:00
f0a22a4a4c chore: modify sniff error log 2022-05-07 12:44:28 +08:00
4ab91520bd refactor: reuse uuid namespace 2022-05-07 12:35:14 +08:00
980d8a2641 refactor: string map to uuidv5 2022-05-06 14:02:34 +08:00
a95d439852 chore: the uuid-map is transferred to the protocol 2022-05-06 13:28:09 +08:00
a08e39faec fix uuid match 2022-05-06 13:08:27 +08:00
b3295262c1 chore: Initialize provider ahead of time 2022-05-05 21:14:46 +08:00
27aa026568 fix: use actual metadata 2022-05-04 20:13:12 +08:00
9969e1706e fix: loadbalance group npe 2022-05-04 19:52:48 +08:00
fb58595d44 feat: Expose remote destination (udp proxy maybe domain of node) 2022-05-04 16:57:08 +08:00
bdfa16ca6f fix: wrong parameters 2022-05-04 01:04:43 +08:00
b307bcb4a9 Merge remote-tracking branch 'Meta/Alpha' into Alpha
# Conflicts:
#	listener/tun/ipstack/commons/router_linux.go
2022-05-04 01:04:11 +08:00
f26941091b fix: default router with fakeIP when tun enable 2022-05-04 00:59:04 +08:00
6cd5769ed7 fix: default router with fakeIP when tun enable 2022-05-04 00:36:44 +08:00
41adfa65b3 Merge remote-tracking branch 'Meta/Alpha' into Alpha
# Conflicts:
#	listener/tun/ipstack/commons/router_linux.go
2022-05-03 23:59:41 +08:00
3fbb7c7a2d chore: add default router when tun enable 2022-05-03 23:58:11 +08:00
0aa82c04ea chore: add default router 198.18.0.0/16 when tun enable 2022-05-03 23:28:02 +08:00
5c6f2694c7 chore: sniffer param skip-sni renamed to ship-domain, old param will be removed in the release version 2022-05-03 23:10:59 +08:00
eca7615f08 fix: patch update support tun 2022-05-03 19:31:00 +08:00
52d559bb38 feat: rule-provider support NetWork rule 2022-05-03 01:36:03 +08:00
259736390a feat: rule-provider support rules field 2022-05-03 00:53:22 +08:00
7db07630a7 fix: DNS mapping error when sniffing result is ip, Discard sniffs that result in ip 2022-05-02 22:24:14 +08:00
d617b0f447 style: uid log tidy 2022-05-02 19:52:34 +08:00
80ff5917f7 fix: The sniffer does not clean up the original address 2022-05-02 17:09:24 +08:00
b401da5eba refactor: provider init order 2022-05-02 16:47:48 +08:00
05b25c334f Merge branch 'makefile' into Alpha 2022-05-02 14:43:51 +08:00
2c5a47a275 fix: Failed to get version tag 2022-05-02 14:43:01 +08:00
b2605a9012 fix: tun dns 2022-05-02 14:21:37 +08:00
b929a19f48 refactor: Unified active health detection, supported by load balancing policy group 2022-05-02 13:50:10 +08:00
4b04faa88b fix: http sniffer return host that was handled correctly 2022-05-02 09:51:26 +08:00
5fee0b5bf1 chore: adjust pass to reject.go 2022-05-02 09:16:47 +08:00
27120fb0f5 Merge remote-tracking branch 'Meta/Alpha' into Alpha 2022-05-02 08:49:23 +08:00
0cf539fb82 chore: adjust sniffer constant 2022-05-02 08:49:18 +08:00
26a38bd8de chore: adjust sniffer constant 2022-05-02 08:46:24 +08:00
ebbce4d061 Merge remote-tracking branch 'meta/Alpha' into Alpha 2022-05-02 08:28:00 +08:00
5acd2f6c3a chore: workflow 2022-05-02 08:27:17 +08:00
fe2bc903b8 fix trojan and snell's normal udp 2022-05-02 06:28:27 +08:00
658f1f5cda fix code mistake 2022-05-02 05:34:20 +08:00
5ccc047fe4 chore: adjust sniffer err info 2022-05-02 05:17:13 +08:00
6d704b9cd1 feat: sniffer support http 2022-05-02 05:10:18 +08:00
5f957b5cf9 Merge remote-tracking branch 'Meta/Alpha' into Alpha
# Conflicts:
#	.github/workflows/prerelease.yml
#	.github/workflows/release.yaml
2022-05-02 05:01:33 +08:00
8681728b8d chore: doq parameters 2022-05-02 05:01:07 +08:00
bb1eb5979b chore: Merge alpha and beta 2022-05-02 01:14:30 +08:00
032b6a2cc5 chore: workflow 2022-05-02 00:59:41 +08:00
45a02e3439 chore: workflow 2022-05-02 00:19:28 +08:00
9d8cd036ff refactor: remove dns and tun relationship, the enabled of dns module should be decided by user 2022-05-01 09:41:27 +08:00
2aad9818e8 fix trojan and snell's udp over tcp 2022-04-30 22:26:38 +08:00
4d5c0d2bf4 fix: auto-route priority wlan0 in Android 2022-04-30 17:43:37 +08:00
861205dbbe support udp in relay if last proxy could udp-over-tcp 2022-04-30 11:36:42 +08:00
9dbe20f2c5 fix: npe when with resolver is nil 2022-04-29 13:03:55 +08:00
8dea62cd63 refactor: del useless file 2022-04-28 23:49:24 +08:00
2d99f86780 fix: dhcp ifacename type 2022-04-28 23:44:37 +08:00
fac63625fc fix: replace with sync.map for GroupBase 2022-04-28 23:43:10 +08:00
b9f270162a refactor: field name 2022-04-28 23:10:08 +08:00
bbbe371ea9 fix: dns specified interface does not change 2022-04-28 22:40:06 +08:00
e2409f9639 Merge branch 'doq' into Alpha 2022-04-28 22:24:02 +08:00
4f634edaa9 refactor: doq dialer 2022-04-28 22:21:48 +08:00
f1dab9e9ce refactor: optimize the performance of filter in proxy-group 2022-04-28 19:01:13 +08:00
8217db82ce Merge remote-tracking branch 'origin/Alpha' into Alpha 2022-04-28 17:47:29 +08:00
f9dd0a6bfc [Skip CI] chore: add docker workflow 2022-04-28 14:24:38 +08:00
b9b25be68d Merge remote-tracking branch 'Meta/Alpha' into Alpha
# Conflicts:
#	.github/workflows/docker.yaml
2022-04-28 14:19:25 +08:00
179e5aad58 chore: add docker workflow 2022-04-28 14:18:54 +08:00
fc1eaf9a9d chore: add docker workflow 2022-04-28 14:11:35 +08:00
1756730693 Merge remote-tracking branch 'meta/Alpha' into Alpha 2022-04-28 12:44:51 +08:00
c7bc67e44c fix: handle metadata when dst is ip:port 2022-04-28 12:44:27 +08:00
949c1551f8 fixup! chore: system err log 2022-04-28 12:37:53 +08:00
b23a333acf Merge remote-tracking branch 'origin/Alpha' into Alpha 2022-04-28 11:51:57 +08:00
47568051bf fix: problems caused when uid is 0 2022-04-28 11:51:40 +08:00
22c1e05e1c fix: rule provider http api crash 2022-04-28 09:44:29 +08:00
c161d5e6be fix: inner request error 2022-04-28 09:24:40 +08:00
573be855f7 fix: file not change modify time when updated rule 2022-04-28 09:07:58 +08:00
a38b2bcb6d Merge remote-tracking branch 'meta/Alpha' into Alpha 2022-04-28 08:56:00 +08:00
2e74986fe4 refactor: adjust provider loading order, remove meaningless pointers 2022-04-28 08:55:45 +08:00
30eaa8add6 fix: count error 2022-04-28 08:54:33 +08:00
b498d2dda2 chore: system err log 2022-04-27 22:54:12 +08:00
96a32f5038 refactor: tcp concurrent 2022-04-27 21:37:20 +08:00
e11f6f84df chore: adjust workflows 2022-04-27 18:05:03 +08:00
5a1e1050b7 chore: adjust sniffer log 2022-04-27 18:04:02 +08:00
73aa8c7be7 feat: support uuid with custom string 2022-04-27 18:02:29 +08:00
183973e823 chore: Adjust the tcp-concurrent and sniffer log 2022-04-27 15:22:42 +08:00
2e08a4b4e1 fix: undefined parameter 2022-04-27 14:42:58 +08:00
564a6fdf35 Chore: http 2022-04-27 12:52:11 +08:00
cca3a1a934 Fix: http proxy Upgrade behavior (#2097) 2022-04-27 12:38:31 +08:00
ffb49ba4c5 fix: gvisor panic 2022-04-25 18:39:45 +08:00
e4fb10faf9 Chore: update dependencies 2022-04-25 13:23:56 +08:00
16a35527b7 Chore: increase nattable capacity 2022-04-25 13:18:41 +08:00
4fd7d0f707 Chore: use generics as possible 2022-04-25 13:18:30 +08:00
dee1aeb6c3 fix: logic of auto-detect-interface 2022-04-23 23:42:42 +08:00
2f95d56a12 pref: uid style in log 2022-04-23 17:37:50 +08:00
c77993eed3 refactor: tidy auto-route code 2022-04-23 17:31:16 +08:00
ce663a7b96 fix: ipv6 enable logic 2022-04-23 14:21:58 +08:00
9c2ca0feb0 fix: uid match 2022-04-23 13:37:37 +08:00
b8d5321615 feat: cache uid 2022-04-23 12:11:26 +08:00
eb6f7e3158 fix: relay conn error when addr is domain 2022-04-23 10:26:22 +08:00
0947cb4a5a fix: whitelist 2022-04-23 09:52:23 +08:00
0368bb4180 fix: sniffer port whitelist error 2022-04-23 09:36:11 +08:00
4aeac0e227 chore: Adjust the connection IP log 2022-04-23 08:53:51 +08:00
bd3c493c9f fix: ipv6 enable logic 2022-04-23 02:03:10 +08:00
0a99fc4d74 fix: wrong parameter name 2022-04-23 00:45:43 +08:00
19fc70b2c4 fix: general ipv6 is false should be broke ipv6 conn 2022-04-23 00:30:25 +08:00
81b5543b0d feat: support tcp concurrent, Separate dialing and dns resolver ipv6
tcp-concurrent:true
2022-04-23 00:27:22 +08:00
2e1d9a4f2e fix: hotspot for android 2022-04-22 22:25:45 +08:00
de4341c8cd Revert: "fix: proxy-groups filter logic"
This reverts commit 8a85c63b08.
2022-04-22 18:56:35 +08:00
8a85c63b08 fix: proxy-groups filter logic 2022-04-22 17:27:55 +08:00
b0dd74e74e fix: sniffer 2022-04-22 17:00:39 +08:00
4dd9e199b7 fix: uid rule only support linux and android 2022-04-22 16:51:01 +08:00
3d6aea4c1e feat: support uid rule
eg. UID,1000/5000-6000,Proxy
2022-04-22 16:27:51 +08:00
0cb5270452 Merge remote-tracking branch 'origin/Alpha' into Alpha 2022-04-22 15:58:57 +08:00
3f6d2e5f91 feat: dnsHijack support "any"
chore: adjust process debug display logic
2022-04-22 13:30:04 +08:00
3b16fcef92 Chore: wait for system stack to close 2022-04-22 12:44:51 +08:00
f91d106cdf Chore: fix typos 2022-04-22 12:42:20 +08:00
9e6ba64940 fix: add wait timeout, and log 2022-04-21 08:08:37 -07:00
bee1bddceb feat: add sniffer port whitelist, when empty will add all ports 2022-04-21 07:06:08 -07:00
e98dcc4267 [fix] logic 2022-04-21 18:56:33 +08:00
4b79f8de93 [fix] auto-route for android 2022-04-21 17:47:04 +08:00
f40c2eb71d Chore: update dependencies 2022-04-20 22:55:30 +08:00
7ca1a03d73 Refactor: metadata use netip.Addr 2022-04-20 22:52:05 +08:00
6c4791480e Chore: IpToAddr 2022-04-20 22:09:16 +08:00
42d853a7e6 chore: upgrade dependencies 2022-04-20 01:31:33 +08:00
5d36d8b139 Improve: replace bootstrap dns (#2080) 2022-04-19 22:49:39 +08:00
5a4441c47f Chore: add none alias to dummy on ShadowsocksR (#2056) 2022-04-19 22:49:22 +08:00
0ca10798ea Chore: fix typo 2022-04-19 22:38:20 +08:00
3ea3653d7a Chore: persistence fakeip pool state 2022-04-19 22:37:47 +08:00
58cd8f9ac1 fix:force-domain invalid 2022-04-17 21:17:21 +08:00
ea0d236259 chore: change comments 2022-04-17 20:03:53 +08:00
48a01adb7a refactor: sniffer param force and reverses deprecated, will be removed when release version, replace force-domain and skip-sni,
force-domain add '+' equivalent to force is true
sniffer:
  enable: true
  force-domain:
    - "google.com"
  skip-sni:
    - www.baidu.com
  sniffing:
    - tls
2022-04-17 20:02:13 +08:00
f8d7f29856 fix: PASS policy inconsistent names 2022-04-17 14:11:58 +08:00
1cf9321aa0 fix: domain tree match failed 2022-04-16 11:55:49 +08:00
71a1f5dfbd fix: domain type fix Mapping 2022-04-16 09:51:31 +08:00
25426cba33 chore: log style 2022-04-16 09:04:43 +08:00
9d364f66e9 fix: reverse error when force is false 2022-04-16 08:53:31 +08:00
7c23fa2bd4 fix: sniffer npe 2022-04-16 08:45:18 +08:00
0658ecadd3 fix: adjust loading timing 2022-04-16 08:29:38 +08:00
efbc334b3b Merge branch 'logic-rule' into Alpha 2022-04-16 08:22:08 +08:00
80764217c2 feat: add domain list for sniffer, reverse force logic
when force is false, if domain in the list, will force replace
when force is true, if sniff domain in the list, will skip it
2022-04-16 08:21:31 +08:00
45fe6e996b fix: npe when parse rule 2022-04-16 00:21:08 +08:00
36a719e2f8 feat: support http headers 2022-04-14 13:07:39 +08:00
1b6b0052c2 chore:adjust sniffer debuglog info 2022-04-13 08:38:55 +08:00
c981ef0f28 chore: update dependencies 2022-04-13 02:32:55 +08:00
1bf291d240 chore: update dependencies 2022-04-13 02:24:21 +08:00
b179d09efb Chore: adjust ipstack 2022-04-13 02:20:53 +08:00
4be17653e0 Fix: fakeip pool cycle used 2022-04-13 02:19:42 +08:00
21446ba5d4 chore: adjust code 2022-04-12 21:39:31 +08:00
75ce6b59bf Refactor: fakeip pool use netip.Prefix, supports ipv6 range 2022-04-12 20:32:08 +08:00
ce96ac35fb chore:merge & adjust code 2022-04-12 20:20:04 +08:00
173e10abe6 Chore: fix typos 2022-04-12 19:08:13 +08:00
a6eb11ce18 Refactor: DomainTrie use generics 2022-04-12 18:45:47 +08:00
0c65f6962a Refactor: queue use generics 2022-04-12 18:44:13 +08:00
baa9e02af6 Refactor: cache use generics 2022-04-12 18:44:10 +08:00
673541e2a8 Refactor: lrucache use generics 2022-04-12 18:44:07 +08:00
14878b37f6 fix: trojan fail may panic 2022-04-12 18:43:55 +08:00
83e0abaa8c chore: adjust code 2022-04-11 13:23:59 +08:00
7166db2ac9 fix: code logic error 2022-04-10 20:01:35 +08:00
815a060309 Update metadata.go
revet commit 13012a9
2022-04-10 00:47:22 +08:00
544e0f137d feat: sniffer support
sniffer:
  enable: true
  force: false # Overwrite domain
  sniffing:
    - tls
2022-04-09 22:30:36 +08:00
07906c0aa5 fix: parse logic rule error 2022-04-09 22:25:39 +08:00
b2981f921c chore: reduce a little memory 2022-04-09 22:24:48 +08:00
7be3e617ab disable process name on android 2022-04-09 17:54:01 +08:00
9a3bc8ef9e fix: auto detect interface add param[auto-detect-interface], default is true, only use it when tun is enabled 2022-04-07 21:36:19 +08:00
e083d1c57d Revert "Add docker workflow"
This reverts commit ce4902e5e6.
2022-04-07 10:24:23 +08:00
ce4902e5e6 Add docker workflow 2022-04-06 08:31:34 +08:00
4b9edc3b66 revert:the name of tun device on mac 2022-04-05 23:04:59 +08:00
91e48b707b Merge remote-tracking branch 'yaling888/with-tun' into Alpha 2022-04-05 14:44:40 +08:00
7a8af90b86 feat: add SMTPS/POP3S/IMAPS port to sni detect 2022-04-05 03:26:23 +08:00
93d2cfa091 fix: when ssh connect to a ip, if this ip map to a domain in clash, change ip to host may redirect to a diffrent ip 2022-04-05 03:26:23 +08:00
5719b9d22f fix: npe panic 2022-04-04 22:28:47 +08:00
b553dd749b refactor: Some adjustments 2022-04-03 19:15:16 +08:00
9461bcd44e fix: default-nameserver allow DOT and DOH with host is ip 2022-04-03 19:14:21 +08:00
6548dc90fa Merge remote-tracking branch 'Plus/with-tun' into Alpha 2022-04-02 20:48:11 +08:00
908ca20afa fix: dns over proxy may due to cancel request, but proxy live status is fine 2022-04-02 18:24:11 +08:00
13012a9f89 fix: dns over proxy may due to cancel request, but proxy live status is fine 2022-04-02 17:32:37 +08:00
afdcb6cfc7 fix: log level ajust and lint fix 2022-03-31 21:27:25 +08:00
c495d314d4 feat: 添加tls sni 嗅探
# Conflicts:
#	tunnel/statistic/tracker.go
#	tunnel/tunnel.go
2022-03-31 21:27:25 +08:00
e877b68179 Chore: revert "Feature: add tls SNI sniffing (#68)"
This reverts commit 24ce6622a2.
2022-03-31 21:20:46 +08:00
24ce6622a2 Feature: add tls SNI sniffing (#68) 2022-03-31 19:34:40 +08:00
88e4b3575e [Chore] fallback dependency 2022-03-31 00:26:01 +08:00
559b3ff9f3 [Fix] VLESS http conn with tls false
[Chore] Upgrade Dependencies
2022-03-31 00:08:43 +08:00
127634028d Merge remote-tracking branch 'Meta/Alpha' into Alpha 2022-03-30 13:19:05 +08:00
81c5a65f23 Merge remote-tracking branch 'Pro-Plus/with-tun' into Alpha
# Conflicts:
#	README.md
#	adapter/outbound/trojan.go
#	adapter/outbound/vless.go
#	transport/trojan/trojan.go
2022-03-30 13:15:45 +08:00
591ee119c2 docs: warning 2022-03-30 13:05:46 +08:00
5b03cc56e7 Merge remote-tracking branch 'Clash-dev/dev' into Alpha 2022-03-30 12:41:16 +08:00
9ff1f5530e Feature: Trojan XTLS 2022-03-30 00:15:39 +08:00
b3ea2ff8b6 Chore: adjust VLESS 2022-03-29 23:50:41 +08:00
c4216218c8 Merge pull request #24 from MarksonHon/patch-2
Fix systemd service
2022-03-29 14:53:02 +08:00
63840b3358 Fix systemd service 2022-03-29 14:50:12 +08:00
131e9d38b6 Fix: Vless UDP 2022-03-29 07:24:11 +08:00
56e2c172e1 Chore: adjust tun_wireguard cache buffer 2022-03-29 07:24:11 +08:00
b3b7a393f8 Chore: merge branch 'ogn-dev' into with-tun 2022-03-29 07:22:52 +08:00
045dd0589b fix: classical missing count 2022-03-28 21:04:50 +08:00
705311b70e [Chore]修改workflows 2022-03-28 20:52:09 +08:00
55ce40fbd1 [Chore]升级项目依赖
[Chore]隐藏TUN模式在system堆栈启动时弹窗
2022-03-28 20:44:52 +08:00
07fda93111 [Chore]升级项目依赖
[Chore]隐藏TUN模式在system堆栈启动时弹窗
2022-03-28 19:48:32 +08:00
012e044c54 [Chore]完成调试workflows 2022-03-28 19:02:51 +08:00
b323315583 [Chore]调试workflows 2022-03-28 18:58:23 +08:00
4c10d6e212 [Chore]调试workflows 2022-03-28 18:54:00 +08:00
ece3bb360a [Chore]调试workflows 2022-03-28 18:52:19 +08:00
5a7b9bdf45 [Chore]调试workflows 2022-03-28 18:49:24 +08:00
028ecb70c5 [Chore]调整workflows流程2 2022-03-28 18:44:27 +08:00
4e0b22f42d [Chore]调整workflows流程2 2022-03-28 18:41:30 +08:00
dbd27ef910 [Chore]调整workflows流程 2022-03-28 17:07:11 +08:00
ffff1418f2 [Fixed]尝试修复PASS空指针问题
[Chore]调整workflows测试
2022-03-28 16:36:34 +08:00
dd9bdf4e2f Fix: convert size to unit32 in getoridst to solve some mips64 devices cannot get redirect origin dst (#2041)
Change-Id: I40aa73dcea692132e38db980320a8a07ed427fe6

Co-authored-by: Zhao Guowei <zhaoguowei@bytedance.com>
2022-03-28 14:48:51 +08:00
64a5fd02da Merge remote-tracking branch 'tun/with-tun' into Alpha 2022-03-28 10:51:59 +08:00
8df8f8cb08 Chore: adjust gVisor stack 2022-03-28 03:25:55 +08:00
fe76cbf31c Chore: code style 2022-03-28 03:18:51 +08:00
7e2c6e5188 Chore: adjust HealthCheck at first check 2022-03-28 00:46:44 +08:00
4502776513 Refactor: MainResolver 2022-03-28 00:44:13 +08:00
611ce5f5f1 [commit]
[Feat] add Pass type for support temporary skip rule set
2022-03-27 23:44:51 +08:00
9bab2c504e Chore: regenerate protoc file 2022-03-27 07:12:12 +08:00
94b3c7e99a Chore: merge branch 'ogn-dev' into with-tun 2022-03-27 00:27:05 +08:00
0a0b8074f4 refactor: rule-set and its provider 2022-03-26 20:27:41 +08:00
f66c3b6f86 [Bilud]
正常编译
2022-03-26 16:39:50 +08:00
a3d49d1ed4 Merge remote-tracking branch 'dev/dev' into Alpha 2022-03-26 16:27:17 +08:00
0d068e7b5f [Fixed]
弃用过期函数,修复Process Name获取问题
2022-03-26 16:17:44 +08:00
275cc7edf3 Chore: structure support weakly type from float to int (#2042) 2022-03-25 15:22:31 +08:00
24583009c4 Merge remote-tracking branch 'tun/with-tun' into Alpha 2022-03-25 14:20:05 +08:00
4a4b1bdb83 Chore: adjust tun RelayDnsPacket 2022-03-25 04:09:11 +08:00
c6efa74a6b Fix: udp 4In6 of tun system stack 2022-03-25 03:42:46 +08:00
a593d68c42 build test 2022-03-24 23:42:49 +08:00
520657e953 [Fix] use direct to update http providers when proxy 寄 2022-03-24 12:34:45 +08:00
6c64164bee [skip ci] [Fix] ban auto set iptables when tun is enabled 2022-03-23 20:37:46 +08:00
9b4ddbed2c [skip ci] [Pre] avoid npe 2022-03-23 13:48:21 +08:00
79d984ee8e [Fix] url-test npe 2022-03-23 13:29:51 +08:00
7a54d616c4 [SKIP CI]
Merge remote-tracking branch 'Pro-Plus/with-tun' into Alpha

# Conflicts:
#	README.md
#	hub/route/server.go
2022-03-23 13:23:34 +08:00
f19b67fe9d bypass support for auto-iptables 2022-03-23 11:36:13 +08:00
91e83ea955 delete useless field 2022-03-23 10:18:26 +08:00
a375b85fa0 [skip ci]
# Conflicts:
#	.github/workflows/linter.yml
#	.github/workflows/release.yml
#	config/config.go
#	go.mod
#	go.sum
#	hub/executor/executor.go
2022-03-23 01:41:42 +08:00
ef915c94dc Feature: flush fakeip pool 2022-03-23 01:05:43 +08:00
4cc661920e [Fix] redir-host use host not ip 2022-03-22 23:31:23 +08:00
f4312cfa5a Chore: adjust the signal 2022-03-22 18:40:33 +08:00
ac4cde1411 Refactor: iptables auto config, disabled by default 2022-03-22 05:38:42 +08:00
b5f6f26de4 Update version.go
[BUILD TEST]
2022-03-22 01:39:00 +08:00
e068563b58 Merge pull request #22 from Adlyq/Alpha-pr
[skip ci]
[Fix] skip when country code not found in GeoIP.dat
2022-03-22 00:33:02 +08:00
bf6839e5f3 Merge pull request #23 from Adlyq/Alpha-pr-iptabls
[skip ci] auto change interface for tproxy
2022-03-22 00:32:47 +08:00
e0040b7e5d [Fix] do not monitor when auto-iptables is false 2022-03-21 20:29:07 +08:00
3beb71b6e1 auto change interface for tproxy 2022-03-21 19:51:27 +08:00
668d29d91f init sequence adjustment 2022-03-21 19:47:21 +08:00
5386c3903d delete useless code 2022-03-21 18:09:36 +08:00
6a4d2b3368 Change type conversion method 2022-03-21 12:34:32 +08:00
d9d8507c8f [Fix] skip when country code not found in GeoIP.dat 2022-03-21 12:24:39 +08:00
2c0890854e Fix: retry create TUN on Windows 2022-03-20 21:27:33 +08:00
bac04ab54b Merge branch 'ogn-dev' into with-tun 2022-03-20 21:26:25 +08:00
8c9e0b3884 Chore: use GOAMD64 v1 on build docker image 2022-03-20 11:32:18 +08:00
fc8092f7cc Fix: wintun dns address 2022-03-20 04:19:48 +08:00
5b7f46bc97 [skip ci][内容]
1.调整部分代码
2022-03-20 02:39:48 +08:00
d1838f663e Merge remote-tracking branch 'yaling888/with-tun' into Alpha
# Conflicts:
#	listener/tun/tun_adapter.go
2022-03-19 22:37:51 +08:00
2d1c031ce0 [skip ci][内容]
1.修复部分空指针问题
2.修改go.mod
2022-03-19 22:28:28 +08:00
e67f94b87a [内容]
同步至最新v1.10.0
2022-03-19 15:01:49 +08:00
2df890c4ee Merge remote-tracking branch 'clash/dev' into Alpha
# Conflicts:
#	Makefile
2022-03-19 14:53:47 +08:00
30d4668008 Chore: fix typo (#2033) 2022-03-19 13:58:51 +08:00
02333a859a Chore: split amd64 v3 to special release 2022-03-19 13:42:06 +08:00
f9cc1cc363 Fix: routing-mark option doesn't work on proxies (#2028) 2022-03-19 13:29:30 +08:00
520256365e [内容]
1.wintun.dll 0.14.1
2022-03-19 01:54:21 +08:00
9270d3c475 [内容]
1.autoIptables 开关
2.go.mod 调整
3.processName 调整
4.makefile 调整
5.Tun模块 部分代码调整
2022-03-19 01:11:27 +08:00
c8b1050c15 Merge pull request #19 from Adlyq/Alpha-pr
[skip ci]Only prompt when interface cannot be found
2022-03-18 21:45:50 +08:00
39de5d58c8 Only prompt when interface cannot be found 2022-03-18 17:41:06 +08:00
7b7abf6973 Feature: auto detect interface if switch network 2022-03-18 17:03:50 +08:00
a38f30ec3b Merge pull request #18 from Adlyq/Alpha
[Fix] Process name display for Android
2022-03-18 13:20:35 +08:00
2ea92d70f9 Merge remote-tracking branch 'upstream/Alpha' into Alpha 2022-03-18 12:38:16 +08:00
8e5f01597e Fix: build 2022-03-18 05:21:28 +08:00
546f2fa739 Chore: make fake ip pool start with the third ip 2022-03-18 05:17:47 +08:00
ea2e715da9 Merge remote-tracking branch 'origin/Alpha' into Alpha
# Conflicts:
#	go.mod
#	go.sum
2022-03-18 02:36:09 +08:00
1350330fe0 1.fix module package
2.fix govet error
2022-03-18 02:35:15 +08:00
317797acc8 1.fix module package
2.fix govet error
2022-03-18 01:25:59 +08:00
8766764d49 fix 2022-03-18 00:40:39 +08:00
b8d48e1618 Merge remote-tracking branch 'upstream/Alpha' into Alpha 2022-03-18 00:33:27 +08:00
f972d1fa58 update 2022-03-18 00:27:48 +08:00
df78ba8fa6 update 2022-03-18 00:24:38 +08:00
0c83575302 Merge remote-tracking branch 'upstream/Alpha' into Alpha 2022-03-18 00:10:37 +08:00
e9151bc43f update 2022-03-17 23:57:58 +08:00
68345b6a19 Merge remote-tracking branch 'upstream/Alpha' into Alpha 2022-03-17 23:40:51 +08:00
435bee0ca2 update 2022-03-17 23:24:07 +08:00
92d169ca81 [Fix] Process name display for Android 2022-03-17 20:31:16 +08:00
30f1b29257 Merge remote-tracking branch 'yaling888/with-tun' into Alpha
# Conflicts:
#	.github/workflows/codeql-analysis.yml
#	.github/workflows/linter.yml
#	.github/workflows/release.yml
#	Makefile
#	README.md
#	adapter/outbound/vless.go
#	component/geodata/memconservative/cache.go
#	component/geodata/router/condition.go
#	component/geodata/router/condition_geoip.go
#	component/geodata/standard/standard.go
#	component/geodata/utils.go
#	config/config.go
#	config/initial.go
#	constant/metadata.go
#	constant/path.go
#	constant/rule.go
#	constant/rule_extra.go
#	dns/client.go
#	dns/filters.go
#	dns/resolver.go
#	go.mod
#	go.sum
#	hub/executor/executor.go
#	hub/route/configs.go
#	listener/listener.go
#	listener/tproxy/tproxy_linux_iptables.go
#	listener/tun/dev/dev.go
#	listener/tun/dev/dev_darwin.go
#	listener/tun/dev/dev_linux.go
#	listener/tun/dev/dev_windows.go
#	listener/tun/dev/wintun/config.go
#	listener/tun/dev/wintun/dll_windows.go
#	listener/tun/dev/wintun/session_windows.go
#	listener/tun/dev/wintun/wintun_windows.go
#	listener/tun/ipstack/commons/dns.go
#	listener/tun/ipstack/gvisor/tun.go
#	listener/tun/ipstack/gvisor/tundns.go
#	listener/tun/ipstack/gvisor/utils.go
#	listener/tun/ipstack/stack_adapter.go
#	listener/tun/ipstack/system/dns.go
#	listener/tun/ipstack/system/tcp.go
#	listener/tun/ipstack/system/tun.go
#	listener/tun/tun_adapter.go
#	main.go
#	rule/common/base.go
#	rule/common/domain.go
#	rule/common/domain_keyword.go
#	rule/common/domain_suffix.go
#	rule/common/final.go
#	rule/common/geoip.go
#	rule/common/geosite.go
#	rule/common/ipcidr.go
#	rule/common/port.go
#	rule/parser.go
#	rule/process.go
#	test/go.mod
#	test/go.sum
#	transport/vless/xtls.go
#	tunnel/tunnel.go
2022-03-17 17:41:02 +08:00
c503e44324 Merge pull request #17 from Adlyq/Alpha
[Fix] Parse
2022-03-17 12:28:45 +08:00
ce509295c0 [Fix] Parse 2022-03-17 12:26:43 +08:00
f671d6a1fd [Fix] Parse 2022-03-17 12:23:50 +08:00
8d0ae4284d Chore: use gateway address of fake ip pool as the TUN device address 2022-03-17 07:41:18 +08:00
e194efcecb Migration: go 1.18 2022-03-17 01:51:28 +08:00
609d69191a Merge remote-tracking branch 'clash/dev' into Alpha
# Conflicts:
#	.github/workflows/docker.yml
#	adapter/outboundgroup/fallback.go
#	adapter/outboundgroup/loadbalance.go
#	adapter/outboundgroup/relay.go
#	adapter/outboundgroup/selector.go
#	adapter/outboundgroup/urltest.go
#	config/config.go
#	go.mod
#	go.sum
#	main.go
#	test/go.mod
#	test/go.sum
2022-03-17 01:41:51 +08:00
c791044ddf Merge remote-tracking branch 'origin/Alpha' into Alpha 2022-03-17 00:12:26 +08:00
dc2abe6eeb [Build test] 1.18
[Updata] wintun.dll
2022-03-17 00:12:11 +08:00
1071e3f4a3 [Build test] 1.18
[Updata] wintun.dll
2022-03-17 00:02:22 +08:00
acc249495d [Build test] 1.18 2022-03-16 23:30:29 +08:00
5a2cc9a36f [Fix] 优化geodata初始化逻辑 2022-03-16 23:09:05 +08:00
1cc6cfab9c [Fix] 优化geodata初始化逻辑 2022-03-16 23:02:16 +08:00
0183d752a0 [Fix] 优化geodata初始化逻辑 2022-03-16 22:55:18 +08:00
b8d635a4b3 Migration: go 1.18 2022-03-16 22:00:20 +08:00
2f24e49ff6 [build test] 1.18 2022-03-16 21:47:00 +08:00
346d817dba Chore: Merge branch 'ogn-dev' into with-tun 2022-03-16 20:16:30 +08:00
3a9bbf6c73 Fix: should keep alive in tcp relay 2022-03-16 18:17:28 +08:00
016862f7a5 [build test]1.18 2022-03-16 17:54:44 +08:00
c3df768f79 [build test] 2022-03-16 17:33:08 +08:00
0f2123179a [build test] 2022-03-16 17:29:09 +08:00
fb7d340233 Fix: docker build makefile 2022-03-16 12:13:59 +08:00
6a661bff0c Migration: go 1.18 2022-03-16 12:10:13 +08:00
1034780e8e [build test] 2022-03-16 00:43:08 +08:00
f01ac69654 Merge remote-tracking branch 'clash/dev' into Alpha
# Conflicts:
#	.github/workflows/codeql-analysis.yml
#	.github/workflows/docker.yml
#	.github/workflows/linter.yml
#	.github/workflows/stale.yml
#	Makefile
#	component/dialer/dialer.go
#	config/config.go
#	constant/metadata.go
#	constant/rule.go
#	rule/common/domain.go
#	rule/common/domain_keyword.go
#	rule/common/domain_suffix.go
#	rule/common/final.go
#	rule/common/ipcidr.go
#	rule/geoip.go
#	rule/parser.go
#	rule/port.go
#	rule/process.go
2022-03-15 23:13:41 +08:00
c85305ead8 [Skip CI] 2022-03-15 22:25:33 +08:00
3e89bee524 [Skip CI] 2022-03-15 11:47:42 +08:00
d1dd21417b Feature: add tzdata to Dockerfile (#2027)
Co-authored-by: suyaqi <suyaqi@wy.net>
2022-03-15 11:30:52 +08:00
9ff32d9e29 Chore: use slice instead of map for system stack udp receiver queue 2022-03-15 05:19:29 +08:00
d486ee467a Fix: test 2022-03-15 03:39:45 +08:00
20b66d9550 Style: code style 2022-03-15 02:55:06 +08:00
5abd03e241 Fix: exclude the broadcast address to fake ip pool 2022-03-15 02:43:40 +08:00
68fccfacc0 [Skip CI] 2022-03-15 02:20:19 +08:00
cf52fbed65 [Skip CI] 2022-03-15 02:06:57 +08:00
a924819fbf [Fixed] rule-set of classical allow adding GEOIP 2022-03-14 21:48:36 +08:00
13c82754ff [Fixed] show rule count when parse failed 2022-03-14 21:43:58 +08:00
002163f07b [Fixed] memory leak 2022-03-13 18:35:55 +08:00
9c5b184db6 [Fixed] handle network protocol[0] panic (not pretty) 2022-03-13 18:34:49 +08:00
359f8ffca3 Fix: should use the correct gateway for TUN system stack 2022-03-13 17:48:43 +08:00
46b9a1092d Chore: embed the RuleExtra into Base 2022-03-13 01:22:05 +08:00
8fbf93ccc8 Chore: Merge branch 'ogn-dev' into with-tun 2022-03-13 01:15:35 +08:00
b866f06414 Chore: move find connection process to tunnel (#2016) 2022-03-12 19:07:53 +08:00
8b4f9a35f6 Chore: bump to go1.18rc1, use netip.Addr to replace net.IP with system TUN stack 2022-03-12 02:16:13 +08:00
9683c297a7 Chore: add more details to process resolving (#2017) 2022-03-09 13:41:50 +08:00
8333815e95 Chore: refactor TUN 2022-03-09 05:08:35 +08:00
d49871224c Fix: should only resolve local process name 2022-03-09 00:32:21 +08:00
ba7bcce895 Chore: code style 2022-03-09 00:32:21 +08:00
71e002c2ef Merge branch 'ogn-dev' into with-tun 2022-03-09 00:30:38 +08:00
f6c7281bb7 Chore: update github action workflow 2022-03-06 21:48:37 +08:00
83bfe521b1 Fix: should split linux process name with space (#2008) 2022-03-05 18:25:16 +08:00
3ab784dd80 Merge pull request #16 from Dabrit/test
[Skip CI]README Improvements
2022-03-05 09:03:25 +08:00
f1c4d85eb3 Update README.md 2022-03-05 01:44:48 +08:00
b9fc393f95 Naming edited 2022-03-05 01:33:11 +08:00
557347d366 Merge pull request #15 from Dabrit/test
Optimize reading experience of linux users
2022-03-04 23:25:02 +08:00
a7f3b85200 Edit username to adapt Linux username naming rule 2022-03-04 22:36:15 +08:00
7550067fde [Fixed] skip maybe invaild ip data packet 2022-03-04 22:32:33 +08:00
076a0840bf [Fixed] domian or ipcidr is used before initialization 2022-03-04 22:32:25 +08:00
5ebcc526de [Fixed] match not some ip in ipcidr provider 2022-03-04 22:32:25 +08:00
3772ad8ddb Revise mismatching targets from README. 2022-03-04 22:22:49 +08:00
17c53b92b9 Fix: iptables routing mark init 2022-03-03 05:02:17 +08:00
0b9022b868 Chore: update dependencies 2022-03-03 04:23:03 +08:00
5e0d4930cb Merge branch 'ogn-dev' into with-tun 2022-03-03 04:17:00 +08:00
b52d0c16e9 Chore: vmess test remove all alterid 2022-02-27 18:00:04 +08:00
5ad7237fa7 Merge pull request #14 from Adlyq/Alpha
Fix the filter under proxy-group to filter other groups
2022-02-27 00:34:08 +08:00
49e25f502f Merge pull request #11 from ttyykpe/patch-1
Makefile add android-armv8
2022-02-27 00:33:58 +08:00
06942c67fd Fix the filter under proxy-group to filter other groups 2022-02-23 16:17:29 +08:00
705e5098ab Chore: use SIMD for AMD64 and ARM64 system stack checksum 2022-02-23 14:51:04 +08:00
ac5c57ecef Chore: compatible with VMESS WS older version configurations 2022-02-23 14:21:53 +08:00
cd3b139c3f Chore: use "-m mark --mark" instead of "-m owner --uid-owner" 2022-02-23 14:19:59 +08:00
592b6a785e Fix: find process name by UDP network on macOS 2022-02-23 14:04:47 +08:00
2f234cf6bc Feature: process condition for rules 2022-02-23 14:01:53 +08:00
132a6a6a2f Fix: listener tcp keepalive & reuse net.BufferedConn (#1987) 2022-02-23 11:22:46 +08:00
d876d6e74c Feature: resolve ip with a proxy adapter 2022-02-23 02:38:50 +08:00
b192238699 Merge from remote branch 2022-02-23 01:00:27 +08:00
3b2ec3d880 Chore: upgrade gvisor 2022-02-22 22:30:41 +08:00
9259c9f3ff Makefile add android-armv8 2022-02-21 18:04:38 +08:00
03e4b5d525 Chore: use golangci-lint config file 2022-02-19 00:08:51 +08:00
a0221bf897 Fix: routing-mark should effect on root 2022-02-17 14:23:47 +08:00
37cf166d14 Merge pull request #10 from Adlyq/Alpha
Full regexp support
2022-02-16 23:10:07 +08:00
27292dac0c Replace the regular implementation of the filter for proxy-providers and proxy-groups with regex2 2022-02-16 22:18:05 +08:00
847c91503b [build] 2022-02-06 05:08:11 +08:00
ca8ed0a01b [Fix]GeoSite.dat initial in logic rule 2022-02-06 04:41:34 +08:00
46dc262e8e 合并拉取请求 #9
add the doc of local build
2022-02-06 04:34:08 +08:00
7465eaafa1 [Fix]GeoSite.dat initial in logic rule 2022-02-06 04:30:54 +08:00
d70cfefde7 add the doc of local build 2022-02-06 04:02:26 +08:00
52c37f7140 Merge pull request #8 from qzi/Dev
add trojan xtls sample
2022-02-06 03:52:41 +08:00
180bce2940 add trojan xtls sample 2022-02-06 03:37:40 +08:00
4a446c4e31 [build] 2022-02-06 01:59:35 +08:00
d7f5e8d3de [Skip CI] 2022-02-06 00:56:13 +08:00
0a180eeb40 忽略geosite文件大小写 2022-02-06 00:51:37 +08:00
7ff48ea42d [build] 2022-02-05 22:05:20 +08:00
a0e44f4041 [FEAT]
1.Add geodata loader mode switch
yaml   geodata-loader: memconservative / standard
2.Add AutoIptables mode switch
yaml   auto-iptables: true
3.support trojan xtls
4.update gvisor
5.Fix process
6.Fix darwin autoRoute
2022-02-05 21:33:49 +08:00
2f6f9ebc2e Merge branch 'Dev' into Meta
# Conflicts:
#	config/config.go
2022-02-05 19:30:12 +08:00
28a1475f66 [FEAT] Add geodata loader mode switch 2022-02-05 02:42:49 +08:00
c28f42d823 [FEAT] Add geodata loader mode switch 2022-02-05 00:51:06 +08:00
2bf34c766e [Feat]
support trojan xtls
change geodataloader mode as memconservative
2022-02-04 23:33:36 +08:00
35b19c3d7f Merge branch 'Dev' into Feature
# Conflicts:
#	Makefile
2022-02-04 18:44:35 +08:00
90e6ed4612 [Fixed] Fixed clash process name is Clash.Meta 2022-02-04 17:38:06 +08:00
ae5a790510 [Fixed] Abnormal rule when host is ip addr 2022-02-04 17:38:06 +08:00
3b277aa8ec [Feat]
update gvisor
Chore: use "-m mark --mark" instead of "-m owner --uid-owner"
2022-02-04 06:11:24 +08:00
176eb3926b Merge remote-tracking branch 'pro-plus/plus-pro' into Feature
# Conflicts:
#	.github/workflows/Alpha.yml
#	.github/workflows/codeql-analysis.yml
#	.github/workflows/docker.yml
#	.github/workflows/linter.yml
#	.github/workflows/stale.yml
#	Makefile
#	README.md
#	adapter/outbound/vless.go
#	component/dialer/dialer.go
#	component/geodata/geodata.go
#	component/geodata/router/condition.go
#	config/config.go
#	config/initial.go
#	constant/metadata.go
#	constant/path.go
#	constant/rule.go
#	constant/rule_extra.go
#	dns/filters.go
#	go.mod
#	go.sum
#	hub/executor/executor.go
#	hub/route/configs.go
#	listener/listener.go
#	listener/tun/dev/dev.go
#	listener/tun/dev/dev_darwin.go
#	listener/tun/dev/dev_linux.go
#	listener/tun/dev/dev_windows.go
#	listener/tun/dev/dev_windows_extra.go
#	listener/tun/dev/wintun/dll_windows.go
#	listener/tun/dev/wintun/session_windows.go
#	listener/tun/ipstack/gvisor/tun.go
#	listener/tun/ipstack/gvisor/tundns.go
#	listener/tun/ipstack/stack_adapter.go
#	listener/tun/ipstack/system/tun.go
#	listener/tun/tun_adapter.go
#	main.go
#	rule/base.go
#	rule/common/process.go
#	rule/geoip.go
#	rule/parser.go
#	rule/port.go
#	test/go.mod
#	test/go.sum
#	test/vless_test.go
#	transport/vless/xtls.go
#	tunnel/tunnel.go
2022-02-04 05:30:21 +08:00
776728fb30 [Feat]
update gvisor
Chore: use "-m mark --mark" instead of "-m owner --uid-owner"
2022-02-04 04:47:40 +08:00
a732e1a603 Merge remote-tracking branch 'clash/dev' into Dev 2022-02-04 02:40:15 +08:00
1cdaf782ba Merge remote-tracking branch 'clash/dev' into Feature 2022-02-04 02:38:32 +08:00
f1157d0a09 Chore: use "-m mark --mark" instead of "-m owner --uid-owner" 2022-02-02 21:59:44 +08:00
f376409041 Chore: upgrade gvisor 2022-02-01 02:00:10 +08:00
45b3afdd33 Fix: new version golangci-lint check 2022-01-30 01:49:27 +08:00
875fdb3a5b Revert "Chore: upgrade gvisor version"
This reverts commit d633e3d96e.
2022-01-30 00:45:02 +08:00
25e115d042 Feature: process condition for rules 2022-01-28 22:52:35 +08:00
d633e3d96e Chore: upgrade gvisor version 2022-01-28 22:42:58 +08:00
6e9d837a7d Merge from remote branch 2022-01-28 19:51:40 +08:00
63b9d66365 [Feat]
1.Add DNS over QUIC support
2.Replace Country.mmdb with GeoIP.dat
3.build with Alpha tag
2022-01-27 12:45:11 +08:00
be0fadc09e [Feat]
1.Add DNS over QUIC support
2.Replace Country.mmdb with GeoIP.dat
3.build with Alpha tag
2022-01-27 12:25:53 +08:00
b1a639feae Fix: domain trie search 2022-01-26 22:28:13 +08:00
76dccebbf6 github action build config 2022-01-26 21:35:18 +08:00
cd5b735973 [Refactor] logic rule parse 2022-01-26 21:34:49 +08:00
9e4e1482d9 [chore] Replace Country.mmdb with GeoIP.dat 2022-01-26 12:01:14 +08:00
9974fba56e Update dev.yml 2022-01-25 21:59:48 +08:00
4bd5764c4e Update Makefile 2022-01-25 21:47:11 +08:00
deeab8b45f [test] dev build 2022-01-25 21:34:06 +08:00
af30664c51 [test] dev build 2022-01-25 21:12:49 +08:00
6962f0b7e1 [update] dev build 2022-01-25 20:54:56 +08:00
6e5859d1bf [update] dev build 2022-01-25 20:53:07 +08:00
87ca93b979 Update build.yaml 2022-01-25 20:40:03 +08:00
11052d8f77 github action add build
(cherry picked from commit bdec838673767977c14191861ac1b9a8291e2ffc)
2022-01-25 20:33:30 +08:00
a5ce62db33 Merge branch 'clash-dev' into Dev 2022-01-25 15:05:24 +08:00
2f8e575308 [Fixed] modified RULE-SET supported rule 2022-01-23 18:35:48 +08:00
62b70725ef [Fixed] GEOSITE rule load fail 2022-01-23 18:27:44 +08:00
8595d6c2e9 [Feature]
1.Add Network rule, match network type(TCP/UDP)
2.Add logic rules(NOT,OR,AND)
-AND,((DOMAIN,baidu.com),(NETWORK,UDP)),REJECT

(cherry picked from commit d7092e2e37f2c48282c878edea1b2ebc2912b09a)
2022-01-22 22:37:07 +08:00
03b956b7a3 [Fixed] auto-route support use ip route 2022-01-22 13:24:31 +08:00
e5c99cbee7 modify gitignore 2022-01-21 22:39:00 +08:00
58a47e1835 [Style] clear unless notes 2022-01-21 22:38:28 +08:00
daf83eb6f7 [Fixed] select group crash 2022-01-21 22:38:02 +08:00
bb68b59c9a Merge pull request #7 from CHIZI-0618/DnsHijack
Fix DnsHijack default value bug.
2022-01-21 18:27:26 +08:00
c3cfa3d6cd Fix DnsHijack default value bug. 2022-01-21 18:11:21 +08:00
b15344ec78 [Refactor]
1.allow maybe empty group
2.use COMPATIBLE(DIRECT alias) when proxy group is empty
3.http provider pass through tunnel
2022-01-18 21:09:36 +08:00
cfe7354c07 Improve: change provider file modify time when updated (#1918) 2022-01-18 13:32:47 +08:00
56c38890f9 Merge from remote branch[ssh] 2022-01-18 10:05:06 +08:00
daae846db3 Merge from remote branch 2022-01-18 09:51:20 +08:00
9732efe938 Fix: tls handshake requires a timeout (#1893) 2022-01-15 19:33:21 +08:00
ee6c1871a9 [Refactor] lazy loading geosite.bat 2022-01-11 22:17:24 +08:00
8f3385bbb6 Feature: support snell v3 (#1884) 2022-01-10 20:24:20 +08:00
00e44cd141 [Style] Modify the default configuration, tun config delete default hijack dns and modify auto-route to false. modify NameServer to 223.5.5.5 and 119.29.29.29 by Skyxim 2022-01-09 00:36:05 +08:00
4ab986cccb [Refactor] gvisor support hijack dns list
dns-hijack:
 - 1.1.1.1
 - 8.8.8.8:53
 - tcp://1.1.1.1:53
 - udp://223.5.5.5
 - 10.0.0.1:5353
2022-01-09 00:35:45 +08:00
64869d0f17 [Fixed] Remove the Linux automatic routing configuration Change the name of the Linux network card to utun 2022-01-08 16:57:59 +08:00
7f0368da66 [Style] Adjust delete routes on macos 2022-01-08 16:55:02 +08:00
4f1b227ca2 [Style] Positive health check 2022-01-08 09:23:49 +08:00
16abba385a [Style] Adjust the routing table of tun on mac 2022-01-07 22:40:05 +08:00
75b5f633cd [Fixed] Positive health check multithreading is not safe 2022-01-07 12:58:40 +08:00
8ae68552a6 [Fixed] Stupid mistakes 2022-01-06 10:49:50 +08:00
d35d6c9ac9 [Fixed] Stupid mistakes 2022-01-06 10:49:26 +08:00
a832cfdb65 [Fixed] compatible cfw 2022-01-05 19:28:54 +08:00
951a5a0eb5 [update]readme 2022-01-05 18:45:32 +08:00
89609cc4a2 [update]readme 2022-01-05 17:04:56 +08:00
bfb976bbdc [test]Add name filter to proxy group 2022-01-05 12:19:49 +08:00
d237b041b3 Fix: ignore empty dns server error 2022-01-05 11:41:31 +08:00
a15d2535f1 升级版本号 2022-01-05 11:41:17 +08:00
610c79570a make tun config compatible with premium 2022-01-05 11:24:00 +08:00
051c81518c make tun config compatible with premium 2022-01-05 01:56:35 +08:00
0209efd423 Revert "make tun config compatible with premium"
This reverts commit ba6fdd2962.
2022-01-05 01:56:05 +08:00
ba6fdd2962 make tun config compatible with premium 2022-01-05 01:50:43 +08:00
c14dd79e69 Merge from remote branch 2022-01-05 01:46:37 +08:00
9475799615 make tun config compatible with premium 2022-01-05 00:33:42 +08:00
14917c8af1 merge clash 1.9.0 2022-01-04 17:58:50 +08:00
3bb32d12e0 Merge remote-tracking branch 'clash/dev' into Meta
# Conflicts:
#	.github/workflows/docker.yml
#	dns/server.go
#	go.mod
#	go.sum
#	hub/executor/executor.go
#	test/go.mod
#	test/go.sum
2022-01-04 17:31:07 +08:00
3cb87e083c Fix: duplicate provider err typo 2022-01-03 17:21:27 +08:00
8c6d0c6757 Chore: fix docker dependencies security warning 2022-01-02 11:15:40 +08:00
cb95326aca Chore: update dependencies 2022-01-02 01:15:49 +08:00
8679968ab0 Fix: multiple port string parsing overflow (#1868)
Ports in TCP and UDP should be parsed as an unsigned integer,
otherwise ports > 32767 get truncated to 32767. As this is
the case with Metadata.UDPAddr(), this fundamentally breaks
UDP connections where demand for high port numbers is high.

This commit fixes all known cases where ParseInt is used for ports,
and has been verified to fix Discord voice connections on port
50001~50004.

Fixes: d40e5e4fe6

Co-authored-by: Hamster Tian <haotia@gmail.com>
2022-01-02 01:09:29 +08:00
204a72bbd3 Chore: remove forward compatible code 2022-01-02 00:48:57 +08:00
013b839678 [Fix] Linux Tun 2021-12-27 07:09:45 +08:00
a06382cebc [test] 2021-12-27 06:44:17 +08:00
ebc3f36236 [fix]autoIptables 2021-12-27 03:29:14 +08:00
e2a0437685 [fix] 2021-12-27 03:16:48 +08:00
82c8e02d02 [Style] Add User-Agent for provider request 2021-12-26 22:26:53 +08:00
7267c58913 Chore: ReCreate* do side effect job (#1849) 2021-12-26 22:08:53 +08:00
a210ec4197 [Feature] 添加unified-delay boolean 控制延迟测试,默认为false,当设置true时忽略握手延迟,将统一延迟结果,从而利于不同协议的url-test 2021-12-26 21:20:41 +08:00
14ae87fcd0 Chore: remove reduce regex compile (#1855) 2021-12-26 20:47:12 +08:00
0b72395704 Merge pull request #5 from xsxun/patch-1
Update vless.go, fix udp blocked
2021-12-20 18:13:43 +08:00
8955107d6b Update vless.go 2021-12-20 12:59:06 +08:00
Fan
ee6fc12709 Fix: when both providers and proxies are present, use the health check configuration for proxies (#1821)
Co-authored-by: Ho <ho@fluidex.com>
2021-12-12 20:37:30 +08:00
69aef9cec0 [Fixed] Configure tun interface on linux 2021-12-11 22:34:45 +08:00
9e44e21406 [Fixed] launch resolver an enhancer when tun mode 2021-12-09 23:00:54 +08:00
b0fdd8dc47 [Fixed] Add retry to open tun 2021-12-09 22:52:32 +08:00
e92ef587bb [Fixed] The array may be sent out of bounds 2021-12-09 22:52:32 +08:00
5657aa50cf Merge from remote branch 2021-12-09 21:38:24 +08:00
7d17d53a8f [readme] 2021-12-09 17:54:53 +08:00
78e105f3b2 Chore: builtin right mime of .js (#1808) 2021-12-08 13:38:25 +08:00
58ef4ddbba [Fixed]Meaningless pointer 2021-12-07 20:49:39 +08:00
a78b89d16e Revert: Revert Redir-Host, please add fallback dns and append proxy adapter
DNS pass proxy use:
- protocol://ip:port#AdapterName
- protocol://ip:port/query#AdapterName

sure as:
- tls://1.1.1.1:853#DNS
2021-12-06 22:45:59 +08:00
833b43a538 Fixed: Does RuleSet resolve ip logic modification 2021-12-06 21:47:22 +08:00
8df3efe932 [Fix] 修正因xray服务端alpn参数为http/1.1而导致无法连接的问题 2021-12-06 00:19:03 +08:00
645c3154d6 [Fix] 修正因xray服务端alpn参数为http/1.1而导致无法连接的问题 2021-12-05 03:51:26 +08:00
a847d7b58d [Fix] 修正因xray服务端alpn参数为http/1.1而导致无法连接的问题 2021-12-05 02:18:58 +08:00
37ea8aff5c README 2021-12-05 00:48:35 +08:00
cb4ce8be6a Makefile 2021-12-04 21:43:33 +08:00
a85395e777 readme 2021-12-04 20:50:57 +08:00
819b29956b readme 2021-12-04 20:40:09 +08:00
eb999b3bf1 fix AutoIptables 2021-12-04 19:59:41 +08:00
8580ee8898 [style] 2021-12-04 17:41:13 +08:00
58552447ef [fix]Linux TProxy 2021-12-04 14:34:01 +08:00
23ca356447 Fixed: Modify the trigger condition, only if it fails successively 2021-12-04 00:16:39 +08:00
fae65b97ec fix Makefile 2021-12-03 22:13:05 +08:00
99f0231a9b style 2021-12-03 21:54:45 +08:00
edf1bb476d test 2021-12-03 20:38:40 +08:00
5c53243e81 Experimental: Positive health testing 2021-12-03 14:35:21 +08:00
b99b4ad15f Fixed:Rule-Set Supported RuleExtra 2021-12-02 23:32:30 +08:00
6369921364 Merge pull request #4 from Skyxim/meta
Feature:Supported Rule-Set
2021-12-02 23:17:02 +08:00
c6f923041f Feature:Supported Rule-Set 2021-12-02 22:56:17 +08:00
08607fb6b4 Feature: add linux/arm/v6 for the container image (#1771) 2021-12-02 21:12:45 +08:00
53eb3f15bb Revert "[fix]code"
This reverts commit 0431969a73.
2021-12-02 20:08:34 +08:00
b15a7c8b6f Revert "[test]"
This reverts commit bf6bfdd930.
2021-12-02 20:08:28 +08:00
038f973f90 Merge remote-tracking branch 'origin/Meta' into Meta
# Conflicts:
#	tunnel/tunnel.go
2021-12-02 18:06:47 +08:00
bf6bfdd930 [test] 2021-12-02 18:06:14 +08:00
0431969a73 [fix]code 2021-12-02 03:39:37 +08:00
c7b257b188 [style] 2021-12-01 19:25:32 +08:00
885f69b81d [style] 2021-12-01 17:08:44 +08:00
cb52682790 [style] 2021-12-01 16:51:31 +08:00
c65835d9e4 [style] embed_wintun.dll 2021-11-30 18:00:19 +08:00
92bb026f70 [style] embed_wintun.dll 2021-11-30 17:58:21 +08:00
c22c7efd07 [fix] embed_windows 2021-11-27 22:10:37 +08:00
e4b30dacd4 [fix] embed_windows 2021-11-27 21:51:38 +08:00
353ae30839 [test] embed_windows 2021-11-27 21:36:10 +08:00
828ff82ff2 [test] embed_windows 2021-11-27 21:23:34 +08:00
35cf39e415 Revert "[test] rule providers"
This reverts commit 078389f4f6.
2021-11-26 00:57:41 +08:00
340efef2d8 Revert "[test] rule providers"
This reverts commit 14af94205c.
2021-11-26 00:57:36 +08:00
796eb5c95c Revert "[test] rule providers"
This reverts commit d4cc650633.
2021-11-26 00:57:33 +08:00
0f2b87497b Revert "[fix]code"
This reverts commit 06e9243fda.
2021-11-26 00:57:29 +08:00
06e9243fda [fix]code 2021-11-26 00:27:00 +08:00
d4cc650633 [test] rule providers 2021-11-25 23:33:06 +08:00
14af94205c [test] rule providers 2021-11-25 23:20:08 +08:00
078389f4f6 [test] rule providers 2021-11-25 23:14:31 +08:00
cad18b7529 [fix] rule providers 2021-11-25 21:52:07 +08:00
075d8ed094 Fix: fakeip pool cycle used 2021-11-23 22:01:49 +08:00
b1bed7623d Fix: provider filter potential panic 2021-11-21 17:44:03 +08:00
aeddc8eb1d fix proxies callback 2021-11-21 16:57:22 +08:00
f7393509a3 fix python310 2021-11-21 15:09:22 +08:00
1401a82bb0 Feature: add filter on proxy provider (#1511) 2021-11-20 23:38:49 +08:00
8e641a4e31 Fix: should return io.EOF immediately 2021-11-20 23:01:22 +08:00
4524cf4418 Fix: should return io.EOF immediately 2021-11-20 12:44:31 +08:00
0db15d46c3 Change: use nop packet conn for reject 2021-11-20 12:34:14 +08:00
223de1f3fd [update]version 2021-11-18 23:54:20 +08:00
1fb2bc07d7 [update]readme 2021-11-17 19:55:14 +08:00
eb57d246cf [test]tun 2021-11-17 19:35:34 +08:00
0001a1b844 [Fix]Vless tls must not be true 2021-11-17 19:09:01 +08:00
b20e202321 [Fix]Vless tls must not be true 2021-11-17 17:56:24 +08:00
900e852525 [test] 2021-11-17 16:03:47 +08:00
1f3968bd50 [test]core 1.8 2021-11-17 15:00:32 +08:00
5d510eb5aa [test]core 1.8 2021-11-16 20:08:52 +08:00
3d246d5150 Merge from remote branch 2021-11-14 20:25:22 +08:00
08c43b8876 Fix: revert ssr udp fix 2021-11-14 14:48:00 +08:00
3686446919 Fix: resolver dial context options 2021-11-12 11:05:02 +08:00
a412745314 Merge from remote branch 2021-11-11 00:54:43 +08:00
d0c23998d2 Fix: resolver dial context udp 2021-11-11 00:53:42 +08:00
499beb7344 Fix: bind iface should throw control error 2021-11-10 22:19:11 +08:00
038cc1f6b5 Merge from remote branch 2021-11-09 21:12:08 +08:00
6bd186d3c0 Merge from remote branch 2021-11-09 21:11:38 +08:00
4c6bb7178b Feature: resolve ip with proxy adapter 2021-11-09 19:44:16 +08:00
cec14db4a8 Merge pull request #1 from Dreamacro/master
更新
2021-11-09 16:14:04 +08:00
c9be614821 Fix: windows arm7 build 2021-11-08 21:24:39 +08:00
b56d35040d Chore: update dependencies and rename profile props 2021-11-08 20:48:29 +08:00
bd2ea2b917 Feature: mark on socket (#1705) 2021-11-08 16:59:48 +08:00
e622d8dd38 Fix: parse dial interface option 2021-11-08 13:31:08 +08:00
d40e5e4fe6 Fix: codeql alerts 2021-11-08 00:32:21 +08:00
1a7830f18e Feature: dial different NIC for all proxies (#1714) 2021-11-07 16:48:51 +08:00
53287d597b Chore: use custom buffer pool for lwIP stack 2021-11-04 18:33:11 +08:00
964bbe1957 Chore: adjust all udp alloc size
Chore: adjust all udp alloc size
2021-11-04 00:44:16 +08:00
bcb301b730 Chore: adjust all udp alloc size 2021-11-03 22:29:24 +08:00
c824ace2d7 Wintun: use new swdevice-based API for upcoming Wintun 0.14 2021-11-03 15:10:31 +08:00
ac9e5c6913 Wintun: use new swdevice-based API for upcoming Wintun 0.14 2021-11-03 15:02:40 +08:00
b515a4e270 Chore: move "geodata" to package "component" 2021-11-02 18:23:01 +08:00
78cef7df59 Chore: move "geodata" to package "component" 2021-10-29 00:52:44 +08:00
62b3ebe49f Chore: update dependencies 2021-10-28 13:35:27 +08:00
325b7f455f Chore: version fmt 2021-10-28 12:55:40 +08:00
ff420ed2ee Merge from remote branch 2021-10-28 12:30:30 +08:00
d1568325e6 Merge from remote branch 2021-10-28 12:30:02 +08:00
ddf28dfe8b Merge from remote branch 2021-10-28 11:36:41 +08:00
2680e8ffa3 Merge from remote branch 2021-10-28 11:36:11 +08:00
2953772a0e Style: format code 2021-10-28 00:06:55 +08:00
5a27df899f Chore: script built 2021-10-27 23:10:11 +08:00
ebbc9604ce Chore: use uber max procs 2021-10-27 21:27:19 +08:00
ab12b440aa Merge remote branch 2021-10-21 22:40:07 +08:00
4b614090f8 Merge remote branch 2021-10-21 22:37:30 +08:00
63d07db4bf Chore: script built 2021-10-21 20:22:23 +08:00
a7aea12aa6 Fix: remove ResponseHeaderTimeout limitation (#1690) 2021-10-20 13:44:05 +08:00
c6cceeb0c5 Chore: use alpn http 1.1 only on trojan websocket by default 2021-10-19 22:34:18 +08:00
967932d02c Fix: set dnsmode behavior 2021-10-18 23:03:25 +08:00
81d5da51a3 Fix: unexpected proxy dial behavior on mapping mode 2021-10-18 21:08:27 +08:00
fea9d1c5e2 Fix: replace vmess grpc test image 2021-10-16 20:35:06 +08:00
df3a491d40 Feature: support trojan websocket 2021-10-16 20:19:59 +08:00
68753b4ae1 Chore: contexify ProxyAdapter ListenPacket 2021-10-15 21:44:53 +08:00
cbea46b0c8 Merge remote branch 2021-10-15 14:14:51 +08:00
c0e9d69163 Feature: add mode script 2021-10-15 14:11:14 +08:00
583b2a5ace Change: use interface HardwareAddr for dhcp discovery 2021-10-14 22:54:43 +08:00
13bd601cac Fix: #1660 panic 2021-10-11 21:05:38 +08:00
3d5681cffd Feature: persistence fakeip (#1662) 2021-10-11 20:48:58 +08:00
a1c2478e74 Chore: actions split lint and release 2021-10-11 20:08:18 +08:00
f1cf7e9269 Style: use gofumpt for fmt 2021-10-10 23:44:09 +08:00
4ce35870fe Chore: remove deprecated ioutil 2021-10-09 20:35:06 +08:00
1996bef9e6 Chore: doh request should with id 0 (#1660) 2021-10-07 22:57:55 +08:00
66cb0b1218 Fix: cache kv db should not block on init 2021-10-05 22:47:26 +08:00
b9d470cf79 Fix: dhcp client should request special interface 2021-10-05 13:31:19 +08:00
4f1fac02ab Chore: add remove TODO 2021-10-05 12:42:21 +08:00
537b672fcf Change: use bbolt as cache db 2021-10-04 19:20:11 +08:00
ced9749104 Fix: http proxy should response correct http version (#1651) 2021-09-30 16:30:07 +08:00
d29d824da8 Improve: avoid bufconn twice (#1650) 2021-09-30 04:11:37 +08:00
862174d21b Feature: add lwIP TCP/IP stack to tun listener 2021-09-30 04:05:52 +08:00
9aeb4c8cfe Improve: avoid bufconn twice (#1650) 2021-09-28 23:15:53 +08:00
433d35e866 Chore: format with go 1.17 2021-09-24 04:37:04 +08:00
32d8f849ee Chore: update gvisor 2021-09-23 02:42:17 +08:00
8be1d5effb Merge from remote branch 2021-09-22 22:11:51 +08:00
70c8605cca Improve: use one bytes.Buffer pool 2021-09-20 21:02:18 +08:00
5b1a0a523f Chore: update README.md 2021-09-20 17:22:40 +08:00
5f03238c8a Chore: force set latest go version to action 2021-09-18 18:22:28 +08:00
b398f1e6f3 Chore: force set latest go version to action 2021-09-18 00:18:47 +08:00
6f94d56383 Fix: gvisor ipv6 routeing in Tun 2021-09-17 16:49:53 +08:00
fbda82218e Merge from remote branch 2021-09-17 15:07:27 +08:00
b3cd4ebbd3 Fix: use 1.17.x on github actions 2021-09-15 20:21:30 +08:00
b0f83e401f Fix: socks4 request continues after authentication failed (#1624) 2021-09-15 16:45:57 +08:00
f5806d9263 Fix: http/https proxy authentication (#1613) 2021-09-14 00:08:23 +08:00
55600c49c9 Fix: potential pitfalls 2021-09-13 23:58:48 +08:00
beb88cc46f Fix: should not trust address of http.Client (#1616) 2021-09-13 23:46:39 +08:00
d49b38b00f Fix: should not unmarshal to pointer (#1615) 2021-09-13 23:43:28 +08:00
85dc0b5527 Fix: potential overflow in ssr (#1600) 2021-09-09 22:07:27 +08:00
0c79d1207e Fix: potential overflow in ssr (#1600) 2021-09-09 20:30:34 +08:00
77a6a08192 Fix: VLESS test cases 2021-09-08 23:34:57 +08:00
1df5317e13 Feature: add VLESS test case [ssh] 2021-09-08 22:36:54 +08:00
ae619e4163 Fix: VLESS WSOpts 2021-09-08 21:32:08 +08:00
738bd3b0dd Fix: vmess ws headers not set properly (#1595) 2021-09-08 21:20:16 +08:00
400dc923e0 Fix: vmess ws headers not set properly (#1595) 2021-09-08 14:44:24 +08:00
03be2512ca Merge from remote branch 2021-09-08 04:43:53 +08:00
6ddd9e6fb8 Merge from remote branch 2021-09-08 04:42:56 +08:00
9254d2411e Fix: VLESS WSOpts Headers 2021-09-08 04:34:11 +08:00
5b7f0de48b Chore: update dependencies 2021-09-07 20:16:07 +08:00
a5b950a779 Feature: add dhcp type dns client (#1509) 2021-09-06 23:07:34 +08:00
a2d59d6ef5 Feature: skip DIRECT proxies in relay (#1583) 2021-09-06 21:39:28 +08:00
8ef5cdb8be Test: use local clash pkg 2021-09-05 14:38:07 +08:00
c7b718f651 Test: release resources correctly 2021-09-05 14:25:55 +08:00
ff56e5c5de Test: fix direct listen fail 2021-09-04 22:44:18 +08:00
661c417fce Test: use shadowsocks-rust for ss benchmark 2021-09-04 22:31:08 +08:00
b904ca0bcc Feature: add source ipcidr condition to rule final 2021-09-01 18:29:48 +08:00
fb836fe441 Fix: remove trim source ipcidr 2021-09-01 01:02:42 +08:00
b23bc77001 Fix: source ipcidr condition for rule IPCIDR 2021-09-01 00:53:13 +08:00
16fcee802b Merge from remote branch 2021-09-01 00:41:32 +08:00
48aef1829f Merge from remote branch 2021-09-01 00:38:43 +08:00
4cc16e0136 Feature: add source ipcidr condition for all rules 2021-08-31 21:46:04 +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
83c9664c17 Merge from remote branch 2021-08-05 00:49:17 +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
ac9e90c812 Merge from remote branch 2021-07-28 22:14:20 +08:00
ba2fd00f01 Merge from remote branch 2021-07-28 22:13:21 +08:00
3705996974 Chore: split SOCKS version inbound metadata type (#1513) 2021-07-27 13:58:29 +08:00
09299e5e5a Fix: error var name 2021-07-27 02:38:41 +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
96e5a52651 Style: code style 2021-07-09 02:19:43 +08:00
5852245045 Merge from remote branch 2021-07-07 03:53:32 +08:00
b4d93c4438 Feature: add xtls support for VLESS 2021-07-06 23:55:34 +08:00
56dff65149 Feature: support multiport condition for rule SRC-PORT and DST-PORT 2021-07-06 15:07:05 +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
e2c7b19000 Fix: fix yaml syntax 2021-07-03 22:41:31 +08:00
8a488bab72 Merge from remote branch 2021-07-03 22:33:18 +08:00
3afe3810bf Merge from remote branch 2021-07-03 22:31:12 +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
d7732f6ebc Code: refresh code 2021-07-01 22:49:29 +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
50d778da3c Chore: cache process name when resolve failed (#900) 2020-08-15 16:55:55 +08:00
8b7c731fd6 Fix: ssr broken (#895) 2020-08-12 20:50:56 +08:00
0b7918de9c Migration: go 1.15 2020-08-12 13:47:50 +08:00
4f61c04519 Fix: ssr typo (#887) 2020-08-11 10:35:30 +08:00
89cf06036d Feature: dns server could lookup hosts (#872) 2020-08-11 10:28:17 +08:00
4ba6f248bc Fix: ssr bounds out of range panic (#882) 2020-08-11 10:17:40 +08:00
83a684c551 Change: adjust tolerance logic (#864) 2020-08-06 20:12:03 +08:00
92a23f1eab Feature: PROCESS-NAME for windows (#840) 2020-08-06 19:59:20 +08:00
622ac45258 Feature: PROCESS-NAME for freebsd (#855) 2020-07-31 20:01:19 +08:00
791d203b5f Fix: update cache if a process was found (#850) 2020-07-30 17:15:06 +08:00
77d6f9ae6f Fix: handle snell server reported error message properly (#848) 2020-07-30 15:54:26 +08:00
b1d9dfd6bf Improve: simplify macOS process searching 2020-07-29 11:27:18 +08:00
6532947e71 Fix: invert should resolve ip (#836) 2020-07-27 13:47:00 +08:00
6c5f23f552 Merge branch 'dev' of github.com:Dreamacro/clash into dev 2020-07-27 11:58:02 +08:00
78c3034158 Chore: rename NoResolveIP to ShouldResolveIP 2020-07-27 11:57:55 +08:00
8f0098092d Fix: protect alive with atomic value (#834) 2020-07-25 17:47:11 +08:00
33a6579a3a Feature: add ssr support (#805)
* Refactor ssr stream cipher to expose iv and key

References:
https://github.com/Dreamacro/go-shadowsocks2
https://github.com/sh4d0wfiend/go-shadowsocksr2

* Implement ssr obfs

Reference:
https://github.com/mzz2017/shadowsocksR

* Implement ssr protocol

References:
https://github.com/mzz2017/shadowsocksR
https://github.com/shadowsocksRb/shadowsocksr-libev
https://github.com/shadowsocksr-backup/shadowsocksr
2020-07-22 23:02:15 +08:00
b4221d4b74 Chore: README.md style fixed (#825)
make every item in TODO list has the same style
2020-07-22 21:34:37 +08:00
0e4b9daaad Improve: add cache for macOS PROCESS-NAME 2020-07-22 20:35:27 +08:00
ee72865f48 Fix: recycle buf on http obfs 2020-07-22 20:29:39 +08:00
6521acf8f1 Improve: check uid on process search & fix typo (#824) 2020-07-22 20:22:34 +08:00
4f73410618 Feature: add PROCESS-NAME rule for linux (#822) 2020-07-22 19:05:10 +08:00
20eff200b1 Fix: dns should put msg to cache while exchangeWithoutCache (#820) 2020-07-20 21:16:36 +08:00
ae1e1dc9f6 Feature: support PROCESS-NAME on macOS 2020-07-19 13:18:23 +08:00
cf9e1545a4 Improve: fix go test race detect 2020-07-18 20:56:13 +08:00
6c7a8fffe0 Chore: should not write file on file provider 2020-07-18 19:32:40 +08:00
3a3e2c05af Chore: add rule payload in log 2020-07-18 19:22:09 +08:00
02c7fd8d70 Fix: write msg cache multiple times (#812)
Co-authored-by: john.xu <john.xu@bytedance.com>
2020-07-17 17:34:40 +08:00
e6aa452b51 Fix: ticker leak 2020-07-13 00:25:54 +08:00
35449bfa17 Feature: add github stale action 2020-07-09 10:27:05 +08:00
acd51bbc90 Fix: obfs host should not have 80 port 2020-07-01 00:01:36 +08:00
f44cd9180c Chore: update GitHub issue template 2020-06-30 13:55:26 +08:00
93c987a6cb Fix: typo in dialer.go (#767) 2020-06-28 10:59:04 +08:00
3f0584ac09 Chore: move documentations to wiki (#766) 2020-06-28 10:39:30 +08:00
59968fff1c Fix: github actions tag build 2020-06-27 21:09:04 +08:00
7c62fe41b4 Chore: remove forward compatibility code 2020-06-27 14:28:10 +08:00
2781090405 Chore: move experimental features to stable 2020-06-27 14:19:31 +08:00
14c9cf1b97 Fix: domain trie crash if not match in #758 (#762) 2020-06-24 19:46:37 +08:00
3dfff84cc3 Fix: domain trie should backtrack to parent if match fail (#758) 2020-06-24 18:41:23 +08:00
5f3db72422 Fix: docker multiplatform build 2020-06-21 12:38:14 +08:00
18bb285a90 Fix: external-ui should relative with clash HomeDir 2020-06-18 21:33:57 +08:00
60bad66bc3 Change: ipv6 logic 2020-06-18 18:11:02 +08:00
99b34e8d8b Fix: cannot listen socks5 port on wsl (#748) 2020-06-15 10:34:15 +08:00
9f1d85ab6e Fix: fake-ip-filter on fakeip mode should lookup ip-host mapping (#743) 2020-06-14 00:41:53 +08:00
4323dd24d0 Fix: don't auto health check on provider health check disabled 2020-06-14 00:32:04 +08:00
59bda1d547 Change: local resolve DNS in UDP request due to TURN failed 2020-06-12 23:39:03 +08:00
1c760935f4 Chore: add error msg when dial vmess 2020-06-11 22:19:47 +08:00
4f674755ce Fix: trim . for socks5 host 2020-06-11 12:11:44 +08:00
f1b792bd26 Fix: trim FQDN on http proxy request 2020-06-11 11:10:08 +08:00
58c077b45e Fix: actions tag replace 2020-06-08 13:53:04 +08:00
1854199c47 Chore: update dependencies 2020-06-07 18:14:04 +08:00
ecac8eb8e5 Fix: add lock for inbound proxy recreate 2020-06-07 17:57:41 +08:00
48cff50a4c Feature: connections add rule payload 2020-06-07 17:28:56 +08:00
fb628e9c62 Feature: add default hosts localhost 2020-06-07 17:25:51 +08:00
2dece02df6 Chore: code adjustments 2020-06-07 16:54:41 +08:00
8f32e6a60f Improve: safe write provider file 2020-06-07 00:36:54 +08:00
98614a1f3f Chore: move rule parser to rules 2020-06-05 17:43:50 +08:00
c1b4c94b9c Chore: remove unused hooks directory 2020-06-05 12:49:24 +08:00
7ddbc12cdb Chore: rm unused Dockerfile 2020-06-04 10:57:43 +08:00
1a217e21e9 Chore: use actions build docker image 2020-06-04 10:38:30 +08:00
147a7ce779 Fix: panic of socks5 client missing authentication 2020-06-03 18:49:57 +08:00
fb0289bb4c Chore: open ForceAttemptHTTP2 on DoH 2020-06-01 13:43:26 +08:00
3e7970612a Chore: provider error adjust 2020-06-01 00:39:41 +08:00
46244a6496 Chore: mode use lower case (backward compatible) 2020-06-01 00:32:37 +08:00
71d30e6654 Feature: support vmess tls custom servername 2020-06-01 00:27:04 +08:00
008731c249 Fix: make os.Stat return correct err on provider 2020-05-29 21:56:29 +08:00
5628f97da1 Feature: add tolerance for url-test 2020-05-29 17:47:50 +08:00
8d0c6c6e66 Feature: domain trie support wildcard alias 2020-05-28 12:13:05 +08:00
5073c3cde8 Chore: add trimpath for go build 2020-05-20 15:13:33 +08:00
3a27cfc4a1 Feature: add Mixed(http+socks5) proxy listening (#685) 2020-05-12 11:29:53 +08:00
3638b077cd Chore: update premium link 2020-05-08 21:52:17 +08:00
646bd4eeb4 Chore: update dependencies and README.md 2020-05-07 21:58:53 +08:00
752f87a8dc Feature: support proxy-group in relay (#597) 2020-05-07 21:42:52 +08:00
b979ff0bc2 Feature: implemented a strategy similar to optimistic DNS (#647) 2020-05-07 15:10:14 +08:00
b085addbb0 Fix: use domain first on direct dial (#672) 2020-05-05 12:39:25 +08:00
94e0e4b000 Fix: make selector react immediately 2020-04-30 20:13:27 +08:00
7d51ab5846 Fix: dns return empty success for AAAA & recursion in fake ip mode (#663) 2020-04-29 11:21:37 +08:00
41a9488cfa Feature: add more command-line options (#656)
add command-line options to override `external-controller`, `secret` and `external-ui` (#531)
2020-04-27 22:23:09 +08:00
51b6b8521b Fix: typo (#657) 2020-04-27 22:20:35 +08:00
e5379558f6 Fix: redir-host should lookup hosts 2020-04-27 21:28:24 +08:00
d1fd57c432 Fix: select group can use provider real-time 2020-04-27 21:23:23 +08:00
18603c9a46 Improve: provider can be auto GC 2020-04-26 22:38:15 +08:00
5036f62a9c Chore: update dependencies 2020-04-25 00:43:32 +08:00
2047b8eda1 Chore: remove unused parameter netType (#651) 2020-04-25 00:39:30 +08:00
0e56c195bb Improve: pool buffer alloc 2020-04-25 00:30:40 +08:00
2b33bfae6b Fix: API auth bypass 2020-04-24 23:49:35 +08:00
3fc6d55003 Fix: domain wildcard behavior 2020-04-24 23:49:19 +08:00
8eddcd77bf Chore: dialer hook should return a error 2020-04-24 23:48:55 +08:00
27dd1d7944 Improve: add basic auth support for provider URL (#645) 2020-04-20 21:22:23 +08:00
b1cf2ec837 Fix: dns tcp-tls X509.HostnameError (#638) 2020-04-17 11:29:59 +08:00
84f627f302 Feature: verify mmdb on initial 2020-04-16 19:12:25 +08:00
5c03613858 Chore: picker support get first error 2020-04-16 18:31:40 +08:00
1825535abd Improve: recycle buffer after packet used 2020-04-16 18:19:36 +08:00
2750c7ead0 Fix: set SO_REUSEADDR for UDP listeners on linux (#630) 2020-04-11 21:45:56 +08:00
3ccd7def86 Fix: typo (#624) 2020-04-08 15:49:12 +08:00
65dab4e34f Feature: domain trie support dot dot wildcard 2020-04-08 15:45:59 +08:00
5591e15452 Fix: vmess pure TLS mode 2020-04-03 16:04:24 +08:00
19f809b1c8 Feature: refactor vmess & add http network 2020-03-31 16:07:21 +08:00
206767247e Fix: udp traffic track (#608) 2020-03-28 20:05:38 +08:00
518354e7eb Fix: dns request panic and close #527 2020-03-24 10:13:53 +08:00
86dfb6562c Chore: update dependencies 2020-03-22 17:41:58 +08:00
c0a2473160 Feature: support relay (proxy chains) (#539) 2020-03-21 23:46:49 +08:00
70a19b999d Chore: update README to better describe what Clash do atm (#586) 2020-03-20 12:35:30 +08:00
e54f51af81 Fix: trojan split udp packet 2020-03-20 00:02:05 +08:00
b068466108 Improve: add session cache for trojan 2020-03-19 22:39:09 +08:00
b562f28c1b Feature: support trojan 2020-03-19 20:26:53 +08:00
230e01f078 Fix: config parse panic 2020-03-19 11:04:56 +08:00
082847b403 Chore: support MarshalYAML to some config filed (#581) 2020-03-15 19:40:39 +08:00
9471d80785 Fix: dns fallback logic 2020-03-13 00:11:54 +08:00
b263095533 Fix: TPROXY fakeip (#572) 2020-03-10 20:36:24 +08:00
Ti
14d5137703 Fix: rules parse (#568) 2020-03-09 10:40:21 +08:00
d8a771916a Fix: provider parse 2020-03-08 23:34:46 +08:00
f7f30d3406 Feature: add UDP TPROXY support on Linux (#562) 2020-03-08 21:58:49 +08:00
b2c9cbb43e Chore: update dependencies 2020-03-08 13:01:06 +08:00
c733f80793 Fix: #563 and fallback error return 2020-03-08 13:00:42 +08:00
88d8f93793 Change: rename some field 2020-03-07 20:01:24 +08:00
e57a13ed7a Fix: mutable SplitAddr cause panic 2020-03-02 23:47:23 +08:00
23525ecc15 Migration: go 1.14 2020-03-01 01:48:08 +08:00
814bd05315 Fix: ss udp return error when addr parse failed 2020-03-01 01:46:02 +08:00
e81b88fb94 Feature: add configuration test command (#524) 2020-02-29 17:48:26 +08:00
c4994d6429 Fix: dns not cache RcodeServerFailure 2020-02-25 21:53:28 +08:00
0740d20ba0 Chore: disable url-test http redirect (#536) 2020-02-25 16:08:13 +08:00
9eaca6e4ab Fix: provider fallback should reparse proxies 2020-02-22 15:18:03 +08:00
609869bf5a Change: make ping under authentication 2020-02-21 18:00:19 +08:00
d68339cea7 Fix: socks5 inbound return remote udp addr for identity 2020-02-20 11:29:16 +08:00
0f4cdbf187 Chore: remove unused code 2020-02-18 16:05:12 +08:00
f3f8e7e52f Chore: remove println 2020-02-18 14:26:42 +08:00
8d07c1eb3e Chore: initial config with port 2020-02-18 13:48:15 +08:00
46edae9896 Fix: domain dns crash 2020-02-17 22:13:15 +08:00
df0ab6aa8e Fix: ipv6 dns crash 2020-02-17 20:11:46 +08:00
7b48138ad0 Fix: vmess udp crash 2020-02-17 17:34:19 +08:00
e9032c55fa Chore: update dependencies 2020-02-17 12:25:55 +08:00
d75cb069d9 Feature: add default-nameserver and outbound interface 2020-02-15 21:42:46 +08:00
f69f635e0b Chore: add issue templates 2020-02-14 19:16:43 +08:00
8b5e511426 Fix: use the fastest whether the result is successful 2020-02-14 16:36:20 +08:00
6641bf7c07 Fix: vmessUDPConn should return a correctly address 2020-02-12 13:12:07 +08:00
afc9f3f59a Chore: use custom dialer 2020-02-09 17:02:48 +08:00
a55be58c01 Fix: provider should fallback to read remote when local file invalid 2020-02-08 16:21:52 +08:00
dcf97ff5b4 Fix: should prehandle metadata before resolve 2020-02-07 20:53:43 +08:00
72c0af9739 Chore: udp resolve ip on local 2020-01-31 19:26:33 +08:00
b0f9c6afa8 Fix: should close socks udp PacketConn 2020-01-31 15:03:59 +08:00
19bb0b655c Fix: match log display 2020-01-31 14:58:54 +08:00
26ce3e8814 Improve: udp NAT type 2020-01-31 14:43:54 +08:00
aa207ec664 Fix: qemu permission 2020-01-30 21:19:51 +08:00
c626b988a6 Fix: add docker hub pre build 2020-01-30 17:25:55 +08:00
82c387e92b Chore: fix typo (#490) 2020-01-30 17:03:10 +08:00
14fb789002 Feature: add arm32 and arm64 docker image 2020-01-28 16:39:52 +08:00
9071351022 Chore: aggregate mmdb (#474) 2020-01-11 21:07:01 +08:00
f688eda2c2 Chore: fix typo (#479) 2020-01-11 21:02:55 +08:00
2810533df4 Chore: export raw config struct (#475) 2020-01-11 00:22:34 +08:00
6b7144acce Chore: export reset manager statistic api (#476) 2020-01-11 00:20:10 +08:00
e68c0d088b Fix: upstream dns ExchangeContext workaround (#468) 2020-01-10 14:13:44 +08:00
2c0cc374d3 Fix: README typo 2020-01-09 18:13:15 +08:00
86d3d77a7f Chore: increase DNS timeout (#464) 2020-01-01 19:23:34 +08:00
477 changed files with 38399 additions and 6136 deletions

20
.github/workflows/build.yaml vendored Normal file
View File

@ -0,0 +1,20 @@
name: Build All
on:
workflow_dispatch:
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Set up Go
uses: actions/setup-go@v1
with:
go-version: 1.18
- name: Check out code
uses: actions/checkout@v1
- name: Build
run: make all
- name: Release
uses: softprops/action-gh-release@v1
with:
files: bin/*
draft: true

61
.github/workflows/docker.yaml vendored Normal file
View File

@ -0,0 +1,61 @@
name: Docker
on:
push:
branches:
- Beta
tags:
- "v*"
env:
REGISTRY: docker.io
jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- name: Checkout repository
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Set up QEMU
uses: docker/setup-qemu-action@v1
- name: Setup Docker buildx
uses: docker/setup-buildx-action@v1
with:
version: latest
# Extract metadata (tags, labels) for Docker
# https://github.com/docker/metadata-action
- name: Extract Docker metadata
id: meta
uses: docker/metadata-action@v3
with:
images: ${{ env.REGISTRY }}/${{ secrets.DOCKERHUB_ACCOUNT }}/${{secrets.DOCKERHUB_REPO}}
- name: Log into registry
if: github.event_name != 'pull_request'
uses: docker/login-action@v1
with:
registry: ${{ env.REGISTRY }}
username: ${{ secrets.DOCKER_HUB_USER }}
password: ${{ secrets.DOCKER_HUB_TOKEN }}
# Build and push Docker image with Buildx (don't push on PR)
# https://github.com/docker/build-push-action
- name: Build and push Docker image
id: build-and-push
uses: docker/build-push-action@v2
with:
context: .
file: ./Dockerfile
push: ${{ github.event_name != 'pull_request' }}
platforms: |
linux/386
linux/amd64
linux/arm64/v8
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}

View File

@ -1,44 +0,0 @@
name: Go
on: [push, pull_request]
jobs:
build:
name: Build
runs-on: ubuntu-latest
steps:
- name: Setup Go
uses: actions/setup-go@v1
with:
go-version: 1.13.x
- name: Check out code into the Go module directory
uses: actions/checkout@v1
- name: Cache go module
uses: actions/cache@v1
with:
path: ~/go/pkg/mod
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go-
- name: Get dependencies and run test
run: |
go test ./...
- name: Build
if: startsWith(github.ref, 'refs/tags/')
env:
NAME: clash
BINDIR: bin
run: make -j releases
- 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

70
.github/workflows/prerelease.yml vendored Normal file
View File

@ -0,0 +1,70 @@
name: Prerelease
on:
push:
branches:
- Alpha
- Beta
pull_request:
branches:
- Alpha
- Beta
jobs:
Build:
runs-on: ubuntu-latest
steps:
- name: Get latest go version
id: version
run: |
echo ::set-output name=go_version::$(curl -s https://raw.githubusercontent.com/actions/go-versions/main/versions-manifest.json | grep -oE '"version": "[0-9]{1}.[0-9]{1,}(.[0-9]{1,})?"' | head -1 | cut -d':' -f2 | sed 's/ //g; s/"//g')
- name: Setup Go
uses: actions/setup-go@v2
with:
go-version: ${{ steps.version.outputs.go_version }}
- name: Check out code into the Go module directory
uses: actions/checkout@v3
- name: Cache go module
uses: actions/cache@v2
with:
path: ~/go/pkg/mod
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go-
- name: Test
if: ${{github.ref_name=='Beta'}}
run: |
go test ./...
- name: Build
if: success()
env:
NAME: Clash.Meta
BINDIR: bin
run: make -j$(($(nproc) + 1)) releases
- name: Delete current release assets
uses: andreaswilli/delete-release-assets-action@v2.0.0
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
tag: Prerelease-${{ github.ref_name }}
deleteOnlyFromDrafts: false
- name: Tag Repo
uses: richardsimko/update-tag@v1
with:
tag_name: Prerelease-${{ github.ref_name }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Upload Alpha
uses: softprops/action-gh-release@v1
if: ${{ success() }}
with:
tag: ${{ github.ref_name }}
tag_name: Prerelease-${{ github.ref_name }}
files: bin/*
prerelease: true
generate_release_notes: true

44
.github/workflows/release.yaml vendored Normal file
View File

@ -0,0 +1,44 @@
name: Release
on:
push:
tags:
- "v*"
jobs:
Build:
runs-on: ubuntu-latest
steps:
- name: Get latest go version
id: version
run: |
echo ::set-output name=go_version::$(curl -s https://raw.githubusercontent.com/actions/go-versions/main/versions-manifest.json | grep -oE '"version": "[0-9]{1}.[0-9]{1,}(.[0-9]{1,})?"' | head -1 | cut -d':' -f2 | sed 's/ //g; s/"//g')
- name: Setup Go
uses: actions/setup-go@v2
with:
go-version: ${{ steps.version.outputs.go_version }}
- name: Check out code into the Go module directory
uses: actions/checkout@v3
- name: Cache go module
uses: actions/cache@v2
with:
path: ~/go/pkg/mod
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go-
- name: Test
run: |
go test ./...
- name: Build
if: success()
env:
NAME: Clash.Meta
BINDIR: bin
run: make -j$(($(nproc) + 1)) releases
- name: Upload Release
uses: softprops/action-gh-release@v1
if: ${{ success() && startsWith(github.ref, 'refs/tags/')}}
with:
tag: ${{ github.ref }}
files: bin/*
generate_release_notes: true

7
.gitignore vendored
View File

@ -12,7 +12,7 @@ bin/*
# Output of the go coverage tool, specifically when used with LiteIDE
*.out
# dep
# go mod vendor
vendor
# GoLand
@ -20,3 +20,8 @@ vendor
# macOS file
.DS_Store
# test suite
test/config/cache*
/output
/.vscode

16
.golangci.yaml Normal file
View File

@ -0,0 +1,16 @@
linters:
disable-all: true
enable:
- gofumpt
- staticcheck
- govet
- gci
linters-settings:
gci:
sections:
- standard
- prefix(github.com/Dreamacro/clash)
- default
staticcheck:
go: '1.18'

View File

@ -1,16 +1,26 @@
FROM golang:alpine as builder
RUN apk add --no-cache make git && \
wget -O /Country.mmdb https://github.com/Dreamacro/maxmind-geoip/releases/latest/download/Country.mmdb
WORKDIR /clash-src
mkdir /clash-config && \
wget -O /clash-config/Country.mmdb https://raw.githubusercontent.com/Loyalsoldier/geoip/release/Country.mmdb && \
wget -O /clash-config/geosite.dat https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat && \
wget -O /clash-config/geoip.dat https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat
COPY . /clash-src
RUN go mod download && \
make linux-amd64 && \
mv ./bin/clash-linux-amd64 /clash
WORKDIR /clash-src
RUN go mod download &&\
make docker &&\
mv ./bin/Clash.Meta-docker /clash
FROM alpine:latest
LABEL org.opencontainers.image.source="https://github.com/MetaCubeX/Clash.Meta"
RUN apk add --no-cache ca-certificates
COPY --from=builder /Country.mmdb /root/.config/clash/
COPY --from=builder /clash /
ENTRYPOINT ["/clash"]
RUN apk add --no-cache ca-certificates tzdata
VOLUME ["/root/.config/clash/"]
COPY --from=builder /clash-config/ /root/.config/clash/
COPY --from=builder /clash /clash
RUN chmod +x /clash
ENTRYPOINT [ "/clash" ]

View File

@ -1,42 +1,75 @@
NAME=clash
NAME=Clash.Meta
BINDIR=bin
VERSION=$(shell git describe --tags || echo "unknown version")
BRANCH=$(shell git branch --show-current)
ifeq ($(BRANCH),Alpha)
VERSION=alpha-$(shell git rev-parse --short HEAD)
else ifeq ($(BRANCH),Beta)
VERSION=beta-$(shell git rev-parse --short HEAD)
else ifeq ($(BRANCH),)
VERSION=$(shell git describe --tags)
else
VERSION=$(shell git rev-parse --short HEAD)
endif
BUILDTIME=$(shell date -u)
GOBUILD=CGO_ENABLED=0 go build -ldflags '-X "github.com/Dreamacro/clash/constant.Version=$(VERSION)" \
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 \
linux-386 \
darwin-arm64 \
linux-amd64-compatible \
linux-amd64 \
linux-armv5 \
linux-armv6 \
linux-armv7 \
linux-armv8 \
linux-arm64 \
linux-mips64 \
linux-mips64le \
linux-mips-softfloat \
linux-mips-hardfloat \
linux-mipsle-softfloat \
linux-mipsle-hardfloat \
linux-mips64 \
linux-mips64le \
android-arm64 \
freebsd-386 \
freebsd-amd64
freebsd-amd64 \
freebsd-arm64
WINDOWS_ARCH_LIST = \
windows-386 \
windows-amd64
windows-amd64-compatible \
windows-amd64 \
windows-arm64 \
windows-arm32v7
all: linux-amd64 darwin-amd64 windows-amd64 # Most used
all:linux-amd64 linux-arm64\
darwin-amd64 darwin-arm64\
windows-amd64 windows-arm64\
docker:
GOAMD64=v3 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
darwin-amd64:
GOARCH=amd64 GOOS=darwin $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
GOARCH=amd64 GOOS=darwin GOAMD64=v3 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
darwin-amd64-compatible:
GOARCH=amd64 GOOS=darwin GOAMD64=v2 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
darwin-arm64:
GOARCH=arm64 GOOS=darwin $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
linux-386:
GOARCH=386 GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
linux-amd64:
GOARCH=amd64 GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
GOARCH=amd64 GOOS=linux GOAMD64=v3 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
linux-amd64-compatible:
GOARCH=amd64 GOOS=linux GOAMD64=v2 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
linux-arm64:
GOARCH=arm64 GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
linux-armv5:
GOARCH=arm GOOS=linux GOARM=5 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
@ -47,9 +80,6 @@ linux-armv6:
linux-armv7:
GOARCH=arm GOOS=linux GOARM=7 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
linux-armv8:
GOARCH=arm64 GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
linux-mips-softfloat:
GOARCH=mips GOMIPS=softfloat GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
@ -68,17 +98,32 @@ linux-mips64:
linux-mips64le:
GOARCH=mips64le GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
android-arm64:
GOARCH=arm64 GOOS=android $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
freebsd-386:
GOARCH=386 GOOS=freebsd $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
freebsd-amd64:
GOARCH=amd64 GOOS=freebsd $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
GOARCH=amd64 GOOS=freebsd GOAMD64=v3 $(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
GOARCH=amd64 GOOS=windows GOAMD64=v3 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.exe
windows-amd64-compatible:
GOARCH=amd64 GOOS=windows GOAMD64=v2 $(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))
@ -93,5 +138,12 @@ $(zip_releases): %.zip : %
all-arch: $(PLATFORM_LIST) $(WINDOWS_ARCH_LIST)
releases: $(gz_releases) $(zip_releases)
vet:
go test ./...
lint:
golangci-lint run ./...
clean:
rm $(BINDIR)/*

BIN
Meta.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 KiB

517
README.md
View File

@ -1,323 +1,298 @@
<h1 align="center">
<img src="https://github.com/Dreamacro/clash/raw/master/docs/logo.png" alt="Clash" width="200">
<br>Clash<br>
<img src="Meta.png" alt="Meta Kennel" width="200">
<br>Meta Kernel<br>
</h1>
<h4 align="center">A rule-based tunnel in Go.</h4>
<h3 align="center">Another Clash Kernel.</h3>
<p align="center">
<a href="https://github.com/Dreamacro/clash/actions">
<img src="https://img.shields.io/github/workflow/status/Dreamacro/clash/Go?style=flat-square" alt="Github Actions">
<a href="https://goreportcard.com/report/github.com/Clash-Mini/Clash.Meta">
<img src="https://goreportcard.com/badge/github.com/Clash-Mini/Clash.Meta?style=flat-square">
</a>
<a href="https://goreportcard.com/report/github.com/Dreamacro/clash">
<img src="https://goreportcard.com/badge/github.com/Dreamacro/clash?style=flat-square">
<img src="https://img.shields.io/github/go-mod/go-version/Dreamacro/clash?style=flat-square">
<a href="https://github.com/Clash-Mini/Clash.Meta/releases">
<img src="https://img.shields.io/github/release/Clash-Mini/Clash.Meta/all.svg?style=flat-square">
</a>
<a href="https://github.com/Dreamacro/clash/releases">
<img src="https://img.shields.io/github/release/Dreamacro/clash/all.svg?style=flat-square">
<a href="https://github.com/Clash-Mini/Clash.Meta">
<img src="https://img.shields.io/badge/release-Meta-00b4f0?style=flat-square">
</a>
</p>
## Features
- Local HTTP/HTTPS/SOCKS server
- GeoIP rule support
- Supports Vmess, Shadowsocks, Snell and SOCKS5 protocol
- Supports Netfilter TCP redirecting
- Comprehensive HTTP API
- Local HTTP/HTTPS/SOCKS server with authentication support
- VMess, Shadowsocks, Trojan, Snell protocol support for remote connections
- Built-in DNS server that aims to minimize DNS pollution attack impact, supports DoH/DoT upstream and fake IP.
- Rules based off domains, GEOIP, IPCIDR or Process to forward packets to different nodes
- Remote groups allow users to implement powerful rules. Supports automatic fallback, load balancing or auto select node based off latency
- Remote providers, allowing users to get node lists remotely instead of hardcoding in config
- Netfilter TCP redirecting. Deploy Clash on your Internet gateway with `iptables`.
- Comprehensive HTTP RESTful API controller
## Install
## Getting Started
Documentations are now moved to [GitHub Wiki](https://github.com/Dreamacro/clash/wiki).
Clash Requires Go >= 1.13. You can build it from source:
## Advanced usage for this branch
```sh
$ go get -u -v github.com/Dreamacro/clash
### DNS configuration
Support `geosite` with `fallback-filter`.
Restore `Redir remote resolution`.
Support resolve ip with a `Proxy Tunnel`.
```yaml
proxy-groups:
- name: DNS
type: url-test
use:
- HK
url: http://cp.cloudflare.com
interval: 180
lazy: true
```
```yaml
dns:
enable: true
use-hosts: true
ipv6: false
enhanced-mode: redir-host
fake-ip-range: 198.18.0.1/16
listen: 127.0.0.1:6868
default-nameserver:
- 119.29.29.29
- 114.114.114.114
nameserver:
- https://doh.pub/dns-query
- tls://223.5.5.5:853
fallback:
- 'https://1.0.0.1/dns-query#DNS' # append the proxy adapter name or group name to the end of DNS URL with '#' prefix.
- 'tls://8.8.4.4:853#DNS'
fallback-filter:
geoip: false
geosite:
- gfw # `geosite` filter only use fallback server to resolve ip, prevent DNS leaks to unsafe DNS providers.
domain:
- +.example.com
ipcidr:
- 0.0.0.0/32
```
Pre-built binaries are available here: [release](https://github.com/Dreamacro/clash/releases)
### TUN configuration
Pre-built TUN mode binaries are available here: [TUN release](https://github.com/Dreamacro/clash/releases/tag/TUN)
Supports macOS, Linux and Windows.
Check Clash version with:
Built-in [Wintun](https://www.wintun.net) driver.
```sh
$ clash -v
```yaml
# Enable the TUN listener
tun:
enable: true
stack: gvisor # only gvisor
dns-hijack:
- 0.0.0.0:53 # additional dns server listen on TUN
auto-route: true # auto set global route
```
### Rules configuration
- Support rule `GEOSITE`.
- Support rule-providers `RULE-SET`.
- Support `multiport` condition for rule `SRC-PORT` and `DST-PORT`.
- Support `network` condition for all rules.
- Support source IPCIDR condition for all rules, just append to the end.
- The `GEOSITE` databases via https://github.com/Loyalsoldier/v2ray-rules-dat.
```yaml
rules:
# network(tcp/udp) condition for all rules
- DOMAIN-SUFFIX,bilibili.com,DIRECT,tcp
- DOMAIN-SUFFIX,bilibili.com,REJECT,udp
# multiport condition for rules SRC-PORT and DST-PORT
- DST-PORT,123/136/137-139,DIRECT,udp
# rule GEOSITE
- GEOSITE,category-ads-all,REJECT
- GEOSITE,icloud@cn,DIRECT
- GEOSITE,apple@cn,DIRECT
- GEOSITE,apple-cn,DIRECT
- GEOSITE,microsoft@cn,DIRECT
- GEOSITE,facebook,PROXY
- GEOSITE,youtube,PROXY
- GEOSITE,geolocation-cn,DIRECT
- GEOSITE,geolocation-!cn,PROXY
# source IPCIDR condition for all rules in gateway proxy
#- GEOSITE,geolocation-!cn,REJECT,192.168.1.88/32,192.168.1.99/32
- GEOIP,telegram,PROXY,no-resolve
- GEOIP,private,DIRECT,no-resolve
- GEOIP,cn,DIRECT
- MATCH,PROXY
```
## Daemon
Unfortunately, there is no native and elegant way to implement daemons on Golang.
### Proxies configuration
So we can use third-party daemon tools like PM2, Supervisor or the like.
Active health detection `urltest / fallback` (based on tcp handshake, multiple failures within a limited time will actively trigger health detection to use the node)
In the case of [pm2](https://github.com/Unitech/pm2), we can start the daemon this way:
Support `Policy Group Filter`
```yaml
proxy-groups:
- name: 🚀 HK Group
type: select
use:
- ALL
filter: 'HK'
- name: 🚀 US Group
type: select
use:
- ALL
filter: 'US'
proxy-providers:
ALL:
type: http
url: "xxxxx"
interval: 3600
path: "xxxxx"
health-check:
enable: true
interval: 600
url: http://www.gstatic.com/generate_204
```sh
$ pm2 start clash
```
If you have Docker installed, you can run clash directly using `docker-compose`.
[Run clash in docker](https://github.com/Dreamacro/clash/wiki/Run-clash-in-docker)
## Config
Support outbound transport protocol `VLESS`.
The default configuration directory is `$HOME/.config/clash`.
The name of the configuration file is `config.yaml`.
If you want to use another directory, use `-d` to control the configuration directory.
For example, you can use the current directory as the configuration directory:
```sh
$ clash -d .
```
<details>
<summary>This is an example configuration file (click to expand)</summary>
```yml
# port of HTTP
port: 7890
# port of SOCKS5
socks-port: 7891
# redir port for Linux and macOS
# redir-port: 7892
allow-lan: false
# Only applicable when setting allow-lan to true
# "*": bind all IP addresses
# 192.168.122.11: bind a single IPv4 address
# "[aaaa::a8aa:ff:fe09:57d8]": bind a single IPv6 address
# bind-address: "*"
# Rule / Global/ Direct (default is Rule)
mode: Rule
# set log level to stdout (default is info)
# info / warning / error / debug / silent
log-level: info
# RESTful API for clash
external-controller: 127.0.0.1:9090
# you can put the static web resource (such as clash-dashboard) to a directory, and clash would serve in `${API}/ui`
# input is a relative path to the configuration directory or an absolute path
# external-ui: folder
# Secret for RESTful API (Optional)
# secret: ""
# experimental feature
experimental:
ignore-resolve-fail: true # ignore dns resolve fail, default value is true
# authentication of local SOCKS5/HTTP(S) server
# authentication:
# - "user1:pass1"
# - "user2:pass2"
# # experimental hosts, support wildcard (e.g. *.clash.dev Even *.foo.*.example.com)
# # static domain has a higher priority than wildcard domain (foo.example.com > *.example.com)
# hosts:
# '*.clash.dev': 127.0.0.1
# 'alpha.clash.dev': '::1'
# dns:
# enable: true # set true to enable dns (default is false)
# ipv6: false # default is false
# listen: 0.0.0.0:53
# enhanced-mode: redir-host # or fake-ip
# # fake-ip-range: 198.18.0.1/16 # if you don't know what it is, don't change it
# fake-ip-filter: # fake ip white domain list
# - *.lan
# - localhost.ptlogin2.qq.com
# nameserver:
# - 114.114.114.114
# - tls://dns.rubyfish.cn:853 # dns over tls
# - https://1.1.1.1/dns-query # dns over https
# fallback: # concurrent request with nameserver, fallback used when GEOIP country isn't CN
# - tcp://1.1.1.1
# fallback-filter:
# geoip: true # default
# ipcidr: # ips in these subnets will be considered polluted
# - 240.0.0.0/4
Proxy:
# shadowsocks
# The supported ciphers(encrypt methods):
# aes-128-gcm aes-192-gcm aes-256-gcm
# aes-128-cfb aes-192-cfb aes-256-cfb
# aes-128-ctr aes-192-ctr aes-256-ctr
# rc4-md5 chacha20-ietf xchacha20
# chacha20-ietf-poly1305 xchacha20-ietf-poly1305
- name: "ss1"
type: ss
server: server
port: 443
cipher: chacha20-ietf-poly1305
password: "password"
# udp: true
# old obfs configuration format remove after prerelease
- name: "ss2"
type: ss
server: server
port: 443
cipher: chacha20-ietf-poly1305
password: "password"
plugin: obfs
plugin-opts:
mode: tls # or http
# host: bing.com
- name: "ss3"
type: ss
server: server
port: 443
cipher: chacha20-ietf-poly1305
password: "password"
plugin: v2ray-plugin
plugin-opts:
mode: websocket # no QUIC now
# tls: true # wss
# skip-cert-verify: true
# host: bing.com
# path: "/"
# mux: true
# headers:
# custom: value
# vmess
# cipher support auto/aes-128-gcm/chacha20-poly1305/none
- name: "vmess"
type: vmess
The XTLS support (TCP/UDP) transport by the XRAY-CORE.
```yaml
proxies:
- name: "vless"
type: vless
server: server
port: 443
uuid: uuid
alterId: 32
cipher: auto
# udp: true
# tls: true
servername: example.com # AKA SNI
# flow: xtls-rprx-direct # xtls-rprx-origin # enable XTLS
# skip-cert-verify: true
# network: ws
# ws-path: /path
# ws-headers:
# Host: v2ray.com
# socks5
- name: "socks"
type: socks5
- name: "vless-ws"
type: vless
server: server
port: 443
# username: username
# password: password
# tls: true
uuid: uuid
tls: true
udp: true
network: ws
servername: example.com # priority over wss host
# skip-cert-verify: true
# udp: true
ws-opts:
path: /path
headers: { Host: example.com, Edge: "12a00c4.fm.huawei.com:82897" }
# http
- name: "http"
type: http
- name: "vless-grpc"
type: vless
server: server
port: 443
# username: username
# password: password
# tls: true # https
uuid: uuid
tls: true
udp: true
network: grpc
servername: example.com # priority over wss host
# skip-cert-verify: true
# snell
- name: "snell"
type: snell
server: server
port: 44046
psk: yourpsk
# obfs-opts:
# mode: http # or tls
# host: bing.com
Proxy Group:
# url-test select which proxy will be used by benchmarking speed to a URL.
- name: "auto"
type: url-test
proxies:
- ss1
- ss2
- vmess1
url: 'http://www.gstatic.com/generate_204'
interval: 300
# fallback select an available policy by priority. The availability is tested by accessing an URL, just like an auto url-test group.
- name: "fallback-auto"
type: fallback
proxies:
- ss1
- ss2
- vmess1
url: 'http://www.gstatic.com/generate_204'
interval: 300
# load-balance: The request of the same eTLD will be dial on the same proxy.
- name: "load-balance"
type: load-balance
proxies:
- ss1
- ss2
- vmess1
url: 'http://www.gstatic.com/generate_204'
interval: 300
# select is used for selecting proxy or proxy group
# you can use RESTful API to switch proxy, is recommended for use in GUI.
- name: Proxy
type: select
proxies:
- ss1
- ss2
- vmess1
- auto
Rule:
- DOMAIN-SUFFIX,google.com,auto
- DOMAIN-KEYWORD,google,auto
- DOMAIN,google.com,auto
- DOMAIN-SUFFIX,ad.com,REJECT
# rename SOURCE-IP-CIDR and would remove after prerelease
- SRC-IP-CIDR,192.168.1.201/32,DIRECT
# optional param "no-resolve" for IP rules (GEOIP IP-CIDR)
- IP-CIDR,127.0.0.0/8,DIRECT
- GEOIP,CN,DIRECT
- DST-PORT,80,DIRECT
- SRC-PORT,7777,DIRECT
# FINAL would remove after prerelease
# you also can use `FINAL,Proxy` or `FINAL,,Proxy` now
- MATCH,auto
grpc-opts:
grpc-service-name: grpcname
```
</details>
## Advanced
[Provider](https://github.com/Dreamacro/clash/wiki/Provider)
### IPTABLES configuration
Work on Linux OS who's supported `iptables`
## Documentations
https://clash.gitbook.io/
```yaml
# Enable the TPROXY listener
tproxy-port: 9898
## Thanks
iptables:
enable: true # default is false
inbound-interface: eth0 # detect the inbound interface, default is 'lo'
```
[riobard/go-shadowsocks2](https://github.com/riobard/go-shadowsocks2)
[v2ray/v2ray-core](https://github.com/v2ray/v2ray-core)
### General installation guide for Linux
+ Create user given name `clash-meta`
+ Download and decompress pre-built binaries from [releases](https://github.com/MetaCubeX/Clash.Meta/releases)
+ Rename executable file to `Clash-Meta` and move to `/usr/local/bin/`
+ Create folder `/etc/Clash-Meta/` as working directory
Run Meta Kernel by user `clash-meta` as a daemon.
Create the systemd configuration file at `/etc/systemd/system/Clash-Meta.service`:
```
[Unit]
Description=Clash-Meta Daemon, Another Clash Kernel.
After=network.target NetworkManager.service systemd-networkd.service iwd.service
[Service]
Type=simple
User=clash-meta
Group=clash-meta
LimitNPROC=500
LimitNOFILE=1000000
CapabilityBoundingSet=cap_net_admin
AmbientCapabilities=cap_net_admin
Restart=always
ExecStartPre=/usr/bin/sleep 1s
ExecStart=/usr/local/bin/Clash-Meta -d /etc/Clash-Meta
[Install]
WantedBy=multi-user.target
```
Launch clashd on system startup with:
```shell
$ systemctl enable Clash-Meta
```
Launch clashd immediately with:
```shell
$ systemctl start Clash-Meta
```
### Display Process name
Clash add field `Process` to `Metadata` and prepare to get process name for Restful API `GET /connections`.
To display process name in GUI please use [Dashboard For Meta](https://github.com/Clash-Mini/Dashboard).
![img.png](https://github.com/Clash-Mini/Dashboard/raw/master/View/Dashboard-Process.png)
## 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
* [Dreamacro/clash](https://github.com/Dreamacro/clash)
* [riobard/go-shadowsocks2](https://github.com/riobard/go-shadowsocks2)
* [v2ray/v2ray-core](https://github.com/v2ray/v2ray-core)
* [WireGuard/wireguard-go](https://github.com/WireGuard/wireguard-go)
* [yaling888/clash-plus-pro](https://github.com/yaling888/clash)
## License
This software is released under the GPL-3.0 license.
[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2FDreamacro%2Fclash.svg?type=large)](https://app.fossa.io/projects/git%2Bgithub.com%2FDreamacro%2Fclash?ref=badge_large)
## TODO
- [x] Complementing the necessary rule operators
- [x] Redir proxy
- [x] UDP support
- [x] Connection manager
- [ ] Event API

207
adapter/adapter.go Normal file
View File

@ -0,0 +1,207 @@
package adapter
import (
"context"
"encoding/json"
"fmt"
"net"
"net/http"
"net/netip"
"net/url"
"strings"
"time"
"github.com/Dreamacro/clash/common/queue"
"github.com/Dreamacro/clash/component/dialer"
C "github.com/Dreamacro/clash/constant"
"go.uber.org/atomic"
)
var UnifiedDelay = atomic.NewBool(false)
type Proxy struct {
C.ProxyAdapter
history *queue.Queue[C.DelayHistory]
alive *atomic.Bool
}
// Alive implements C.Proxy
func (p *Proxy) Alive() bool {
return p.alive.Load()
}
// Dial implements C.Proxy
func (p *Proxy) Dial(metadata *C.Metadata) (C.Conn, error) {
ctx, cancel := context.WithTimeout(context.Background(), C.DefaultTCPTimeout)
defer cancel()
return p.DialContext(ctx, metadata)
}
// DialContext implements C.ProxyAdapter
func (p *Proxy) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.Conn, error) {
conn, err := p.ProxyAdapter.DialContext(ctx, metadata, opts...)
wasCancel := false
if err != nil {
wasCancel = strings.Contains(err.Error(), "operation was canceled")
}
p.alive.Store(err == nil || wasCancel)
return conn, err
}
// DialUDP implements C.ProxyAdapter
func (p *Proxy) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
ctx, cancel := context.WithTimeout(context.Background(), C.DefaultUDPTimeout)
defer cancel()
return p.ListenPacketContext(ctx, metadata)
}
// ListenPacketContext implements C.ProxyAdapter
func (p *Proxy) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) {
pc, err := p.ProxyAdapter.ListenPacketContext(ctx, metadata, opts...)
p.alive.Store(err == nil)
return pc, err
}
// DelayHistory implements C.Proxy
func (p *Proxy) DelayHistory() []C.DelayHistory {
queueM := p.history.Copy()
histories := []C.DelayHistory{}
for _, item := range queueM {
histories = append(histories, item)
}
return histories
}
// LastDelay return last history record. if proxy is not alive, return the max value of uint16.
// implements C.Proxy
func (p *Proxy) LastDelay() (delay uint16) {
var max uint16 = 0xffff
if !p.alive.Load() {
return max
}
history := p.history.Last()
if history.Delay == 0 {
return max
}
return history.Delay
}
// MarshalJSON implements C.ProxyAdapter
func (p *Proxy) MarshalJSON() ([]byte, error) {
inner, err := p.ProxyAdapter.MarshalJSON()
if err != nil {
return inner, err
}
mapping := map[string]any{}
_ = json.Unmarshal(inner, &mapping)
mapping["history"] = p.DelayHistory()
mapping["name"] = p.Name()
mapping["udp"] = p.SupportUDP()
return json.Marshal(mapping)
}
// URLTest get the delay for the specified URL
// implements C.Proxy
func (p *Proxy) URLTest(ctx context.Context, url string) (t uint16, err error) {
defer func() {
p.alive.Store(err == nil)
record := C.DelayHistory{Time: time.Now()}
if err == nil {
record.Delay = t
}
p.history.Put(record)
if p.history.Len() > 10 {
p.history.Pop()
}
}()
unifiedDelay := UnifiedDelay.Load()
addr, err := urlToMetadata(url)
if err != nil {
return
}
start := time.Now()
instance, err := p.DialContext(ctx, &addr)
if err != nil {
return
}
defer func() {
_ = instance.Close()
}()
req, err := http.NewRequest(http.MethodHead, url, nil)
if err != nil {
return
}
req = req.WithContext(ctx)
transport := &http.Transport{
DialContext: func(context.Context, string, string) (net.Conn, error) {
return instance, nil
},
// from http.DefaultTransport
MaxIdleConns: 100,
IdleConnTimeout: 90 * time.Second,
TLSHandshakeTimeout: 10 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
}
client := http.Client{
Transport: transport,
CheckRedirect: func(req *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse
},
}
defer client.CloseIdleConnections()
resp, err := client.Do(req)
if err != nil {
return
}
if unifiedDelay {
start = time.Now()
resp, err = client.Do(req)
if err != nil {
return
}
}
_ = resp.Body.Close()
t = uint16(time.Since(start) / time.Millisecond)
return
}
func NewProxy(adapter C.ProxyAdapter) *Proxy {
return &Proxy{adapter, queue.New[C.DelayHistory](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: netip.Addr{},
DstPort: port,
}
return
}

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

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

View File

@ -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
metadata.Type = C.HTTPS
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"
M "github.com/sagernet/sing/common/metadata"
)
// PacketAdapter is a UDP Packet adapter for socks/redir/tun
@ -17,9 +17,9 @@ func (s *PacketAdapter) Metadata() *C.Metadata {
}
// NewPacket is PacketAdapter generator
func NewPacket(target socks5.Addr, packet C.UDPPacket, source C.Type, netType C.NetWork) *PacketAdapter {
metadata := parseSocksAddr(target)
metadata.NetWork = netType
func NewPacket(target M.Socksaddr, packet C.UDPPacket, source C.Type) *PacketAdapter {
metadata := socksAddrToMetadata(target)
metadata.NetWork = C.UDP
metadata.Type = source
if ip, port, err := parseAddr(packet.LocalAddr().String()); err == nil {
metadata.SrcIP = ip

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

@ -0,0 +1,51 @@
package inbound
import (
"net"
"net/netip"
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
remoteAddr := conn.RemoteAddr()
// Filter when net.Addr interface is nil
if remoteAddr != nil {
if ip, port, err := parseAddr(remoteAddr.String()); err == nil {
metadata.SrcIP = ip
metadata.SrcPort = port
}
}
return context.NewConnContext(conn, metadata)
}
func NewInner(conn net.Conn, dst string, host string) *context.ConnContext {
metadata := &C.Metadata{}
metadata.NetWork = C.TCP
metadata.Type = C.INNER
metadata.DNSMode = C.DNSMapping
metadata.Host = host
metadata.AddrType = C.AtypDomainName
metadata.Process = C.ClashName
if h, port, err := net.SplitHostPort(dst); err == nil {
metadata.DstPort = port
if host == "" {
if ip, err := netip.ParseAddr(h); err == nil {
metadata.DstIP = ip
metadata.AddrType = C.AtypIPv4
if ip.Is6() {
metadata.AddrType = C.AtypIPv6
}
}
}
}
return context.NewConnContext(conn, metadata)
}

94
adapter/inbound/util.go Normal file
View File

@ -0,0 +1,94 @@
package inbound
import (
"net"
"net/http"
"net/netip"
"strconv"
"strings"
"github.com/Dreamacro/clash/common/nnip"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/transport/socks5"
M "github.com/sagernet/sing/common/metadata"
)
func socksAddrToMetadata(addr M.Socksaddr) *C.Metadata {
metadata := &C.Metadata{}
switch addr.Family() {
case M.AddressFamilyIPv4:
metadata.AddrType = C.AtypIPv4
metadata.DstIP = addr.Addr
case M.AddressFamilyIPv6:
metadata.AddrType = C.AtypIPv6
metadata.DstIP = addr.Addr
case M.AddressFamilyFqdn:
metadata.AddrType = C.AtypDomainName
metadata.Host = addr.Fqdn
}
metadata.DstPort = strconv.Itoa(int(addr.Port))
return metadata
}
func parseSocksAddr(target socks5.Addr) *C.Metadata {
metadata := &C.Metadata{
AddrType: int(target[0]),
}
switch target[0] {
case socks5.AtypDomainName:
// trim for FQDN
metadata.Host = strings.TrimRight(string(target[2:2+target[1]]), ".")
metadata.DstPort = strconv.Itoa((int(target[2+target[1]]) << 8) | int(target[2+target[1]+1]))
case socks5.AtypIPv4:
metadata.DstIP = nnip.IpToAddr(net.IP(target[1 : 1+net.IPv4len]))
metadata.DstPort = strconv.Itoa((int(target[1+net.IPv4len]) << 8) | int(target[1+net.IPv4len+1]))
case socks5.AtypIPv6:
metadata.DstIP = nnip.IpToAddr(net.IP(target[1 : 1+net.IPv6len]))
metadata.DstPort = strconv.Itoa((int(target[1+net.IPv6len]) << 8) | int(target[1+net.IPv6len+1]))
}
return metadata
}
func parseHTTPAddr(request *http.Request) *C.Metadata {
host := request.URL.Hostname()
port := request.URL.Port()
if port == "" {
port = "80"
}
// trim FQDN (#737)
host = strings.TrimRight(host, ".")
metadata := &C.Metadata{
NetWork: C.TCP,
AddrType: C.AtypDomainName,
Host: host,
DstIP: netip.Addr{},
DstPort: port,
}
ip, err := netip.ParseAddr(host)
if err == nil {
switch {
case ip.Is6():
metadata.AddrType = C.AtypIPv6
default:
metadata.AddrType = C.AtypIPv4
}
metadata.DstIP = ip
}
return metadata
}
func parseAddr(addr string) (netip.Addr, string, error) {
host, port, err := net.SplitHostPort(addr)
if err != nil {
return netip.Addr{}, "", err
}
ip, err := netip.ParseAddr(host)
return ip, port, err
}

211
adapter/outbound/base.go Normal file
View File

@ -0,0 +1,211 @@
package outbound
import (
"context"
"encoding/json"
"errors"
"net"
"strings"
"github.com/Dreamacro/clash/component/dialer"
C "github.com/Dreamacro/clash/constant"
"github.com/gofrs/uuid"
"github.com/sagernet/sing/common/buf"
"github.com/sagernet/sing/common/bufio"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
)
type Base struct {
name string
addr string
iface string
tp C.AdapterType
udp bool
rmark int
id string
}
// Name implements C.ProxyAdapter
func (b *Base) Name() string {
return b.name
}
// Id implements C.ProxyAdapter
func (b *Base) Id() string {
if b.id == "" {
id, err := uuid.NewV6()
if err != nil {
b.id = b.name
} else {
b.id = id.String()
}
}
return b.id
}
// 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")
}
func (b *Base) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.Conn, error) {
return nil, errors.New("no support")
}
// ListenPacketContext implements C.ProxyAdapter
func (b *Base) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) {
return nil, errors.New("no support")
}
// ListenPacketOnStreamConn implements C.ProxyAdapter
func (b *Base) ListenPacketOnStreamConn(c net.Conn, metadata *C.Metadata) (_ C.PacketConn, err error) {
return nil, errors.New("no support")
}
// SupportUOT implements C.ProxyAdapter
func (b *Base) SupportUOT() bool {
return false
}
// 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(),
"id": b.Id(),
})
}
// 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
}
// DialOptions return []dialer.Option from struct
func (b *Base) DialOptions(opts ...dialer.Option) []dialer.Option {
if b.iface != "" {
opts = append(opts, dialer.WithInterface(b.iface))
}
if b.rmark != 0 {
opts = append(opts, dialer.WithRoutingMark(b.rmark))
}
return opts
}
type BasicOption struct {
Interface string `proxy:"interface-name,omitempty" group:"interface-name,omitempty"`
RoutingMark int `proxy:"routing-mark,omitempty" group:"routing-mark,omitempty"`
}
type BaseOption struct {
Name string
Addr string
Type C.AdapterType
UDP bool
Interface string
RoutingMark int
}
func NewBase(opt BaseOption) *Base {
return &Base{
name: opt.Name,
addr: opt.Addr,
tp: opt.Type,
udp: opt.UDP,
iface: opt.Interface,
rmark: opt.RoutingMark,
}
}
type conn struct {
net.Conn
chain C.Chain
actualRemoteDestination string
}
func (c *conn) RemoteDestination() string {
return c.actualRemoteDestination
}
// 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()}, parseRemoteDestination(a.Addr())}
}
type packetConn struct {
net.PacketConn
nc N.PacketConn
chain C.Chain
actualRemoteDestination string
}
func (c *packetConn) RemoteDestination() string {
return c.actualRemoteDestination
}
// 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 (c *packetConn) ReadPacket(buffer *buf.Buffer) (addr M.Socksaddr, err error) {
return c.nc.ReadPacket(buffer)
}
func (c *packetConn) WritePacket(buffer *buf.Buffer, addr M.Socksaddr) error {
return c.nc.WritePacket(buffer, addr)
}
func newPacketConn(pc net.PacketConn, a C.ProxyAdapter) C.PacketConn {
var nc N.PacketConn
if n, isNc := pc.(N.PacketConn); isNc {
nc = n
} else {
nc = &bufio.PacketConnWrapper{PacketConn: pc}
}
return &packetConn{pc, nc, []string{a.Name()}, parseRemoteDestination(a.Addr())}
}
func parseRemoteDestination(addr string) string {
if dst, _, err := net.SplitHostPort(addr); err == nil {
return dst
} else {
if addrError, ok := err.(*net.AddrError); ok && strings.Contains(addrError.Err, "missing port") {
return dst
} else {
return ""
}
}
}

View File

@ -0,0 +1,58 @@
package outbound
import (
"context"
"net"
"github.com/Dreamacro/clash/component/dialer"
C "github.com/Dreamacro/clash/constant"
)
type Direct struct {
*Base
}
// DialContext implements C.ProxyAdapter
func (d *Direct) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.Conn, error) {
opts = append(opts, dialer.WithDirect())
c, err := dialer.DialContext(ctx, "tcp", metadata.RemoteAddress(), d.Base.DialOptions(opts...)...)
if err != nil {
return nil, err
}
tcpKeepAlive(c)
return NewConn(c, d), nil
}
// ListenPacketContext implements C.ProxyAdapter
func (d *Direct) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) {
opts = append(opts, dialer.WithDirect())
pc, err := dialer.ListenPacket(ctx, "udp", "", d.Base.DialOptions(opts...)...)
if err != nil {
return nil, err
}
return newPacketConn(&directPacketConn{pc}, d), nil
}
type directPacketConn struct {
net.PacketConn
}
func NewDirect() *Direct {
return &Direct{
Base: &Base{
name: "DIRECT",
tp: C.Direct,
udp: true,
},
}
}
func NewCompatible() *Direct {
return &Direct{
Base: &Base{
name: "COMPATIBLE",
tp: C.Compatible,
udp: true,
},
}
}

View File

@ -13,44 +13,64 @@ import (
"net/url"
"strconv"
"github.com/Dreamacro/clash/component/dialer"
C "github.com/Dreamacro/clash/constant"
)
type Http struct {
*Base
addr string
user string
pass string
tlsConfig *tls.Config
option *HttpOption
}
type HttpOption struct {
Name string `proxy:"name"`
Server string `proxy:"server"`
Port int `proxy:"port"`
UserName string `proxy:"username,omitempty"`
Password string `proxy:"password,omitempty"`
TLS bool `proxy:"tls,omitempty"`
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
BasicOption
Name string `proxy:"name"`
Server string `proxy:"server"`
Port int `proxy:"port"`
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"`
Headers map[string]string `proxy:"headers,omitempty"`
}
func (h *Http) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
c, err := dialContext(ctx, "tcp", h.addr)
if err == nil && h.tlsConfig != nil {
// 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)
err = cc.Handshake()
err := cc.Handshake()
c = cc
if err != nil {
return nil, fmt.Errorf("%s connect error: %w", h.addr, err)
}
}
if err := h.shakeHand(metadata, c); err != nil {
return nil, err
}
return c, nil
}
// DialContext implements C.ProxyAdapter
func (h *Http) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) {
c, err := dialer.DialContext(ctx, "tcp", h.addr, h.Base.DialOptions(opts...)...)
if err != nil {
return nil, fmt.Errorf("%s connect error: %w", h.addr, err)
}
tcpKeepAlive(c)
if err := h.shakeHand(metadata, c); err != nil {
defer safeConnClose(c, err)
c, err = h.StreamConn(c, metadata)
if err != nil {
return nil, err
}
return newConn(c, h), nil
return NewConn(c, h), nil
}
func (h *Http) shakeHand(metadata *C.Metadata, rw io.ReadWriter) error {
@ -66,6 +86,13 @@ func (h *Http) shakeHand(metadata *C.Metadata, rw io.ReadWriter) error {
},
}
// 增加headers
if len(h.option.Headers) != 0 {
for key, value := range h.option.Headers {
req.Header.Add(key, value)
}
}
if h.user != "" && h.pass != "" {
auth := h.user + ":" + h.pass
req.Header.Add("Proxy-Authorization", "Basic "+base64.StdEncoding.EncodeToString([]byte(auth)))
@ -102,21 +129,27 @@ 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,
}
}
return &Http{
Base: &Base{
name: option.Name,
tp: C.Http,
name: option.Name,
addr: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)),
tp: C.Http,
iface: option.Interface,
rmark: option.RoutingMark,
},
addr: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)),
user: option.UserName,
pass: option.Password,
tlsConfig: tlsConfig,
option: &option,
}
}

View File

@ -0,0 +1,270 @@
package outbound
import (
"context"
"crypto/tls"
"crypto/x509"
"encoding/base64"
"errors"
"fmt"
"io/ioutil"
"net"
"regexp"
"strconv"
"time"
"github.com/Dreamacro/clash/component/dialer"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/log"
"github.com/lucas-clemente/quic-go"
"github.com/lucas-clemente/quic-go/congestion"
M "github.com/sagernet/sing/common/metadata"
hyCongestion "github.com/tobyxdd/hysteria/pkg/congestion"
"github.com/tobyxdd/hysteria/pkg/core"
"github.com/tobyxdd/hysteria/pkg/obfs"
"github.com/tobyxdd/hysteria/pkg/pmtud_fix"
"github.com/tobyxdd/hysteria/pkg/transport"
)
const (
mbpsToBps = 125000
minSpeedBPS = 16384
DefaultStreamReceiveWindow = 15728640 // 15 MB/s
DefaultConnectionReceiveWindow = 67108864 // 64 MB/s
DefaultMaxIncomingStreams = 1024
DefaultALPN = "hysteria"
DefaultProtocol = "udp"
)
var rateStringRegexp = regexp.MustCompile(`^(\d+)\s*([KMGT]?)([Bb])ps$`)
type Hysteria struct {
*Base
client *core.Client
clientTransport *transport.ClientTransport
}
func (h *Hysteria) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.Conn, error) {
tcpConn, err := h.client.DialTCP(metadata.RemoteAddress(), hyDialer(func() (net.PacketConn, error) {
return dialer.ListenPacket(ctx, "udp", "", h.Base.DialOptions(opts...)...)
}))
if err != nil {
return nil, err
}
return NewConn(tcpConn, h), nil
}
func (h *Hysteria) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) {
udpConn, err := h.client.DialUDP(hyDialer(func() (net.PacketConn, error) {
return dialer.ListenPacket(ctx, "udp", "", h.Base.DialOptions(opts...)...)
}))
if err != nil {
return nil, err
}
return newPacketConn(&hyPacketConn{udpConn}, h), nil
}
type HysteriaOption struct {
BasicOption
Name string `proxy:"name"`
Server string `proxy:"server"`
Port int `proxy:"port"`
Protocol string `proxy:"protocol,omitempty"`
Up string `proxy:"up,omitempty"`
UpMbps int `proxy:"up_mbps,omitempty"`
Down string `proxy:"down,omitempty"`
DownMbps int `proxy:"down_mbps,omitempty"`
Auth string `proxy:"auth,omitempty"`
AuthString string `proxy:"auth_str,omitempty"`
Obfs string `proxy:"obfs,omitempty"`
SNI string `proxy:"sni,omitempty"`
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
ALPN string `proxy:"alpn,omitempty"`
CustomCA string `proxy:"ca,omitempty"`
CustomCAString string `proxy:"ca_str,omitempty"`
ReceiveWindowConn int `proxy:"recv_window_conn,omitempty"`
ReceiveWindow int `proxy:"recv_window,omitempty"`
DisableMTUDiscovery bool `proxy:"disable_mtu_discovery,omitempty"`
}
func (c *HysteriaOption) Speed() (uint64, uint64, error) {
var up, down uint64
if len(c.Up) > 0 {
up = stringToBps(c.Up)
if up == 0 {
return 0, 0, errors.New("invalid speed format")
}
} else {
up = uint64(c.UpMbps) * mbpsToBps
}
if len(c.Down) > 0 {
down = stringToBps(c.Down)
if down == 0 {
return 0, 0, errors.New("invalid speed format")
}
} else {
down = uint64(c.DownMbps) * mbpsToBps
}
return up, down, nil
}
func NewHysteria(option HysteriaOption) (*Hysteria, error) {
clientTransport := &transport.ClientTransport{
Dialer: &net.Dialer{
Timeout: 8 * time.Second,
},
}
addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port))
serverName := option.Server
if option.SNI != "" {
serverName = option.Server
}
tlsConfig := &tls.Config{
ServerName: serverName,
InsecureSkipVerify: option.SkipCertVerify,
MinVersion: tls.VersionTLS13,
}
if len(option.ALPN) > 0 {
tlsConfig.NextProtos = []string{option.ALPN}
} else {
tlsConfig.NextProtos = []string{DefaultALPN}
}
if len(option.CustomCA) > 0 {
bs, err := ioutil.ReadFile(option.CustomCA)
if err != nil {
return nil, fmt.Errorf("hysteria %s load ca error: %w", addr, err)
}
cp := x509.NewCertPool()
if !cp.AppendCertsFromPEM(bs) {
return nil, fmt.Errorf("hysteria %s failed to parse ca_str", addr)
}
tlsConfig.RootCAs = cp
} else if option.CustomCAString != "" {
cp := x509.NewCertPool()
if !cp.AppendCertsFromPEM([]byte(option.CustomCAString)) {
return nil, fmt.Errorf("hysteria %s failed to parse ca_str", addr)
}
tlsConfig.RootCAs = cp
}
quicConfig := &quic.Config{
InitialStreamReceiveWindow: uint64(option.ReceiveWindowConn),
MaxStreamReceiveWindow: uint64(option.ReceiveWindowConn),
InitialConnectionReceiveWindow: uint64(option.ReceiveWindow),
MaxConnectionReceiveWindow: uint64(option.ReceiveWindow),
KeepAlive: true,
DisablePathMTUDiscovery: option.DisableMTUDiscovery,
EnableDatagrams: true,
}
if option.Protocol == "" {
option.Protocol = DefaultProtocol
}
if option.ReceiveWindowConn == 0 {
quicConfig.InitialStreamReceiveWindow = DefaultStreamReceiveWindow
quicConfig.MaxStreamReceiveWindow = DefaultStreamReceiveWindow
}
if option.ReceiveWindow == 0 {
quicConfig.InitialConnectionReceiveWindow = DefaultConnectionReceiveWindow
quicConfig.MaxConnectionReceiveWindow = DefaultConnectionReceiveWindow
}
if !quicConfig.DisablePathMTUDiscovery && pmtud_fix.DisablePathMTUDiscovery {
log.Infoln("hysteria: Path MTU Discovery is not yet supported on this platform")
}
var auth []byte
if option.Auth != "" {
authBytes, err := base64.StdEncoding.DecodeString(option.Auth)
if err != nil {
return nil, fmt.Errorf("hysteria %s parse auth error: %w", addr, err)
}
auth = authBytes
} else {
auth = []byte(option.AuthString)
}
var obfuscator obfs.Obfuscator
if len(option.Obfs) > 0 {
obfuscator = obfs.NewXPlusObfuscator([]byte(option.Obfs))
}
up, down, _ := option.Speed()
client, err := core.NewClient(
addr, option.Protocol, auth, tlsConfig, quicConfig, clientTransport, up, down, func(refBPS uint64) congestion.CongestionControl {
return hyCongestion.NewBrutalSender(congestion.ByteCount(refBPS))
}, obfuscator,
)
if err != nil {
return nil, fmt.Errorf("hysteria %s create error: %w", addr, err)
}
return &Hysteria{
Base: &Base{
name: option.Name,
addr: addr,
tp: C.Hysteria,
udp: true,
iface: option.Interface,
rmark: option.RoutingMark,
},
client: client,
clientTransport: clientTransport,
}, nil
}
func stringToBps(s string) uint64 {
if s == "" {
return 0
}
m := rateStringRegexp.FindStringSubmatch(s)
if m == nil {
return 0
}
var n uint64
switch m[2] {
case "K":
n = 1 << 10
case "M":
n = 1 << 20
case "G":
n = 1 << 30
case "T":
n = 1 << 40
default:
n = 1
}
v, _ := strconv.ParseUint(m[1], 10, 64)
n = v * n
if m[3] == "b" {
// Bits, need to convert to bytes
n = n >> 3
}
return n
}
type hyPacketConn struct {
core.UDPConn
}
func (c *hyPacketConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) {
b, addrStr, err := c.UDPConn.ReadFrom()
if err != nil {
return
}
n = copy(p, b)
addr = M.ParseSocksaddr(addrStr).UDPAddr()
return
}
func (c *hyPacketConn) WriteTo(p []byte, addr net.Addr) (n int, err error) {
err = c.UDPConn.WriteTo(p, M.SocksaddrFromNet(addr).String())
if err != nil {
return
}
n = len(p)
return
}
type hyDialer func() (net.PacketConn, error)
func (h hyDialer) ListenPacket() (net.PacketConn, error) {
return h()
}

View File

@ -0,0 +1,72 @@
package outbound
import (
"context"
"io"
"net"
"time"
"github.com/Dreamacro/clash/component/dialer"
C "github.com/Dreamacro/clash/constant"
)
type Reject struct {
*Base
}
// DialContext implements C.ProxyAdapter
func (r *Reject) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.Conn, error) {
return NewConn(&nopConn{}, r), nil
}
// ListenPacketContext implements C.ProxyAdapter
func (r *Reject) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) {
return newPacketConn(&nopPacketConn{}, r), nil
}
func NewReject() *Reject {
return &Reject{
Base: &Base{
name: "REJECT",
tp: C.Reject,
udp: true,
},
}
}
func NewPass() *Reject {
return &Reject{
Base: &Base{
name: "PASS",
tp: C.Pass,
udp: true,
},
}
}
type nopConn struct{}
func (rw *nopConn) Read(b []byte) (int, error) {
return 0, io.EOF
}
func (rw *nopConn) Write(b []byte) (int, error) {
return 0, io.EOF
}
func (rw *nopConn) Close() error { return nil }
func (rw *nopConn) LocalAddr() net.Addr { return nil }
func (rw *nopConn) RemoteAddr() net.Addr { return nil }
func (rw *nopConn) SetDeadline(time.Time) error { return nil }
func (rw *nopConn) SetReadDeadline(time.Time) error { return nil }
func (rw *nopConn) SetWriteDeadline(time.Time) error { return nil }
type nopPacketConn struct{}
func (npc *nopPacketConn) WriteTo(b []byte, addr net.Addr) (n int, err error) { return len(b), nil }
func (npc *nopPacketConn) ReadFrom(b []byte) (int, net.Addr, error) { return 0, nil, io.EOF }
func (npc *nopPacketConn) Close() error { return nil }
func (npc *nopPacketConn) LocalAddr() net.Addr { return &net.UDPAddr{IP: net.IPv4zero, Port: 0} }
func (npc *nopPacketConn) SetDeadline(time.Time) error { return nil }
func (npc *nopPacketConn) SetReadDeadline(time.Time) error { return nil }
func (npc *nopPacketConn) SetWriteDeadline(time.Time) error { return nil }

View File

@ -0,0 +1,201 @@
package outbound
import (
"context"
"errors"
"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/socks5"
v2rayObfs "github.com/Dreamacro/clash/transport/v2ray-plugin"
"github.com/sagernet/sing-shadowsocks"
"github.com/sagernet/sing-shadowsocks/shadowimpl"
"github.com/sagernet/sing/common/bufio"
M "github.com/sagernet/sing/common/metadata"
)
type ShadowSocks struct {
*Base
method shadowsocks.Method
// obfs
obfsMode string
obfsOption *simpleObfsOption
v2rayOption *v2rayObfs.Option
}
type ShadowSocksOption struct {
BasicOption
Name string `proxy:"name"`
Server string `proxy:"server"`
Port int `proxy:"port"`
Password string `proxy:"password"`
Cipher string `proxy:"cipher"`
UDP bool `proxy:"udp,omitempty"`
Plugin string `proxy:"plugin,omitempty"`
PluginOpts map[string]any `proxy:"plugin-opts,omitempty"`
}
type simpleObfsOption struct {
Mode string `obfs:"mode,omitempty"`
Host string `obfs:"host,omitempty"`
}
type v2rayObfsOption struct {
Mode string `obfs:"mode"`
Host string `obfs:"host,omitempty"`
Path string `obfs:"path,omitempty"`
TLS bool `obfs:"tls,omitempty"`
Headers map[string]string `obfs:"headers,omitempty"`
SkipCertVerify bool `obfs:"skip-cert-verify,omitempty"`
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":
c = obfs.NewTLSObfs(c, ss.obfsOption.Host)
case "http":
_, port, _ := net.SplitHostPort(ss.addr)
c = obfs.NewHTTPObfs(c, ss.obfsOption.Host, port)
case "websocket":
var err error
c, err = v2rayObfs.NewV2rayObfs(c, ss.v2rayOption)
if err != nil {
return nil, fmt.Errorf("%s connect error: %w", ss.addr, err)
}
}
return ss.method.DialConn(c, M.ParseSocksaddr(metadata.RemoteAddress()))
}
// DialContext implements C.ProxyAdapter
func (ss *ShadowSocks) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) {
c, err := dialer.DialContext(ctx, "tcp", ss.addr, ss.Base.DialOptions(opts...)...)
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
}
// ListenPacketContext implements C.ProxyAdapter
func (ss *ShadowSocks) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) {
pc, err := dialer.ListenPacket(ctx, "udp", "", ss.Base.DialOptions(opts...)...)
if err != nil {
return nil, err
}
addr, err := resolveUDPAddr("udp", ss.addr)
if err != nil {
pc.Close()
return nil, err
}
pc = ss.method.DialPacketConn(&bufio.BindPacketConn{PacketConn: pc, Addr: addr})
return newPacketConn(pc, ss), nil
}
func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) {
addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port))
method, err := shadowimpl.FetchMethod(option.Cipher, option.Password)
if err != nil {
return nil, fmt.Errorf("ss %s initialize error: %w", addr, err)
}
var v2rayOption *v2rayObfs.Option
var obfsOption *simpleObfsOption
obfsMode := ""
decoder := structure.NewDecoder(structure.Option{TagName: "obfs", WeaklyTypedInput: true})
if option.Plugin == "obfs" {
opts := simpleObfsOption{Host: "bing.com"}
if err := decoder.Decode(option.PluginOpts, &opts); err != nil {
return nil, fmt.Errorf("ss %s initialize obfs error: %w", addr, err)
}
if opts.Mode != "tls" && opts.Mode != "http" {
return nil, fmt.Errorf("ss %s obfs mode error: %s", addr, opts.Mode)
}
obfsMode = opts.Mode
obfsOption = &opts
} else if option.Plugin == "v2ray-plugin" {
opts := v2rayObfsOption{Host: "bing.com", Mux: true}
if err := decoder.Decode(option.PluginOpts, &opts); err != nil {
return nil, fmt.Errorf("ss %s initialize v2ray-plugin error: %w", addr, err)
}
if opts.Mode != "websocket" {
return nil, fmt.Errorf("ss %s obfs mode error: %s", addr, opts.Mode)
}
obfsMode = opts.Mode
v2rayOption = &v2rayObfs.Option{
Host: opts.Host,
Path: opts.Path,
Headers: opts.Headers,
Mux: opts.Mux,
}
if opts.TLS {
v2rayOption.TLS = true
v2rayOption.SkipCertVerify = opts.SkipCertVerify
}
}
return &ShadowSocks{
Base: &Base{
name: option.Name,
addr: addr,
tp: C.Shadowsocks,
udp: option.UDP,
iface: option.Interface,
rmark: option.RoutingMark,
},
method: method,
obfsMode: obfsMode,
v2rayOption: v2rayOption,
obfsOption: obfsOption,
}, nil
}
type ssPacketConn struct {
net.PacketConn
rAddr net.Addr
}
func (spc *ssPacketConn) WriteTo(b []byte, addr net.Addr) (n int, err error) {
packet, err := socks5.EncodeUDPPacket(socks5.ParseAddrToSocksAddr(addr), b)
if err != nil {
return
}
return spc.PacketConn.WriteTo(packet[3:], spc.rAddr)
}
func (spc *ssPacketConn) ReadFrom(b []byte) (int, net.Addr, error) {
n, _, e := spc.PacketConn.ReadFrom(b)
if e != nil {
return 0, nil, e
}
addr := socks5.SplitAddr(b[:n])
if addr == nil {
return 0, nil, errors.New("parse addr error")
}
udpAddr := addr.UDPAddr()
if udpAddr == nil {
return 0, nil, errors.New("parse addr error")
}
copy(b, b[len(addr):])
return n - len(addr), udpAddr, e
}

View File

@ -0,0 +1,157 @@
package outbound
import (
"context"
"fmt"
"net"
"strconv"
"github.com/Dreamacro/clash/component/dialer"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/transport/shadowsocks/core"
"github.com/Dreamacro/clash/transport/shadowsocks/shadowaead"
"github.com/Dreamacro/clash/transport/shadowsocks/shadowstream"
"github.com/Dreamacro/clash/transport/ssr/obfs"
"github.com/Dreamacro/clash/transport/ssr/protocol"
)
type ShadowSocksR struct {
*Base
cipher core.Cipher
obfs obfs.Obfs
protocol protocol.Protocol
}
type ShadowSocksROption struct {
BasicOption
Name string `proxy:"name"`
Server string `proxy:"server"`
Port int `proxy:"port"`
Password string `proxy:"password"`
Cipher string `proxy:"cipher"`
Obfs string `proxy:"obfs"`
ObfsParam string `proxy:"obfs-param,omitempty"`
Protocol string `proxy:"protocol"`
ProtocolParam string `proxy:"protocol-param,omitempty"`
UDP bool `proxy:"udp,omitempty"`
}
// StreamConn implements C.ProxyAdapter
func (ssr *ShadowSocksR) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
c = ssr.obfs.StreamConn(c)
c = ssr.cipher.StreamConn(c)
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")
}
c = ssr.protocol.StreamConn(c, iv)
_, err = c.Write(serializesSocksAddr(metadata))
return c, err
}
// DialContext implements C.ProxyAdapter
func (ssr *ShadowSocksR) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) {
c, err := dialer.DialContext(ctx, "tcp", ssr.addr, ssr.Base.DialOptions(opts...)...)
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
}
// ListenPacketContext implements C.ProxyAdapter
func (ssr *ShadowSocksR) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) {
pc, err := dialer.ListenPacket(ctx, "udp", "", ssr.Base.DialOptions(opts...)...)
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 = ssr.protocol.PacketConn(pc)
return newPacketConn(&ssPacketConn{PacketConn: pc, rAddr: addr}, ssr), nil
}
func NewShadowSocksR(option ShadowSocksROption) (*ShadowSocksR, error) {
// SSR protocol compatibility
// https://github.com/Dreamacro/clash/pull/2056
if option.Cipher == "none" {
option.Cipher = "dummy"
}
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 error: %w", addr, err)
}
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 none or a supported stream cipher in ssr", cipher)
}
ivSize = ciph.IVSize()
key = ciph.Key
}
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{
Key: key,
Overhead: obfsOverhead,
Param: option.ProtocolParam,
})
if err != nil {
return nil, fmt.Errorf("ssr %s initialize protocol error: %w", addr, err)
}
return &ShadowSocksR{
Base: &Base{
name: option.Name,
addr: addr,
tp: C.ShadowsocksR,
udp: option.UDP,
iface: option.Interface,
rmark: option.RoutingMark,
},
cipher: coreCiph,
obfs: obfs,
protocol: protocol,
}, nil
}

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

@ -0,0 +1,179 @@
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 {
BasicOption
Name string `proxy:"name"`
Server string `proxy:"server"`
Port int `proxy:"port"`
Psk string `proxy:"psk"`
UDP bool `proxy:"udp,omitempty"`
Version int `proxy:"version,omitempty"`
ObfsOpts map[string]any `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})
if metadata.NetWork == C.UDP {
err := snell.WriteUDPHeader(c, s.version)
return c, err
}
port, _ := strconv.ParseUint(metadata.DstPort, 10, 16)
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, opts ...dialer.Option) (_ C.Conn, err error) {
if s.version == snell.Version2 && len(opts) == 0 {
c, err := s.pool.Get()
if err != nil {
return nil, err
}
port, _ := strconv.ParseUint(metadata.DstPort, 10, 16)
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, s.Base.DialOptions(opts...)...)
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
}
// ListenPacketContext implements C.ProxyAdapter
func (s *Snell) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) {
c, err := dialer.DialContext(ctx, "tcp", s.addr, s.Base.DialOptions(opts...)...)
if err != nil {
return nil, err
}
tcpKeepAlive(c)
c = streamConn(c, streamOption{s.psk, s.version, s.addr, s.obfsOption})
err = snell.WriteUDPHeader(c, s.version)
if err != nil {
return nil, err
}
pc := snell.PacketConn(c)
return newPacketConn(pc, s), nil
}
// ListenPacketOnStreamConn implements C.ProxyAdapter
func (s *Snell) ListenPacketOnStreamConn(c net.Conn, metadata *C.Metadata) (_ C.PacketConn, err error) {
pc := snell.PacketConn(c)
return newPacketConn(pc, s), nil
}
// SupportUOT implements C.ProxyAdapter
func (s *Snell) SupportUOT() bool {
return true
}
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
}
switch option.Version {
case snell.Version1, snell.Version2:
if option.UDP {
return nil, fmt.Errorf("snell version %d not support UDP", option.Version)
}
case snell.Version3:
default:
return nil, fmt.Errorf("snell version error: %d", option.Version)
}
s := &Snell{
Base: &Base{
name: option.Name,
addr: addr,
tp: C.Snell,
udp: option.UDP,
iface: option.Interface,
rmark: option.RoutingMark,
},
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, s.Base.DialOptions()...)
if err != nil {
return nil, err
}
tcpKeepAlive(c)
return streamConn(c, streamOption{psk, option.Version, addr, obfsOption}), nil
})
}
return s, nil
}

View File

@ -3,19 +3,19 @@ package outbound
import (
"context"
"crypto/tls"
"errors"
"fmt"
"io"
"io/ioutil"
"net"
"strconv"
"github.com/Dreamacro/clash/component/socks5"
"github.com/Dreamacro/clash/component/dialer"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/transport/socks5"
)
type Socks5 struct {
*Base
addr string
user string
pass string
tls bool
@ -24,6 +24,7 @@ type Socks5 struct {
}
type Socks5Option struct {
BasicOption
Name string `proxy:"name"`
Server string `proxy:"server"`
Port int `proxy:"port"`
@ -34,19 +35,17 @@ type Socks5Option struct {
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
}
func (ss *Socks5) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
c, err := dialContext(ctx, "tcp", ss.addr)
if err == nil && ss.tls {
// 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)
err = cc.Handshake()
err := cc.Handshake()
c = cc
if err != nil {
return nil, fmt.Errorf("%s connect error: %w", ss.addr, err)
}
}
if err != nil {
return nil, fmt.Errorf("%s connect error: %w", ss.addr, err)
}
tcpKeepAlive(c)
var user *socks5.User
if ss.user != "" {
user = &socks5.User{
@ -57,13 +56,30 @@ func (ss *Socks5) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn
if _, err := socks5.ClientHandshake(c, serializesSocksAddr(metadata), socks5.CmdConnect, user); err != nil {
return nil, err
}
return newConn(c, ss), nil
return c, nil
}
func (ss *Socks5) DialUDP(metadata *C.Metadata) (_ C.PacketConn, _ net.Addr, err error) {
ctx, cancel := context.WithTimeout(context.Background(), tcpTimeout)
defer cancel()
c, err := dialContext(ctx, "tcp", ss.addr)
// DialContext implements C.ProxyAdapter
func (ss *Socks5) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) {
c, err := dialer.DialContext(ctx, "tcp", ss.addr, ss.Base.DialOptions(opts...)...)
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
}
return NewConn(c, ss), nil
}
// ListenPacketContext implements C.ProxyAdapter
func (ss *Socks5) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.PacketConn, err error) {
c, err := dialer.DialContext(ctx, "tcp", ss.addr, ss.Base.DialOptions(opts...)...)
if err != nil {
err = fmt.Errorf("%s connect error: %w", ss.addr, err)
return
@ -75,11 +91,7 @@ func (ss *Socks5) DialUDP(metadata *C.Metadata) (_ C.PacketConn, _ net.Addr, err
c = cc
}
defer func() {
if err != nil {
c.Close()
}
}()
defer safeConnClose(c, err)
tcpKeepAlive(c)
var user *socks5.User
@ -96,30 +108,34 @@ func (ss *Socks5) DialUDP(metadata *C.Metadata) (_ C.PacketConn, _ net.Addr, err
return
}
addr, err := net.ResolveUDPAddr("udp", bindAddr.String())
if err != nil {
return
}
targetAddr := socks5.ParseAddr(metadata.RemoteAddress())
if targetAddr == nil {
return nil, nil, fmt.Errorf("parse address %s error: %s", metadata.String(), metadata.DstPort)
}
pc, err := net.ListenPacket("udp", "")
pc, err := dialer.ListenPacket(ctx, "udp", "", ss.Base.DialOptions(opts...)...)
if err != nil {
return
}
go func() {
io.Copy(ioutil.Discard, c)
io.Copy(io.Discard, c)
c.Close()
// A UDP association terminates when the TCP connection that the UDP
// ASSOCIATE request arrived on terminates. RFC1928
pc.Close()
}()
return newPacketConn(&socksUDPConn{PacketConn: pc, rAddr: targetAddr, tcpConn: c}, ss), addr, 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 {
@ -127,18 +143,19 @@ func NewSocks5(option Socks5Option) *Socks5 {
if option.TLS {
tlsConfig = &tls.Config{
InsecureSkipVerify: option.SkipCertVerify,
ClientSessionCache: getClientSessionCache(),
ServerName: option.Server,
}
}
return &Socks5{
Base: &Base{
name: option.Name,
tp: C.Socks5,
udp: option.UDP,
name: option.Name,
addr: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)),
tp: C.Socks5,
udp: option.UDP,
iface: option.Interface,
rmark: option.RoutingMark,
},
addr: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)),
user: option.UserName,
pass: option.Password,
tls: option.TLS,
@ -147,22 +164,22 @@ func NewSocks5(option Socks5Option) *Socks5 {
}
}
type socksUDPConn struct {
type socksPacketConn struct {
net.PacketConn
rAddr socks5.Addr
rAddr net.Addr
tcpConn net.Conn
}
func (uc *socksUDPConn) WriteTo(b []byte, addr net.Addr) (n int, err error) {
packet, err := socks5.EncodeUDPPacket(uc.rAddr, b)
func (uc *socksPacketConn) WriteTo(b []byte, addr net.Addr) (n int, err error) {
packet, err := socks5.EncodeUDPPacket(socks5.ParseAddrToSocksAddr(addr), b)
if err != nil {
return
}
return uc.PacketConn.WriteTo(packet, addr)
return uc.PacketConn.WriteTo(packet, uc.rAddr)
}
func (uc *socksUDPConn) ReadFrom(b []byte) (int, net.Addr, error) {
n, a, e := uc.PacketConn.ReadFrom(b)
func (uc *socksPacketConn) ReadFrom(b []byte) (int, net.Addr, error) {
n, _, e := uc.PacketConn.ReadFrom(b)
if e != nil {
return 0, nil, e
}
@ -170,13 +187,18 @@ func (uc *socksUDPConn) ReadFrom(b []byte) (int, net.Addr, error) {
if err != nil {
return 0, nil, err
}
udpAddr := addr.UDPAddr()
if udpAddr == nil {
return 0, nil, errors.New("parse udp addr error")
}
// due to DecodeUDPPacket is mutable, record addr length
addrLength := len(addr)
copy(b, payload)
return n - addrLength - 3, a, nil
return n - len(addr) - 3, udpAddr, nil
}
func (uc *socksUDPConn) Close() error {
func (uc *socksPacketConn) Close() error {
uc.tcpConn.Close()
return uc.PacketConn.Close()
}

251
adapter/outbound/trojan.go Normal file
View File

@ -0,0 +1,251 @@
package outbound
import (
"context"
"crypto/tls"
"fmt"
"net"
"net/http"
"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"
"github.com/Dreamacro/clash/transport/vless"
)
type Trojan struct {
*Base
instance *trojan.Trojan
option *TrojanOption
// for gun mux
gunTLSConfig *tls.Config
gunConfig *gun.Config
transport *gun.TransportWrap
}
type TrojanOption struct {
BasicOption
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"`
WSOpts WSOptions `proxy:"ws-opts,omitempty"`
Flow string `proxy:"flow,omitempty"`
FlowShow bool `proxy:"flow-show,omitempty"`
}
func (t *Trojan) plainStream(c net.Conn) (net.Conn, error) {
if t.option.Network == "ws" {
host, port, _ := net.SplitHostPort(t.addr)
wsOpts := &trojan.WebsocketOption{
Host: host,
Port: port,
Path: t.option.WSOpts.Path,
}
if t.option.SNI != "" {
wsOpts.Host = t.option.SNI
}
if len(t.option.WSOpts.Headers) != 0 {
header := http.Header{}
for key, value := range t.option.WSOpts.Headers {
header.Add(key, value)
}
wsOpts.Headers = header
}
return t.instance.StreamWebsocketConn(c, wsOpts)
}
return t.instance.StreamConn(c)
}
// 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.plainStream(c)
}
if err != nil {
return nil, fmt.Errorf("%s connect error: %w", t.addr, err)
}
c, err = t.instance.PresetXTLSConn(c)
if err != nil {
return nil, err
}
if metadata.NetWork == C.UDP {
err = t.instance.WriteHeader(c, trojan.CommandUDP, serializesSocksAddr(metadata))
return c, 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, opts ...dialer.Option) (_ C.Conn, err error) {
// gun transport
if t.transport != nil && len(opts) == 0 {
c, err := gun.StreamGunWithTransport(t.transport, t.gunConfig)
if err != nil {
return nil, err
}
c, err = t.instance.PresetXTLSConn(c)
if err != nil {
c.Close()
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, t.Base.DialOptions(opts...)...)
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
}
// ListenPacketContext implements C.ProxyAdapter
func (t *Trojan) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.PacketConn, err error) {
var c net.Conn
// grpc transport
if t.transport != nil && len(opts) == 0 {
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 {
c, err = dialer.DialContext(ctx, "tcp", t.addr, t.Base.DialOptions(opts...)...)
if err != nil {
return nil, fmt.Errorf("%s connect error: %w", t.addr, err)
}
defer safeConnClose(c, err)
tcpKeepAlive(c)
c, err = t.plainStream(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
}
// ListenPacketOnStreamConn implements C.ProxyAdapter
func (t *Trojan) ListenPacketOnStreamConn(c net.Conn, metadata *C.Metadata) (_ C.PacketConn, err error) {
pc := t.instance.PacketConn(c)
return newPacketConn(pc, t), err
}
// SupportUOT implements C.ProxyAdapter
func (t *Trojan) SupportUOT() bool {
return true
}
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,
FlowShow: option.FlowShow,
}
if option.Network != "ws" && len(option.Flow) >= 16 {
option.Flow = option.Flow[:16]
switch option.Flow {
case vless.XRO, vless.XRD, vless.XRS:
tOption.Flow = option.Flow
default:
return nil, fmt.Errorf("unsupported xtls flow type: %s", option.Flow)
}
}
if option.SNI != "" {
tOption.ServerName = option.SNI
}
t := &Trojan{
Base: &Base{
name: option.Name,
addr: addr,
tp: C.Trojan,
udp: option.UDP,
iface: option.Interface,
rmark: option.RoutingMark,
},
instance: trojan.New(tOption),
option: &option,
}
if option.Network == "grpc" {
dialFn := func(network, addr string) (net.Conn, error) {
c, err := dialer.DialContext(context.Background(), "tcp", t.addr, t.Base.DialOptions()...)
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,
}
if t.option.Flow != "" {
t.transport = gun.NewHTTP2XTLSClient(dialFn, tlsConfig)
} else {
t.transport = gun.NewHTTP2Client(dialFn, tlsConfig)
}
t.gunTLSConfig = tlsConfig
t.gunConfig = &gun.Config{
ServiceName: option.GrpcOpts.GrpcServiceName,
Host: tOption.ServerName,
}
}
return t, nil
}

81
adapter/outbound/util.go Normal file
View File

@ -0,0 +1,81 @@
package outbound
import (
"bytes"
"crypto/tls"
"net"
"strconv"
"sync"
"time"
"github.com/Dreamacro/clash/component/resolver"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/transport/socks5"
xtls "github.com/xtls/go"
)
var (
globalClientSessionCache tls.ClientSessionCache
globalClientXSessionCache xtls.ClientSessionCache
once sync.Once
)
func tcpKeepAlive(c net.Conn) {
if tcp, ok := c.(*net.TCPConn); ok {
_ = tcp.SetKeepAlive(true)
_ = tcp.SetKeepAlivePeriod(30 * time.Second)
}
}
func getClientSessionCache() tls.ClientSessionCache {
once.Do(func() {
globalClientSessionCache = tls.NewLRUClientSessionCache(128)
})
return globalClientSessionCache
}
func getClientXSessionCache() xtls.ClientSessionCache {
once.Do(func() {
globalClientXSessionCache = xtls.NewLRUClientSessionCache(128)
})
return globalClientXSessionCache
}
func serializesSocksAddr(metadata *C.Metadata) []byte {
var buf [][]byte
aType := uint8(metadata.AddrType)
p, _ := strconv.ParseUint(metadata.DstPort, 10, 16)
port := []byte{uint8(p >> 8), uint8(p & 0xff)}
switch metadata.AddrType {
case socks5.AtypDomainName:
lenM := uint8(len(metadata.Host))
host := []byte(metadata.Host)
buf = [][]byte{{aType, lenM}, host, port}
case socks5.AtypIPv4:
host := metadata.DstIP.AsSlice()
buf = [][]byte{{aType}, host, port}
case socks5.AtypIPv6:
host := metadata.DstIP.AsSlice()
buf = [][]byte{{aType}, host, port}
}
return bytes.Join(buf, nil)
}
func resolveUDPAddr(network, address string) (*net.UDPAddr, error) {
host, port, err := net.SplitHostPort(address)
if err != nil {
return nil, err
}
ip, err := resolver.ResolveProxyServerHost(host)
if err != nil {
return nil, err
}
return net.ResolveUDPAddr(network, net.JoinHostPort(ip.String(), port))
}
func safeConnClose(c net.Conn, err error) {
if err != nil {
_ = c.Close()
}
}

458
adapter/outbound/vless.go Normal file
View File

@ -0,0 +1,458 @@
package outbound
import (
"context"
"crypto/tls"
"encoding/binary"
"errors"
"fmt"
"io"
"net"
"net/http"
"strconv"
"sync"
"github.com/Dreamacro/clash/common/convert"
"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/vless"
"github.com/Dreamacro/clash/transport/vmess"
)
const (
// max packet length
maxLength = 1024 << 3
)
type Vless struct {
*Base
client *vless.Client
option *VlessOption
// for gun mux
gunTLSConfig *tls.Config
gunConfig *gun.Config
transport *gun.TransportWrap
}
type VlessOption struct {
BasicOption
Name string `proxy:"name"`
Server string `proxy:"server"`
Port int `proxy:"port"`
UUID string `proxy:"uuid"`
Flow string `proxy:"flow,omitempty"`
FlowShow bool `proxy:"flow-show,omitempty"`
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"`
}
func (v *Vless) 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.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.WSOpts.Headers {
header.Add(key, value)
}
wsOpts.Headers = header
}
wsOpts.TLS = true
wsOpts.TLSConfig = &tls.Config{
MinVersion: tls.VersionTLS12,
ServerName: host,
InsecureSkipVerify: v.option.SkipCertVerify,
NextProtos: []string{"http/1.1"},
}
if v.option.ServerName != "" {
wsOpts.TLSConfig.ServerName = v.option.ServerName
} else if host := wsOpts.Headers.Get("Host"); host != "" {
wsOpts.TLSConfig.ServerName = host
} else {
wsOpts.Headers.Set("Host", convert.RandHost())
convert.SetUserAgent(wsOpts.Headers)
}
c, err = vmess.StreamWebsocketConn(c, wsOpts)
case "http":
// readability first, so just copy default TLS logic
c, err = v.streamTLSOrXTLSConn(c, false)
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":
c, err = v.streamTLSOrXTLSConn(c, true)
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":
if v.isXTLSEnabled() {
c, err = gun.StreamGunWithXTLSConn(c, v.gunTLSConfig, v.gunConfig)
} else {
c, err = gun.StreamGunWithConn(c, v.gunTLSConfig, v.gunConfig)
}
default:
// default tcp network
// handle TLS And XTLS
c, err = v.streamTLSOrXTLSConn(c, false)
}
if err != nil {
return nil, err
}
return v.client.StreamConn(c, parseVlessAddr(metadata))
}
func (v *Vless) streamTLSOrXTLSConn(conn net.Conn, isH2 bool) (net.Conn, error) {
host, _, _ := net.SplitHostPort(v.addr)
if v.isXTLSEnabled() {
xtlsOpts := vless.XTLSConfig{
Host: host,
SkipCertVerify: v.option.SkipCertVerify,
}
if isH2 {
xtlsOpts.NextProtos = []string{"h2"}
}
if v.option.ServerName != "" {
xtlsOpts.Host = v.option.ServerName
}
return vless.StreamXTLSConn(conn, &xtlsOpts)
} else if v.option.TLS {
tlsOpts := vmess.TLSConfig{
Host: host,
SkipCertVerify: v.option.SkipCertVerify,
}
if isH2 {
tlsOpts.NextProtos = []string{"h2"}
}
if v.option.ServerName != "" {
tlsOpts.Host = v.option.ServerName
}
return vmess.StreamTLSConn(conn, &tlsOpts)
}
return conn, nil
}
func (v *Vless) isXTLSEnabled() bool {
return v.client.Addons != nil
}
// DialContext implements C.ProxyAdapter
func (v *Vless) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) {
// gun transport
if v.transport != nil && len(opts) == 0 {
c, err := gun.StreamGunWithTransport(v.transport, v.gunConfig)
if err != nil {
return nil, err
}
defer safeConnClose(c, err)
c, err = v.client.StreamConn(c, parseVlessAddr(metadata))
if err != nil {
return nil, err
}
return NewConn(c, v), nil
}
c, err := dialer.DialContext(ctx, "tcp", v.addr, v.Base.DialOptions(opts...)...)
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
}
// ListenPacketContext implements C.ProxyAdapter
func (v *Vless) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.PacketConn, err error) {
// vless 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 && len(opts) == 0 {
c, err = gun.StreamGunWithTransport(v.transport, v.gunConfig)
if err != nil {
return nil, err
}
defer safeConnClose(c, err)
c, err = v.client.StreamConn(c, parseVlessAddr(metadata))
} else {
c, err = dialer.DialContext(ctx, "tcp", v.addr, v.Base.DialOptions(opts...)...)
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 vless client error: %v", err)
}
return v.ListenPacketOnStreamConn(c, metadata)
}
// ListenPacketOnStreamConn implements C.ProxyAdapter
func (v *Vless) ListenPacketOnStreamConn(c net.Conn, metadata *C.Metadata) (_ C.PacketConn, err error) {
return newPacketConn(&vlessPacketConn{Conn: c, rAddr: metadata.UDPAddr()}, v), nil
}
// SupportUOT implements C.ProxyAdapter
func (v *Vless) SupportUOT() bool {
return true
}
func parseVlessAddr(metadata *C.Metadata) *vless.DstAddr {
var addrType byte
var addr []byte
switch metadata.AddrType {
case C.AtypIPv4:
addrType = vless.AtypIPv4
addr = make([]byte, net.IPv4len)
copy(addr[:], metadata.DstIP.AsSlice())
case C.AtypIPv6:
addrType = vless.AtypIPv6
addr = make([]byte, net.IPv6len)
copy(addr[:], metadata.DstIP.AsSlice())
case C.AtypDomainName:
addrType = vless.AtypDomainName
addr = make([]byte, len(metadata.Host)+1)
addr[0] = byte(len(metadata.Host))
copy(addr[1:], metadata.Host)
}
port, _ := strconv.ParseUint(metadata.DstPort, 10, 16)
return &vless.DstAddr{
UDP: metadata.NetWork == C.UDP,
AddrType: addrType,
Addr: addr,
Port: uint(port),
}
}
type vlessPacketConn struct {
net.Conn
rAddr net.Addr
remain int
mux sync.Mutex
cache [2]byte
}
func (c *vlessPacketConn) writePacket(payload []byte) (int, error) {
binary.BigEndian.PutUint16(c.cache[:], uint16(len(payload)))
if _, err := c.Conn.Write(c.cache[:]); err != nil {
return 0, err
}
return c.Conn.Write(payload)
}
func (c *vlessPacketConn) WriteTo(b []byte, addr net.Addr) (int, error) {
total := len(b)
if total == 0 {
return 0, nil
}
if total <= maxLength {
return c.writePacket(b)
}
offset := 0
for offset < total {
cursor := offset + maxLength
if cursor > total {
cursor = total
}
n, err := c.writePacket(b[offset:cursor])
if err != nil {
return offset + n, err
}
offset = cursor
if offset == total {
break
}
}
return total, nil
}
func (c *vlessPacketConn) ReadFrom(b []byte) (int, net.Addr, error) {
c.mux.Lock()
defer c.mux.Unlock()
if c.remain > 0 {
length := len(b)
if c.remain < length {
length = c.remain
}
n, err := c.Conn.Read(b[:length])
if err != nil {
return 0, c.rAddr, err
}
c.remain -= n
return n, c.rAddr, nil
}
if _, err := c.Conn.Read(b[:2]); err != nil {
return 0, c.rAddr, err
}
total := int(binary.BigEndian.Uint16(b[:2]))
if total == 0 {
return 0, c.rAddr, nil
}
length := len(b)
if length > total {
length = total
}
if _, err := io.ReadFull(c.Conn, b[:length]); err != nil {
return 0, c.rAddr, errors.New("read packet error")
}
c.remain = total - length
return length, c.rAddr, nil
}
func NewVless(option VlessOption) (*Vless, error) {
var addons *vless.Addons
if option.Network != "ws" && len(option.Flow) >= 16 {
option.Flow = option.Flow[:16]
switch option.Flow {
case vless.XRO, vless.XRD, vless.XRS:
addons = &vless.Addons{
Flow: option.Flow,
}
default:
return nil, fmt.Errorf("unsupported xtls flow type: %s", option.Flow)
}
}
client, err := vless.NewClient(option.UUID, addons, option.FlowShow)
if err != nil {
return nil, err
}
v := &Vless{
Base: &Base{
name: option.Name,
addr: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)),
tp: C.Vless,
udp: option.UDP,
iface: option.Interface,
},
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, v.Base.DialOptions()...)
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
if v.isXTLSEnabled() {
v.transport = gun.NewHTTP2XTLSClient(dialFn, tlsConfig)
} else {
v.transport = gun.NewHTTP2Client(dialFn, tlsConfig)
}
}
return v, nil
}

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

@ -0,0 +1,386 @@
package outbound
import (
"context"
"crypto/tls"
"errors"
"fmt"
"net"
"net/http"
"strconv"
"strings"
"github.com/Dreamacro/clash/common/convert"
"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"
)
type Vmess struct {
*Base
client *vmess.Client
option *VmessOption
// for gun mux
gunTLSConfig *tls.Config
gunConfig *gun.Config
transport *gun.TransportWrap
}
type VmessOption struct {
BasicOption
Name string `proxy:"name"`
Server string `proxy:"server"`
Port int `proxy:"port"`
UUID string `proxy:"uuid"`
AlterID int `proxy:"alterId"`
Cipher string `proxy:"cipher"`
UDP bool `proxy:"udp,omitempty"`
Network string `proxy:"network,omitempty"`
TLS bool `proxy:"tls,omitempty"`
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
ServerName string `proxy:"servername,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"`
// TODO: compatible with VMESS WS older version configurations
WSHeaders map[string]string `proxy:"ws-headers,omitempty"`
WSPath string `proxy:"ws-path,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":
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,
Headers: make(http.Header),
}
if len(v.option.WSOpts.Headers) != 0 {
for key, value := range v.option.WSOpts.Headers {
wsOpts.Headers.Add(key, value)
}
}
if v.option.TLS {
wsOpts.TLS = true
wsOpts.TLSConfig = &tls.Config{
ServerName: host,
InsecureSkipVerify: v.option.SkipCertVerify,
NextProtos: []string{"http/1.1"},
}
if v.option.ServerName != "" {
wsOpts.TLSConfig.ServerName = v.option.ServerName
} else if host := wsOpts.Headers.Get("Host"); host != "" {
wsOpts.TLSConfig.ServerName = host
}
} else {
wsOpts.Headers.Set("Host", convert.RandHost())
convert.SetUserAgent(wsOpts.Headers)
}
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
}
} else {
http.Header(v.option.HTTPOpts.Headers).Set("Host", convert.RandHost())
convert.SetUserAgent(v.option.HTTPOpts.Headers)
}
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, opts ...dialer.Option) (_ C.Conn, err error) {
// gun transport
if v.transport != nil && len(opts) == 0 {
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, v.Base.DialOptions(opts...)...)
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
}
// ListenPacketContext implements C.ProxyAdapter
func (v *Vmess) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ 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 && len(opts) == 0 {
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 {
c, err = dialer.DialContext(ctx, "tcp", v.addr, v.Base.DialOptions(opts...)...)
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 v.ListenPacketOnStreamConn(c, metadata)
}
// ListenPacketOnStreamConn implements C.ProxyAdapter
func (v *Vmess) ListenPacketOnStreamConn(c net.Conn, metadata *C.Metadata) (_ C.PacketConn, err error) {
return newPacketConn(&vmessPacketConn{Conn: c, rAddr: metadata.UDPAddr()}, v), nil
}
// SupportUOT implements C.ProxyAdapter
func (v *Vmess) SupportUOT() bool {
return true
}
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,
iface: option.Interface,
rmark: option.RoutingMark,
},
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, v.Base.DialOptions()...)
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.AsSlice())
case C.AtypIPv6:
addrType = byte(vmess.AtypIPv6)
addr = make([]byte, net.IPv6len)
copy(addr[:], metadata.DstIP.AsSlice())
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.ParseUint(metadata.DstPort, 10, 16)
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,134 @@
package outboundgroup
import (
"context"
"encoding/json"
"errors"
"time"
"github.com/Dreamacro/clash/adapter/outbound"
"github.com/Dreamacro/clash/component/dialer"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/constant/provider"
)
type Fallback struct {
*GroupBase
disableUDP bool
testUrl string
selected string
}
func (f *Fallback) Now() string {
proxy := f.findAliveProxy(false)
return proxy.Name()
}
// DialContext implements C.ProxyAdapter
func (f *Fallback) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.Conn, error) {
proxy := f.findAliveProxy(true)
c, err := proxy.DialContext(ctx, metadata, f.Base.DialOptions(opts...)...)
if err == nil {
c.AppendToChains(f)
f.onDialSuccess()
} else {
f.onDialFailed()
}
return c, err
}
// ListenPacketContext implements C.ProxyAdapter
func (f *Fallback) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) {
proxy := f.findAliveProxy(true)
pc, err := proxy.ListenPacketContext(ctx, metadata, f.Base.DialOptions(opts...)...)
if err == nil {
pc.AppendToChains(f)
}
return pc, err
}
// SupportUDP implements C.ProxyAdapter
func (f *Fallback) SupportUDP() bool {
if f.disableUDP {
return false
}
proxy := f.findAliveProxy(false)
return proxy.SupportUDP()
}
// MarshalJSON implements C.ProxyAdapter
func (f *Fallback) MarshalJSON() ([]byte, error) {
all := []string{}
for _, proxy := range f.GetProxies(false) {
all = append(all, proxy.Name())
}
return json.Marshal(map[string]any{
"type": f.Type().String(),
"now": f.Now(),
"all": all,
})
}
// Unwrap implements C.ProxyAdapter
func (f *Fallback) Unwrap(metadata *C.Metadata) C.Proxy {
proxy := f.findAliveProxy(true)
return proxy
}
func (f *Fallback) findAliveProxy(touch bool) C.Proxy {
proxies := f.GetProxies(touch)
al := proxies[0]
for i := len(proxies) - 1; i > -1; i-- {
proxy := proxies[i]
if proxy.Name() == f.selected && proxy.Alive() {
return proxy
}
if proxy.Alive() {
al = proxy
}
}
return al
}
func (f *Fallback) Set(name string) error {
var p C.Proxy
for _, proxy := range f.GetProxies(false) {
if proxy.Name() == name {
p = proxy
break
}
}
if p == nil {
return errors.New("proxy not exist")
}
f.selected = name
if !p.Alive() {
ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond*time.Duration(5000))
defer cancel()
_, _ = p.URLTest(ctx, f.testUrl)
}
return nil
}
func NewFallback(option *GroupCommonOption, providers []provider.ProxyProvider) *Fallback {
return &Fallback{
GroupBase: NewGroupBase(GroupBaseOption{
outbound.BaseOption{
Name: option.Name,
Type: C.Fallback,
Interface: option.Interface,
RoutingMark: option.RoutingMark,
},
option.Filter,
providers,
}),
disableUDP: option.DisableUDP,
testUrl: option.URL,
}
}

View File

@ -0,0 +1,189 @@
package outboundgroup
import (
"context"
"fmt"
"sync"
"time"
"github.com/Dreamacro/clash/adapter/outbound"
C "github.com/Dreamacro/clash/constant"
types "github.com/Dreamacro/clash/constant/provider"
"github.com/Dreamacro/clash/constant/provider"
"github.com/Dreamacro/clash/log"
"github.com/Dreamacro/clash/tunnel"
"github.com/dlclark/regexp2"
"go.uber.org/atomic"
)
type GroupBase struct {
*outbound.Base
filter *regexp2.Regexp
providers []provider.ProxyProvider
versions sync.Map // map[string]uint
proxies sync.Map // map[string][]C.Proxy
failedTestMux sync.Mutex
failedTimes int
failedTime time.Time
failedTesting *atomic.Bool
}
type GroupBaseOption struct {
outbound.BaseOption
filter string
providers []provider.ProxyProvider
}
func NewGroupBase(opt GroupBaseOption) *GroupBase {
var filter *regexp2.Regexp = nil
if opt.filter != "" {
filter = regexp2.MustCompile(opt.filter, 0)
}
return &GroupBase{
Base: outbound.NewBase(opt.BaseOption),
filter: filter,
providers: opt.providers,
failedTesting: atomic.NewBool(false),
}
}
func (gb *GroupBase) GetProxies(touch bool) []C.Proxy {
if gb.filter == nil {
var proxies []C.Proxy
for _, pd := range gb.providers {
if touch {
pd.Touch()
}
proxies = append(proxies, pd.Proxies()...)
}
if len(proxies) == 0 {
return append(proxies, tunnel.Proxies()["COMPATIBLE"])
}
return proxies
}
for _, pd := range gb.providers {
if touch {
pd.Touch()
}
if pd.VehicleType() == types.Compatible {
gb.proxies.Store(pd.Name(), pd.Proxies())
gb.versions.Store(pd.Name(), pd.Version())
continue
}
if version, ok := gb.versions.Load(pd.Name()); !ok || version != pd.Version() {
var (
proxies []C.Proxy
newProxies []C.Proxy
)
proxies = pd.Proxies()
for _, p := range proxies {
if mat, _ := gb.filter.FindStringMatch(p.Name()); mat != nil {
newProxies = append(newProxies, p)
}
}
gb.proxies.Store(pd.Name(), newProxies)
gb.versions.Store(pd.Name(), pd.Version())
}
}
var proxies []C.Proxy
gb.proxies.Range(func(key, value any) bool {
proxies = append(proxies, value.([]C.Proxy)...)
return true
})
if len(proxies) == 0 {
return append(proxies, tunnel.Proxies()["COMPATIBLE"])
}
return proxies
}
func (gb *GroupBase) URLTest(ctx context.Context, url string) (map[string]uint16, error) {
var wg sync.WaitGroup
var lock sync.Mutex
mp := map[string]uint16{}
proxies := gb.GetProxies(false)
for _, proxy := range proxies {
proxy := proxy
wg.Add(1)
go func() {
delay, err := proxy.URLTest(ctx, url)
lock.Lock()
if err == nil {
mp[proxy.Name()] = delay
}
lock.Unlock()
wg.Done()
}()
}
wg.Wait()
if len(mp) == 0 {
return mp, fmt.Errorf("get delay: all proxies timeout")
} else {
return mp, nil
}
}
func (gb *GroupBase) onDialFailed() {
if gb.failedTesting.Load() {
return
}
go func() {
gb.failedTestMux.Lock()
defer gb.failedTestMux.Unlock()
gb.failedTimes++
if gb.failedTimes == 1 {
log.Debugln("ProxyGroup: %s first failed", gb.Name())
gb.failedTime = time.Now()
} else {
if time.Since(gb.failedTime) > gb.failedTimeoutInterval() {
return
}
log.Debugln("ProxyGroup: %s failed count: %d", gb.Name(), gb.failedTimes)
if gb.failedTimes >= gb.maxFailedTimes() {
gb.failedTesting.Store(true)
log.Warnln("because %s failed multiple times, active health check", gb.Name())
wg := sync.WaitGroup{}
for _, proxyProvider := range gb.providers {
wg.Add(1)
proxyProvider := proxyProvider
go func() {
defer wg.Done()
proxyProvider.HealthCheck()
}()
}
wg.Wait()
gb.failedTesting.Store(false)
gb.failedTimes = 0
}
}
}()
}
func (gb *GroupBase) failedIntervalTime() int64 {
return 5 * time.Second.Milliseconds()
}
func (gb *GroupBase) onDialSuccess() {
if !gb.failedTesting.Load() {
gb.failedTimes = 0
}
}
func (gb *GroupBase) maxFailedTimes() int {
return 5
}
func (gb *GroupBase) failedTimeoutInterval() time.Duration {
return 5 * time.Second
}

View File

@ -0,0 +1,230 @@
package outboundgroup
import (
"context"
"encoding/json"
"errors"
"fmt"
"net"
"time"
"github.com/Dreamacro/clash/adapter/outbound"
"github.com/Dreamacro/clash/common/cache"
"github.com/Dreamacro/clash/common/murmur3"
"github.com/Dreamacro/clash/component/dialer"
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 {
*GroupBase
disableUDP bool
strategyFn strategyFn
}
var errStrategy = errors.New("unsupported strategy")
func parseStrategy(config map[string]any) 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 == nil {
return ""
}
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.IsValid() {
return ""
}
return metadata.DstIP.String()
}
func getKeyWithSrcAndDst(metadata *C.Metadata) string {
dst := getKey(metadata)
src := ""
if metadata != nil {
src = metadata.SrcIP.String()
}
return fmt.Sprintf("%s%s", src, dst)
}
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, opts ...dialer.Option) (c C.Conn, err error) {
defer func() {
if err == nil {
c.AppendToChains(lb)
lb.onDialSuccess()
} else {
lb.onDialFailed()
}
}()
proxy := lb.Unwrap(metadata)
c, err = proxy.DialContext(ctx, metadata, lb.Base.DialOptions(opts...)...)
return
}
// ListenPacketContext implements C.ProxyAdapter
func (lb *LoadBalance) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (pc C.PacketConn, err error) {
defer func() {
if err == nil {
pc.AppendToChains(lb)
}
}()
proxy := lb.Unwrap(metadata)
return proxy.ListenPacketContext(ctx, metadata, lb.Base.DialOptions(opts...)...)
}
// 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]
}
}
func strategyStickySessions() strategyFn {
ttl := time.Minute * 10
maxRetry := 5
lruCache := cache.NewLRUCache[uint64, int](
cache.WithAge[uint64, int](int64(ttl.Seconds())),
cache.WithSize[uint64, int](1000))
return func(proxies []C.Proxy, metadata *C.Metadata) C.Proxy {
key := uint64(murmur3.Sum32([]byte(getKeyWithSrcAndDst(metadata))))
length := len(proxies)
idx, has := lruCache.Get(key)
if !has {
idx = int(jumpHash(key+uint64(time.Now().UnixNano()), int32(length)))
}
nowIdx := idx
for i := 1; i < maxRetry; i++ {
proxy := proxies[nowIdx]
if proxy.Alive() {
if nowIdx != idx {
lruCache.Delete(key)
lruCache.Set(key, nowIdx)
}
return proxy
} else {
nowIdx = int(jumpHash(key+uint64(time.Now().UnixNano()), int32(length)))
}
}
lruCache.Delete(key)
lruCache.Set(key, 0)
return proxies[0]
}
}
// Unwrap implements C.ProxyAdapter
func (lb *LoadBalance) Unwrap(metadata *C.Metadata) C.Proxy {
proxies := lb.GetProxies(true)
return lb.strategyFn(proxies, metadata)
}
// MarshalJSON implements C.ProxyAdapter
func (lb *LoadBalance) MarshalJSON() ([]byte, error) {
var all []string
for _, proxy := range lb.GetProxies(false) {
all = append(all, proxy.Name())
}
return json.Marshal(map[string]any{
"type": lb.Type().String(),
"all": all,
})
}
func NewLoadBalance(option *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()
case "sticky-sessions":
strategyFn = strategyStickySessions()
default:
return nil, fmt.Errorf("%w: %s", errStrategy, strategy)
}
return &LoadBalance{
GroupBase: NewGroupBase(GroupBaseOption{
outbound.BaseOption{
Name: option.Name,
Type: C.LoadBalance,
Interface: option.Interface,
RoutingMark: option.RoutingMark,
},
option.Filter,
providers,
}),
strategyFn: strategyFn,
disableUDP: option.DisableUDP,
}, nil
}

View File

@ -0,0 +1,154 @@
package outboundgroup
import (
"errors"
"fmt"
"github.com/Dreamacro/clash/adapter/outbound"
"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")
errMissProxy = errors.New("`use` or `proxies` missing")
errMissHealthCheck = errors.New("`url` or `interval` missing")
errDuplicateProvider = errors.New("duplicate provider name")
)
type GroupCommonOption struct {
outbound.BasicOption
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"`
Filter string `group:"filter,omitempty"`
}
func ParseProxyGroup(config map[string]any, proxyMap map[string]C.Proxy, providersMap map[string]types.ProxyProvider) (C.ProxyAdapter, error) {
decoder := structure.NewDecoder(structure.Option{TagName: "group", WeaklyTypedInput: true})
groupOption := &GroupCommonOption{
Lazy: true,
}
if err := decoder.Decode(config, groupOption); err != nil {
return nil, errFormat
}
if groupOption.Type == "" || groupOption.Name == "" {
return nil, errFormat
}
groupName := groupOption.Name
providers := []types.ProxyProvider{}
if len(groupOption.Proxies) == 0 && len(groupOption.Use) == 0 {
return nil, errMissProxy
}
if len(groupOption.Proxies) != 0 {
ps, err := getProxies(proxyMap, groupOption.Proxies)
if err != nil {
return nil, err
}
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, true)
pd, err := provider.NewCompatibleProvider(groupName, ps, hc)
if err != nil {
return nil, err
}
providers = append(providers, pd)
providersMap[groupName] = pd
} else {
if groupOption.URL == "" {
groupOption.URL = "http://www.gstatic.com/generate_204"
}
if groupOption.Interval == 0 {
groupOption.Interval = 300
}
hc := provider.NewHealthCheck(ps, groupOption.URL, uint(groupOption.Interval), groupOption.Lazy)
pd, err := provider.NewCompatibleProvider(groupName, ps, hc)
if err != nil {
return nil, err
}
providers = append(providers, pd)
providersMap[groupName] = pd
}
}
if len(groupOption.Use) != 0 {
list, err := getProviders(providersMap, groupOption.Use)
if err != nil {
return nil, err
}
providers = append(providers, list...)
} else {
groupOption.Filter = ""
}
var group C.ProxyAdapter
switch groupOption.Type {
case "url-test":
opts := parseURLTestOption(config)
group = NewURLTest(groupOption, providers, opts...)
case "select":
group = NewSelector(groupOption, providers)
case "fallback":
group = NewFallback(groupOption, providers)
case "load-balance":
strategy := parseStrategy(config)
return NewLoadBalance(groupOption, providers, strategy)
case "relay":
group = NewRelay(groupOption, providers)
default:
return nil, fmt.Errorf("%w: %s", errType, groupOption.Type)
}
return group, nil
}
func getProxies(mapping map[string]C.Proxy, list []string) ([]C.Proxy, error) {
var ps []C.Proxy
for _, name := range list {
p, ok := mapping[name]
if !ok {
return nil, fmt.Errorf("'%s' not found", name)
}
ps = append(ps, p)
}
return ps, nil
}
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() == types.Compatible {
return nil, fmt.Errorf("proxy group %s can't contains in `use`", name)
}
ps = append(ps, p)
}
return ps, nil
}

View File

@ -0,0 +1,191 @@
package outboundgroup
import (
"context"
"encoding/json"
"fmt"
"github.com/Dreamacro/clash/adapter/outbound"
"github.com/Dreamacro/clash/component/dialer"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/constant/provider"
)
type Relay struct {
*GroupBase
}
// DialContext implements C.ProxyAdapter
func (r *Relay) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.Conn, error) {
proxies, chainProxies := r.proxies(metadata, true)
switch len(proxies) {
case 0:
return outbound.NewDirect().DialContext(ctx, metadata, r.Base.DialOptions(opts...)...)
case 1:
return proxies[0].DialContext(ctx, metadata, r.Base.DialOptions(opts...)...)
}
first := proxies[0]
last := proxies[len(proxies)-1]
c, err := dialer.DialContext(ctx, "tcp", first.Addr(), r.Base.DialOptions(opts...)...)
if err != nil {
return nil, fmt.Errorf("%s connect error: %w", first.Addr(), err)
}
tcpKeepAlive(c)
var currentMeta *C.Metadata
for _, proxy := range proxies[1:] {
currentMeta, err = addrToMetadata(proxy.Addr())
if err != nil {
return nil, err
}
c, err = first.StreamConn(c, currentMeta)
if err != nil {
return nil, fmt.Errorf("%s connect error: %w", first.Addr(), err)
}
first = proxy
}
c, err = last.StreamConn(c, metadata)
if err != nil {
return nil, fmt.Errorf("%s connect error: %w", last.Addr(), err)
}
conn := outbound.NewConn(c, last)
for i := len(chainProxies) - 2; i >= 0; i-- {
conn.AppendToChains(chainProxies[i])
}
conn.AppendToChains(r)
return conn, nil
}
// ListenPacketContext implements C.ProxyAdapter
func (r *Relay) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.PacketConn, err error) {
proxies, chainProxies := r.proxies(metadata, true)
switch len(proxies) {
case 0:
return outbound.NewDirect().ListenPacketContext(ctx, metadata, r.Base.DialOptions(opts...)...)
case 1:
return proxies[0].ListenPacketContext(ctx, metadata, r.Base.DialOptions(opts...)...)
}
first := proxies[0]
last := proxies[len(proxies)-1]
c, err := dialer.DialContext(ctx, "tcp", first.Addr(), r.Base.DialOptions(opts...)...)
if err != nil {
return nil, fmt.Errorf("%s connect error: %w", first.Addr(), err)
}
tcpKeepAlive(c)
var currentMeta *C.Metadata
for _, proxy := range proxies[1:] {
currentMeta, err = addrToMetadata(proxy.Addr())
if err != nil {
return nil, err
}
c, err = first.StreamConn(c, currentMeta)
if err != nil {
return nil, fmt.Errorf("%s connect error: %w", first.Addr(), err)
}
first = proxy
}
c, err = last.StreamConn(c, metadata)
if err != nil {
return nil, fmt.Errorf("%s connect error: %w", last.Addr(), err)
}
var pc C.PacketConn
pc, err = last.ListenPacketOnStreamConn(c, metadata)
if err != nil {
return nil, fmt.Errorf("%s connect error: %w", first.Addr(), err)
}
for i := len(chainProxies) - 2; i >= 0; i-- {
pc.AppendToChains(chainProxies[i])
}
pc.AppendToChains(r)
return pc, nil
}
// SupportUDP implements C.ProxyAdapter
func (r *Relay) SupportUDP() bool {
proxies, _ := r.proxies(nil, false)
if len(proxies) == 0 { // C.Direct
return true
}
last := proxies[len(proxies)-1]
return last.SupportUDP() && last.SupportUOT()
}
// MarshalJSON implements C.ProxyAdapter
func (r *Relay) MarshalJSON() ([]byte, error) {
all := []string{}
for _, proxy := range r.GetProxies(false) {
all = append(all, proxy.Name())
}
return json.Marshal(map[string]any{
"type": r.Type().String(),
"all": all,
})
}
func (r *Relay) proxies(metadata *C.Metadata, touch bool) ([]C.Proxy, []C.Proxy) {
rawProxies := r.GetProxies(touch)
var proxies []C.Proxy
var chainProxies []C.Proxy
var targetProxies []C.Proxy
for n, proxy := range rawProxies {
proxies = append(proxies, proxy)
chainProxies = append(chainProxies, proxy)
subproxy := proxy.Unwrap(metadata)
for subproxy != nil {
chainProxies = append(chainProxies, subproxy)
proxies[n] = subproxy
subproxy = subproxy.Unwrap(metadata)
}
}
for _, proxy := range proxies {
if proxy.Type() != C.Direct && proxy.Type() != C.Compatible {
targetProxies = append(targetProxies, proxy)
}
}
return targetProxies, chainProxies
}
func (r *Relay) Addr() string {
proxies, _ := r.proxies(nil, true)
return proxies[len(proxies)-1].Addr()
}
func NewRelay(option *GroupCommonOption, providers []provider.ProxyProvider) *Relay {
return &Relay{
GroupBase: NewGroupBase(GroupBaseOption{
outbound.BaseOption{
Name: option.Name,
Type: C.Relay,
Interface: option.Interface,
RoutingMark: option.RoutingMark,
},
"",
providers,
}),
}
}

View File

@ -0,0 +1,107 @@
package outboundgroup
import (
"context"
"encoding/json"
"errors"
"github.com/Dreamacro/clash/adapter/outbound"
"github.com/Dreamacro/clash/component/dialer"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/constant/provider"
)
type Selector struct {
*GroupBase
disableUDP bool
selected string
}
// DialContext implements C.ProxyAdapter
func (s *Selector) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.Conn, error) {
c, err := s.selectedProxy(true).DialContext(ctx, metadata, s.Base.DialOptions(opts...)...)
if err == nil {
c.AppendToChains(s)
}
return c, err
}
// ListenPacketContext implements C.ProxyAdapter
func (s *Selector) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) {
pc, err := s.selectedProxy(true).ListenPacketContext(ctx, metadata, s.Base.DialOptions(opts...)...)
if err == nil {
pc.AppendToChains(s)
}
return pc, err
}
// SupportUDP implements C.ProxyAdapter
func (s *Selector) SupportUDP() bool {
if s.disableUDP {
return false
}
return s.selectedProxy(false).SupportUDP()
}
// MarshalJSON implements C.ProxyAdapter
func (s *Selector) MarshalJSON() ([]byte, error) {
all := []string{}
for _, proxy := range s.GetProxies(false) {
all = append(all, proxy.Name())
}
return json.Marshal(map[string]any{
"type": s.Type().String(),
"now": s.Now(),
"all": all,
})
}
func (s *Selector) Now() string {
return s.selectedProxy(false).Name()
}
func (s *Selector) Set(name string) error {
for _, proxy := range s.GetProxies(false) {
if proxy.Name() == name {
s.selected = name
return nil
}
}
return errors.New("proxy not exist")
}
// Unwrap implements C.ProxyAdapter
func (s *Selector) Unwrap(*C.Metadata) C.Proxy {
return s.selectedProxy(true)
}
func (s *Selector) selectedProxy(touch bool) C.Proxy {
proxies := s.GetProxies(touch)
for _, proxy := range proxies {
if proxy.Name() == s.selected {
return proxy
}
}
return proxies[0]
}
func NewSelector(option *GroupCommonOption, providers []provider.ProxyProvider) *Selector {
return &Selector{
GroupBase: NewGroupBase(GroupBaseOption{
outbound.BaseOption{
Name: option.Name,
Type: C.Selector,
Interface: option.Interface,
RoutingMark: option.RoutingMark,
},
option.Filter,
providers,
}),
selected: "COMPATIBLE",
disableUDP: option.DisableUDP,
}
}

View File

@ -0,0 +1,153 @@
package outboundgroup
import (
"context"
"encoding/json"
"time"
"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 urlTestOption func(*URLTest)
func urlTestWithTolerance(tolerance uint16) urlTestOption {
return func(u *URLTest) {
u.tolerance = tolerance
}
}
type URLTest struct {
*GroupBase
tolerance uint16
disableUDP bool
fastNode C.Proxy
fastSingle *singledo.Single[C.Proxy]
}
func (u *URLTest) Now() string {
return u.fast(false).Name()
}
// DialContext implements C.ProxyAdapter
func (u *URLTest) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (c C.Conn, err error) {
c, err = u.fast(true).DialContext(ctx, metadata, u.Base.DialOptions(opts...)...)
if err == nil {
c.AppendToChains(u)
u.onDialSuccess()
} else {
u.onDialFailed()
}
return c, err
}
// ListenPacketContext implements C.ProxyAdapter
func (u *URLTest) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) {
pc, err := u.fast(true).ListenPacketContext(ctx, metadata, u.Base.DialOptions(opts...)...)
if err == nil {
pc.AppendToChains(u)
}
return pc, err
}
// Unwrap implements C.ProxyAdapter
func (u *URLTest) Unwrap(*C.Metadata) C.Proxy {
return u.fast(true)
}
func (u *URLTest) fast(touch bool) C.Proxy {
elm, _, _ := u.fastSingle.Do(func() (C.Proxy, error) {
proxies := u.GetProxies(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
}
delay := proxy.LastDelay()
if delay < min {
fast = proxy
min = delay
}
}
// tolerance
if u.fastNode == nil || fastNotExist || !u.fastNode.Alive() || u.fastNode.LastDelay() > fast.LastDelay()+u.tolerance {
u.fastNode = fast
}
return u.fastNode, nil
})
return elm
}
// SupportUDP implements C.ProxyAdapter
func (u *URLTest) SupportUDP() bool {
if u.disableUDP {
return false
}
return u.fast(false).SupportUDP()
}
// MarshalJSON implements C.ProxyAdapter
func (u *URLTest) MarshalJSON() ([]byte, error) {
all := []string{}
for _, proxy := range u.GetProxies(false) {
all = append(all, proxy.Name())
}
return json.Marshal(map[string]any{
"type": u.Type().String(),
"now": u.Now(),
"all": all,
})
}
func parseURLTestOption(config map[string]any) []urlTestOption {
opts := []urlTestOption{}
// tolerance
if elm, ok := config["tolerance"]; ok {
if tolerance, ok := elm.(int); ok {
opts = append(opts, urlTestWithTolerance(uint16(tolerance)))
}
}
return opts
}
func NewURLTest(option *GroupCommonOption, providers []provider.ProxyProvider, options ...urlTestOption) *URLTest {
urlTest := &URLTest{
GroupBase: NewGroupBase(GroupBaseOption{
outbound.BaseOption{
Name: option.Name,
Type: C.URLTest,
Interface: option.Interface,
RoutingMark: option.RoutingMark,
},
option.Filter,
providers,
}),
fastSingle: singledo.NewSingle[C.Proxy](time.Second * 10),
disableUDP: option.DisableUDP,
}
for _, option := range options {
option(urlTest)
}
return urlTest
}

View File

@ -0,0 +1,57 @@
package outboundgroup
import (
"fmt"
"net"
"net/netip"
"time"
C "github.com/Dreamacro/clash/constant"
)
func addrToMetadata(rawAddress string) (addr *C.Metadata, err error) {
host, port, err := net.SplitHostPort(rawAddress)
if err != nil {
err = fmt.Errorf("addrToMetadata failed: %w", err)
return
}
ip, err := netip.ParseAddr(host)
if err != nil {
addr = &C.Metadata{
AddrType: C.AtypDomainName,
Host: host,
DstIP: netip.Addr{},
DstPort: port,
}
err = nil
return
} else if ip.Is4() {
addr = &C.Metadata{
AddrType: C.AtypIPv4,
Host: "",
DstIP: ip,
DstPort: port,
}
return
}
addr = &C.Metadata{
AddrType: C.AtypIPv6,
Host: "",
DstIP: ip,
DstPort: port,
}
return
}
func tcpKeepAlive(c net.Conn) {
if tcp, ok := c.(*net.TCPConn); ok {
_ = tcp.SetKeepAlive(true)
_ = tcp.SetKeepAlivePeriod(30 * time.Second)
}
}
type SelectAble interface {
Set(string) error
}

100
adapter/parser.go Normal file
View File

@ -0,0 +1,100 @@
package adapter
import (
"fmt"
"github.com/Dreamacro/clash/adapter/outbound"
"github.com/Dreamacro/clash/common/structure"
C "github.com/Dreamacro/clash/constant"
)
func ParseProxy(mapping map[string]any) (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")
}
var (
proxy C.ProxyAdapter
err error
)
switch proxyType {
case "ss":
ssOption := &outbound.ShadowSocksOption{}
err = decoder.Decode(mapping, ssOption)
if err != nil {
break
}
proxy, err = outbound.NewShadowSocks(*ssOption)
case "ssr":
ssrOption := &outbound.ShadowSocksROption{}
err = decoder.Decode(mapping, ssrOption)
if err != nil {
break
}
proxy, err = outbound.NewShadowSocksR(*ssrOption)
case "socks5":
socksOption := &outbound.Socks5Option{}
err = decoder.Decode(mapping, socksOption)
if err != nil {
break
}
proxy = outbound.NewSocks5(*socksOption)
case "http":
httpOption := &outbound.HttpOption{}
err = decoder.Decode(mapping, httpOption)
if err != nil {
break
}
proxy = outbound.NewHttp(*httpOption)
case "vmess":
vmessOption := &outbound.VmessOption{
HTTPOpts: outbound.HTTPOptions{
Method: "GET",
Path: []string{"/"},
},
}
err = decoder.Decode(mapping, vmessOption)
if err != nil {
break
}
proxy, err = outbound.NewVmess(*vmessOption)
case "vless":
vlessOption := &outbound.VlessOption{}
err = decoder.Decode(mapping, vlessOption)
if err != nil {
break
}
proxy, err = outbound.NewVless(*vlessOption)
case "snell":
snellOption := &outbound.SnellOption{}
err = decoder.Decode(mapping, snellOption)
if err != nil {
break
}
proxy, err = outbound.NewSnell(*snellOption)
case "trojan":
trojanOption := &outbound.TrojanOption{}
err = decoder.Decode(mapping, trojanOption)
if err != nil {
break
}
proxy, err = outbound.NewTrojan(*trojanOption)
case "hysteria":
hyOption := &outbound.HysteriaOption{}
err = decoder.Decode(mapping, hyOption)
if err != nil {
break
}
proxy, err = outbound.NewHysteria(*hyOption)
default:
return nil, fmt.Errorf("unsupport proxy type: %s", proxyType)
}
if err != nil {
return nil, err
}
return NewProxy(proxy), nil
}

198
adapter/provider/fetcher.go Normal file
View File

@ -0,0 +1,198 @@
package provider
import (
"bytes"
"crypto/md5"
"os"
"path/filepath"
"time"
types "github.com/Dreamacro/clash/constant/provider"
"github.com/Dreamacro/clash/log"
)
var (
fileMode os.FileMode = 0o666
dirMode os.FileMode = 0o755
)
type parser[V any] func([]byte) (V, error)
type fetcher[V any] struct {
name string
vehicle types.Vehicle
updatedAt *time.Time
ticker *time.Ticker
done chan struct{}
hash [16]byte
parser parser[V]
interval time.Duration
onUpdate func(V)
}
func (f *fetcher[V]) Name() string {
return f.name
}
func (f *fetcher[V]) VehicleType() types.VehicleType {
return f.vehicle.Type()
}
func (f *fetcher[V]) Initial() (V, error) {
var (
buf []byte
err error
isLocal bool
)
if stat, fErr := os.Stat(f.vehicle.Path()); fErr == nil {
buf, err = os.ReadFile(f.vehicle.Path())
modTime := stat.ModTime()
f.updatedAt = &modTime
isLocal = true
if f.interval != 0 && modTime.Add(f.interval).Before(time.Now()) {
defer func() {
log.Infoln("[Provider] %s's proxies not updated for a long time, force refresh", f.Name())
go f.Update()
}()
}
} else {
buf, err = f.vehicle.Read()
}
if err != nil {
return getZero[V](), err
}
proxies, err := f.parser(buf)
if err != nil {
if !isLocal {
return getZero[V](), err
}
// parse local file error, fallback to remote
buf, err = f.vehicle.Read()
if err != nil {
return getZero[V](), err
}
proxies, err = f.parser(buf)
if err != nil {
return getZero[V](), err
}
isLocal = false
}
if f.vehicle.Type() != types.File && !isLocal {
if err := safeWrite(f.vehicle.Path(), buf); err != nil {
return getZero[V](), err
}
}
f.hash = md5.Sum(buf)
// pull proxies automatically
if f.ticker != nil {
go f.pullLoop()
}
return proxies, nil
}
func (f *fetcher[V]) Update() (V, bool, error) {
buf, err := f.vehicle.Read()
if err != nil {
return getZero[V](), false, err
}
now := time.Now()
hash := md5.Sum(buf)
if bytes.Equal(f.hash[:], hash[:]) {
f.updatedAt = &now
os.Chtimes(f.vehicle.Path(), now, now)
return getZero[V](), true, nil
}
proxies, err := f.parser(buf)
if err != nil {
return getZero[V](), false, err
}
if f.vehicle.Type() != types.File {
if err := safeWrite(f.vehicle.Path(), buf); err != nil {
return getZero[V](), false, err
}
}
f.updatedAt = &now
f.hash = hash
return proxies, false, nil
}
func (f *fetcher[V]) Destroy() error {
if f.ticker != nil {
f.done <- struct{}{}
}
return nil
}
func (f *fetcher[V]) pullLoop() {
for {
select {
case <-f.ticker.C:
elm, same, err := f.Update()
if err != nil {
log.Warnln("[Provider] %s pull error: %s", f.Name(), err.Error())
continue
}
if same {
log.Debugln("[Provider] %s's proxies doesn't change", f.Name())
continue
}
log.Infoln("[Provider] %s's proxies update", f.Name())
if f.onUpdate != nil {
f.onUpdate(elm)
}
case <-f.done:
f.ticker.Stop()
return
}
}
}
func safeWrite(path string, buf []byte) error {
dir := filepath.Dir(path)
if _, err := os.Stat(dir); os.IsNotExist(err) {
if err := os.MkdirAll(dir, dirMode); err != nil {
return err
}
}
return os.WriteFile(path, buf, fileMode)
}
func newFetcher[V any](name string, interval time.Duration, vehicle types.Vehicle, parser parser[V], onUpdate func(V)) *fetcher[V] {
var ticker *time.Ticker
if interval != 0 {
ticker = time.NewTicker(interval)
}
return &fetcher[V]{
name: name,
ticker: ticker,
vehicle: vehicle,
parser: parser,
done: make(chan struct{}, 1),
onUpdate: onUpdate,
}
}
func getZero[V any]() V {
var result V
return result
}

View File

@ -0,0 +1,91 @@
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 func() {
time.Sleep(30 * time.Second)
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[bool](context.Background(), batch.WithConcurrencyNum[bool](10))
for _, proxy := range hc.proxies {
p := proxy
b.Go(p.Name(), func() (bool, error) {
ctx, cancel := context.WithTimeout(context.Background(), defaultURLTestTimeout)
defer cancel()
_, _ = p.URLTest(ctx, hc.url)
return false, 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,16 +7,16 @@ import (
"github.com/Dreamacro/clash/common/structure"
C "github.com/Dreamacro/clash/constant"
types "github.com/Dreamacro/clash/constant/provider"
)
var (
errVehicleType = errors.New("unsupport vehicle type")
)
var errVehicleType = errors.New("unsupport vehicle type")
type healthCheckSchema struct {
Enable bool `provider:"enable"`
URL string `provider:"url"`
Interval int `provider:"interval"`
Lazy bool `provider:"lazy,omitempty"`
}
type proxyProviderSchema struct {
@ -24,26 +24,31 @@ type proxyProviderSchema struct {
Path string `provider:"path"`
URL string `provider:"url,omitempty"`
Interval int `provider:"interval,omitempty"`
Filter string `provider:"filter,omitempty"`
HealthCheck healthCheckSchema `provider:"health-check,omitempty"`
}
func ParseProxyProvider(name string, mapping map[string]interface{}) (ProxyProvider, error) {
func ParseProxyProvider(name string, mapping map[string]any) (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.Reslove(schema.Path)
path := C.Path.Resolve(schema.Path)
var vehicle Vehicle
var vehicle types.Vehicle
switch schema.Type {
case "file":
vehicle = NewFileVehicle(path)
@ -54,5 +59,6 @@ func ParseProxyProvider(name string, mapping map[string]interface{}) (ProxyProvi
}
interval := time.Duration(uint(schema.Interval)) * time.Second
return NewProxySetProvider(name, interval, vehicle, hc), nil
filter := schema.Filter
return NewProxySetProvider(name, interval, filter, vehicle, hc)
}

View File

@ -0,0 +1,257 @@
package provider
import (
"encoding/json"
"errors"
"fmt"
"math"
"runtime"
"time"
"github.com/Dreamacro/clash/adapter"
"github.com/Dreamacro/clash/common/convert"
C "github.com/Dreamacro/clash/constant"
types "github.com/Dreamacro/clash/constant/provider"
"github.com/dlclark/regexp2"
"gopkg.in/yaml.v3"
)
const (
ReservedName = "default"
)
type ProxySchema struct {
Proxies []map[string]any `yaml:"proxies"`
}
// ProxySetProvider for auto gc
type ProxySetProvider struct {
*proxySetProvider
}
type proxySetProvider struct {
*fetcher[[]C.Proxy]
proxies []C.Proxy
healthCheck *HealthCheck
version uint
}
func (pp *proxySetProvider) MarshalJSON() ([]byte, error) {
return json.Marshal(map[string]any{
"name": pp.Name(),
"type": pp.Type().String(),
"vehicleType": pp.VehicleType().String(),
"proxies": pp.Proxies(),
"updatedAt": pp.updatedAt,
})
}
func (pp *proxySetProvider) Version() uint {
return pp.version
}
func (pp *proxySetProvider) Name() string {
return pp.name
}
func (pp *proxySetProvider) HealthCheck() {
pp.healthCheck.check()
}
func (pp *proxySetProvider) Update() error {
elm, same, err := pp.fetcher.Update()
if err == nil && !same {
pp.onUpdate(elm)
}
return err
}
func (pp *proxySetProvider) Initial() error {
elm, err := pp.fetcher.Initial()
if err != nil {
return err
}
pp.onUpdate(elm)
return nil
}
func (pp *proxySetProvider) Type() types.ProviderType {
return types.Proxy
}
func (pp *proxySetProvider) Proxies() []C.Proxy {
return pp.proxies
}
func (pp *proxySetProvider) Touch() {
pp.healthCheck.touch()
}
func (pp *proxySetProvider) setProxies(proxies []C.Proxy) {
pp.proxies = proxies
pp.healthCheck.setProxy(proxies)
if pp.healthCheck.auto() {
defer func() { go pp.healthCheck.check() }()
}
}
func stopProxyProvider(pd *ProxySetProvider) {
pd.healthCheck.close()
_ = pd.fetcher.Destroy()
}
func NewProxySetProvider(name string, interval time.Duration, filter string, vehicle types.Vehicle, hc *HealthCheck) (*ProxySetProvider, error) {
filterReg, err := regexp2.Compile(filter, 0)
if err != nil {
return nil, fmt.Errorf("invalid filter regex: %w", err)
}
if hc.auto() {
go hc.process()
}
pd := &proxySetProvider{
proxies: []C.Proxy{},
healthCheck: hc,
}
fetcher := newFetcher[[]C.Proxy](name, interval, vehicle, proxiesParseAndFilter(filter, filterReg), proxiesOnUpdate(pd))
pd.fetcher = fetcher
wrapper := &ProxySetProvider{pd}
runtime.SetFinalizer(wrapper, stopProxyProvider)
return wrapper, nil
}
// CompatibleProvider for auto gc
type CompatibleProvider struct {
*compatibleProvider
}
type compatibleProvider struct {
name string
healthCheck *HealthCheck
proxies []C.Proxy
version uint
}
func (cp *compatibleProvider) MarshalJSON() ([]byte, error) {
return json.Marshal(map[string]any{
"name": cp.Name(),
"type": cp.Type().String(),
"vehicleType": cp.VehicleType().String(),
"proxies": cp.Proxies(),
})
}
func (cp *compatibleProvider) Version() uint {
return cp.version
}
func (cp *compatibleProvider) Name() string {
return cp.name
}
func (cp *compatibleProvider) HealthCheck() {
cp.healthCheck.check()
}
func (cp *compatibleProvider) Update() error {
return nil
}
func (cp *compatibleProvider) Initial() error {
return nil
}
func (cp *compatibleProvider) VehicleType() types.VehicleType {
return types.Compatible
}
func (cp *compatibleProvider) Type() types.ProviderType {
return types.Proxy
}
func (cp *compatibleProvider) Proxies() []C.Proxy {
return cp.proxies
}
func (cp *compatibleProvider) Touch() {
cp.healthCheck.touch()
}
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")
}
if hc.auto() {
go hc.process()
}
pd := &compatibleProvider{
name: name,
proxies: proxies,
healthCheck: hc,
}
wrapper := &CompatibleProvider{pd}
runtime.SetFinalizer(wrapper, stopCompatibleProvider)
return wrapper, nil
}
func proxiesOnUpdate(pd *proxySetProvider) func([]C.Proxy) {
return func(elm []C.Proxy) {
pd.setProxies(elm)
if pd.version == math.MaxUint {
pd.version = 0
} else {
pd.version++
}
}
}
func proxiesParseAndFilter(filter string, filterReg *regexp2.Regexp) parser[[]C.Proxy] {
return func(buf []byte) ([]C.Proxy, error) {
schema := &ProxySchema{}
if err := yaml.Unmarshal(buf, schema); err != nil {
proxies, err1 := convert.ConvertsV2Ray(buf)
if err1 != nil {
return nil, fmt.Errorf("%s, %w", err.Error(), err1)
}
schema.Proxies = proxies
}
if schema.Proxies == nil {
return nil, errors.New("file must have a `proxies` field")
}
proxies := []C.Proxy{}
for idx, mapping := range schema.Proxies {
name, ok := mapping["name"]
mat, _ := filterReg.FindStringMatch(name.(string))
if ok && len(filter) > 0 && mat == nil {
continue
}
proxy, err := adapter.ParseProxy(mapping)
if err != nil {
return nil, fmt.Errorf("proxy %d error: %w", idx, err)
}
proxies = append(proxies, proxy)
}
if len(proxies) == 0 {
if len(filter) > 0 {
return nil, errors.New("doesn't match any proxy, please check your filter")
}
return nil, errors.New("file doesn't have any proxy")
}
return proxies, nil
}
}

View File

@ -0,0 +1,65 @@
package provider
import (
"context"
"io"
"net/http"
"os"
"time"
netHttp "github.com/Dreamacro/clash/component/http"
types "github.com/Dreamacro/clash/constant/provider"
)
type FileVehicle struct {
path string
}
func (f *FileVehicle) Type() types.VehicleType {
return types.File
}
func (f *FileVehicle) Path() string {
return f.path
}
func (f *FileVehicle) Read() ([]byte, error) {
return os.ReadFile(f.path)
}
func NewFileVehicle(path string) *FileVehicle {
return &FileVehicle{path: path}
}
type HTTPVehicle struct {
url string
path string
}
func (h *HTTPVehicle) Type() types.VehicleType {
return types.HTTP
}
func (h *HTTPVehicle) Path() string {
return h.path
}
func (h *HTTPVehicle) Read() ([]byte, error) {
ctx, cancel := context.WithTimeout(context.Background(), time.Second*20)
defer cancel()
resp, err := netHttp.HttpRequest(ctx, h.url, http.MethodGet, nil, nil)
if err != nil {
return nil, err
}
defer resp.Body.Close()
buf, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
return buf, nil
}
func NewHTTPVehicle(url string, path string) *HTTPVehicle {
return &HTTPVehicle{url, path}
}

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, netType C.NetWork) *SocketAdapter {
metadata := parseSocksAddr(target)
metadata.NetWork = netType
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,71 +0,0 @@
package inbound
import (
"net"
"net/http"
"strconv"
"github.com/Dreamacro/clash/component/socks5"
C "github.com/Dreamacro/clash/constant"
)
func parseSocksAddr(target socks5.Addr) *C.Metadata {
metadata := &C.Metadata{
AddrType: int(target[0]),
}
switch target[0] {
case socks5.AtypDomainName:
metadata.Host = string(target[2 : 2+target[1]])
metadata.DstPort = strconv.Itoa((int(target[2+target[1]]) << 8) | int(target[2+target[1]+1]))
case socks5.AtypIPv4:
ip := net.IP(target[1 : 1+net.IPv4len])
metadata.DstIP = ip
metadata.DstPort = strconv.Itoa((int(target[1+net.IPv4len]) << 8) | int(target[1+net.IPv4len+1]))
case socks5.AtypIPv6:
ip := net.IP(target[1 : 1+net.IPv6len])
metadata.DstIP = ip
metadata.DstPort = strconv.Itoa((int(target[1+net.IPv6len]) << 8) | int(target[1+net.IPv6len+1]))
}
return metadata
}
func parseHTTPAddr(request *http.Request) *C.Metadata {
host := request.URL.Hostname()
port := request.URL.Port()
if port == "" {
port = "80"
}
metadata := &C.Metadata{
NetWork: C.TCP,
AddrType: C.AtypDomainName,
Host: host,
DstIP: nil,
DstPort: port,
}
ip := net.ParseIP(host)
if ip != nil {
switch {
case ip.To4() == nil:
metadata.AddrType = C.AtypIPv6
default:
metadata.AddrType = C.AtypIPv4
}
metadata.DstIP = ip
}
return metadata
}
func parseAddr(addr string) (net.IP, string, error) {
host, port, err := net.SplitHostPort(addr)
if err != nil {
return nil, "", err
}
ip := net.ParseIP(host)
return ip, port, nil
}

View File

@ -1,204 +0,0 @@
package outbound
import (
"context"
"encoding/json"
"errors"
"net"
"net/http"
"time"
"github.com/Dreamacro/clash/common/queue"
C "github.com/Dreamacro/clash/constant"
)
var (
defaultURLTestTimeout = time.Second * 5
)
type Base struct {
name 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) DialUDP(metadata *C.Metadata) (C.PacketConn, net.Addr, error) {
return nil, 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 NewBase(name string, tp C.AdapterType, udp bool) *Base {
return &Base{name, 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(c net.PacketConn, a C.ProxyAdapter) C.PacketConn {
return &packetConn{c, []string{a.Name()}}
}
type Proxy struct {
C.ProxyAdapter
history *queue.Queue
alive bool
}
func (p *Proxy) Alive() bool {
return p.alive
}
func (p *Proxy) Dial(metadata *C.Metadata) (C.Conn, error) {
ctx, cancel := context.WithTimeout(context.Background(), tcpTimeout)
defer cancel()
return p.DialContext(ctx, metadata)
}
func (p *Proxy) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
conn, err := p.ProxyAdapter.DialContext(ctx, metadata)
if err != nil {
p.alive = false
}
return conn, err
}
func (p *Proxy) DelayHistory() []C.DelayHistory {
queue := p.history.Copy()
histories := []C.DelayHistory{}
for _, item := range queue {
histories = append(histories, item.(C.DelayHistory))
}
return histories
}
// LastDelay return last history record. if proxy is not alive, return the max value of uint16.
func (p *Proxy) LastDelay() (delay uint16) {
var max uint16 = 0xffff
if !p.alive {
return max
}
last := p.history.Last()
if last == nil {
return max
}
history := last.(C.DelayHistory)
if history.Delay == 0 {
return max
}
return history.Delay
}
func (p *Proxy) MarshalJSON() ([]byte, error) {
inner, err := p.ProxyAdapter.MarshalJSON()
if err != nil {
return inner, err
}
mapping := map[string]interface{}{}
json.Unmarshal(inner, &mapping)
mapping["history"] = p.DelayHistory()
mapping["name"] = p.Name()
return json.Marshal(mapping)
}
// URLTest get the delay for the specified URL
func (p *Proxy) URLTest(ctx context.Context, url string) (t uint16, err error) {
defer func() {
p.alive = err == nil
record := C.DelayHistory{Time: time.Now()}
if err == nil {
record.Delay = t
}
p.history.Put(record)
if p.history.Len() > 10 {
p.history.Pop()
}
}()
addr, err := urlToMetadata(url)
if err != nil {
return
}
start := time.Now()
instance, err := p.DialContext(ctx, &addr)
if err != nil {
return
}
defer instance.Close()
req, err := http.NewRequest(http.MethodHead, url, nil)
if err != nil {
return
}
req = req.WithContext(ctx)
transport := &http.Transport{
Dial: func(string, string) (net.Conn, error) {
return instance, nil
},
// from http.DefaultTransport
MaxIdleConns: 100,
IdleConnTimeout: 90 * time.Second,
TLSHandshakeTimeout: 10 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
}
client := http.Client{Transport: transport}
resp, err := client.Do(req)
if err != nil {
return
}
resp.Body.Close()
t = uint16(time.Since(start) / time.Millisecond)
return
}
func NewProxy(adapter C.ProxyAdapter) *Proxy {
return &Proxy{adapter, queue.New(10), true}
}

View File

@ -1,49 +0,0 @@
package outbound
import (
"context"
"net"
C "github.com/Dreamacro/clash/constant"
)
type Direct struct {
*Base
}
func (d *Direct) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
address := net.JoinHostPort(metadata.Host, metadata.DstPort)
if metadata.DstIP != nil {
address = net.JoinHostPort(metadata.DstIP.String(), metadata.DstPort)
}
c, err := dialContext(ctx, "tcp", address)
if err != nil {
return nil, err
}
tcpKeepAlive(c)
return newConn(c, d), nil
}
func (d *Direct) DialUDP(metadata *C.Metadata) (C.PacketConn, net.Addr, error) {
pc, err := net.ListenPacket("udp", "")
if err != nil {
return nil, nil, err
}
addr, err := resolveUDPAddr("udp", metadata.RemoteAddress())
if err != nil {
return nil, nil, err
}
return newPacketConn(pc, d), addr, nil
}
func NewDirect() *Direct {
return &Direct{
Base: &Base{
name: "DIRECT",
tp: C.Direct,
udp: true,
},
}
}

View File

@ -1,64 +0,0 @@
package outbound
import (
"fmt"
"github.com/Dreamacro/clash/common/structure"
C "github.com/Dreamacro/clash/constant"
)
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")
}
var proxy C.ProxyAdapter
err := fmt.Errorf("Cannot parse")
switch proxyType {
case "ss":
ssOption := &ShadowSocksOption{}
err = decoder.Decode(mapping, ssOption)
if err != nil {
break
}
proxy, err = NewShadowSocks(*ssOption)
case "socks5":
socksOption := &Socks5Option{}
err = decoder.Decode(mapping, socksOption)
if err != nil {
break
}
proxy = NewSocks5(*socksOption)
case "http":
httpOption := &HttpOption{}
err = decoder.Decode(mapping, httpOption)
if err != nil {
break
}
proxy = NewHttp(*httpOption)
case "vmess":
vmessOption := &VmessOption{}
err = decoder.Decode(mapping, vmessOption)
if err != nil {
break
}
proxy, err = NewVmess(*vmessOption)
case "snell":
snellOption := &SnellOption{}
err = decoder.Decode(mapping, snellOption)
if err != nil {
break
}
proxy, err = NewSnell(*snellOption)
default:
return nil, fmt.Errorf("Unsupport proxy type: %s", proxyType)
}
if err != nil {
return nil, err
}
return NewProxy(proxy), nil
}

View File

@ -1,61 +0,0 @@
package outbound
import (
"context"
"errors"
"io"
"net"
"time"
C "github.com/Dreamacro/clash/constant"
)
type Reject struct {
*Base
}
func (r *Reject) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
return newConn(&NopConn{}, r), nil
}
func (r *Reject) DialUDP(metadata *C.Metadata) (C.PacketConn, net.Addr, error) {
return nil, nil, errors.New("match reject rule")
}
func NewReject() *Reject {
return &Reject{
Base: &Base{
name: "REJECT",
tp: C.Reject,
udp: true,
},
}
}
type NopConn struct{}
func (rw *NopConn) Read(b []byte) (int, error) {
return 0, io.EOF
}
func (rw *NopConn) Write(b []byte) (int, error) {
return 0, io.EOF
}
// Close is fake function for net.Conn
func (rw *NopConn) Close() error { return nil }
// LocalAddr is fake function for net.Conn
func (rw *NopConn) LocalAddr() net.Addr { return nil }
// RemoteAddr is fake function for net.Conn
func (rw *NopConn) RemoteAddr() net.Addr { return nil }
// SetDeadline is fake function for net.Conn
func (rw *NopConn) SetDeadline(time.Time) error { return nil }
// SetReadDeadline is fake function for net.Conn
func (rw *NopConn) SetReadDeadline(time.Time) error { return nil }
// SetWriteDeadline is fake function for net.Conn
func (rw *NopConn) SetWriteDeadline(time.Time) error { return nil }

View File

@ -1,213 +0,0 @@
package outbound
import (
"context"
"crypto/tls"
"encoding/json"
"fmt"
"net"
"strconv"
"github.com/Dreamacro/clash/common/structure"
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"
"github.com/Dreamacro/go-shadowsocks2/core"
)
type ShadowSocks struct {
*Base
server string
cipher core.Cipher
// obfs
obfsMode string
obfsOption *simpleObfsOption
v2rayOption *v2rayObfs.Option
}
type ShadowSocksOption struct {
Name string `proxy:"name"`
Server string `proxy:"server"`
Port int `proxy:"port"`
Password string `proxy:"password"`
Cipher string `proxy:"cipher"`
UDP bool `proxy:"udp,omitempty"`
Plugin string `proxy:"plugin,omitempty"`
PluginOpts map[string]interface{} `proxy:"plugin-opts,omitempty"`
// deprecated when bump to 1.0
Obfs string `proxy:"obfs,omitempty"`
ObfsHost string `proxy:"obfs-host,omitempty"`
}
type simpleObfsOption struct {
Mode string `obfs:"mode"`
Host string `obfs:"host,omitempty"`
}
type v2rayObfsOption struct {
Mode string `obfs:"mode"`
Host string `obfs:"host,omitempty"`
Path string `obfs:"path,omitempty"`
TLS bool `obfs:"tls,omitempty"`
Headers map[string]string `obfs:"headers,omitempty"`
SkipCertVerify bool `obfs:"skip-cert-verify,omitempty"`
Mux bool `obfs:"mux,omitempty"`
}
func (ss *ShadowSocks) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
c, err := dialContext(ctx, "tcp", ss.server)
if err != nil {
return nil, fmt.Errorf("%s connect error: %w", ss.server, err)
}
tcpKeepAlive(c)
switch ss.obfsMode {
case "tls":
c = obfs.NewTLSObfs(c, ss.obfsOption.Host)
case "http":
_, port, _ := net.SplitHostPort(ss.server)
c = obfs.NewHTTPObfs(c, ss.obfsOption.Host, port)
case "websocket":
var err error
c, err = v2rayObfs.NewV2rayObfs(c, ss.v2rayOption)
if err != nil {
return nil, fmt.Errorf("%s connect error: %w", ss.server, err)
}
}
c = ss.cipher.StreamConn(c)
_, err = c.Write(serializesSocksAddr(metadata))
return newConn(c, ss), err
}
func (ss *ShadowSocks) DialUDP(metadata *C.Metadata) (C.PacketConn, net.Addr, error) {
pc, err := net.ListenPacket("udp", "")
if err != nil {
return nil, nil, err
}
addr, err := resolveUDPAddr("udp", ss.server)
if err != nil {
return nil, nil, err
}
targetAddr := socks5.ParseAddr(metadata.RemoteAddress())
if targetAddr == nil {
return nil, nil, fmt.Errorf("parse address %s error: %s", metadata.String(), metadata.DstPort)
}
pc = ss.cipher.PacketConn(pc)
return newPacketConn(&ssUDPConn{PacketConn: pc, rAddr: targetAddr}, ss), addr, nil
}
func (ss *ShadowSocks) MarshalJSON() ([]byte, error) {
return json.Marshal(map[string]string{
"type": ss.Type().String(),
})
}
func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) {
server := net.JoinHostPort(option.Server, strconv.Itoa(option.Port))
cipher := option.Cipher
password := option.Password
ciph, err := core.PickCipher(cipher, nil, password)
if err != nil {
return nil, fmt.Errorf("ss %s initialize error: %w", server, err)
}
var v2rayOption *v2rayObfs.Option
var obfsOption *simpleObfsOption
obfsMode := ""
// forward compatibility before 1.0
if option.Obfs != "" {
obfsMode = option.Obfs
obfsOption = &simpleObfsOption{
Host: "bing.com",
}
if option.ObfsHost != "" {
obfsOption.Host = option.ObfsHost
}
}
decoder := structure.NewDecoder(structure.Option{TagName: "obfs", WeaklyTypedInput: true})
if option.Plugin == "obfs" {
opts := simpleObfsOption{Host: "bing.com"}
if err := decoder.Decode(option.PluginOpts, &opts); err != nil {
return nil, fmt.Errorf("ss %s initialize obfs error: %w", server, err)
}
if opts.Mode != "tls" && opts.Mode != "http" {
return nil, fmt.Errorf("ss %s obfs mode error: %s", server, opts.Mode)
}
obfsMode = opts.Mode
obfsOption = &opts
} else if option.Plugin == "v2ray-plugin" {
opts := v2rayObfsOption{Host: "bing.com", Mux: true}
if err := decoder.Decode(option.PluginOpts, &opts); err != nil {
return nil, fmt.Errorf("ss %s initialize v2ray-plugin error: %w", server, err)
}
if opts.Mode != "websocket" {
return nil, fmt.Errorf("ss %s obfs mode error: %s", server, opts.Mode)
}
obfsMode = opts.Mode
var tlsConfig *tls.Config
if opts.TLS {
tlsConfig = &tls.Config{
ServerName: opts.Host,
InsecureSkipVerify: opts.SkipCertVerify,
ClientSessionCache: getClientSessionCache(),
}
}
v2rayOption = &v2rayObfs.Option{
Host: opts.Host,
Path: opts.Path,
Headers: opts.Headers,
TLSConfig: tlsConfig,
Mux: opts.Mux,
}
}
return &ShadowSocks{
Base: &Base{
name: option.Name,
tp: C.Shadowsocks,
udp: option.UDP,
},
server: server,
cipher: ciph,
obfsMode: obfsMode,
v2rayOption: v2rayOption,
obfsOption: obfsOption,
}, nil
}
type ssUDPConn struct {
net.PacketConn
rAddr socks5.Addr
}
func (uc *ssUDPConn) WriteTo(b []byte, addr net.Addr) (n int, err error) {
packet, err := socks5.EncodeUDPPacket(uc.rAddr, b)
if err != nil {
return
}
return uc.PacketConn.WriteTo(packet[3:], addr)
}
func (uc *ssUDPConn) ReadFrom(b []byte) (int, net.Addr, error) {
n, _, e := uc.PacketConn.ReadFrom(b)
addr := socks5.SplitAddr(b[:n])
var from net.Addr
if e == nil {
// Get the source IP/Port of packet.
from = addr.UDPAddr()
}
copy(b, b[len(addr):])
return n - len(addr), from, e
}

View File

@ -1,72 +0,0 @@
package outbound
import (
"context"
"fmt"
"net"
"strconv"
"github.com/Dreamacro/clash/common/structure"
obfs "github.com/Dreamacro/clash/component/simple-obfs"
"github.com/Dreamacro/clash/component/snell"
C "github.com/Dreamacro/clash/constant"
)
type Snell struct {
*Base
server string
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) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
c, err := dialContext(ctx, "tcp", s.server)
if err != nil {
return nil, fmt.Errorf("%s connect error: %w", s.server, err)
}
tcpKeepAlive(c)
switch s.obfsOption.Mode {
case "tls":
c = obfs.NewTLSObfs(c, s.obfsOption.Host)
case "http":
_, port, _ := net.SplitHostPort(s.server)
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 newConn(c, s), err
}
func NewSnell(option SnellOption) (*Snell, error) {
server := 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", server, err)
}
if obfsOption.Mode != "tls" && obfsOption.Mode != "http" {
return nil, fmt.Errorf("snell %s obfs mode error: %s", server, obfsOption.Mode)
}
return &Snell{
Base: &Base{
name: option.Name,
tp: C.Snell,
},
server: server,
psk: psk,
obfsOption: obfsOption,
}, nil
}

View File

@ -1,180 +0,0 @@
package outbound
import (
"bytes"
"context"
"crypto/tls"
"fmt"
"net"
"net/url"
"strconv"
"sync"
"time"
"github.com/Dreamacro/clash/component/socks5"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/dns"
)
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)
tcp.SetKeepAlivePeriod(30 * time.Second)
}
}
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)
p, _ := strconv.Atoi(metadata.DstPort)
port := []byte{uint8(p >> 8), uint8(p & 0xff)}
switch metadata.AddrType {
case socks5.AtypDomainName:
len := uint8(len(metadata.Host))
host := []byte(metadata.Host)
buf = [][]byte{{aType, len}, host, port}
case socks5.AtypIPv4:
host := metadata.DstIP.To4()
buf = [][]byte{{aType}, host, port}
case socks5.AtypIPv6:
host := metadata.DstIP.To16()
buf = [][]byte{{aType}, host, port}
}
return bytes.Join(buf, nil)
}
func dialContext(ctx context.Context, network, address string) (net.Conn, error) {
host, port, err := net.SplitHostPort(address)
if err != nil {
return nil, err
}
returned := make(chan struct{})
defer close(returned)
type dialResult struct {
net.Conn
error
resolved bool
ipv6 bool
done bool
}
results := make(chan dialResult)
var primary, fallback dialResult
startRacer := func(ctx context.Context, host string, ipv6 bool) {
dialer := net.Dialer{}
result := dialResult{ipv6: ipv6, done: true}
defer func() {
select {
case results <- result:
case <-returned:
if result.Conn != nil {
result.Conn.Close()
}
}
}()
var ip net.IP
if ipv6 {
ip, result.error = dns.ResolveIPv6(host)
} else {
ip, result.error = dns.ResolveIPv4(host)
}
if result.error != nil {
return
}
result.resolved = true
if ipv6 {
result.Conn, result.error = dialer.DialContext(ctx, "tcp6", net.JoinHostPort(ip.String(), port))
} else {
result.Conn, result.error = dialer.DialContext(ctx, "tcp4", net.JoinHostPort(ip.String(), port))
}
}
go startRacer(ctx, host, false)
go startRacer(ctx, host, true)
for {
select {
case res := <-results:
if res.error == nil {
return res.Conn, nil
}
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 {
return nil, primary.error
}
}
}
}
}
func resolveUDPAddr(network, address string) (*net.UDPAddr, error) {
host, port, err := net.SplitHostPort(address)
if err != nil {
return nil, err
}
ip, err := dns.ResolveIP(host)
if err != nil {
return nil, err
}
return net.ResolveUDPAddr(network, net.JoinHostPort(ip.String(), port))
}

View File

@ -1,129 +0,0 @@
package outbound
import (
"context"
"fmt"
"net"
"strconv"
"strings"
"github.com/Dreamacro/clash/component/vmess"
C "github.com/Dreamacro/clash/constant"
)
type Vmess struct {
*Base
server string
client *vmess.Client
}
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"`
WSPath string `proxy:"ws-path,omitempty"`
WSHeaders map[string]string `proxy:"ws-headers,omitempty"`
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
}
func (v *Vmess) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
c, err := dialContext(ctx, "tcp", v.server)
if err != nil {
return nil, fmt.Errorf("%s connect error", v.server)
}
tcpKeepAlive(c)
c, err = v.client.New(c, parseVmessAddr(metadata))
return newConn(c, v), err
}
func (v *Vmess) DialUDP(metadata *C.Metadata) (C.PacketConn, net.Addr, error) {
ctx, cancel := context.WithTimeout(context.Background(), tcpTimeout)
defer cancel()
c, err := dialContext(ctx, "tcp", v.server)
if err != nil {
return nil, nil, fmt.Errorf("%s connect error", v.server)
}
tcpKeepAlive(c)
c, err = v.client.New(c, parseVmessAddr(metadata))
if err != nil {
return nil, nil, fmt.Errorf("new vmess client error: %v", err)
}
return newPacketConn(&vmessUDPConn{Conn: c}, v), c.RemoteAddr(), 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,
TLS: option.TLS,
HostName: option.Server,
Port: strconv.Itoa(option.Port),
NetWork: option.Network,
WebSocketPath: option.WSPath,
WebSocketHeaders: option.WSHeaders,
SkipCertVerify: option.SkipCertVerify,
SessionCache: getClientSessionCache(),
})
if err != nil {
return nil, err
}
return &Vmess{
Base: &Base{
name: option.Name,
tp: C.Vmess,
udp: true,
},
server: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)),
client: client,
}, 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 vmessUDPConn struct {
net.Conn
}
func (uc *vmessUDPConn) WriteTo(b []byte, addr net.Addr) (int, error) {
return uc.Conn.Write(b)
}
func (uc *vmessUDPConn) ReadFrom(b []byte) (int, net.Addr, error) {
n, err := uc.Conn.Read(b)
return n, uc.RemoteAddr(), 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,85 +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/singledo"
C "github.com/Dreamacro/clash/constant"
)
type Fallback struct {
*outbound.Base
single *singledo.Single
providers []provider.ProxyProvider
}
func (f *Fallback) Now() string {
proxy := f.findAliveProxy()
return proxy.Name()
}
func (f *Fallback) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
proxy := f.findAliveProxy()
c, err := proxy.DialContext(ctx, metadata)
if err == nil {
c.AppendToChains(f)
}
return c, err
}
func (f *Fallback) DialUDP(metadata *C.Metadata) (C.PacketConn, net.Addr, error) {
proxy := f.findAliveProxy()
pc, addr, err := proxy.DialUDP(metadata)
if err == nil {
pc.AppendToChains(f)
}
return pc, addr, err
}
func (f *Fallback) SupportUDP() bool {
proxy := f.findAliveProxy()
return proxy.SupportUDP()
}
func (f *Fallback) MarshalJSON() ([]byte, error) {
var all []string
for _, proxy := range f.proxies() {
all = append(all, proxy.Name())
}
return json.Marshal(map[string]interface{}{
"type": f.Type().String(),
"now": f.Now(),
"all": all,
})
}
func (f *Fallback) proxies() []C.Proxy {
elm, _, _ := f.single.Do(func() (interface{}, error) {
return getProvidersProxies(f.providers), nil
})
return elm.([]C.Proxy)
}
func (f *Fallback) findAliveProxy() C.Proxy {
proxies := f.proxies()
for _, proxy := range proxies {
if proxy.Alive() {
return proxy
}
}
return f.proxies()[0]
}
func NewFallback(name string, providers []provider.ProxyProvider) *Fallback {
return &Fallback{
Base: outbound.NewBase(name, C.Fallback, false),
single: singledo.NewSingle(defaultGetProxiesDuration),
providers: providers,
}
}

View File

@ -1,128 +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)
}
}()
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() {
c, err = proxy.DialContext(ctx, metadata)
return
}
}
c, err = proxies[0].DialContext(ctx, metadata)
return
}
func (lb *LoadBalance) DialUDP(metadata *C.Metadata) (pc C.PacketConn, addr net.Addr, err error) {
defer func() {
if err == nil {
pc.AppendToChains(lb)
}
}()
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.DialUDP(metadata)
}
}
return proxies[0].DialUDP(metadata)
}
func (lb *LoadBalance) SupportUDP() bool {
return true
}
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,144 +0,0 @@
package outboundgroup
import (
"errors"
"fmt"
"github.com/Dreamacro/clash/adapters/provider"
"github.com/Dreamacro/clash/common/structure"
C "github.com/Dreamacro/clash/constant"
)
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"`
}
func ParseProxyGroup(config map[string]interface{}, proxyMap map[string]C.Proxy, providersMap map[string]provider.ProxyProvider) (C.ProxyAdapter, error) {
decoder := structure.NewDecoder(structure.Option{TagName: "group", WeaklyTypedInput: true})
groupOption := &GroupCommonOption{}
if err := decoder.Decode(config, groupOption); err != nil {
return nil, errFormat
}
if groupOption.Type == "" || groupOption.Name == "" {
return nil, errFormat
}
groupName := groupOption.Name
providers := []provider.ProxyProvider{}
if len(groupOption.Proxies) == 0 && len(groupOption.Use) == 0 {
return nil, errMissProxy
}
if len(groupOption.Proxies) != 0 {
ps, err := getProxies(proxyMap, groupOption.Proxies)
if err != nil {
return nil, err
}
// if Use not empty, drop health check options
if len(groupOption.Use) != 0 {
hc := provider.NewHealthCheck(ps, "", 0)
pd, err := provider.NewCompatibleProvier(groupName, ps, hc)
if err != nil {
return nil, err
}
providers = append(providers, pd)
} else {
// select don't need health check
if groupOption.Type == "select" {
hc := provider.NewHealthCheck(ps, "", 0)
pd, err := provider.NewCompatibleProvier(groupName, ps, hc)
if err != nil {
return nil, err
}
providers = append(providers, pd)
providersMap[groupName] = pd
} else {
if groupOption.URL == "" || groupOption.Interval == 0 {
return nil, errMissHealthCheck
}
hc := provider.NewHealthCheck(ps, groupOption.URL, uint(groupOption.Interval))
pd, err := provider.NewCompatibleProvier(groupName, ps, hc)
if err != nil {
return nil, err
}
providers = append(providers, pd)
providersMap[groupName] = pd
}
}
}
if len(groupOption.Use) != 0 {
list, err := getProviders(providersMap, groupOption.Use)
if err != nil {
return nil, err
}
providers = append(providers, list...)
}
var group C.ProxyAdapter
switch groupOption.Type {
case "url-test":
group = NewURLTest(groupName, providers)
case "select":
group = NewSelector(groupName, providers)
case "fallback":
group = NewFallback(groupName, providers)
case "load-balance":
group = NewLoadBalance(groupName, providers)
default:
return nil, fmt.Errorf("%w: %s", errType, groupOption.Type)
}
return group, nil
}
func getProxies(mapping map[string]C.Proxy, list []string) ([]C.Proxy, error) {
var ps []C.Proxy
for _, name := range list {
p, ok := mapping[name]
if !ok {
return nil, fmt.Errorf("'%s' not found", name)
}
ps = append(ps, p)
}
return ps, nil
}
func getProviders(mapping map[string]provider.ProxyProvider, list []string) ([]provider.ProxyProvider, error) {
var ps []provider.ProxyProvider
for _, name := range list {
p, ok := mapping[name]
if !ok {
return nil, fmt.Errorf("'%s' not found", name)
}
if p.VehicleType() == provider.Compatible {
return nil, fmt.Errorf("proxy group %s can't contains in `use`", name)
}
ps = append(ps, p)
}
return ps, nil
}

View File

@ -1,86 +0,0 @@
package outboundgroup
import (
"context"
"encoding/json"
"errors"
"net"
"github.com/Dreamacro/clash/adapters/outbound"
"github.com/Dreamacro/clash/adapters/provider"
"github.com/Dreamacro/clash/common/singledo"
C "github.com/Dreamacro/clash/constant"
)
type Selector struct {
*outbound.Base
single *singledo.Single
selected C.Proxy
providers []provider.ProxyProvider
}
func (s *Selector) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
c, err := s.selected.DialContext(ctx, metadata)
if err == nil {
c.AppendToChains(s)
}
return c, err
}
func (s *Selector) DialUDP(metadata *C.Metadata) (C.PacketConn, net.Addr, error) {
pc, addr, err := s.selected.DialUDP(metadata)
if err == nil {
pc.AppendToChains(s)
}
return pc, addr, err
}
func (s *Selector) SupportUDP() bool {
return s.selected.SupportUDP()
}
func (s *Selector) MarshalJSON() ([]byte, error) {
var all []string
for _, proxy := range s.proxies() {
all = append(all, proxy.Name())
}
return json.Marshal(map[string]interface{}{
"type": s.Type().String(),
"now": s.Now(),
"all": all,
})
}
func (s *Selector) Now() string {
return s.selected.Name()
}
func (s *Selector) Set(name string) error {
for _, proxy := range s.proxies() {
if proxy.Name() == name {
s.selected = proxy
return nil
}
}
return errors.New("Proxy does not exist")
}
func (s *Selector) proxies() []C.Proxy {
elm, _, _ := s.single.Do(func() (interface{}, error) {
return getProvidersProxies(s.providers), nil
})
return elm.([]C.Proxy)
}
func NewSelector(name string, providers []provider.ProxyProvider) *Selector {
selected := providers[0].Proxies()[0]
return &Selector{
Base: outbound.NewBase(name, C.Selector, false),
single: singledo.NewSingle(defaultGetProxiesDuration),
providers: providers,
selected: selected,
}
}

View File

@ -1,95 +0,0 @@
package outboundgroup
import (
"context"
"encoding/json"
"net"
"time"
"github.com/Dreamacro/clash/adapters/outbound"
"github.com/Dreamacro/clash/adapters/provider"
"github.com/Dreamacro/clash/common/singledo"
C "github.com/Dreamacro/clash/constant"
)
type URLTest struct {
*outbound.Base
single *singledo.Single
fastSingle *singledo.Single
providers []provider.ProxyProvider
}
func (u *URLTest) Now() string {
return u.fast().Name()
}
func (u *URLTest) DialContext(ctx context.Context, metadata *C.Metadata) (c C.Conn, err error) {
c, err = u.fast().DialContext(ctx, metadata)
if err == nil {
c.AppendToChains(u)
}
return c, err
}
func (u *URLTest) DialUDP(metadata *C.Metadata) (C.PacketConn, net.Addr, error) {
pc, addr, err := u.fast().DialUDP(metadata)
if err == nil {
pc.AppendToChains(u)
}
return pc, addr, err
}
func (u *URLTest) proxies() []C.Proxy {
elm, _, _ := u.single.Do(func() (interface{}, error) {
return getProvidersProxies(u.providers), nil
})
return elm.([]C.Proxy)
}
func (u *URLTest) fast() C.Proxy {
elm, _, _ := u.fastSingle.Do(func() (interface{}, error) {
proxies := u.proxies()
fast := proxies[0]
min := fast.LastDelay()
for _, proxy := range proxies[1:] {
if !proxy.Alive() {
continue
}
delay := proxy.LastDelay()
if delay < min {
fast = proxy
min = delay
}
}
return fast, nil
})
return elm.(C.Proxy)
}
func (u *URLTest) SupportUDP() bool {
return u.fast().SupportUDP()
}
func (u *URLTest) MarshalJSON() ([]byte, error) {
var all []string
for _, proxy := range u.proxies() {
all = append(all, proxy.Name())
}
return json.Marshal(map[string]interface{}{
"type": u.Type().String(),
"now": u.Now(),
"all": all,
})
}
func NewURLTest(name string, providers []provider.ProxyProvider) *URLTest {
return &URLTest{
Base: outbound.NewBase(name, C.URLTest, false),
single: singledo.NewSingle(defaultGetProxiesDuration),
fastSingle: singledo.NewSingle(time.Second * 10),
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),
}
}

View File

@ -1,318 +0,0 @@
package provider
import (
"bytes"
"crypto/md5"
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"os"
"time"
"github.com/Dreamacro/clash/adapters/outbound"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/log"
"gopkg.in/yaml.v2"
)
const (
ReservedName = "default"
fileMode = 0666
)
// 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
Reload() error
Destroy() error
}
// ProxyProvider interface
type ProxyProvider interface {
Provider
Proxies() []C.Proxy
HealthCheck()
Update() error
}
type ProxySchema struct {
Proxies []map[string]interface{} `yaml:"proxies"`
}
type ProxySetProvider struct {
name string
vehicle Vehicle
hash [16]byte
proxies []C.Proxy
healthCheck *HealthCheck
ticker *time.Ticker
updatedAt *time.Time
}
func (pp *ProxySetProvider) MarshalJSON() ([]byte, error) {
return json.Marshal(map[string]interface{}{
"name": pp.Name(),
"type": pp.Type().String(),
"vehicleType": pp.VehicleType().String(),
"proxies": pp.Proxies(),
"updatedAt": pp.updatedAt,
})
}
func (pp *ProxySetProvider) Name() string {
return pp.name
}
func (pp *ProxySetProvider) Reload() error {
return nil
}
func (pp *ProxySetProvider) HealthCheck() {
pp.healthCheck.check()
}
func (pp *ProxySetProvider) Update() error {
return pp.pull()
}
func (pp *ProxySetProvider) Destroy() error {
pp.healthCheck.close()
if pp.ticker != nil {
pp.ticker.Stop()
}
return nil
}
func (pp *ProxySetProvider) Initial() error {
var buf []byte
var err error
if stat, err := os.Stat(pp.vehicle.Path()); err == nil {
buf, err = ioutil.ReadFile(pp.vehicle.Path())
modTime := stat.ModTime()
pp.updatedAt = &modTime
} else {
buf, err = pp.vehicle.Read()
}
if err != nil {
return err
}
proxies, err := pp.parse(buf)
if err != nil {
return err
}
if err := ioutil.WriteFile(pp.vehicle.Path(), buf, fileMode); err != nil {
return err
}
pp.hash = md5.Sum(buf)
pp.setProxies(proxies)
// pull proxies automatically
if pp.ticker != nil {
go pp.pullLoop()
}
return nil
}
func (pp *ProxySetProvider) VehicleType() VehicleType {
return pp.vehicle.Type()
}
func (pp *ProxySetProvider) Type() ProviderType {
return Proxy
}
func (pp *ProxySetProvider) Proxies() []C.Proxy {
return pp.proxies
}
func (pp *ProxySetProvider) pullLoop() {
for range pp.ticker.C {
if err := pp.pull(); err != nil {
log.Warnln("[Provider] %s pull error: %s", pp.Name(), err.Error())
}
}
}
func (pp *ProxySetProvider) pull() error {
buf, err := pp.vehicle.Read()
if err != nil {
return err
}
now := time.Now()
hash := md5.Sum(buf)
if bytes.Equal(pp.hash[:], hash[:]) {
log.Debugln("[Provider] %s's proxies doesn't change", pp.Name())
pp.updatedAt = &now
return nil
}
proxies, err := pp.parse(buf)
if err != nil {
return err
}
log.Infoln("[Provider] %s's proxies update", pp.Name())
if err := ioutil.WriteFile(pp.vehicle.Path(), buf, fileMode); err != nil {
return err
}
pp.updatedAt = &now
pp.hash = hash
pp.setProxies(proxies)
return nil
}
func (pp *ProxySetProvider) parse(buf []byte) ([]C.Proxy, error) {
schema := &ProxySchema{}
if err := yaml.Unmarshal(buf, schema); err != nil {
return nil, err
}
if schema.Proxies == nil {
return nil, errors.New("File must have a `proxies` field")
}
proxies := []C.Proxy{}
for idx, mapping := range schema.Proxies {
proxy, err := outbound.ParseProxy(mapping)
if err != nil {
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 proxies, nil
}
func (pp *ProxySetProvider) setProxies(proxies []C.Proxy) {
pp.proxies = proxies
pp.healthCheck.setProxy(proxies)
go pp.healthCheck.check()
}
func NewProxySetProvider(name string, interval time.Duration, vehicle Vehicle, hc *HealthCheck) *ProxySetProvider {
var ticker *time.Ticker
if interval != 0 {
ticker = time.NewTicker(interval)
}
if hc.auto() {
go hc.process()
}
return &ProxySetProvider{
name: name,
vehicle: vehicle,
proxies: []C.Proxy{},
healthCheck: hc,
ticker: ticker,
}
}
type CompatibleProvier struct {
name string
healthCheck *HealthCheck
proxies []C.Proxy
}
func (cp *CompatibleProvier) MarshalJSON() ([]byte, error) {
return json.Marshal(map[string]interface{}{
"name": cp.Name(),
"type": cp.Type().String(),
"vehicleType": cp.VehicleType().String(),
"proxies": cp.Proxies(),
})
}
func (cp *CompatibleProvier) Name() string {
return cp.name
}
func (cp *CompatibleProvier) Reload() error {
return nil
}
func (cp *CompatibleProvier) Destroy() error {
cp.healthCheck.close()
return nil
}
func (cp *CompatibleProvier) HealthCheck() {
cp.healthCheck.check()
}
func (cp *CompatibleProvier) Update() error {
return nil
}
func (cp *CompatibleProvier) Initial() error {
return nil
}
func (cp *CompatibleProvier) VehicleType() VehicleType {
return Compatible
}
func (cp *CompatibleProvier) Type() ProviderType {
return Proxy
}
func (cp *CompatibleProvier) Proxies() []C.Proxy {
return cp.proxies
}
func NewCompatibleProvier(name string, proxies []C.Proxy, hc *HealthCheck) (*CompatibleProvier, error) {
if len(proxies) == 0 {
return nil, errors.New("Provider need one proxy at least")
}
if hc.auto() {
go hc.process()
}
return &CompatibleProvier{
name: name,
proxies: proxies,
healthCheck: hc,
}, nil
}

View File

@ -1,106 +0,0 @@
package provider
import (
"context"
"io/ioutil"
"net/http"
"time"
)
// 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) Path() string {
return f.path
}
func (f *FileVehicle) Read() ([]byte, error) {
return ioutil.ReadFile(f.path)
}
func NewFileVehicle(path string) *FileVehicle {
return &FileVehicle{path: path}
}
type HTTPVehicle struct {
url string
path string
}
func (h *HTTPVehicle) Type() VehicleType {
return HTTP
}
func (h *HTTPVehicle) Path() string {
return h.path
}
func (h *HTTPVehicle) Read() ([]byte, error) {
ctx, cancel := context.WithTimeout(context.Background(), time.Second*20)
defer cancel()
req, err := http.NewRequest(http.MethodGet, h.url, nil)
if err != nil {
return nil, err
}
req = req.WithContext(ctx)
transport := &http.Transport{
// from http.DefaultTransport
MaxIdleConns: 100,
IdleConnTimeout: 90 * time.Second,
TLSHandshakeTimeout: 10 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
}
client := http.Client{Transport: transport}
resp, err := client.Do(req)
if err != nil {
return nil, err
}
buf, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
return buf, nil
}
func NewHTTPVehicle(url string, path string) *HTTPVehicle {
return &HTTPVehicle{url, path}
}

28
check_amd64.sh Normal file
View File

@ -0,0 +1,28 @@
#!/bin/sh
flags=$(grep '^flags\b' </proc/cpuinfo | head -n 1)
flags=" ${flags#*:} "
has_flags () {
for flag; do
case "$flags" in
*" $flag "*) :;;
*) return 1;;
esac
done
}
determine_level () {
level=0
has_flags lm cmov cx8 fpu fxsr mmx syscall sse2 || return 0
level=1
has_flags cx16 lahf_lm popcnt sse4_1 sse4_2 ssse3 || return 0
level=2
has_flags avx avx2 bmi1 bmi2 f16c fma abm movbe xsave || return 0
level=3
has_flags avx512f avx512bw avx512cd avx512dq avx512vl || return 0
level=4
}
determine_level
echo "Your CPU supports amd64-v$level"
return $level

105
common/batch/batch.go Normal file
View File

@ -0,0 +1,105 @@
package batch
import (
"context"
"sync"
)
type Option[T any] func(b *Batch[T])
type Result[T any] struct {
Value T
Err error
}
type Error struct {
Key string
Err error
}
func WithConcurrencyNum[T any](n int) Option[T] {
return func(b *Batch[T]) {
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[T any] struct {
result map[string]Result[T]
queue chan struct{}
wg sync.WaitGroup
mux sync.Mutex
err *Error
once sync.Once
cancel func()
}
func (b *Batch[T]) Go(key string, fn func() (T, 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[T]{value, err}
b.mux.Lock()
defer b.mux.Unlock()
b.result[key] = ret
}()
}
func (b *Batch[T]) Wait() *Error {
b.wg.Wait()
if b.cancel != nil {
b.cancel()
}
return b.err
}
func (b *Batch[T]) WaitAndGetResult() (map[string]Result[T], *Error) {
err := b.Wait()
return b.Result(), err
}
func (b *Batch[T]) Result() map[string]Result[T] {
b.mux.Lock()
defer b.mux.Unlock()
copyM := map[string]Result[T]{}
for k, v := range b.result {
copyM[k] = v
}
return copyM
}
func New[T any](ctx context.Context, opts ...Option[T]) (*Batch[T], context.Context) {
ctx, cancel := context.WithCancel(ctx)
b := &Batch[T]{
result: map[string]Result[T]{},
}
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[string](context.Background())
now := time.Now()
b.Go("foo", func() (string, error) {
time.Sleep(time.Millisecond * 100)
return "foo", nil
})
b.Go("bar", func() (string, 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)
}
}
func TestBatchWithConcurrencyNum(t *testing.T) {
b, _ := New[string](
context.Background(),
WithConcurrencyNum[string](3),
)
now := time.Now()
for i := 0; i < 7; i++ {
idx := i
b.Go(strconv.Itoa(idx), func() (string, 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)
}
}
func TestBatchContext(t *testing.T) {
b, ctx := New[string](context.Background())
b.Go("error", func() (string, error) {
time.Sleep(time.Millisecond * 100)
return "", errors.New("test error")
})
b.Go("ctx", func() (string, error) {
<-ctx.Done()
return "", ctx.Err()
})
result, err := b.WaitAndGetResult()
assert.NotNil(t, err)
assert.Equal(t, "error", err.Key)
assert.Equal(t, ctx.Err(), result["ctx"].Err)
}

50
common/cache/cache.go vendored
View File

@ -7,50 +7,50 @@ import (
)
// Cache store element with a expired time
type Cache struct {
*cache
type Cache[K comparable, V any] struct {
*cache[K, V]
}
type cache struct {
type cache[K comparable, V any] struct {
mapping sync.Map
janitor *janitor
janitor *janitor[K, V]
}
type element struct {
type element[V any] struct {
Expired time.Time
Payload interface{}
Payload V
}
// Put element in Cache with its ttl
func (c *cache) Put(key interface{}, payload interface{}, ttl time.Duration) {
c.mapping.Store(key, &element{
func (c *cache[K, V]) Put(key K, payload V, ttl time.Duration) {
c.mapping.Store(key, &element[V]{
Payload: payload,
Expired: time.Now().Add(ttl),
})
}
// Get element in Cache, and drop when it expired
func (c *cache) Get(key interface{}) interface{} {
func (c *cache[K, V]) Get(key K) V {
item, exist := c.mapping.Load(key)
if !exist {
return nil
return getZero[V]()
}
elm := item.(*element)
elm := item.(*element[V])
// expired
if time.Since(elm.Expired) > 0 {
c.mapping.Delete(key)
return nil
return getZero[V]()
}
return elm.Payload
}
// GetWithExpire element in Cache with Expire Time
func (c *cache) GetWithExpire(key interface{}) (payload interface{}, expired time.Time) {
func (c *cache[K, V]) GetWithExpire(key K) (payload V, expired time.Time) {
item, exist := c.mapping.Load(key)
if !exist {
return
}
elm := item.(*element)
elm := item.(*element[V])
// expired
if time.Since(elm.Expired) > 0 {
c.mapping.Delete(key)
@ -59,10 +59,10 @@ func (c *cache) GetWithExpire(key interface{}) (payload interface{}, expired tim
return elm.Payload, elm.Expired
}
func (c *cache) cleanup() {
c.mapping.Range(func(k, v interface{}) bool {
func (c *cache[K, V]) cleanup() {
c.mapping.Range(func(k, v any) bool {
key := k.(string)
elm := v.(*element)
elm := v.(*element[V])
if time.Since(elm.Expired) > 0 {
c.mapping.Delete(key)
}
@ -70,12 +70,12 @@ func (c *cache) cleanup() {
})
}
type janitor struct {
type janitor[K comparable, V any] struct {
interval time.Duration
stop chan struct{}
}
func (j *janitor) process(c *cache) {
func (j *janitor[K, V]) process(c *cache[K, V]) {
ticker := time.NewTicker(j.interval)
for {
select {
@ -88,19 +88,19 @@ func (j *janitor) process(c *cache) {
}
}
func stopJanitor(c *Cache) {
func stopJanitor[K comparable, V any](c *Cache[K, V]) {
c.janitor.stop <- struct{}{}
}
// New return *Cache
func New(interval time.Duration) *Cache {
j := &janitor{
func New[K comparable, V any](interval time.Duration) *Cache[K, V] {
j := &janitor[K, V]{
interval: interval,
stop: make(chan struct{}),
}
c := &cache{janitor: j}
c := &cache[K, V]{janitor: j}
go j.process(c)
C := &Cache{c}
runtime.SetFinalizer(C, stopJanitor)
C := &Cache[K, V]{c}
runtime.SetFinalizer(C, stopJanitor[K, V])
return C
}

View File

@ -11,48 +11,50 @@ import (
func TestCache_Basic(t *testing.T) {
interval := 200 * time.Millisecond
ttl := 20 * time.Millisecond
c := New(interval)
c := New[string, int](interval)
c.Put("int", 1, ttl)
c.Put("string", "a", ttl)
d := New[string, string](interval)
d.Put("string", "a", ttl)
i := c.Get("int")
assert.Equal(t, i.(int), 1, "should recv 1")
assert.Equal(t, i, 1, "should recv 1")
s := c.Get("string")
assert.Equal(t, s.(string), "a", "should recv 'a'")
s := d.Get("string")
assert.Equal(t, s, "a", "should recv 'a'")
}
func TestCache_TTL(t *testing.T) {
interval := 200 * time.Millisecond
ttl := 20 * time.Millisecond
now := time.Now()
c := New(interval)
c := New[string, int](interval)
c.Put("int", 1, ttl)
c.Put("int2", 2, ttl)
i := c.Get("int")
_, expired := c.GetWithExpire("int2")
assert.Equal(t, i.(int), 1, "should recv 1")
assert.Equal(t, i, 1, "should recv 1")
assert.True(t, now.Before(expired))
time.Sleep(ttl * 2)
i = c.Get("int")
j, _ := c.GetWithExpire("int2")
assert.Nil(t, i, "should recv nil")
assert.Nil(t, j, "should recv nil")
assert.True(t, i == 0, "should recv 0")
assert.True(t, j == 0, "should recv 0")
}
func TestCache_AutoCleanup(t *testing.T) {
interval := 10 * time.Millisecond
ttl := 15 * time.Millisecond
c := New(interval)
c := New[string, int](interval)
c.Put("int", 1, ttl)
time.Sleep(ttl * 2)
i := c.Get("int")
j, _ := c.GetWithExpire("int")
assert.Nil(t, i, "should recv nil")
assert.Nil(t, j, "should recv nil")
assert.True(t, i == 0, "should recv 0")
assert.True(t, j == 0, "should recv 0")
}
func TestCache_AutoGC(t *testing.T) {
@ -60,7 +62,7 @@ func TestCache_AutoGC(t *testing.T) {
go func() {
interval := 10 * time.Millisecond
ttl := 15 * time.Millisecond
c := New(interval)
c := New[string, int](interval)
c.Put("int", 1, ttl)
sign <- struct{}{}
}()

View File

@ -3,63 +3,73 @@ package cache
// Modified by https://github.com/die-net/lrucache
import (
"container/list"
"sync"
"time"
"github.com/Dreamacro/clash/common/generics/list"
)
// Option is part of Functional Options Pattern
type Option func(*LruCache)
type Option[K comparable, V any] func(*LruCache[K, V])
// EvictCallback is used to get a callback when a cache entry is evicted
type EvictCallback func(key interface{}, value interface{})
type EvictCallback[K comparable, V any] func(key K, value V)
// WithEvict set the evict callback
func WithEvict(cb EvictCallback) Option {
return func(l *LruCache) {
func WithEvict[K comparable, V any](cb EvictCallback[K, V]) Option[K, V] {
return func(l *LruCache[K, V]) {
l.onEvict = cb
}
}
// WithUpdateAgeOnGet update expires when Get element
func WithUpdateAgeOnGet() Option {
return func(l *LruCache) {
func WithUpdateAgeOnGet[K comparable, V any]() Option[K, V] {
return func(l *LruCache[K, V]) {
l.updateAgeOnGet = true
}
}
// WithAge defined element max age (second)
func WithAge(maxAge int64) Option {
return func(l *LruCache) {
func WithAge[K comparable, V any](maxAge int64) Option[K, V] {
return func(l *LruCache[K, V]) {
l.maxAge = maxAge
}
}
// WithSize defined max length of LruCache
func WithSize(maxSize int) Option {
return func(l *LruCache) {
func WithSize[K comparable, V any](maxSize int) Option[K, V] {
return func(l *LruCache[K, V]) {
l.maxSize = maxSize
}
}
// WithStale decide whether Stale return is enabled.
// If this feature is enabled, element will not get Evicted according to `WithAge`.
func WithStale[K comparable, V any](stale bool) Option[K, V] {
return func(l *LruCache[K, V]) {
l.staleReturn = stale
}
}
// LruCache is a thread-safe, in-memory lru-cache that evicts the
// least recently used entries from memory when (if set) the entries are
// older than maxAge (in seconds). Use the New constructor to create one.
type LruCache struct {
type LruCache[K comparable, V any] struct {
maxAge int64
maxSize int
mu sync.Mutex
cache map[interface{}]*list.Element
lru *list.List // Front is least-recent
cache map[K]*list.Element[*entry[K, V]]
lru *list.List[*entry[K, V]] // Front is least-recent
updateAgeOnGet bool
onEvict EvictCallback
staleReturn bool
onEvict EvictCallback[K, V]
}
// NewLRUCache creates an LruCache
func NewLRUCache(options ...Option) *LruCache {
lc := &LruCache{
lru: list.New(),
cache: make(map[interface{}]*list.Element),
func NewLRUCache[K comparable, V any](options ...Option[K, V]) *LruCache[K, V] {
lc := &LruCache[K, V]{
lru: list.New[*entry[K, V]](),
cache: make(map[K]*list.Element[*entry[K, V]]),
}
for _, option := range options {
@ -69,36 +79,33 @@ func NewLRUCache(options ...Option) *LruCache {
return lc
}
// Get returns the interface{} representation of a cached response and a bool
// Get returns the any representation of a cached response and a bool
// set to true if the key was found.
func (c *LruCache) Get(key interface{}) (interface{}, bool) {
c.mu.Lock()
defer c.mu.Unlock()
le, ok := c.cache[key]
if !ok {
return nil, false
func (c *LruCache[K, V]) Get(key K) (V, bool) {
el := c.get(key)
if el == nil {
return getZero[V](), false
}
if c.maxAge > 0 && le.Value.(*entry).expires <= time.Now().Unix() {
c.deleteElement(le)
c.maybeDeleteOldest()
return nil, false
}
c.lru.MoveToBack(le)
entry := le.Value.(*entry)
if c.maxAge > 0 && c.updateAgeOnGet {
entry.expires = time.Now().Unix() + c.maxAge
}
value := entry.value
value := el.value
return value, true
}
// GetWithExpire returns the any representation of a cached response,
// a time.Time Give expected expires,
// and a bool set to true if the key was found.
// This method will NOT check the maxAge of element and will NOT update the expires.
func (c *LruCache[K, V]) GetWithExpire(key K) (V, time.Time, bool) {
el := c.get(key)
if el == nil {
return getZero[V](), time.Time{}, false
}
return el.value, time.Unix(el.expires, 0), true
}
// Exist returns if key exist in cache but not put item to the head of linked list
func (c *LruCache) Exist(key interface{}) bool {
func (c *LruCache[K, V]) Exist(key K) bool {
c.mu.Lock()
defer c.mu.Unlock()
@ -106,27 +113,32 @@ func (c *LruCache) Exist(key interface{}) bool {
return ok
}
// Set stores the interface{} representation of a response for a given key.
func (c *LruCache) Set(key interface{}, value interface{}) {
c.mu.Lock()
defer c.mu.Unlock()
// Set stores the any representation of a response for a given key.
func (c *LruCache[K, V]) Set(key K, value V) {
expires := int64(0)
if c.maxAge > 0 {
expires = time.Now().Unix() + c.maxAge
}
c.SetWithExpire(key, value, time.Unix(expires, 0))
}
// SetWithExpire stores the any representation of a response for a given key and given expires.
// The expires time will round to second.
func (c *LruCache[K, V]) SetWithExpire(key K, value V, expires time.Time) {
c.mu.Lock()
defer c.mu.Unlock()
if le, ok := c.cache[key]; ok {
c.lru.MoveToBack(le)
e := le.Value.(*entry)
e := le.Value
e.value = value
e.expires = expires
e.expires = expires.Unix()
} else {
e := &entry{key: key, value: value, expires: expires}
e := &entry[K, V]{key: key, value: value, expires: expires.Unix()}
c.cache[key] = c.lru.PushBack(e)
if c.maxSize > 0 {
if len := c.lru.Len(); len > c.maxSize {
if elLen := c.lru.Len(); elLen > c.maxSize {
c.deleteElement(c.lru.Front())
}
}
@ -135,8 +147,49 @@ func (c *LruCache) Set(key interface{}, value interface{}) {
c.maybeDeleteOldest()
}
// CloneTo clone and overwrite elements to another LruCache
func (c *LruCache[K, V]) CloneTo(n *LruCache[K, V]) {
c.mu.Lock()
defer c.mu.Unlock()
n.mu.Lock()
defer n.mu.Unlock()
n.lru = list.New[*entry[K, V]]()
n.cache = make(map[K]*list.Element[*entry[K, V]])
for e := c.lru.Front(); e != nil; e = e.Next() {
elm := e.Value
n.cache[elm.key] = n.lru.PushBack(elm)
}
}
func (c *LruCache[K, V]) get(key K) *entry[K, V] {
c.mu.Lock()
defer c.mu.Unlock()
le, ok := c.cache[key]
if !ok {
return nil
}
if !c.staleReturn && c.maxAge > 0 && le.Value.expires <= time.Now().Unix() {
c.deleteElement(le)
c.maybeDeleteOldest()
return nil
}
c.lru.MoveToBack(le)
el := le.Value
if c.maxAge > 0 && c.updateAgeOnGet {
el.expires = time.Now().Unix() + c.maxAge
}
return el
}
// Delete removes the value associated with a key.
func (c *LruCache) Delete(key string) {
func (c *LruCache[K, V]) Delete(key K) {
c.mu.Lock()
if le, ok := c.cache[key]; ok {
@ -146,26 +199,40 @@ func (c *LruCache) Delete(key string) {
c.mu.Unlock()
}
func (c *LruCache) maybeDeleteOldest() {
if c.maxAge > 0 {
func (c *LruCache[K, V]) maybeDeleteOldest() {
if !c.staleReturn && c.maxAge > 0 {
now := time.Now().Unix()
for le := c.lru.Front(); le != nil && le.Value.(*entry).expires <= now; le = c.lru.Front() {
for le := c.lru.Front(); le != nil && le.Value.expires <= now; le = c.lru.Front() {
c.deleteElement(le)
}
}
}
func (c *LruCache) deleteElement(le *list.Element) {
func (c *LruCache[K, V]) deleteElement(le *list.Element[*entry[K, V]]) {
c.lru.Remove(le)
e := le.Value.(*entry)
e := le.Value
delete(c.cache, e.key)
if c.onEvict != nil {
c.onEvict(e.key, e.value)
}
}
type entry struct {
key interface{}
value interface{}
func (c *LruCache[K, V]) Clear() error {
c.mu.Lock()
c.cache = make(map[K]*list.Element[*entry[K, V]])
c.mu.Unlock()
return nil
}
type entry[K comparable, V any] struct {
key K
value V
expires int64
}
func getZero[T any]() T {
var result T
return result
}

View File

@ -19,7 +19,7 @@ var entries = []struct {
}
func TestLRUCache(t *testing.T) {
c := NewLRUCache()
c := NewLRUCache[string, string]()
for _, e := range entries {
c.Set(e.key, e.value)
@ -32,7 +32,7 @@ func TestLRUCache(t *testing.T) {
for _, e := range entries {
value, ok := c.Get(e.key)
if assert.True(t, ok) {
assert.Equal(t, e.value, value.(string))
assert.Equal(t, e.value, value)
}
}
@ -45,25 +45,25 @@ func TestLRUCache(t *testing.T) {
}
func TestLRUMaxAge(t *testing.T) {
c := NewLRUCache(WithAge(86400))
c := NewLRUCache[string, string](WithAge[string, string](86400))
now := time.Now().Unix()
expected := now + 86400
// Add one expired entry
c.Set("foo", "bar")
c.lru.Back().Value.(*entry).expires = now
c.lru.Back().Value.expires = now
// Reset
c.Set("foo", "bar")
e := c.lru.Back().Value.(*entry)
e := c.lru.Back().Value
assert.True(t, e.expires >= now)
c.lru.Back().Value.(*entry).expires = now
c.lru.Back().Value.expires = now
// Set a few and verify expiration times
for _, s := range entries {
c.Set(s.key, s.value)
e := c.lru.Back().Value.(*entry)
e := c.lru.Back().Value
assert.True(t, e.expires >= expected && e.expires <= expected+10)
}
@ -77,7 +77,7 @@ func TestLRUMaxAge(t *testing.T) {
for _, s := range entries {
le, ok := c.cache[s.key]
if assert.True(t, ok) {
le.Value.(*entry).expires = now
le.Value.expires = now
}
}
@ -88,22 +88,22 @@ func TestLRUMaxAge(t *testing.T) {
}
func TestLRUpdateOnGet(t *testing.T) {
c := NewLRUCache(WithAge(86400), WithUpdateAgeOnGet())
c := NewLRUCache[string, string](WithAge[string, string](86400), WithUpdateAgeOnGet[string, string]())
now := time.Now().Unix()
expires := now + 86400/2
// Add one expired entry
c.Set("foo", "bar")
c.lru.Back().Value.(*entry).expires = expires
c.lru.Back().Value.expires = expires
_, ok := c.Get("foo")
assert.True(t, ok)
assert.True(t, c.lru.Back().Value.(*entry).expires > expires)
assert.True(t, c.lru.Back().Value.expires > expires)
}
func TestMaxSize(t *testing.T) {
c := NewLRUCache(WithSize(2))
c := NewLRUCache[string, string](WithSize[string, string](2))
// Add one expired entry
c.Set("foo", "bar")
_, ok := c.Get("foo")
@ -117,7 +117,7 @@ func TestMaxSize(t *testing.T) {
}
func TestExist(t *testing.T) {
c := NewLRUCache(WithSize(1))
c := NewLRUCache[int, int](WithSize[int, int](1))
c.Set(1, 2)
assert.True(t, c.Exist(1))
c.Set(2, 3)
@ -126,13 +126,59 @@ func TestExist(t *testing.T) {
func TestEvict(t *testing.T) {
temp := 0
evict := func(key interface{}, value interface{}) {
temp = key.(int) + value.(int)
evict := func(key int, value int) {
temp = key + value
}
c := NewLRUCache(WithEvict(evict), WithSize(1))
c := NewLRUCache[int, int](WithEvict[int, int](evict), WithSize[int, int](1))
c.Set(1, 2)
c.Set(2, 3)
assert.Equal(t, temp, 3)
}
func TestSetWithExpire(t *testing.T) {
c := NewLRUCache[int, *struct{}](WithAge[int, *struct{}](1))
now := time.Now().Unix()
tenSecBefore := time.Unix(now-10, 0)
c.SetWithExpire(1, &struct{}{}, tenSecBefore)
// res is expected not to exist, and expires should be empty time.Time
res, expires, exist := c.GetWithExpire(1)
assert.True(t, nil == res)
assert.Equal(t, time.Time{}, expires)
assert.Equal(t, false, exist)
}
func TestStale(t *testing.T) {
c := NewLRUCache[int, int](WithAge[int, int](1), WithStale[int, int](true))
now := time.Now().Unix()
tenSecBefore := time.Unix(now-10, 0)
c.SetWithExpire(1, 2, tenSecBefore)
res, expires, exist := c.GetWithExpire(1)
assert.Equal(t, 2, res)
assert.Equal(t, tenSecBefore, expires)
assert.Equal(t, true, exist)
}
func TestCloneTo(t *testing.T) {
o := NewLRUCache[string, int](WithSize[string, int](10))
o.Set("1", 1)
o.Set("2", 2)
n := NewLRUCache[string, int](WithSize[string, int](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"))
}

35
common/cmd/cmd.go Normal file
View File

@ -0,0 +1,35 @@
package cmd
import (
"fmt"
"os/exec"
"strings"
)
func ExecCmd(cmdStr string) (string, error) {
args := splitArgs(cmdStr)
var cmd *exec.Cmd
if len(args) == 1 {
cmd = exec.Command(args[0])
} else {
cmd = exec.Command(args[0], args[1:]...)
}
prepareBackgroundCommand(cmd)
out, err := cmd.CombinedOutput()
if err != nil {
return "", fmt.Errorf("%v, %s", err, string(out))
}
return string(out), nil
}
func splitArgs(cmd string) []string {
args := strings.Split(cmd, " ")
// use in pipeline
if len(args) > 2 && strings.ContainsAny(cmd, "|") {
suffix := strings.Join(args[2:], " ")
args = append(args[:2], suffix)
}
return args
}

10
common/cmd/cmd_other.go Normal file
View File

@ -0,0 +1,10 @@
//go:build !windows
package cmd
import (
"os/exec"
)
func prepareBackgroundCommand(cmd *exec.Cmd) {
}

40
common/cmd/cmd_test.go Normal file
View File

@ -0,0 +1,40 @@
package cmd
import (
"runtime"
"testing"
"github.com/stretchr/testify/assert"
)
func TestSplitArgs(t *testing.T) {
args := splitArgs("ls")
args1 := splitArgs("ls -la")
args2 := splitArgs("bash -c ls")
args3 := splitArgs("bash -c ls -lahF | grep 'cmd'")
assert.Equal(t, 1, len(args))
assert.Equal(t, 2, len(args1))
assert.Equal(t, 3, len(args2))
assert.Equal(t, 3, len(args3))
}
func TestExecCmd(t *testing.T) {
if runtime.GOOS == "windows" {
_, err := ExecCmd("dir")
assert.Nil(t, err)
return
}
_, err := ExecCmd("ls")
_, err1 := ExecCmd("ls -la")
_, err2 := ExecCmd("bash -c ls")
_, err3 := ExecCmd("bash -c ls -la")
_, err4 := ExecCmd("bash -c ls -la | grep 'cmd'")
assert.Nil(t, err)
assert.Nil(t, err1)
assert.Nil(t, err2)
assert.Nil(t, err3)
assert.Nil(t, err4)
}

12
common/cmd/cmd_windows.go Normal file
View File

@ -0,0 +1,12 @@
//go:build windows
package cmd
import (
"os/exec"
"syscall"
)
func prepareBackgroundCommand(cmd *exec.Cmd) {
cmd.SysProcAttr = &syscall.SysProcAttr{HideWindow: true}
}

View File

@ -0,0 +1,56 @@
package collections
import "sync"
type (
stack struct {
top *node
length int
lock *sync.RWMutex
}
node struct {
value interface{}
prev *node
}
)
// NewStack Create a new stack
func NewStack() *stack {
return &stack{nil, 0, &sync.RWMutex{}}
}
// Len Return the number of items in the stack
func (this *stack) Len() int {
return this.length
}
// Peek View the top item on the stack
func (this *stack) Peek() interface{} {
if this.length == 0 {
return nil
}
return this.top.value
}
// Pop the top item of the stack and return it
func (this *stack) Pop() interface{} {
this.lock.Lock()
defer this.lock.Unlock()
if this.length == 0 {
return nil
}
n := this.top
this.top = n.prev
this.length--
return n.value
}
// Push a value onto the top of the stack
func (this *stack) Push(value interface{}) {
this.lock.Lock()
defer this.lock.Unlock()
n := &node{value, this.top}
this.top = n
this.length++
}

450
common/convert/converter.go Normal file
View File

@ -0,0 +1,450 @@
package convert
import (
"bytes"
"encoding/base64"
"encoding/json"
"fmt"
"net/url"
"strconv"
"strings"
)
var enc = base64.StdEncoding
func DecodeBase64(buf []byte) ([]byte, error) {
dBuf := make([]byte, enc.DecodedLen(len(buf)))
n, err := enc.Decode(dBuf, buf)
if err != nil {
return nil, err
}
return dBuf[:n], nil
}
// DecodeBase64StringToString decode base64 string to string
func DecodeBase64StringToString(s string) (string, error) {
dBuf, err := enc.DecodeString(s)
if err != nil {
return "", err
}
return string(dBuf), nil
}
// ConvertsV2Ray convert V2Ray subscribe proxies data to clash proxies config
func ConvertsV2Ray(buf []byte) ([]map[string]any, error) {
data, err := DecodeBase64(buf)
if err != nil {
data = buf
}
arr := strings.Split(string(data), "\n")
proxies := make([]map[string]any, 0, len(arr))
names := make(map[string]int, 200)
for _, line := range arr {
line = strings.TrimRight(line, " \r")
if line == "" {
continue
}
scheme, body, found := strings.Cut(line, "://")
if !found {
continue
}
scheme = strings.ToLower(scheme)
switch scheme {
case "hysteria":
urlHysteria, err := url.Parse(line)
if err != nil {
continue
}
query := urlHysteria.Query()
name := uniqueName(names, urlHysteria.Fragment)
hysteria := make(map[string]any, 20)
hysteria["name"] = name
hysteria["type"] = scheme
hysteria["server"] = urlHysteria.Hostname()
hysteria["port"] = urlHysteria.Port()
hysteria["sni"] = query.Get("peer")
hysteria["obfs"] = query.Get("obfs")
hysteria["alpn"] = query.Get("alpn")
hysteria["auth_str"] = query.Get("auth")
hysteria["protocol"] = query.Get("protocol")
hysteria["down_mbps"], _ = strconv.Atoi(query.Get("downmbps"))
hysteria["up_mbps"], _ = strconv.Atoi(query.Get("upmbps"))
hysteria["skip-cert-verify"], _ = strconv.ParseBool(query.Get("insecure"))
proxies = append(proxies, hysteria)
case "trojan":
urlTrojan, err := url.Parse(line)
if err != nil {
continue
}
query := urlTrojan.Query()
name := uniqueName(names, urlTrojan.Fragment)
trojan := make(map[string]any, 20)
trojan["name"] = name
trojan["type"] = scheme
trojan["server"] = urlTrojan.Hostname()
trojan["port"] = urlTrojan.Port()
trojan["password"] = urlTrojan.User.Username()
trojan["udp"] = true
trojan["skip-cert-verify"] = false
sni := query.Get("sni")
if sni != "" {
trojan["sni"] = sni
}
network := strings.ToLower(query.Get("type"))
if network != "" {
trojan["network"] = network
}
switch network {
case "ws":
headers := make(map[string]any)
wsOpts := make(map[string]any)
// headers["Host"] = RandHost()
headers["User-Agent"] = RandUserAgent()
wsOpts["path"] = query.Get("path")
wsOpts["headers"] = headers
trojan["ws-opts"] = wsOpts
case "grpc":
grpcOpts := make(map[string]any)
grpcOpts["grpc-service-name"] = query.Get("serviceName")
trojan["grpc-opts"] = grpcOpts
}
proxies = append(proxies, trojan)
case "vless":
urlVless, err := url.Parse(line)
if err != nil {
continue
}
query := urlVless.Query()
name := uniqueName(names, urlVless.Fragment)
vless := make(map[string]any, 20)
vless["name"] = name
vless["type"] = scheme
vless["server"] = urlVless.Hostname()
vless["port"] = urlVless.Port()
vless["uuid"] = urlVless.User.Username()
vless["udp"] = true
vless["skip-cert-verify"] = false
sni := query.Get("sni")
if sni != "" {
vless["servername"] = sni
}
flow := strings.ToLower(query.Get("flow"))
if flow != "" {
vless["flow"] = flow
}
network := strings.ToLower(query.Get("type"))
if network != "" {
fakeType := strings.ToLower(query.Get("headerType"))
if network == "tcp" && fakeType == "http" {
network = "http"
}
if network == "http" {
network = "h2"
}
vless["network"] = network
}
switch network {
case "http":
headers := make(map[string]any)
httpOpts := make(map[string]any)
if query.Get("method") != "" {
httpOpts["method"] = query.Get("method")
}
if query.Get("path") != "" {
httpOpts["path"] = query.Get("path")
}
headers["User-Agent"] = RandUserAgent()
httpOpts["headers"] = headers
vless["http-opts"] = httpOpts
case "h2":
headers := make(map[string]any)
h2Opts := make(map[string]any)
headers["User-Agent"] = RandUserAgent()
h2Opts["path"] = query.Get("path")
h2Opts["headers"] = headers
vless["h2-opts"] = h2Opts
case "ws":
headers := make(map[string]any)
wsOpts := make(map[string]any)
// headers["Host"] = RandHost()
headers["User-Agent"] = RandUserAgent()
wsOpts["path"] = query.Get("path")
wsOpts["headers"] = headers
vless["ws-opts"] = wsOpts
case "grpc":
grpcOpts := make(map[string]any)
grpcOpts["grpc-service-name"] = query.Get("serviceName")
vless["grpc-opts"] = grpcOpts
}
proxies = append(proxies, vless)
case "vmess":
dcBuf, err := enc.DecodeString(body)
if err != nil {
continue
}
jsonDc := json.NewDecoder(bytes.NewReader(dcBuf))
values := make(map[string]any, 20)
if jsonDc.Decode(&values) != nil {
continue
}
name := uniqueName(names, values["ps"].(string))
vmess := make(map[string]any, 20)
vmess["name"] = name
vmess["type"] = scheme
vmess["server"] = values["add"]
vmess["port"] = values["port"]
vmess["uuid"] = values["id"]
vmess["alterId"] = values["aid"]
vmess["cipher"] = "auto"
vmess["udp"] = true
vmess["skip-cert-verify"] = false
sni := values["sni"]
if sni != "" {
vmess["sni"] = sni
}
host := values["host"]
network := strings.ToLower(values["net"].(string))
vmess["network"] = network
tls := strings.ToLower(values["tls"].(string))
if tls != "" && tls != "0" && tls != "null" {
if host != nil {
vmess["servername"] = host
}
vmess["tls"] = true
}
switch network {
case "http":
headers := make(map[string]any)
httpOpts := make(map[string]any)
// headers["Host"] = RandHost()
headers["User-Agent"] = RandUserAgent()
httpOpts["method"] = values["method"]
httpOpts["path"] = values["path"]
httpOpts["headers"] = headers
vmess["http-opts"] = httpOpts
case "h2":
headers := make(map[string]any)
h2Opts := make(map[string]any)
// headers["Host"] = RandHost()
headers["User-Agent"] = RandUserAgent()
h2Opts["path"] = values["path"]
h2Opts["headers"] = headers
vmess["h2-opts"] = h2Opts
case "ws":
headers := make(map[string]any)
wsOpts := make(map[string]any)
headers["Host"] = RandHost()
headers["User-Agent"] = RandUserAgent()
if values["path"] != nil {
wsOpts["path"] = values["path"]
}
wsOpts["headers"] = headers
vmess["ws-opts"] = wsOpts
case "grpc":
grpcOpts := make(map[string]any)
grpcOpts["grpc-service-name"] = values["path"]
vmess["grpc-opts"] = grpcOpts
}
proxies = append(proxies, vmess)
case "ss":
urlSS, err := url.Parse(line)
if err != nil {
continue
}
name := uniqueName(names, urlSS.Fragment)
port := urlSS.Port()
if port == "" {
dcBuf, err := enc.DecodeString(urlSS.Host)
if err != nil {
continue
}
urlSS, err = url.Parse("ss://" + string(dcBuf))
if err != nil {
continue
}
}
var (
cipher = urlSS.User.Username()
password string
)
if password, found = urlSS.User.Password(); !found {
dcBuf, err := enc.DecodeString(cipher)
if err != nil {
continue
}
cipher, password, found = strings.Cut(string(dcBuf), ":")
if !found {
continue
}
}
ss := make(map[string]any, 20)
ss["name"] = name
ss["type"] = scheme
ss["server"] = urlSS.Hostname()
ss["port"] = urlSS.Port()
ss["cipher"] = cipher
ss["password"] = password
ss["udp"] = true
proxies = append(proxies, ss)
case "ssr":
dcBuf, err := enc.DecodeString(body)
if err != nil {
continue
}
// ssr://host:port:protocol:method:obfs:urlsafebase64pass/?obfsparam=urlsafebase64&protoparam=&remarks=urlsafebase64&group=urlsafebase64&udpport=0&uot=1
before, after, ok := strings.Cut(string(dcBuf), "/?")
if !ok {
continue
}
beforeArr := strings.Split(before, ":")
if len(beforeArr) != 6 {
continue
}
host := beforeArr[0]
port := beforeArr[1]
protocol := beforeArr[2]
method := beforeArr[3]
obfs := beforeArr[4]
password := decodeUrlSafe(urlSafe(beforeArr[5]))
query, err := url.ParseQuery(urlSafe(after))
if err != nil {
continue
}
remarks := decodeUrlSafe(query.Get("remarks"))
name := uniqueName(names, remarks)
obfsParam := decodeUrlSafe(query.Get("obfsparam"))
protocolParam := query.Get("protoparam")
ssr := make(map[string]any, 20)
ssr["name"] = name
ssr["type"] = scheme
ssr["server"] = host
ssr["port"] = port
ssr["cipher"] = method
ssr["password"] = password
ssr["obfs"] = obfs
ssr["protocol"] = protocol
ssr["udp"] = true
if obfsParam != "" {
ssr["obfs-param"] = obfsParam
}
if protocolParam != "" {
ssr["protocol-param"] = protocolParam
}
proxies = append(proxies, ssr)
}
}
if len(proxies) == 0 {
return nil, fmt.Errorf("convert v2ray subscribe error: format invalid")
}
return proxies, nil
}
func urlSafe(data string) string {
return strings.ReplaceAll(strings.ReplaceAll(data, "+", "-"), "/", "_")
}
func decodeUrlSafe(data string) string {
dcBuf, err := base64.URLEncoding.DecodeString(data)
if err != nil {
return ""
}
return string(dcBuf)
}
func uniqueName(names map[string]int, name string) string {
if index, ok := names[name]; ok {
index++
names[name] = index
name = fmt.Sprintf("%s-%02d", name, index)
} else {
index = 0
names[name] = index
}
return name
}

316
common/convert/util.go Normal file
View File

@ -0,0 +1,316 @@
package convert
import (
"encoding/base64"
"math/rand"
"net/http"
"strings"
"github.com/gofrs/uuid"
)
var hostsSuffix = []string{
"-cdn.aliyuncs.com",
".alicdn.com",
".pan.baidu.com",
".tbcache.com",
".aliyuncdn.com",
".vod.miguvideo.com",
".cibntv.net",
".myqcloud.com",
".smtcdns.com",
".alikunlun.com",
".smtcdns.net",
".apcdns.net",
".cdn-go.cn",
".cdntip.com",
".cdntips.com",
".alidayu.com",
".alidns.com",
".cdngslb.com",
".mxhichina.com",
".alibabadns.com",
}
var userAgents = []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",
}
var (
hostsLen = len(hostsSuffix)
uaLen = len(userAgents)
)
func RandHost() string {
id, _ := uuid.NewV4()
base := strings.ToLower(base64.RawURLEncoding.EncodeToString(id.Bytes()))
base = strings.ReplaceAll(base, "-", "")
base = strings.ReplaceAll(base, "_", "")
buf := []byte(base)
prefix := string(buf[:3]) + "---"
prefix += string(buf[6:8]) + "-"
prefix += string(buf[len(buf)-8:])
return prefix + hostsSuffix[rand.Intn(hostsLen)]
}
func RandUserAgent() string {
return userAgents[rand.Intn(uaLen)]
}
func SetUserAgent(header http.Header) {
if header.Get("User-Agent") != "" {
return
}
userAgent := RandUserAgent()
header.Set("User-Agent", userAgent)
}

View File

@ -0,0 +1,235 @@
// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package list implements a doubly linked list.
//
// To iterate over a list (where l is a *List):
// for e := l.Front(); e != nil; e = e.Next() {
// // do something with e.Value
// }
//
package list
// Element is an element of a linked list.
type Element[T any] struct {
// Next and previous pointers in the doubly-linked list of elements.
// To simplify the implementation, internally a list l is implemented
// as a ring, such that &l.root is both the next element of the last
// list element (l.Back()) and the previous element of the first list
// element (l.Front()).
next, prev *Element[T]
// The list to which this element belongs.
list *List[T]
// The value stored with this element.
Value T
}
// Next returns the next list element or nil.
func (e *Element[T]) Next() *Element[T] {
if p := e.next; e.list != nil && p != &e.list.root {
return p
}
return nil
}
// Prev returns the previous list element or nil.
func (e *Element[T]) Prev() *Element[T] {
if p := e.prev; e.list != nil && p != &e.list.root {
return p
}
return nil
}
// List represents a doubly linked list.
// The zero value for List is an empty list ready to use.
type List[T any] struct {
root Element[T] // sentinel list element, only &root, root.prev, and root.next are used
len int // current list length excluding (this) sentinel element
}
// Init initializes or clears list l.
func (l *List[T]) Init() *List[T] {
l.root.next = &l.root
l.root.prev = &l.root
l.len = 0
return l
}
// New returns an initialized list.
func New[T any]() *List[T] { return new(List[T]).Init() }
// Len returns the number of elements of list l.
// The complexity is O(1).
func (l *List[T]) Len() int { return l.len }
// Front returns the first element of list l or nil if the list is empty.
func (l *List[T]) Front() *Element[T] {
if l.len == 0 {
return nil
}
return l.root.next
}
// Back returns the last element of list l or nil if the list is empty.
func (l *List[T]) Back() *Element[T] {
if l.len == 0 {
return nil
}
return l.root.prev
}
// lazyInit lazily initializes a zero List value.
func (l *List[T]) lazyInit() {
if l.root.next == nil {
l.Init()
}
}
// insert inserts e after at, increments l.len, and returns e.
func (l *List[T]) insert(e, at *Element[T]) *Element[T] {
e.prev = at
e.next = at.next
e.prev.next = e
e.next.prev = e
e.list = l
l.len++
return e
}
// insertValue is a convenience wrapper for insert(&Element{Value: v}, at).
func (l *List[T]) insertValue(v T, at *Element[T]) *Element[T] {
return l.insert(&Element[T]{Value: v}, at)
}
// remove removes e from its list, decrements l.len
func (l *List[T]) remove(e *Element[T]) {
e.prev.next = e.next
e.next.prev = e.prev
e.next = nil // avoid memory leaks
e.prev = nil // avoid memory leaks
e.list = nil
l.len--
}
// move moves e to next to at.
func (l *List[T]) move(e, at *Element[T]) {
if e == at {
return
}
e.prev.next = e.next
e.next.prev = e.prev
e.prev = at
e.next = at.next
e.prev.next = e
e.next.prev = e
}
// Remove removes e from l if e is an element of list l.
// It returns the element value e.Value.
// The element must not be nil.
func (l *List[T]) Remove(e *Element[T]) T {
if e.list == l {
// if e.list == l, l must have been initialized when e was inserted
// in l or l == nil (e is a zero Element) and l.remove will crash
l.remove(e)
}
return e.Value
}
// PushFront inserts a new element e with value v at the front of list l and returns e.
func (l *List[T]) PushFront(v T) *Element[T] {
l.lazyInit()
return l.insertValue(v, &l.root)
}
// PushBack inserts a new element e with value v at the back of list l and returns e.
func (l *List[T]) PushBack(v T) *Element[T] {
l.lazyInit()
return l.insertValue(v, l.root.prev)
}
// InsertBefore inserts a new element e with value v immediately before mark and returns e.
// If mark is not an element of l, the list is not modified.
// The mark must not be nil.
func (l *List[T]) InsertBefore(v T, mark *Element[T]) *Element[T] {
if mark.list != l {
return nil
}
// see comment in List.Remove about initialization of l
return l.insertValue(v, mark.prev)
}
// InsertAfter inserts a new element e with value v immediately after mark and returns e.
// If mark is not an element of l, the list is not modified.
// The mark must not be nil.
func (l *List[T]) InsertAfter(v T, mark *Element[T]) *Element[T] {
if mark.list != l {
return nil
}
// see comment in List.Remove about initialization of l
return l.insertValue(v, mark)
}
// MoveToFront moves element e to the front of list l.
// If e is not an element of l, the list is not modified.
// The element must not be nil.
func (l *List[T]) MoveToFront(e *Element[T]) {
if e.list != l || l.root.next == e {
return
}
// see comment in List.Remove about initialization of l
l.move(e, &l.root)
}
// MoveToBack moves element e to the back of list l.
// If e is not an element of l, the list is not modified.
// The element must not be nil.
func (l *List[T]) MoveToBack(e *Element[T]) {
if e.list != l || l.root.prev == e {
return
}
// see comment in List.Remove about initialization of l
l.move(e, l.root.prev)
}
// MoveBefore moves element e to its new position before mark.
// If e or mark is not an element of l, or e == mark, the list is not modified.
// The element and mark must not be nil.
func (l *List[T]) MoveBefore(e, mark *Element[T]) {
if e.list != l || e == mark || mark.list != l {
return
}
l.move(e, mark.prev)
}
// MoveAfter moves element e to its new position after mark.
// If e or mark is not an element of l, or e == mark, the list is not modified.
// The element and mark must not be nil.
func (l *List[T]) MoveAfter(e, mark *Element[T]) {
if e.list != l || e == mark || mark.list != l {
return
}
l.move(e, mark)
}
// PushBackList inserts a copy of another list at the back of list l.
// The lists l and other may be the same. They must not be nil.
func (l *List[T]) PushBackList(other *List[T]) {
l.lazyInit()
for i, e := other.Len(), other.Front(); i > 0; i, e = i-1, e.Next() {
l.insertValue(e.Value, l.root.prev)
}
}
// PushFrontList inserts a copy of another list at the front of list l.
// The lists l and other may be the same. They must not be nil.
func (l *List[T]) PushFrontList(other *List[T]) {
l.lazyInit()
for i, e := other.Len(), other.Back(); i > 0; i, e = i-1, e.Prev() {
l.insertValue(e.Value, &l.root)
}
}

View File

@ -67,7 +67,6 @@ func (d *digest32) bmix(p []byte) (tail []byte) {
}
func (d *digest32) Sum32() (h1 uint32) {
h1 = d.h1
var k1 uint32

44
common/net/bufconn.go Normal file
View File

@ -0,0 +1,44 @@
package net
import (
"bufio"
"net"
)
type BufferedConn struct {
r *bufio.Reader
net.Conn
}
func NewBufferedConn(c net.Conn) *BufferedConn {
if bc, ok := c.(*BufferedConn); ok {
return bc
}
return &BufferedConn{bufio.NewReader(c), c}
}
// Reader returns the internal bufio.Reader.
func (c *BufferedConn) Reader() *bufio.Reader {
return c.r
}
// Peek returns the next n bytes without advancing the reader.
func (c *BufferedConn) Peek(n int) ([]byte, error) {
return c.r.Peek(n)
}
func (c *BufferedConn) Read(p []byte) (int, error) {
return c.r.Read(p)
}
func (c *BufferedConn) ReadByte() (byte, error) {
return c.r.ReadByte()
}
func (c *BufferedConn) UnreadByte() error {
return c.r.UnreadByte()
}
func (c *BufferedConn) Buffered() int {
return c.r.Buffered()
}

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
}

30
common/net/relay.go Normal file
View File

@ -0,0 +1,30 @@
package net
import (
"io"
"net"
"time"
"github.com/Dreamacro/clash/common/pool"
)
// Relay copies between left and right bidirectionally.
func Relay(leftConn, rightConn net.Conn) {
ch := make(chan error)
go func() {
buf := pool.Get(pool.RelayBufferSize)
// Wrapping to avoid using *net.TCPConn.(ReadFrom)
// See also https://github.com/Dreamacro/clash/pull/1209
_, err := io.CopyBuffer(WriteOnlyWriter{Writer: leftConn}, ReadOnlyReader{Reader: rightConn}, buf)
pool.Put(buf)
leftConn.SetReadDeadline(time.Now())
ch <- err
}()
buf := pool.Get(pool.RelayBufferSize)
io.CopyBuffer(WriteOnlyWriter{Writer: rightConn}, ReadOnlyReader{Reader: leftConn}, buf)
pool.Put(buf)
rightConn.SetReadDeadline(time.Now())
<-ch
}

46
common/net/tcpip.go Normal file
View File

@ -0,0 +1,46 @@
package net
import (
"fmt"
"net"
"strings"
)
func SplitNetworkType(s string) (string, string, error) {
var (
shecme string
hostPort string
)
result := strings.Split(s, "://")
if len(result) == 2 {
shecme = result[0]
hostPort = result[1]
} else if len(result) == 1 {
hostPort = result[0]
} else {
return "", "", fmt.Errorf("tcp/udp style error")
}
if len(shecme) == 0 {
shecme = "udp"
}
if shecme != "tcp" && shecme != "udp" {
return "", "", fmt.Errorf("scheme should be tcp:// or udp://")
} else {
return shecme, hostPort, nil
}
}
func SplitHostPort(s string) (host, port string, hasPort bool, err error) {
temp := s
hasPort = true
if !strings.Contains(s, ":") && !strings.Contains(s, "]:") {
temp += ":0"
hasPort = false
}
host, port, err = net.SplitHostPort(temp)
return
}

53
common/nnip/netip.go Normal file
View File

@ -0,0 +1,53 @@
package nnip
import (
"encoding/binary"
"net"
"net/netip"
)
// IpToAddr converts the net.IP to netip.Addr.
// If slice's length is not 4 or 16, IpToAddr returns netip.Addr{}
func IpToAddr(slice net.IP) netip.Addr {
ip := slice
if len(ip) != 4 {
if ip = slice.To4(); ip == nil {
ip = slice
}
}
if addr, ok := netip.AddrFromSlice(ip); ok {
return addr
}
return netip.Addr{}
}
// UnMasked returns p's last IP address.
// If p is invalid, UnMasked returns netip.Addr{}
func UnMasked(p netip.Prefix) netip.Addr {
if !p.IsValid() {
return netip.Addr{}
}
buf := p.Addr().As16()
hi := binary.BigEndian.Uint64(buf[:8])
lo := binary.BigEndian.Uint64(buf[8:])
bits := p.Bits()
if bits <= 32 {
bits += 96
}
hi = hi | ^uint64(0)>>bits
lo = lo | ^(^uint64(0) << (128 - bits))
binary.BigEndian.PutUint64(buf[:8], hi)
binary.BigEndian.PutUint64(buf[8:], lo)
addr := netip.AddrFrom16(buf)
if p.Addr().Is4() {
return addr.Unmap()
}
return addr
}

View File

@ -1,3 +1,3 @@
package observable
type Iterable <-chan interface{}
type Iterable[T any] <-chan T

View File

@ -5,14 +5,14 @@ import (
"sync"
)
type Observable struct {
iterable Iterable
listener map[Subscription]*Subscriber
type Observable[T any] struct {
iterable Iterable[T]
listener map[Subscription[T]]*Subscriber[T]
mux sync.Mutex
done bool
}
func (o *Observable) process() {
func (o *Observable[T]) process() {
for item := range o.iterable {
o.mux.Lock()
for _, sub := range o.listener {
@ -23,7 +23,7 @@ func (o *Observable) process() {
o.close()
}
func (o *Observable) close() {
func (o *Observable[T]) close() {
o.mux.Lock()
defer o.mux.Unlock()
@ -33,18 +33,18 @@ func (o *Observable) close() {
}
}
func (o *Observable) Subscribe() (Subscription, error) {
func (o *Observable[T]) Subscribe() (Subscription[T], error) {
o.mux.Lock()
defer o.mux.Unlock()
if o.done {
return nil, errors.New("Observable is closed")
return nil, errors.New("observable is closed")
}
subscriber := newSubscriber()
subscriber := newSubscriber[T]()
o.listener[subscriber.Out()] = subscriber
return subscriber.Out(), nil
}
func (o *Observable) UnSubscribe(sub Subscription) {
func (o *Observable[T]) UnSubscribe(sub Subscription[T]) {
o.mux.Lock()
defer o.mux.Unlock()
subscriber, exist := o.listener[sub]
@ -55,10 +55,10 @@ func (o *Observable) UnSubscribe(sub Subscription) {
subscriber.Close()
}
func NewObservable(any Iterable) *Observable {
observable := &Observable{
iterable: any,
listener: map[Subscription]*Subscriber{},
func NewObservable[T any](iter Iterable[T]) *Observable[T] {
observable := &Observable[T]{
iterable: iter,
listener: map[Subscription[T]]*Subscriber[T]{},
}
go observable.process()
return observable

View File

@ -1,16 +1,16 @@
package observable
import (
"runtime"
"sync"
"testing"
"time"
"github.com/stretchr/testify/assert"
"go.uber.org/atomic"
)
func iterator(item []interface{}) chan interface{} {
ch := make(chan interface{})
func iterator[T any](item []T) chan T {
ch := make(chan T)
go func() {
time.Sleep(100 * time.Millisecond)
for _, elm := range item {
@ -22,8 +22,8 @@ func iterator(item []interface{}) chan interface{} {
}
func TestObservable(t *testing.T) {
iter := iterator([]interface{}{1, 2, 3, 4, 5})
src := NewObservable(iter)
iter := iterator[int]([]int{1, 2, 3, 4, 5})
src := NewObservable[int](iter)
data, err := src.Subscribe()
assert.Nil(t, err)
count := 0
@ -33,30 +33,30 @@ func TestObservable(t *testing.T) {
assert.Equal(t, count, 5)
}
func TestObservable_MutilSubscribe(t *testing.T) {
iter := iterator([]interface{}{1, 2, 3, 4, 5})
src := NewObservable(iter)
func TestObservable_MultiSubscribe(t *testing.T) {
iter := iterator[int]([]int{1, 2, 3, 4, 5})
src := NewObservable[int](iter)
ch1, _ := src.Subscribe()
ch2, _ := src.Subscribe()
count := 0
count := atomic.NewInt32(0)
var wg sync.WaitGroup
wg.Add(2)
waitCh := func(ch <-chan interface{}) {
waitCh := func(ch <-chan int) {
for range ch {
count++
count.Inc()
}
wg.Done()
}
go waitCh(ch1)
go waitCh(ch2)
wg.Wait()
assert.Equal(t, count, 10)
assert.Equal(t, int32(10), count.Load())
}
func TestObservable_UnSubscribe(t *testing.T) {
iter := iterator([]interface{}{1, 2, 3, 4, 5})
src := NewObservable(iter)
iter := iterator[int]([]int{1, 2, 3, 4, 5})
src := NewObservable[int](iter)
data, err := src.Subscribe()
assert.Nil(t, err)
src.UnSubscribe(data)
@ -65,8 +65,8 @@ func TestObservable_UnSubscribe(t *testing.T) {
}
func TestObservable_SubscribeClosedSource(t *testing.T) {
iter := iterator([]interface{}{1})
src := NewObservable(iter)
iter := iterator[int]([]int{1})
src := NewObservable[int](iter)
data, _ := src.Subscribe()
<-data
@ -75,21 +75,18 @@ func TestObservable_SubscribeClosedSource(t *testing.T) {
}
func TestObservable_UnSubscribeWithNotExistSubscription(t *testing.T) {
sub := Subscription(make(chan interface{}))
iter := iterator([]interface{}{1})
src := NewObservable(iter)
sub := Subscription[int](make(chan int))
iter := iterator[int]([]int{1})
src := NewObservable[int](iter)
src.UnSubscribe(sub)
}
func TestObservable_SubscribeGoroutineLeak(t *testing.T) {
// waiting for other goroutine recycle
time.Sleep(120 * time.Millisecond)
init := runtime.NumGoroutine()
iter := iterator([]interface{}{1, 2, 3, 4, 5})
src := NewObservable(iter)
iter := iterator[int]([]int{1, 2, 3, 4, 5})
src := NewObservable[int](iter)
max := 100
var list []Subscription
var list []Subscription[int]
for i := 0; i < max; i++ {
ch, _ := src.Subscribe()
list = append(list, ch)
@ -97,7 +94,7 @@ func TestObservable_SubscribeGoroutineLeak(t *testing.T) {
var wg sync.WaitGroup
wg.Add(max)
waitCh := func(ch <-chan interface{}) {
waitCh := func(ch <-chan int) {
for range ch {
}
wg.Done()
@ -107,6 +104,43 @@ func TestObservable_SubscribeGoroutineLeak(t *testing.T) {
go waitCh(ch)
}
wg.Wait()
now := runtime.NumGoroutine()
assert.Equal(t, init, now)
for _, sub := range list {
_, more := <-sub
assert.False(t, more)
}
_, more := <-list[0]
assert.False(t, more)
}
func Benchmark_Observable_1000(b *testing.B) {
ch := make(chan int)
o := NewObservable[int](ch)
num := 1000
subs := []Subscription[int]{}
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[int]) {
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 Subscription[T any] <-chan T
type Subscriber struct {
buffer *channels.InfiniteChannel
type Subscriber[T any] struct {
buffer chan T
once sync.Once
}
func (s *Subscriber) Emit(item interface{}) {
s.buffer.In() <- item
func (s *Subscriber[T]) Emit(item T) {
s.buffer <- item
}
func (s *Subscriber) Out() Subscription {
return s.buffer.Out()
func (s *Subscriber[T]) Out() Subscription[T] {
return s.buffer
}
func (s *Subscriber) Close() {
func (s *Subscriber[T]) Close() {
s.once.Do(func() {
s.buffer.Close()
close(s.buffer)
})
}
func newSubscriber() *Subscriber {
sub := &Subscriber{
buffer: channels.NewInfiniteChannel(),
func newSubscriber[T any]() *Subscriber[T] {
sub := &Subscriber[T]{
buffer: make(chan T, 200),
}
return sub
}

View File

@ -9,48 +9,41 @@ import (
// Picker provides synchronization, and Context cancelation
// for groups of goroutines working on subtasks of a common task.
// Inspired by errGroup
type Picker struct {
type Picker[T any] struct {
ctx context.Context
cancel func()
wg sync.WaitGroup
once sync.Once
result interface{}
firstDone chan struct{}
once sync.Once
errOnce sync.Once
result T
err error
}
func newPicker(ctx context.Context, cancel func()) *Picker {
return &Picker{
ctx: ctx,
cancel: cancel,
firstDone: make(chan struct{}, 1),
func newPicker[T any](ctx context.Context, cancel func()) *Picker[T] {
return &Picker[T]{
ctx: ctx,
cancel: cancel,
}
}
// WithContext returns a new Picker and an associated Context derived from ctx.
// and cancel when first element return.
func WithContext(ctx context.Context) (*Picker, context.Context) {
func WithContext[T any](ctx context.Context) (*Picker[T], context.Context) {
ctx, cancel := context.WithCancel(ctx)
return newPicker(ctx, cancel), ctx
return newPicker[T](ctx, cancel), ctx
}
// WithTimeout returns a new Picker and an associated Context derived from ctx with timeout.
func WithTimeout(ctx context.Context, timeout time.Duration) (*Picker, context.Context) {
func WithTimeout[T any](ctx context.Context, timeout time.Duration) (*Picker[T], context.Context) {
ctx, cancel := context.WithTimeout(ctx, timeout)
return newPicker(ctx, cancel), ctx
}
// WithoutAutoCancel returns a new Picker and an associated Context derived from ctx,
// but it wouldn't cancel context when the first element return.
func WithoutAutoCancel(ctx context.Context) *Picker {
return newPicker(ctx, nil)
return newPicker[T](ctx, cancel), ctx
}
// Wait blocks until all function calls from the Go method have returned,
// then returns the first nil error result (if any) from them.
func (p *Picker) Wait() interface{} {
func (p *Picker[T]) Wait() T {
p.wg.Wait()
if p.cancel != nil {
p.cancel()
@ -58,19 +51,14 @@ func (p *Picker) Wait() interface{} {
return p.result
}
// WaitWithoutCancel blocks until the first result return, if timeout will return nil.
func (p *Picker) WaitWithoutCancel() interface{} {
select {
case <-p.firstDone:
return p.result
case <-p.ctx.Done():
return p.result
}
// Error return the first error (if all success return nil)
func (p *Picker[T]) Error() error {
return p.err
}
// Go calls the given function in a new goroutine.
// The first call to return a nil error cancels the group; its result will be returned by Wait.
func (p *Picker) Go(f func() (interface{}, error)) {
func (p *Picker[T]) Go(f func() (T, error)) {
p.wg.Add(1)
go func() {
@ -79,11 +67,14 @@ func (p *Picker) Go(f func() (interface{}, error)) {
if ret, err := f(); err == nil {
p.once.Do(func() {
p.result = ret
p.firstDone <- struct{}{}
if p.cancel != nil {
p.cancel()
}
})
} else {
p.errOnce.Do(func() {
p.err = err
})
}
}()
}

View File

@ -8,59 +8,38 @@ import (
"github.com/stretchr/testify/assert"
)
func sleepAndSend(ctx context.Context, delay int, input interface{}) func() (interface{}, error) {
return func() (interface{}, error) {
func sleepAndSend[T any](ctx context.Context, delay int, input T) func() (T, error) {
return func() (T, error) {
timer := time.NewTimer(time.Millisecond * time.Duration(delay))
select {
case <-timer.C:
return input, nil
case <-ctx.Done():
return nil, ctx.Err()
return getZero[T](), ctx.Err()
}
}
}
func TestPicker_Basic(t *testing.T) {
picker, ctx := WithContext(context.Background())
picker, ctx := WithContext[int](context.Background())
picker.Go(sleepAndSend(ctx, 30, 2))
picker.Go(sleepAndSend(ctx, 20, 1))
number := picker.Wait()
assert.NotNil(t, number)
assert.Equal(t, number.(int), 1)
assert.Equal(t, number, 1)
}
func TestPicker_Timeout(t *testing.T) {
picker, ctx := WithTimeout(context.Background(), time.Millisecond*5)
picker, ctx := WithTimeout[int](context.Background(), time.Millisecond*5)
picker.Go(sleepAndSend(ctx, 20, 1))
number := picker.Wait()
assert.Nil(t, number)
assert.Equal(t, number, getZero[int]())
assert.NotNil(t, picker.Error())
}
func TestPicker_WaitWithoutAutoCancel(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond*60)
defer cancel()
picker := WithoutAutoCancel(ctx)
trigger := false
picker.Go(sleepAndSend(ctx, 10, 1))
picker.Go(func() (interface{}, error) {
timer := time.NewTimer(time.Millisecond * time.Duration(30))
select {
case <-timer.C:
trigger = true
return 2, nil
case <-ctx.Done():
return nil, ctx.Err()
}
})
elm := picker.WaitWithoutCancel()
assert.NotNil(t, elm)
assert.Equal(t, elm.(int), 1)
elm = picker.Wait()
assert.True(t, trigger)
assert.Equal(t, elm.(int), 1)
func getZero[T any]() T {
var result T
return result
}

70
common/pool/alloc.go Normal file
View File

@ -0,0 +1,70 @@
package pool
// Inspired by https://github.com/xtaci/smux/blob/master/alloc.go
import (
"errors"
"math/bits"
"sync"
"github.com/sagernet/sing/common/buf"
)
var defaultAllocator = NewAllocator()
func init() {
buf.DefaultAllocator = defaultAllocator
}
// Allocator for incoming frames, optimized to prevent overwriting after zeroing
type Allocator struct {
buffers []sync.Pool
}
// NewAllocator initiates a []byte allocator for frames less than 65536 bytes,
// the waste(memory fragmentation) of space allocation is guaranteed to be
// no more than 50%.
func NewAllocator() *Allocator {
alloc := new(Allocator)
alloc.buffers = make([]sync.Pool, 17) // 1B -> 64K
for k := range alloc.buffers {
i := k
alloc.buffers[k].New = func() any {
return make([]byte, 1<<uint32(i))
}
}
return alloc
}
// Get a []byte from pool with most appropriate cap
func (alloc *Allocator) Get(size int) []byte {
if size <= 0 || size > 65536 {
return nil
}
bits := msb(size)
if size == 1<<bits {
return alloc.buffers[bits].Get().([]byte)[:size]
}
return alloc.buffers[bits+1].Get().([]byte)[:size]
}
// Put returns a []byte to pool for future use,
// which the cap must be exactly 2^n
func (alloc *Allocator) Put(buf []byte) error {
bits := msb(cap(buf))
if cap(buf) == 0 || cap(buf) > 65536 || cap(buf) != 1<<bits {
return errors.New("allocator Put() incorrect buffer size")
}
//nolint
//lint:ignore SA6002 ignore temporarily
alloc.buffers[bits].Put(buf)
return nil
}
// msb return the pos of most significant bit
func msb(size int) uint16 {
return uint16(bits.Len32(uint32(size)) - 1)
}

48
common/pool/alloc_test.go Normal file
View File

@ -0,0 +1,48 @@
package pool
import (
"math/rand"
"testing"
"github.com/stretchr/testify/assert"
)
func TestAllocGet(t *testing.T) {
alloc := NewAllocator()
assert.Nil(t, alloc.Get(0))
assert.Equal(t, 1, len(alloc.Get(1)))
assert.Equal(t, 2, len(alloc.Get(2)))
assert.Equal(t, 3, len(alloc.Get(3)))
assert.Equal(t, 4, cap(alloc.Get(3)))
assert.Equal(t, 4, cap(alloc.Get(4)))
assert.Equal(t, 1023, len(alloc.Get(1023)))
assert.Equal(t, 1024, cap(alloc.Get(1023)))
assert.Equal(t, 1024, len(alloc.Get(1024)))
assert.Equal(t, 65536, len(alloc.Get(65536)))
assert.Nil(t, alloc.Get(65537))
}
func TestAllocPut(t *testing.T) {
alloc := NewAllocator()
assert.NotNil(t, alloc.Put(nil), "put nil 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)), "put elem:65536 []bytes misbehavior")
assert.NotNil(t, alloc.Put(make([]byte, 65537)), "put elem:65537 []bytes misbehavior")
}
func TestAllocPutThenGet(t *testing.T) {
alloc := NewAllocator()
data := alloc.Get(4)
alloc.Put(data)
newData := alloc.Get(4)
assert.Equal(t, cap(data), cap(newData), "different cap while alloc.Get()")
}
func BenchmarkMSB(b *testing.B) {
for i := 0; i < b.N; i++ {
msb(rand.Int())
}
}

17
common/pool/buffer.go Normal file
View File

@ -0,0 +1,17 @@
package pool
import (
"bytes"
"sync"
)
var bufferPool = sync.Pool{New: func() any { return &bytes.Buffer{} }}
func GetBuffer() *bytes.Buffer {
return bufferPool.Get().(*bytes.Buffer)
}
func PutBuffer(buf *bytes.Buffer) {
buf.Reset()
bufferPool.Put(buf)
}

View File

@ -1,15 +1,21 @@
package pool
import (
"sync"
)
const (
// io.Copy default buffer size is 32 KiB
// but the maximum packet size of vmess/shadowsocks is about 16 KiB
// so define a buffer of 20 KiB to reduce the memory of each TCP relay
bufferSize = 20 * 1024
RelayBufferSize = 20 * 1024
// RelayBufferSize uses 20KiB, but due to the allocator it will actually
// request 32Kib. Most UDPs are smaller than the MTU, and the TUN's MTU
// set to 9000, so the UDP Buffer size set to 16Kib
UDPBufferSize = 16 * 1024
)
// BufPool provide buffer for relay
var BufPool = sync.Pool{New: func() interface{} { return make([]byte, bufferSize) }}
func Get(size int) []byte {
return defaultAllocator.Get(size)
}
func Put(buf []byte) error {
return defaultAllocator.Put(buf)
}

View File

@ -5,13 +5,13 @@ import (
)
// Queue is a simple concurrent safe queue
type Queue struct {
items []interface{}
type Queue[T any] struct {
items []T
lock sync.RWMutex
}
// Put add the item to the queue.
func (q *Queue) Put(items ...interface{}) {
func (q *Queue[T]) Put(items ...T) {
if len(items) == 0 {
return
}
@ -22,9 +22,9 @@ func (q *Queue) Put(items ...interface{}) {
}
// Pop returns the head of items.
func (q *Queue) Pop() interface{} {
func (q *Queue[T]) Pop() T {
if len(q.items) == 0 {
return nil
return GetZero[T]()
}
q.lock.Lock()
@ -35,9 +35,9 @@ func (q *Queue) Pop() interface{} {
}
// Last returns the last of item.
func (q *Queue) Last() interface{} {
func (q *Queue[T]) Last() T {
if len(q.items) == 0 {
return nil
return GetZero[T]()
}
q.lock.RLock()
@ -47,8 +47,8 @@ func (q *Queue) Last() interface{} {
}
// Copy get the copy of queue.
func (q *Queue) Copy() []interface{} {
items := []interface{}{}
func (q *Queue[T]) Copy() []T {
items := []T{}
q.lock.RLock()
items = append(items, q.items...)
q.lock.RUnlock()
@ -56,7 +56,7 @@ func (q *Queue) Copy() []interface{} {
}
// Len returns the number of items in this queue.
func (q *Queue) Len() int64 {
func (q *Queue[T]) Len() int64 {
q.lock.Lock()
defer q.lock.Unlock()
@ -64,8 +64,13 @@ func (q *Queue) Len() int64 {
}
// New is a constructor for a new concurrent safe queue.
func New(hint int64) *Queue {
return &Queue{
items: make([]interface{}, 0, hint),
func New[T any](hint int64) *Queue[T] {
return &Queue[T]{
items: make([]T, 0, hint),
}
}
func GetZero[T any]() T {
var result T
return result
}

View File

@ -5,26 +5,27 @@ import (
"time"
)
type call struct {
type call[T any] struct {
wg sync.WaitGroup
val interface{}
val T
err error
}
type Single struct {
type Single[T any] struct {
mux sync.Mutex
last time.Time
wait time.Duration
call *call
result *Result
call *call[T]
result *Result[T]
}
type Result struct {
Val interface{}
type Result[T any] struct {
Val T
Err error
}
func (s *Single) Do(fn func() (interface{}, error)) (v interface{}, err error, shared bool) {
// Do single.Do likes sync.singleFlight
func (s *Single[T]) Do(fn func() (T, error)) (v T, err error, shared bool) {
s.mux.Lock()
now := time.Now()
if now.Before(s.last.Add(s.wait)) {
@ -32,24 +33,31 @@ func (s *Single) Do(fn func() (interface{}, error)) (v interface{}, err error, s
return s.result.Val, s.result.Err, true
}
if call := s.call; call != nil {
if callM := s.call; callM != nil {
s.mux.Unlock()
call.wg.Wait()
return call.val, call.err, true
callM.wg.Wait()
return callM.val, callM.err, true
}
call := &call{}
call.wg.Add(1)
s.call = call
callM := &call[T]{}
callM.wg.Add(1)
s.call = callM
s.mux.Unlock()
call.val, call.err = fn()
call.wg.Done()
callM.val, callM.err = fn()
callM.wg.Done()
s.mux.Lock()
s.call = nil
s.result = &Result{call.val, call.err}
s.result = &Result[T]{callM.val, callM.err}
s.last = now
return call.val, call.err, false
s.mux.Unlock()
return callM.val, callM.err, false
}
func NewSingle(wait time.Duration) *Single {
return &Single{wait: wait}
func (s *Single[T]) Reset() {
s.last = time.Time{}
}
func NewSingle[T any](wait time.Duration) *Single[T] {
return &Single[T]{wait: wait}
}

View File

@ -6,26 +6,27 @@ import (
"time"
"github.com/stretchr/testify/assert"
"go.uber.org/atomic"
)
func TestBasic(t *testing.T) {
single := NewSingle(time.Millisecond * 30)
single := NewSingle[int](time.Millisecond * 30)
foo := 0
shardCount := 0
call := func() (interface{}, error) {
shardCount := atomic.NewInt32(0)
call := func() (int, error) {
foo++
time.Sleep(time.Millisecond * 5)
return nil, nil
return 0, nil
}
var wg sync.WaitGroup
const n = 10
const n = 5
wg.Add(n)
for i := 0; i < n; i++ {
go func() {
_, _, shard := single.Do(call)
if shard {
shardCount++
shardCount.Inc()
}
wg.Done()
}()
@ -33,21 +34,36 @@ func TestBasic(t *testing.T) {
wg.Wait()
assert.Equal(t, 1, foo)
assert.Equal(t, 9, shardCount)
assert.Equal(t, int32(4), shardCount.Load())
}
func TestTimer(t *testing.T) {
single := NewSingle(time.Millisecond * 30)
single := NewSingle[int](time.Millisecond * 30)
foo := 0
call := func() (interface{}, error) {
callM := func() (int, error) {
foo++
return nil, nil
return 0, nil
}
single.Do(call)
_, _, _ = single.Do(callM)
time.Sleep(10 * time.Millisecond)
_, _, shard := single.Do(call)
_, _, shard := single.Do(callM)
assert.Equal(t, 1, foo)
assert.True(t, shard)
}
func TestReset(t *testing.T) {
single := NewSingle[int](time.Millisecond * 30)
foo := 0
callM := func() (int, error) {
foo++
return 0, nil
}
_, _, _ = single.Do(callM)
single.Reset()
_, _, _ = single.Do(callM)
assert.Equal(t, 2, foo)
}

View File

@ -0,0 +1,19 @@
package sockopt
import (
"net"
"syscall"
)
func UDPReuseaddr(c *net.UDPConn) (err error) {
rc, err := c.SyscallConn()
if err != nil {
return
}
rc.Control(func(fd uintptr) {
err = syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, syscall.SO_REUSEADDR, 1)
})
return
}

View File

@ -0,0 +1,11 @@
//go:build !linux
package sockopt
import (
"net"
)
func UDPReuseaddr(c *net.UDPConn) (err error) {
return
}

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