Compare commits

..

375 Commits

Author SHA1 Message Date
Meta Gowork
584b81e507 [Chore] workflows 2022-04-02 19:02:43 +08:00
Meta Gowork
6596db7257 [Chore] workflows 2022-04-02 19:01:28 +08:00
fishg
908ca20afa fix: dns over proxy may due to cancel request, but proxy live status is fine 2022-04-02 18:24:11 +08:00
Meta Gowork
88e4b3575e [Chore] fallback dependency 2022-03-31 00:26:01 +08:00
Meta Gowork
559b3ff9f3 [Fix] VLESS http conn with tls false
[Chore] Upgrade Dependencies
2022-03-31 00:08:43 +08:00
MetaCubeX
127634028d Merge remote-tracking branch 'Meta/Alpha' into Alpha 2022-03-30 13:19:05 +08:00
MetaCubeX
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
Skyxim
591ee119c2 docs: warning 2022-03-30 13:05:46 +08:00
MetaCubeX
5b03cc56e7 Merge remote-tracking branch 'Clash-dev/dev' into Alpha 2022-03-30 12:41:16 +08:00
yaling888
9ff1f5530e Feature: Trojan XTLS 2022-03-30 00:15:39 +08:00
yaling888
b3ea2ff8b6 Chore: adjust VLESS 2022-03-29 23:50:41 +08:00
Meta
c4216218c8 Merge pull request #24 from MarksonHon/patch-2
Fix systemd service
2022-03-29 14:53:02 +08:00
Markson Hon
63840b3358 Fix systemd service 2022-03-29 14:50:12 +08:00
yaling888
131e9d38b6 Fix: Vless UDP 2022-03-29 07:24:11 +08:00
yaling888
56e2c172e1 Chore: adjust tun_wireguard cache buffer 2022-03-29 07:24:11 +08:00
yaling888
b3b7a393f8 Chore: merge branch 'ogn-dev' into with-tun 2022-03-29 07:22:52 +08:00
Skyxim
045dd0589b fix: classical missing count 2022-03-28 21:04:50 +08:00
Meta
705311b70e [Chore]修改workflows 2022-03-28 20:52:09 +08:00
Meta
55ce40fbd1 [Chore]升级项目依赖
[Chore]隐藏TUN模式在system堆栈启动时弹窗
2022-03-28 20:44:52 +08:00
Meta
07fda93111 [Chore]升级项目依赖
[Chore]隐藏TUN模式在system堆栈启动时弹窗
2022-03-28 19:48:32 +08:00
Meta
012e044c54 [Chore]完成调试workflows 2022-03-28 19:02:51 +08:00
Meta
b323315583 [Chore]调试workflows 2022-03-28 18:58:23 +08:00
Meta
4c10d6e212 [Chore]调试workflows 2022-03-28 18:54:00 +08:00
Meta
ece3bb360a [Chore]调试workflows 2022-03-28 18:52:19 +08:00
Meta
5a7b9bdf45 [Chore]调试workflows 2022-03-28 18:49:24 +08:00
Meta
028ecb70c5 [Chore]调整workflows流程2 2022-03-28 18:44:27 +08:00
Meta
4e0b22f42d [Chore]调整workflows流程2 2022-03-28 18:41:30 +08:00
Meta
dbd27ef910 [Chore]调整workflows流程 2022-03-28 17:07:11 +08:00
Meta
ffff1418f2 [Fixed]尝试修复PASS空指针问题
[Chore]调整workflows测试
2022-03-28 16:36:34 +08:00
Guowei Zhao
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
Meta
64a5fd02da Merge remote-tracking branch 'tun/with-tun' into Alpha 2022-03-28 10:51:59 +08:00
yaling888
8df8f8cb08 Chore: adjust gVisor stack 2022-03-28 03:25:55 +08:00
yaling888
fe76cbf31c Chore: code style 2022-03-28 03:18:51 +08:00
yaling888
7e2c6e5188 Chore: adjust HealthCheck at first check 2022-03-28 00:46:44 +08:00
yaling888
4502776513 Refactor: MainResolver 2022-03-28 00:44:13 +08:00
MetaCubeX
611ce5f5f1 [commit]
[Feat] add Pass type for support temporary skip rule set
2022-03-27 23:44:51 +08:00
yaling888
9bab2c504e Chore: regenerate protoc file 2022-03-27 07:12:12 +08:00
yaling888
94b3c7e99a Chore: merge branch 'ogn-dev' into with-tun 2022-03-27 00:27:05 +08:00
Skyxim
0a0b8074f4 refactor: rule-set and its provider 2022-03-26 20:27:41 +08:00
Meta
f66c3b6f86 [Bilud]
正常编译
2022-03-26 16:39:50 +08:00
Meta
a3d49d1ed4 Merge remote-tracking branch 'dev/dev' into Alpha 2022-03-26 16:27:17 +08:00
Meta
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
Meta
24583009c4 Merge remote-tracking branch 'tun/with-tun' into Alpha 2022-03-25 14:20:05 +08:00
yaling888
4a4b1bdb83 Chore: adjust tun RelayDnsPacket 2022-03-25 04:09:11 +08:00
yaling888
c6efa74a6b Fix: udp 4In6 of tun system stack 2022-03-25 03:42:46 +08:00
MetaCubeX
a593d68c42 build test 2022-03-24 23:42:49 +08:00
Adlyq
520657e953 [Fix] use direct to update http providers when proxy 寄 2022-03-24 12:34:45 +08:00
Adlyq
6c64164bee [skip ci] [Fix] ban auto set iptables when tun is enabled 2022-03-23 20:37:46 +08:00
Adlyq
9b4ddbed2c [skip ci] [Pre] avoid npe 2022-03-23 13:48:21 +08:00
Adlyq
79d984ee8e [Fix] url-test npe 2022-03-23 13:29:51 +08:00
MetaCubeX
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
Adlyq
f19b67fe9d bypass support for auto-iptables 2022-03-23 11:36:13 +08:00
Adlyq
91e83ea955 delete useless field 2022-03-23 10:18:26 +08:00
MetaCubeX
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
yaling888
ef915c94dc Feature: flush fakeip pool 2022-03-23 01:05:43 +08:00
Adlyq
4cc661920e [Fix] redir-host use host not ip 2022-03-22 23:31:23 +08:00
yaling888
f4312cfa5a Chore: adjust the signal 2022-03-22 18:40:33 +08:00
yaling888
ac4cde1411 Refactor: iptables auto config, disabled by default 2022-03-22 05:38:42 +08:00
Meta
b5f6f26de4 Update version.go
[BUILD TEST]
2022-03-22 01:39:00 +08:00
Meta
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
Meta
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
admin
e0040b7e5d [Fix] do not monitor when auto-iptables is false 2022-03-21 20:29:07 +08:00
admin
3beb71b6e1 auto change interface for tproxy 2022-03-21 19:51:27 +08:00
admin
668d29d91f init sequence adjustment 2022-03-21 19:47:21 +08:00
admin
5386c3903d delete useless code 2022-03-21 18:09:36 +08:00
admin
6a4d2b3368 Change type conversion method 2022-03-21 12:34:32 +08:00
admin
d9d8507c8f [Fix] skip when country code not found in GeoIP.dat 2022-03-21 12:24:39 +08:00
yaling888
2c0890854e Fix: retry create TUN on Windows 2022-03-20 21:27:33 +08:00
yaling888
bac04ab54b Merge branch 'ogn-dev' into with-tun 2022-03-20 21:26:25 +08:00
Dreamacro
8c9e0b3884 Chore: use GOAMD64 v1 on build docker image 2022-03-20 11:32:18 +08:00
yaling888
fc8092f7cc Fix: wintun dns address 2022-03-20 04:19:48 +08:00
MetaCubeX
5b7f46bc97 [skip ci][内容]
1.调整部分代码
2022-03-20 02:39:48 +08:00
MetaCubeX
d1838f663e Merge remote-tracking branch 'yaling888/with-tun' into Alpha
# Conflicts:
#	listener/tun/tun_adapter.go
2022-03-19 22:37:51 +08:00
MetaCubeX
2d1c031ce0 [skip ci][内容]
1.修复部分空指针问题
2.修改go.mod
2022-03-19 22:28:28 +08:00
MetaCubeX
e67f94b87a [内容]
同步至最新v1.10.0
2022-03-19 15:01:49 +08:00
MetaCubeX
2df890c4ee Merge remote-tracking branch 'clash/dev' into Alpha
# Conflicts:
#	Makefile
2022-03-19 14:53:47 +08:00
Kr328
30d4668008 Chore: fix typo (#2033) 2022-03-19 13:58:51 +08:00
Dreamacro
02333a859a Chore: split amd64 v3 to special release 2022-03-19 13:42:06 +08:00
risetechlab
f9cc1cc363 Fix: routing-mark option doesn't work on proxies (#2028) 2022-03-19 13:29:30 +08:00
MetaCubeX
520256365e [内容]
1.wintun.dll 0.14.1
2022-03-19 01:54:21 +08:00
MetaCubeX
9270d3c475 [内容]
1.autoIptables 开关
2.go.mod 调整
3.processName 调整
4.makefile 调整
5.Tun模块 部分代码调整
2022-03-19 01:11:27 +08:00
Meta
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
admin
39de5d58c8 Only prompt when interface cannot be found 2022-03-18 17:41:06 +08:00
yaling888
7b7abf6973 Feature: auto detect interface if switch network 2022-03-18 17:03:50 +08:00
Meta
a38f30ec3b Merge pull request #18 from Adlyq/Alpha
[Fix] Process name display for Android
2022-03-18 13:20:35 +08:00
admin
2ea92d70f9 Merge remote-tracking branch 'upstream/Alpha' into Alpha 2022-03-18 12:38:16 +08:00
yaling888
8e5f01597e Fix: build 2022-03-18 05:21:28 +08:00
yaling888
546f2fa739 Chore: make fake ip pool start with the third ip 2022-03-18 05:17:47 +08:00
MetaCubeX
ea2e715da9 Merge remote-tracking branch 'origin/Alpha' into Alpha
# Conflicts:
#	go.mod
#	go.sum
2022-03-18 02:36:09 +08:00
MetaCubeX
1350330fe0 1.fix module package
2.fix govet error
2022-03-18 02:35:15 +08:00
MetaCubeX
317797acc8 1.fix module package
2.fix govet error
2022-03-18 01:25:59 +08:00
MetaCubeX
8766764d49 fix 2022-03-18 00:40:39 +08:00
admin
b8d48e1618 Merge remote-tracking branch 'upstream/Alpha' into Alpha 2022-03-18 00:33:27 +08:00
MetaCubeX
f972d1fa58 update 2022-03-18 00:27:48 +08:00
MetaCubeX
df78ba8fa6 update 2022-03-18 00:24:38 +08:00
admin
0c83575302 Merge remote-tracking branch 'upstream/Alpha' into Alpha 2022-03-18 00:10:37 +08:00
MetaCubeX
e9151bc43f update 2022-03-17 23:57:58 +08:00
admin
68345b6a19 Merge remote-tracking branch 'upstream/Alpha' into Alpha 2022-03-17 23:40:51 +08:00
MetaCubeX
435bee0ca2 update 2022-03-17 23:24:07 +08:00
admin
92d169ca81 [Fix] Process name display for Android 2022-03-17 20:31:16 +08:00
MetaCubeX
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
Meta
c503e44324 Merge pull request #17 from Adlyq/Alpha
[Fix] Parse
2022-03-17 12:28:45 +08:00
admin
ce509295c0 [Fix] Parse 2022-03-17 12:26:43 +08:00
admin
f671d6a1fd [Fix] Parse 2022-03-17 12:23:50 +08:00
yaling888
8d0ae4284d Chore: use gateway address of fake ip pool as the TUN device address 2022-03-17 07:41:18 +08:00
MetaCubeX
e194efcecb Migration: go 1.18 2022-03-17 01:51:28 +08:00
MetaCubeX
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
MetaCubeX
c791044ddf Merge remote-tracking branch 'origin/Alpha' into Alpha 2022-03-17 00:12:26 +08:00
MetaCubeX
dc2abe6eeb [Build test] 1.18
[Updata] wintun.dll
2022-03-17 00:12:11 +08:00
MetaCubeX
1071e3f4a3 [Build test] 1.18
[Updata] wintun.dll
2022-03-17 00:02:22 +08:00
MetaCubeX
acc249495d [Build test] 1.18 2022-03-16 23:30:29 +08:00
MetaCubeX
5a2cc9a36f [Fix] 优化geodata初始化逻辑 2022-03-16 23:09:05 +08:00
MetaCubeX
1cc6cfab9c [Fix] 优化geodata初始化逻辑 2022-03-16 23:02:16 +08:00
MetaCubeX
0183d752a0 [Fix] 优化geodata初始化逻辑 2022-03-16 22:55:18 +08:00
yaling888
b8d635a4b3 Migration: go 1.18 2022-03-16 22:00:20 +08:00
MetaCubeX
2f24e49ff6 [build test] 1.18 2022-03-16 21:47:00 +08:00
yaling888
346d817dba Chore: Merge branch 'ogn-dev' into with-tun 2022-03-16 20:16:30 +08:00
yaling888
3a9bbf6c73 Fix: should keep alive in tcp relay 2022-03-16 18:17:28 +08:00
MetaCubeX
016862f7a5 [build test]1.18 2022-03-16 17:54:44 +08:00
MetaCubeX
c3df768f79 [build test] 2022-03-16 17:33:08 +08:00
MetaCubeX
0f2123179a [build test] 2022-03-16 17:29:09 +08:00
Dreamacro
fb7d340233 Fix: docker build makefile 2022-03-16 12:13:59 +08:00
Dreamacro
6a661bff0c Migration: go 1.18 2022-03-16 12:10:13 +08:00
MetaCubeX
1034780e8e [build test] 2022-03-16 00:43:08 +08:00
MetaCubeX
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
MetaCubeX
c85305ead8 [Skip CI] 2022-03-15 22:25:33 +08:00
MetaCubeX
3e89bee524 [Skip CI] 2022-03-15 11:47:42 +08:00
suyar
d1dd21417b Feature: add tzdata to Dockerfile (#2027)
Co-authored-by: suyaqi <suyaqi@wy.net>
2022-03-15 11:30:52 +08:00
yaling888
9ff32d9e29 Chore: use slice instead of map for system stack udp receiver queue 2022-03-15 05:19:29 +08:00
yaling888
d486ee467a Fix: test 2022-03-15 03:39:45 +08:00
yaling888
20b66d9550 Style: code style 2022-03-15 02:55:06 +08:00
yaling888
5abd03e241 Fix: exclude the broadcast address to fake ip pool 2022-03-15 02:43:40 +08:00
MetaCubeX
68fccfacc0 [Skip CI] 2022-03-15 02:20:19 +08:00
MetaCubeX
cf52fbed65 [Skip CI] 2022-03-15 02:06:57 +08:00
Skyxim
a924819fbf [Fixed] rule-set of classical allow adding GEOIP 2022-03-14 21:48:36 +08:00
Skyxim
13c82754ff [Fixed] show rule count when parse failed 2022-03-14 21:43:58 +08:00
Skyxim
002163f07b [Fixed] memory leak 2022-03-13 18:35:55 +08:00
Skyxim
9c5b184db6 [Fixed] handle network protocol[0] panic (not pretty) 2022-03-13 18:34:49 +08:00
yaling888
359f8ffca3 Fix: should use the correct gateway for TUN system stack 2022-03-13 17:48:43 +08:00
yaling888
46b9a1092d Chore: embed the RuleExtra into Base 2022-03-13 01:22:05 +08:00
yaling888
8fbf93ccc8 Chore: Merge branch 'ogn-dev' into with-tun 2022-03-13 01:15:35 +08:00
Kr328
b866f06414 Chore: move find connection process to tunnel (#2016) 2022-03-12 19:07:53 +08:00
yaling888
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
Kr328
9683c297a7 Chore: add more details to process resolving (#2017) 2022-03-09 13:41:50 +08:00
yaling888
8333815e95 Chore: refactor TUN 2022-03-09 05:08:35 +08:00
yaling888
d49871224c Fix: should only resolve local process name 2022-03-09 00:32:21 +08:00
yaling888
ba7bcce895 Chore: code style 2022-03-09 00:32:21 +08:00
yaling888
71e002c2ef Merge branch 'ogn-dev' into with-tun 2022-03-09 00:30:38 +08:00
Dreamacro
f6c7281bb7 Chore: update github action workflow 2022-03-06 21:48:37 +08:00
Kr328
83bfe521b1 Fix: should split linux process name with space (#2008) 2022-03-05 18:25:16 +08:00
Mazeorz
3ab784dd80 Merge pull request #16 from Dabrit/test
[Skip CI]README Improvements
2022-03-05 09:03:25 +08:00
rorrim
f1c4d85eb3 Update README.md 2022-03-05 01:44:48 +08:00
Dabrit
b9fc393f95 Naming edited 2022-03-05 01:33:11 +08:00
Meta
557347d366 Merge pull request #15 from Dabrit/test
Optimize reading experience of linux users
2022-03-04 23:25:02 +08:00
Dabrit
a7f3b85200 Edit username to adapt Linux username naming rule 2022-03-04 22:36:15 +08:00
Skyxim
7550067fde [Fixed] skip maybe invaild ip data packet 2022-03-04 22:32:33 +08:00
Skyxim
076a0840bf [Fixed] domian or ipcidr is used before initialization 2022-03-04 22:32:25 +08:00
Skyxim
5ebcc526de [Fixed] match not some ip in ipcidr provider 2022-03-04 22:32:25 +08:00
Dabrit
3772ad8ddb Revise mismatching targets from README. 2022-03-04 22:22:49 +08:00
yaling888
17c53b92b9 Fix: iptables routing mark init 2022-03-03 05:02:17 +08:00
yaling888
0b9022b868 Chore: update dependencies 2022-03-03 04:23:03 +08:00
yaling888
5e0d4930cb Merge branch 'ogn-dev' into with-tun 2022-03-03 04:17:00 +08:00
Dreamacro
b52d0c16e9 Chore: vmess test remove all alterid 2022-02-27 18:00:04 +08:00
Meta
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
Meta
49e25f502f Merge pull request #11 from ttyykpe/patch-1
Makefile add android-armv8
2022-02-27 00:33:58 +08:00
admin
06942c67fd Fix the filter under proxy-group to filter other groups 2022-02-23 16:17:29 +08:00
yaling888
705e5098ab Chore: use SIMD for AMD64 and ARM64 system stack checksum 2022-02-23 14:51:04 +08:00
yaling888
ac5c57ecef Chore: compatible with VMESS WS older version configurations 2022-02-23 14:21:53 +08:00
yaling888
cd3b139c3f Chore: use "-m mark --mark" instead of "-m owner --uid-owner" 2022-02-23 14:19:59 +08:00
yaling888
592b6a785e Fix: find process name by UDP network on macOS 2022-02-23 14:04:47 +08:00
yaling888
2f234cf6bc Feature: process condition for rules 2022-02-23 14:01:53 +08:00
Kaming Chan
132a6a6a2f Fix: listener tcp keepalive & reuse net.BufferedConn (#1987) 2022-02-23 11:22:46 +08:00
yaling888
d876d6e74c Feature: resolve ip with a proxy adapter 2022-02-23 02:38:50 +08:00
yaling888
b192238699 Merge from remote branch 2022-02-23 01:00:27 +08:00
yaling888
3b2ec3d880 Chore: upgrade gvisor 2022-02-22 22:30:41 +08:00
ttyykpe
9259c9f3ff Makefile add android-armv8 2022-02-21 18:04:38 +08:00
Dreamacro
03e4b5d525 Chore: use golangci-lint config file 2022-02-19 00:08:51 +08:00
Dreamacro
a0221bf897 Fix: routing-mark should effect on root 2022-02-17 14:23:47 +08:00
Mazeorz
37cf166d14 Merge pull request #10 from Adlyq/Alpha
Full regexp support
2022-02-16 23:10:07 +08:00
admin
27292dac0c Replace the regular implementation of the filter for proxy-providers and proxy-groups with regex2 2022-02-16 22:18:05 +08:00
Clash-Mini
847c91503b [build] 2022-02-06 05:08:11 +08:00
Clash-Mini
ca8ed0a01b [Fix]GeoSite.dat initial in logic rule 2022-02-06 04:41:34 +08:00
Mazeorz
46dc262e8e 合并拉取请求 #9
add the doc of local build
2022-02-06 04:34:08 +08:00
Clash-Mini
7465eaafa1 [Fix]GeoSite.dat initial in logic rule 2022-02-06 04:30:54 +08:00
leon Qiu
d70cfefde7 add the doc of local build 2022-02-06 04:02:26 +08:00
Meta
52c37f7140 Merge pull request #8 from qzi/Dev
add trojan xtls sample
2022-02-06 03:52:41 +08:00
leon Qiu
180bce2940 add trojan xtls sample 2022-02-06 03:37:40 +08:00
Clash-Mini
4a446c4e31 [build] 2022-02-06 01:59:35 +08:00
Clash-Mini
d7f5e8d3de [Skip CI] 2022-02-06 00:56:13 +08:00
Clash-Mini
0a180eeb40 忽略geosite文件大小写 2022-02-06 00:51:37 +08:00
Clash-Mini
7ff48ea42d [build] 2022-02-05 22:05:20 +08:00
Clash-Mini
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
Clash-Mini
2f6f9ebc2e Merge branch 'Dev' into Meta
# Conflicts:
#	config/config.go
2022-02-05 19:30:12 +08:00
Clash-Mini
28a1475f66 [FEAT] Add geodata loader mode switch 2022-02-05 02:42:49 +08:00
Clash-Mini
c28f42d823 [FEAT] Add geodata loader mode switch 2022-02-05 00:51:06 +08:00
Clash-Mini
2bf34c766e [Feat]
support trojan xtls
change geodataloader mode as memconservative
2022-02-04 23:33:36 +08:00
Clash-Mini
35b19c3d7f Merge branch 'Dev' into Feature
# Conflicts:
#	Makefile
2022-02-04 18:44:35 +08:00
Skyxim
90e6ed4612 [Fixed] Fixed clash process name is Clash.Meta 2022-02-04 17:38:06 +08:00
Skyxim
ae5a790510 [Fixed] Abnormal rule when host is ip addr 2022-02-04 17:38:06 +08:00
Clash-Mini
3b277aa8ec [Feat]
update gvisor
Chore: use "-m mark --mark" instead of "-m owner --uid-owner"
2022-02-04 06:11:24 +08:00
Clash-Mini
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
Clash-Mini
776728fb30 [Feat]
update gvisor
Chore: use "-m mark --mark" instead of "-m owner --uid-owner"
2022-02-04 04:47:40 +08:00
Clash-Mini
a732e1a603 Merge remote-tracking branch 'clash/dev' into Dev 2022-02-04 02:40:15 +08:00
Clash-Mini
1cdaf782ba Merge remote-tracking branch 'clash/dev' into Feature 2022-02-04 02:38:32 +08:00
yaling888
f1157d0a09 Chore: use "-m mark --mark" instead of "-m owner --uid-owner" 2022-02-02 21:59:44 +08:00
yaling888
f376409041 Chore: upgrade gvisor 2022-02-01 02:00:10 +08:00
yaling888
45b3afdd33 Fix: new version golangci-lint check 2022-01-30 01:49:27 +08:00
yaling888
875fdb3a5b Revert "Chore: upgrade gvisor version"
This reverts commit d633e3d96e.
2022-01-30 00:45:02 +08:00
yaling888
25e115d042 Feature: process condition for rules 2022-01-28 22:52:35 +08:00
yaling888
d633e3d96e Chore: upgrade gvisor version 2022-01-28 22:42:58 +08:00
yaling888
6e9d837a7d Merge from remote branch 2022-01-28 19:51:40 +08:00
Clash-Mini
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
Clash-Mini
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
Dreamacro
b1a639feae Fix: domain trie search 2022-01-26 22:28:13 +08:00
Skyxim
76dccebbf6 github action build config 2022-01-26 21:35:18 +08:00
Skyxim
cd5b735973 [Refactor] logic rule parse 2022-01-26 21:34:49 +08:00
Clash-Mini
9e4e1482d9 [chore] Replace Country.mmdb with GeoIP.dat 2022-01-26 12:01:14 +08:00
Skyxim
9974fba56e Update dev.yml 2022-01-25 21:59:48 +08:00
Skyxim
4bd5764c4e Update Makefile 2022-01-25 21:47:11 +08:00
Clash-Mini
deeab8b45f [test] dev build 2022-01-25 21:34:06 +08:00
Clash-Mini
af30664c51 [test] dev build 2022-01-25 21:12:49 +08:00
Clash-Mini
6962f0b7e1 [update] dev build 2022-01-25 20:54:56 +08:00
Clash-Mini
6e5859d1bf [update] dev build 2022-01-25 20:53:07 +08:00
Skyxim
87ca93b979 Update build.yaml 2022-01-25 20:40:03 +08:00
Skyxim
11052d8f77 github action add build
(cherry picked from commit bdec838673767977c14191861ac1b9a8291e2ffc)
2022-01-25 20:33:30 +08:00
Clash-Mini
a5ce62db33 Merge branch 'clash-dev' into Dev 2022-01-25 15:05:24 +08:00
Skyxim
2f8e575308 [Fixed] modified RULE-SET supported rule 2022-01-23 18:35:48 +08:00
Skyxim
62b70725ef [Fixed] GEOSITE rule load fail 2022-01-23 18:27:44 +08:00
Skyxim
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
Skyxim
03b956b7a3 [Fixed] auto-route support use ip route 2022-01-22 13:24:31 +08:00
Skyxim
e5c99cbee7 modify gitignore 2022-01-21 22:39:00 +08:00
Skyxim
58a47e1835 [Style] clear unless notes 2022-01-21 22:38:28 +08:00
Skyxim
daf83eb6f7 [Fixed] select group crash 2022-01-21 22:38:02 +08:00
Meta
bb68b59c9a Merge pull request #7 from CHIZI-0618/DnsHijack
Fix DnsHijack default value bug.
2022-01-21 18:27:26 +08:00
CHIZI-0618
c3cfa3d6cd Fix DnsHijack default value bug. 2022-01-21 18:11:21 +08:00
Skyxim
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
Kr328
cfe7354c07 Improve: change provider file modify time when updated (#1918) 2022-01-18 13:32:47 +08:00
yaling888
56c38890f9 Merge from remote branch[ssh] 2022-01-18 10:05:06 +08:00
yaling888
daae846db3 Merge from remote branch 2022-01-18 09:51:20 +08:00
thank243
9732efe938 Fix: tls handshake requires a timeout (#1893) 2022-01-15 19:33:21 +08:00
Skyxim
ee6c1871a9 [Refactor] lazy loading geosite.bat 2022-01-11 22:17:24 +08:00
Digital Pencil
8f3385bbb6 Feature: support snell v3 (#1884) 2022-01-10 20:24:20 +08:00
Skyxim
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
Skyxim
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
Skyxim
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
Skyxim
7f0368da66 [Style] Adjust delete routes on macos 2022-01-08 16:55:02 +08:00
Skyxim
4f1b227ca2 [Style] Positive health check 2022-01-08 09:23:49 +08:00
Skyxim
16abba385a [Style] Adjust the routing table of tun on mac 2022-01-07 22:40:05 +08:00
Skyxim
75b5f633cd [Fixed] Positive health check multithreading is not safe 2022-01-07 12:58:40 +08:00
Skyxim
8ae68552a6 [Fixed] Stupid mistakes 2022-01-06 10:49:50 +08:00
Skyxim
d35d6c9ac9 [Fixed] Stupid mistakes 2022-01-06 10:49:26 +08:00
Skyxim
a832cfdb65 [Fixed] compatible cfw 2022-01-05 19:28:54 +08:00
Clash-Mini
951a5a0eb5 [update]readme 2022-01-05 18:45:32 +08:00
Clash-Mini
89609cc4a2 [update]readme 2022-01-05 17:04:56 +08:00
Clash-Mini
bfb976bbdc [test]Add name filter to proxy group 2022-01-05 12:19:49 +08:00
Dreamacro
d237b041b3 Fix: ignore empty dns server error 2022-01-05 11:41:31 +08:00
Clash-Mini
a15d2535f1 升级版本号 2022-01-05 11:41:17 +08:00
Clash-Mini
610c79570a make tun config compatible with premium 2022-01-05 11:24:00 +08:00
Clash-Mini
051c81518c make tun config compatible with premium 2022-01-05 01:56:35 +08:00
Clash-Mini
0209efd423 Revert "make tun config compatible with premium"
This reverts commit ba6fdd2962.
2022-01-05 01:56:05 +08:00
Clash-Mini
ba6fdd2962 make tun config compatible with premium 2022-01-05 01:50:43 +08:00
yaling888
c14dd79e69 Merge from remote branch 2022-01-05 01:46:37 +08:00
Clash-Mini
9475799615 make tun config compatible with premium 2022-01-05 00:33:42 +08:00
Maze.tsz
14917c8af1 merge clash 1.9.0 2022-01-04 17:58:50 +08:00
Maze.tsz
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
Dreamacro
3cb87e083c Fix: duplicate provider err typo 2022-01-03 17:21:27 +08:00
Dreamacro
8c6d0c6757 Chore: fix docker dependencies security warning 2022-01-02 11:15:40 +08:00
Dreamacro
cb95326aca Chore: update dependencies 2022-01-02 01:15:49 +08:00
HamsterReserved
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
Dreamacro
204a72bbd3 Chore: remove forward compatible code 2022-01-02 00:48:57 +08:00
Maze.tsz
013b839678 [Fix] Linux Tun 2021-12-27 07:09:45 +08:00
Maze.tsz
a06382cebc [test] 2021-12-27 06:44:17 +08:00
Maze.tsz
ebc3f36236 [fix]autoIptables 2021-12-27 03:29:14 +08:00
Maze.tsz
e2a0437685 [fix] 2021-12-27 03:16:48 +08:00
Skyxim
82c8e02d02 [Style] Add User-Agent for provider request 2021-12-26 22:26:53 +08:00
Kr328
7267c58913 Chore: ReCreate* do side effect job (#1849) 2021-12-26 22:08:53 +08:00
Skyxim
a210ec4197 [Feature] 添加unified-delay boolean 控制延迟测试,默认为false,当设置true时忽略握手延迟,将统一延迟结果,从而利于不同协议的url-test 2021-12-26 21:20:41 +08:00
Kr328
14ae87fcd0 Chore: remove reduce regex compile (#1855) 2021-12-26 20:47:12 +08:00
Clash-Mini
0b72395704 Merge pull request #5 from xsxun/patch-1
Update vless.go, fix udp blocked
2021-12-20 18:13:43 +08:00
xsxun
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
Skyxim
69aef9cec0 [Fixed] Configure tun interface on linux 2021-12-11 22:34:45 +08:00
Skyxim
9e44e21406 [Fixed] launch resolver an enhancer when tun mode 2021-12-09 23:00:54 +08:00
Skyxim
b0fdd8dc47 [Fixed] Add retry to open tun 2021-12-09 22:52:32 +08:00
Skyxim
e92ef587bb [Fixed] The array may be sent out of bounds 2021-12-09 22:52:32 +08:00
yaling888
5657aa50cf Merge from remote branch 2021-12-09 21:38:24 +08:00
Maze.tsz
7d17d53a8f [readme] 2021-12-09 17:54:53 +08:00
bobo liu
78e105f3b2 Chore: builtin right mime of .js (#1808) 2021-12-08 13:38:25 +08:00
Skyxim
58ef4ddbba [Fixed]Meaningless pointer 2021-12-07 20:49:39 +08:00
Skyxim
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
Skyxim
833b43a538 Fixed: Does RuleSet resolve ip logic modification 2021-12-06 21:47:22 +08:00
Maze.tsz
8df3efe932 [Fix] 修正因xray服务端alpn参数为http/1.1而导致无法连接的问题 2021-12-06 00:19:03 +08:00
Maze.tsz
645c3154d6 [Fix] 修正因xray服务端alpn参数为http/1.1而导致无法连接的问题 2021-12-05 03:51:26 +08:00
Maze.tsz
a847d7b58d [Fix] 修正因xray服务端alpn参数为http/1.1而导致无法连接的问题 2021-12-05 02:18:58 +08:00
Maze.tsz
37ea8aff5c README 2021-12-05 00:48:35 +08:00
maze.y2b@gmail.com
cb4ce8be6a Makefile 2021-12-04 21:43:33 +08:00
maze.y2b@gmail.com
a85395e777 readme 2021-12-04 20:50:57 +08:00
maze.y2b@gmail.com
819b29956b readme 2021-12-04 20:40:09 +08:00
maze.y2b@gmail.com
eb999b3bf1 fix AutoIptables 2021-12-04 19:59:41 +08:00
Maze.tsz
8580ee8898 [style] 2021-12-04 17:41:13 +08:00
Maze.tsz
58552447ef [fix]Linux TProxy 2021-12-04 14:34:01 +08:00
Skyxim
23ca356447 Fixed: Modify the trigger condition, only if it fails successively 2021-12-04 00:16:39 +08:00
maze.y2b@gmail.com
fae65b97ec fix Makefile 2021-12-03 22:13:05 +08:00
maze.y2b@gmail.com
99f0231a9b style 2021-12-03 21:54:45 +08:00
maze.y2b@gmail.com
edf1bb476d test 2021-12-03 20:38:40 +08:00
Rick
08607fb6b4 Feature: add linux/arm/v6 for the container image (#1771) 2021-12-02 21:12:45 +08:00
Dreamacro
075d8ed094 Fix: fakeip pool cycle used 2021-11-23 22:01:49 +08:00
Dreamacro
b1bed7623d Fix: provider filter potential panic 2021-11-21 17:44:03 +08:00
beyondkmp
1401a82bb0 Feature: add filter on proxy provider (#1511) 2021-11-20 23:38:49 +08:00
yaling888
8e641a4e31 Fix: should return io.EOF immediately 2021-11-20 23:01:22 +08:00
Dreamacro
4524cf4418 Fix: should return io.EOF immediately 2021-11-20 12:44:31 +08:00
Dreamacro
0db15d46c3 Change: use nop packet conn for reject 2021-11-20 12:34:14 +08:00
yaling888
3d246d5150 Merge from remote branch 2021-11-14 20:25:22 +08:00
Dreamacro
08c43b8876 Fix: revert ssr udp fix 2021-11-14 14:48:00 +08:00
yaling888
3686446919 Fix: resolver dial context options 2021-11-12 11:05:02 +08:00
yaling888
a412745314 Merge from remote branch 2021-11-11 00:54:43 +08:00
yaling888
d0c23998d2 Fix: resolver dial context udp 2021-11-11 00:53:42 +08:00
Dreamacro
499beb7344 Fix: bind iface should throw control error 2021-11-10 22:19:11 +08:00
yaling888
038cc1f6b5 Merge from remote branch 2021-11-09 21:12:08 +08:00
yaling888
6bd186d3c0 Merge from remote branch 2021-11-09 21:11:38 +08:00
yaling888
4c6bb7178b Feature: resolve ip with proxy adapter 2021-11-09 19:44:16 +08:00
yaling888
53287d597b Chore: use custom buffer pool for lwIP stack 2021-11-04 18:33:11 +08:00
yaling888
964bbe1957 Chore: adjust all udp alloc size
Chore: adjust all udp alloc size
2021-11-04 00:44:16 +08:00
yaling888
c824ace2d7 Wintun: use new swdevice-based API for upcoming Wintun 0.14 2021-11-03 15:10:31 +08:00
yaling888
ac9e5c6913 Wintun: use new swdevice-based API for upcoming Wintun 0.14 2021-11-03 15:02:40 +08:00
yaling888
b515a4e270 Chore: move "geodata" to package "component" 2021-11-02 18:23:01 +08:00
yaling888
78cef7df59 Chore: move "geodata" to package "component" 2021-10-29 00:52:44 +08:00
yaling888
62b3ebe49f Chore: update dependencies 2021-10-28 13:35:27 +08:00
yaling888
325b7f455f Chore: version fmt 2021-10-28 12:55:40 +08:00
yaling888
ff420ed2ee Merge from remote branch 2021-10-28 12:30:30 +08:00
yaling888
d1568325e6 Merge from remote branch 2021-10-28 12:30:02 +08:00
yaling888
ddf28dfe8b Merge from remote branch 2021-10-28 11:36:41 +08:00
yaling888
2680e8ffa3 Merge from remote branch 2021-10-28 11:36:11 +08:00
yaling888
2953772a0e Style: format code 2021-10-28 00:06:55 +08:00
yaling888
5a27df899f Chore: script built 2021-10-27 23:10:11 +08:00
yaling888
ab12b440aa Merge remote branch 2021-10-21 22:40:07 +08:00
yaling888
4b614090f8 Merge remote branch 2021-10-21 22:37:30 +08:00
yaling888
63d07db4bf Chore: script built 2021-10-21 20:22:23 +08:00
yaling888
cbea46b0c8 Merge remote branch 2021-10-15 14:14:51 +08:00
yaling888
c0e9d69163 Feature: add mode script 2021-10-15 14:11:14 +08:00
yaling888
d29d824da8 Improve: avoid bufconn twice (#1650) 2021-09-30 04:11:37 +08:00
yaling888
862174d21b Feature: add lwIP TCP/IP stack to tun listener 2021-09-30 04:05:52 +08:00
yaling888
433d35e866 Chore: format with go 1.17 2021-09-24 04:37:04 +08:00
yaling888
32d8f849ee Chore: update gvisor 2021-09-23 02:42:17 +08:00
yaling888
8be1d5effb Merge from remote branch 2021-09-22 22:11:51 +08:00
yaling888
5f03238c8a Chore: force set latest go version to action 2021-09-18 18:22:28 +08:00
yaling888
6f94d56383 Fix: gvisor ipv6 routeing in Tun 2021-09-17 16:49:53 +08:00
yaling888
fbda82218e Merge from remote branch 2021-09-17 15:07:27 +08:00
yaling888
85dc0b5527 Fix: potential overflow in ssr (#1600) 2021-09-09 22:07:27 +08:00
yaling888
77a6a08192 Fix: VLESS test cases 2021-09-08 23:34:57 +08:00
yaling888
1df5317e13 Feature: add VLESS test case [ssh] 2021-09-08 22:36:54 +08:00
yaling888
ae619e4163 Fix: VLESS WSOpts 2021-09-08 21:32:08 +08:00
yaling888
738bd3b0dd Fix: vmess ws headers not set properly (#1595) 2021-09-08 21:20:16 +08:00
yaling888
03be2512ca Merge from remote branch 2021-09-08 04:43:53 +08:00
yaling888
6ddd9e6fb8 Merge from remote branch 2021-09-08 04:42:56 +08:00
yaling888
9254d2411e Fix: VLESS WSOpts Headers 2021-09-08 04:34:11 +08:00
yaling888
b904ca0bcc Feature: add source ipcidr condition to rule final 2021-09-01 18:29:48 +08:00
yaling888
fb836fe441 Fix: remove trim source ipcidr 2021-09-01 01:02:42 +08:00
yaling888
b23bc77001 Fix: source ipcidr condition for rule IPCIDR 2021-09-01 00:53:13 +08:00
yaling888
16fcee802b Merge from remote branch 2021-09-01 00:41:32 +08:00
yaling888
48aef1829f Merge from remote branch 2021-09-01 00:38:43 +08:00
yaling888
4cc16e0136 Feature: add source ipcidr condition for all rules 2021-08-31 21:46:04 +08:00
yaling888
83c9664c17 Merge from remote branch 2021-08-05 00:49:17 +08:00
yaling888
ac9e90c812 Merge from remote branch 2021-07-28 22:14:20 +08:00
yaling888
ba2fd00f01 Merge from remote branch 2021-07-28 22:13:21 +08:00
yaling888
09299e5e5a Fix: error var name 2021-07-27 02:38:41 +08:00
yaling888
96e5a52651 Style: code style 2021-07-09 02:19:43 +08:00
yaling888
5852245045 Merge from remote branch 2021-07-07 03:53:32 +08:00
yaling888
b4d93c4438 Feature: add xtls support for VLESS 2021-07-06 23:55:34 +08:00
yaling888
56dff65149 Feature: support multiport condition for rule SRC-PORT and DST-PORT 2021-07-06 15:07:05 +08:00
yaling888
e2c7b19000 Fix: fix yaml syntax 2021-07-03 22:41:31 +08:00
yaling888
8a488bab72 Merge from remote branch 2021-07-03 22:33:18 +08:00
yaling888
3afe3810bf Merge from remote branch 2021-07-03 22:31:12 +08:00
yaling888
d7732f6ebc Code: refresh code 2021-07-01 22:49:29 +08:00
287 changed files with 9727 additions and 5572 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

71
.github/workflows/release.yml vendored Normal file
View File

@@ -0,0 +1,71 @@
name: Release
on: [push]
jobs:
Feature-build:
if: ${{ !contains(github.event.head_commit.message, '[Skip CI]') }}
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: Get dependencies, run test
# run: |
# go test ./...
- name: Build
if: success()
env:
NAME: Clash.Meta
BINDIR: bin
run: make -j releases
- name: Delete current release assets
uses: andreaswilli/delete-release-assets-action@v2.0.0
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
tag: alpha
deleteOnlyFromDrafts: false
- name: Tag Repo
uses: richardsimko/update-tag@v1
with:
tag_name: v1.10.0
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Upload Release
uses: softprops/action-gh-release@v1
if: ${{ env.GIT_BRANCH == 'Meta' && success() }}
with:
tag: ${{ github.ref }}
tag_name: v1.10.0
files: bin/*
prerelease: false
- name: send telegram message on push
uses: appleboy/telegram-action@master
with:
to: ${{ secrets.TTELEGRAM_CHAT_ID }}
token: ${{ secrets.TELEGRAM_TOKEN }}
message: |
${{ github.actor }} created commit:
Commit message: ${{ github.event.commits[0].message }}
Repository: ${{ github.repository }}
See changes: https://github.com/${{ github.repository }}/commit/${{github.sha}}

2
.gitignore vendored
View File

@@ -23,3 +23,5 @@ vendor
# test suite # test suite
test/config/cache* 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

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

167
Makefile
View File

@@ -1,67 +1,133 @@
GOCMD=go NAME=Clash.Meta
XGOCMD=xgo -go=go-1.17.x BINDIR=bin
GOBUILD=CGO_ENABLED=1 $(GOCMD) build -trimpath BRANCH=$(shell git rev-parse --abbrev-ref HEAD)
GOCLEAN=$(GOCMD) clean VERSION=$(shell git describe --tags || echo "unknown version")
NAME=clash
BINDIR=$(shell pwd)/bin
VERSION=$(shell git describe --tags --always 2>/dev/null || date +%F)
BUILDTIME=$(shell date -u) BUILDTIME=$(shell date -u)
BUILD_PACKAGE=. GOBUILD=CGO_ENABLED=0 go build -trimpath -ldflags '-X "github.com/Dreamacro/clash/constant.Version=$(VERSION)" \
RELEASE_LDFLAGS='-X "github.com/Dreamacro/clash/constant.Version=$(VERSION)" \ -X "github.com/Dreamacro/clash/constant.BuildTime=$(BUILDTIME)" \
-X "github.com/Dreamacro/clash/constant.BuildTime=$(BUILDTIME)" \ -w -s -buildid='
-w -s -buildid='
STATIC_LDFLAGS='-X "github.com/Dreamacro/clash/constant.Version=$(VERSION)" \
-X "github.com/Dreamacro/clash/constant.BuildTime=$(BUILDTIME)" \
-extldflags "-static" \
-w -s -buildid='
PLATFORM_LIST = \ PLATFORM_LIST = \
darwin-amd64 \ darwin-amd64v1 \
darwin-amd64v2 \
darwin-amd64v3 \
darwin-arm64 \ darwin-arm64 \
linux-amd64 linux-amd64v1 \
# linux-arm64 linux-amd64v2 \
# linux-386 linux-amd64v3 \
linux-armv5 \
linux-armv6 \
linux-armv7 \
linux-arm64 \
linux-mips64 \
linux-mips64le \
linux-mips-softfloat \
linux-mips-hardfloat \
linux-mipsle-softfloat \
linux-mipsle-hardfloat \
android-arm64 \
freebsd-386 \
freebsd-amd64 \
freebsd-arm64
WINDOWS_ARCH_LIST = \ WINDOWS_ARCH_LIST = \
windows-amd64 \ windows-386 \
windows-386 windows-amd64v1 \
# windows-arm64 windows-amd64v2 \
windows-amd64v3 \
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\
build: docker:
$(GOBUILD) -ldflags $(RELEASE_LDFLAGS) -tags build_local -o $(BINDIR)/$(NAME)-$@ $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
darwin-amd64: darwin-amd64v3:
$(XGOCMD) -dest=$(BINDIR) -out=$(NAME) -trimpath=true -ldflags=$(RELEASE_LDFLAGS) -targets=darwin-10.12/amd64 $(BUILD_PACKAGE) && \ GOARCH=amd64 GOOS=darwin GOAMD64=v3 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
mv $(BINDIR)/$(NAME)-darwin-10.12-amd64 $(BINDIR)/$(NAME)-darwin-amd64
darwin-amd64v2:
GOARCH=amd64 GOOS=darwin GOAMD64=v2 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
darwin-amd64v1:
GOARCH=amd64 GOOS=darwin GOAMD64=v1 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
darwin-arm64: darwin-arm64:
$(XGOCMD) -dest=$(BINDIR) -out=$(NAME) -trimpath=true -ldflags=$(RELEASE_LDFLAGS) -targets=darwin-11.1/arm64 $(BUILD_PACKAGE) && \ GOARCH=arm64 GOOS=darwin $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
mv $(BINDIR)/$(NAME)-darwin-11.1-arm64 $(BINDIR)/$(NAME)-darwin-arm64
linux-386: linux-386:
$(XGOCMD) -dest=$(BINDIR) -out=$(NAME) -trimpath=true -ldflags=$(STATIC_LDFLAGS) -targets=linux/386 $(BUILD_PACKAGE) GOARCH=386 GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
linux-amd64: linux-amd64v3:
$(GOBUILD) -ldflags $(RELEASE_LDFLAGS) -o $(BINDIR)/$(NAME)-$@ GOARCH=amd64 GOOS=linux GOAMD64=v3 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
#GOARCH=amd64 GOOS=linux $(GOBUILD) -ldflags $(RELEASE_LDFLAGS) -o $(BINDIR)/$(NAME)-$@
#$(XGOCMD) -dest=$(BINDIR) -out=$(NAME) -trimpath=true -ldflags=$(STATIC_LDFLAGS) -targets=linux/amd64 $(BUILD_PACKAGE) linux-amd64v2:
GOARCH=amd64 GOOS=linux GOAMD64=v2 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
linux-amd64v1:
GOARCH=amd64 GOOS=linux GOAMD64=v1 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
linux-arm64: linux-arm64:
$(XGOCMD) -dest=$(BINDIR) -out=$(NAME) -trimpath=true -ldflags=$(STATIC_LDFLAGS) -targets=linux/arm64 $(BUILD_PACKAGE) GOARCH=arm64 GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
linux-armv5:
GOARCH=arm GOOS=linux GOARM=5 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
linux-armv6:
GOARCH=arm GOOS=linux GOARM=6 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
linux-armv7:
GOARCH=arm GOOS=linux GOARM=7 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
linux-mips-softfloat:
GOARCH=mips GOMIPS=softfloat GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
linux-mips-hardfloat:
GOARCH=mips GOMIPS=hardfloat GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
linux-mipsle-softfloat:
GOARCH=mipsle GOMIPS=softfloat GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
linux-mipsle-hardfloat:
GOARCH=mipsle GOMIPS=hardfloat GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
linux-mips64:
GOARCH=mips64 GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
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 GOAMD64=v3 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
freebsd-arm64:
GOARCH=arm64 GOOS=freebsd $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
windows-386: windows-386:
$(XGOCMD) -dest=$(BINDIR) -out=$(NAME) -trimpath=true -ldflags=$(RELEASE_LDFLAGS) -targets=windows-4.0/386 $(BUILD_PACKAGE) && \ GOARCH=386 GOOS=windows $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.exe
mv $(BINDIR)/$(NAME)-windows-4.0-386.exe $(BINDIR)/$(NAME)-windows-386.exe
windows-amd64: windows-amd64v3:
$(XGOCMD) -dest=$(BINDIR) -out=$(NAME) -trimpath=true -ldflags=$(RELEASE_LDFLAGS) -targets=windows-4.0/amd64 $(BUILD_PACKAGE) && \ GOARCH=amd64 GOOS=windows GOAMD64=v3 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.exe
mv $(BINDIR)/$(NAME)-windows-4.0-amd64.exe $(BINDIR)/$(NAME)-windows-amd64.exe
#windows-arm64: windows-amd64v2:
# $(XGOCMD) -dest=$(BINDIR) -out=$(NAME) -trimpath=true -ldflags=$(RELEASE_LDFLAGS) -targets=windows/arm64 $(BUILD_PACKAGE) GOARCH=amd64 GOOS=windows GOAMD64=v2 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.exe
# mv $(NAME)-windows-4.0-arm64.exe $(NAME)-windows-arm64.exe
windows-amd64v1:
GOARCH=amd64 GOOS=windows GOAMD64=v1 $(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)) gz_releases=$(addsuffix .gz, $(PLATFORM_LIST))
zip_releases=$(addsuffix .zip, $(WINDOWS_ARCH_LIST)) zip_releases=$(addsuffix .zip, $(WINDOWS_ARCH_LIST))
@@ -78,17 +144,10 @@ all-arch: $(PLATFORM_LIST) $(WINDOWS_ARCH_LIST)
releases: $(gz_releases) $(zip_releases) releases: $(gz_releases) $(zip_releases)
vet: vet:
$(GOCMD) vet -tags build_local ./... go test ./...
lint: lint:
golangci-lint run --build-tags=build_local --disable-all -E govet -E gofumpt -E megacheck ./... golangci-lint run ./...
clean: clean:
rm -rf $(BINDIR)/ rm $(BINDIR)/*
mkdir -p $(BINDIR)
cleancache:
# go build cache may need to cleanup if changing C source code
$(GOCLEAN) -cache
rm -rf $(BINDIR)/
mkdir -p $(BINDIR)

BIN
Meta.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 KiB

260
README.md
View File

@@ -1,23 +1,20 @@
<h1 align="center"> <h1 align="center">
<img src="https://github.com/Dreamacro/clash/raw/master/docs/logo.png" alt="Clash" width="200"> <img src="Meta.png" alt="Meta Kennel" width="200">
<br>Clash<br> <br>Meta Kernel<br>
</h1> </h1>
<h4 align="center">A rule-based tunnel in Go.</h4> <h3 align="center">Another Clash Kernel.</h3>
<p align="center"> <p align="center">
<a href="https://github.com/Dreamacro/clash/actions"> <a href="https://goreportcard.com/report/github.com/Clash-Mini/Clash.Meta">
<img src="https://img.shields.io/github/workflow/status/Dreamacro/clash/Go?style=flat-square" alt="Github Actions"> <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">
</a> </a>
<img src="https://img.shields.io/github/go-mod/go-version/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/Dreamacro/clash/releases"> <a href="https://github.com/Clash-Mini/Clash.Meta/releases">
<img src="https://img.shields.io/github/release/Dreamacro/clash/all.svg?style=flat-square"> <img src="https://img.shields.io/github/release/Clash-Mini/Clash.Meta/all.svg?style=flat-square">
</a> </a>
<a href="https://github.com/Dreamacro/clash/releases/tag/premium"> <a href="https://github.com/Clash-Mini/Clash.Meta">
<img src="https://img.shields.io/badge/release-Premium-00b4f0?style=flat-square"> <img src="https://img.shields.io/badge/release-Meta-00b4f0?style=flat-square">
</a> </a>
</p> </p>
@@ -36,16 +33,32 @@
Documentations are now moved to [GitHub Wiki](https://github.com/Dreamacro/clash/wiki). Documentations are now moved to [GitHub Wiki](https://github.com/Dreamacro/clash/wiki).
## Advanced usage for this branch ## Advanced usage for this branch
### DNS configuration ### DNS configuration
Support resolve ip with a proxy tunnel.
Support `geosite` with `fallback-filter`. 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 ```yaml
dns: dns:
enable: true enable: true
use-hosts: true use-hosts: true
ipv6: false ipv6: false
enhanced-mode: fake-ip enhanced-mode: redir-host
fake-ip-range: 198.18.0.1/16 fake-ip-range: 198.18.0.1/16
listen: 127.0.0.1:6868 listen: 127.0.0.1:6868
default-nameserver: default-nameserver:
@@ -55,8 +68,8 @@ dns:
- https://doh.pub/dns-query - https://doh.pub/dns-query
- tls://223.5.5.5:853 - tls://223.5.5.5:853
fallback: fallback:
- 'https://1.0.0.1/dns-query#Proxy' # append the proxy adapter name to the end of DNS URL with '#' prefix. - '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#Proxy' - 'tls://8.8.4.4:853#DNS'
fallback-filter: fallback-filter:
geoip: false geoip: false
geosite: geosite:
@@ -68,39 +81,29 @@ dns:
``` ```
### TUN configuration ### TUN configuration
Supports macOS, Linux and Windows. Supports macOS, Linux and Windows.
Support lwIP stack, a lightweight TCP/IP stack, it's recommended set to tun. Built-in [Wintun](https://www.wintun.net) driver.
On Windows, you should download the [Wintun](https://www.wintun.net) driver and copy `wintun.dll` into Clash home directory.
```yaml ```yaml
# Enable the TUN listener # Enable the TUN listener
tun: tun:
enable: true enable: true
stack: lwip # lwip(recommended), system or gvisor stack: gvisor # only gvisor
dns-listen: 0.0.0.0:53 # additional dns server listen on TUN dns-hijack:
- 0.0.0.0:53 # additional dns server listen on TUN
auto-route: true # auto set global route auto-route: true # auto set global route
``` ```
### Rules configuration ### Rules configuration
- Support rule `GEOSITE`. - Support rule `GEOSITE`.
- Support rule `SCRIPT`. - Support rule-providers `RULE-SET`.
- Support `multiport` condition for rule `SRC-PORT` and `DST-PORT`. - Support `multiport` condition for rule `SRC-PORT` and `DST-PORT`.
- Support `network` condition for all rules. - Support `network` condition for all rules.
- Support source IPCIDR condition for all rules, just append to the end. - Support source IPCIDR condition for all rules, just append to the end.
- The `GEOSITE` databases via https://github.com/Loyalsoldier/v2ray-rules-dat.
The `GEOSITE` databases via https://github.com/Loyalsoldier/v2ray-rules-dat.
```yaml ```yaml
mode: rule
script:
shortcuts:
quic: 'network == "udp" and dst_port == 443'
privacy: '"analytics" in host or "adservice" in host or "firebase" in host or "safebrowsing" in host or "doubleclick" in host'
rules: rules:
# rule SCRIPT
- SCRIPT,quic,REJECT # Disable QUIC, same as rule "DST-PORT,443,REJECT,udp"
- SCRIPT,privacy,REJECT
# network(tcp/udp) condition for all rules # network(tcp/udp) condition for all rules
- DOMAIN-SUFFIX,bilibili.com,DIRECT,tcp - DOMAIN-SUFFIX,bilibili.com,DIRECT,tcp
@@ -130,92 +133,53 @@ rules:
- MATCH,PROXY - MATCH,PROXY
``` ```
### Script configuration
Script enables users to programmatically select a policy for the packets with more flexibility.
```yaml
mode: script
rules:
# the rule GEOSITE just as a rule provider in mode script
- GEOSITE,category-ads-all,Whatever
- GEOSITE,youtube,Whatever
- GEOSITE,geolocation-cn,Whatever
script:
code: |
def main(ctx, metadata):
if metadata["process_name"] == 'apsd':
return "DIRECT"
if metadata["network"] == 'udp' and metadata["dst_port"] == 443:
return "REJECT"
host = metadata["host"]
for kw in ['analytics', 'adservice', 'firebase', 'bugly', 'safebrowsing', 'doubleclick']:
if kw in host:
return "REJECT"
now = time.now()
if (now.hour < 8 or now.hour > 17) and metadata["src_ip"] == '192.168.1.99':
return "REJECT"
if ctx.rule_providers["geosite:category-ads-all"].match(metadata):
return "REJECT"
if ctx.rule_providers["geosite:youtube"].match(metadata):
ctx.log('[Script] domain %s matched youtube' % host)
return "Proxy"
if ctx.rule_providers["geosite:geolocation-cn"].match(metadata):
ctx.log('[Script] domain %s matched geolocation-cn' % host)
return "DIRECT"
ip = metadata["dst_ip"]
if host != "":
ip = ctx.resolve_ip(host)
if ip == "":
return "Proxy"
code = ctx.geoip(ip)
if code == "LAN" or code == "CN":
return "DIRECT"
return "Proxy" # default policy for requests which are not matched by any other script
```
the context and metadata
```ts
interface Metadata {
type: string // socks5、http
network: string // tcp
host: string
process_name: string
src_ip: string
src_port: int
dst_ip: string
dst_port: int
}
interface Context {
resolve_ip: (host: string) => string // ip string
geoip: (ip: string) => string // country code
log: (log: string) => void
rule_providers: Record<string, { match: (metadata: Metadata) => boolean }>
}
```
### Proxies configuration ### Proxies configuration
Active health detection `urltest / fallback` (based on tcp handshake, multiple failures within a limited time will actively trigger health detection to use the node)
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
```
Support outbound transport protocol `VLESS`. Support outbound transport protocol `VLESS`.
The XTLS only support TCP transport by the XRAY-CORE. The XTLS support (TCP/UDP) transport by the XRAY-CORE.
```yaml ```yaml
proxies: proxies:
- name: "vless-tcp" - name: "vless"
type: vless type: vless
server: server server: server
port: 443 port: 443
uuid: uuid uuid: uuid
network: tcp
servername: example.com # AKA SNI servername: example.com # AKA SNI
# flow: xtls-rprx-direct # xtls-rprx-origin # enable XTLS # flow: xtls-rprx-direct # xtls-rprx-origin # enable XTLS
# skip-cert-verify: true # skip-cert-verify: true
@@ -225,67 +189,103 @@ proxies:
server: server server: server
port: 443 port: 443
uuid: uuid uuid: uuid
tls: true
udp: true udp: true
network: ws network: ws
servername: example.com # priority over wss host servername: example.com # priority over wss host
# skip-cert-verify: true # skip-cert-verify: true
ws-path: /path ws-opts:
ws-headers: path: /path
Host: example.com headers: { Host: example.com, Edge: "12a00c4.fm.huawei.com:82897" }
- name: "vless-grpc"
type: vless
server: server
port: 443
uuid: uuid
tls: true
udp: true
network: grpc
servername: example.com # priority over wss host
# skip-cert-verify: true
grpc-opts:
grpc-service-name: grpcname
``` ```
### IPTABLES auto-configuration ### IPTABLES configuration
Only work on Linux OS who support `iptables`, Clash will auto-configuration iptables for tproxy listener when `tproxy-port` value isn't zero. Work on Linux OS who's supported `iptables`
If `TPROXY` is enabled, the `TUN` must be disabled.
```yaml ```yaml
# Enable the TPROXY listener # Enable the TPROXY listener
tproxy-port: 9898 tproxy-port: 9898
# Disable the TUN listener
tun: iptables:
enable: false enable: true # default is false
inbound-interface: eth0 # detect the inbound interface, default is 'lo'
``` ```
Create user given name `clash`.
Run Clash by user `clash` as a daemon.
Create the systemd configuration file at /etc/systemd/system/clash.service: ### 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] [Unit]
Description=Clash daemon, A rule-based proxy in Go. Description=Clash-Meta Daemon, Another Clash Kernel.
After=network.target After=network.target NetworkManager.service systemd-networkd.service iwd.service
[Service] [Service]
Type=simple Type=simple
User=clash User=clash-meta
Group=clash Group=clash-meta
LimitNPROC=500
LimitNOFILE=1000000
CapabilityBoundingSet=cap_net_admin CapabilityBoundingSet=cap_net_admin
AmbientCapabilities=cap_net_admin AmbientCapabilities=cap_net_admin
Restart=always Restart=always
ExecStart=/usr/local/bin/clash -d /etc/clash ExecStartPre=/usr/bin/sleep 1s
ExecStart=/usr/local/bin/Clash-Meta -d /etc/Clash-Meta
[Install] [Install]
WantedBy=multi-user.target WantedBy=multi-user.target
``` ```
Launch clashd on system startup with: Launch clashd on system startup with:
```shell ```shell
$ systemctl enable clash $ systemctl enable Clash-Meta
``` ```
Launch clashd immediately with: Launch clashd immediately with:
```shell ```shell
$ systemctl start clash $ systemctl start Clash-Meta
``` ```
### Display Process name ### Display Process name
Clash add field `Process` to `Metadata` and prepare to get process name for Restful API `GET /connections`. 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 https://yaling888.github.io/yacd/. 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 ## 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)
If you want to build an application that uses clash as a library, check out the
the [GitHub Wiki](https://github.com/Dreamacro/clash/wiki/use-clash-as-a-library)
## Credits ## Credits
* [Dreamacro/clash](https://github.com/Dreamacro/clash)
* [riobard/go-shadowsocks2](https://github.com/riobard/go-shadowsocks2) * [riobard/go-shadowsocks2](https://github.com/riobard/go-shadowsocks2)
* [v2ray/v2ray-core](https://github.com/v2ray/v2ray-core) * [v2ray/v2ray-core](https://github.com/v2ray/v2ray-core)
* [WireGuard/wireguard-go](https://github.com/WireGuard/wireguard-go) * [WireGuard/wireguard-go](https://github.com/WireGuard/wireguard-go)

View File

@@ -7,6 +7,7 @@ import (
"net" "net"
"net/http" "net/http"
"net/url" "net/url"
"strings"
"time" "time"
"github.com/Dreamacro/clash/common/queue" "github.com/Dreamacro/clash/common/queue"
@@ -16,6 +17,8 @@ import (
"go.uber.org/atomic" "go.uber.org/atomic"
) )
var UnifiedDelay = atomic.NewBool(false)
type Proxy struct { type Proxy struct {
C.ProxyAdapter C.ProxyAdapter
history *queue.Queue history *queue.Queue
@@ -37,7 +40,11 @@ func (p *Proxy) Dial(metadata *C.Metadata) (C.Conn, error) {
// DialContext implements C.ProxyAdapter // DialContext implements C.ProxyAdapter
func (p *Proxy) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.Conn, error) { func (p *Proxy) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.Conn, error) {
conn, err := p.ProxyAdapter.DialContext(ctx, metadata, opts...) conn, err := p.ProxyAdapter.DialContext(ctx, metadata, opts...)
p.alive.Store(err == nil) wasCancel := false
if err != nil {
wasCancel = strings.Contains(err.Error(), "operation was canceled")
}
p.alive.Store(err == nil || wasCancel)
return conn, err return conn, err
} }
@@ -91,7 +98,7 @@ func (p *Proxy) MarshalJSON() ([]byte, error) {
return inner, err return inner, err
} }
mapping := map[string]interface{}{} mapping := map[string]any{}
json.Unmarshal(inner, &mapping) json.Unmarshal(inner, &mapping)
mapping["history"] = p.DelayHistory() mapping["history"] = p.DelayHistory()
mapping["name"] = p.Name() mapping["name"] = p.Name()
@@ -114,6 +121,8 @@ func (p *Proxy) URLTest(ctx context.Context, url string) (t uint16, err error) {
} }
}() }()
unifiedDelay := UnifiedDelay.Load()
addr, err := urlToMetadata(url) addr, err := urlToMetadata(url)
if err != nil { if err != nil {
return return
@@ -150,11 +159,19 @@ func (p *Proxy) URLTest(ctx context.Context, url string) (t uint16, err error) {
}, },
} }
defer client.CloseIdleConnections() defer client.CloseIdleConnections()
resp, err := client.Do(req) resp, err := client.Do(req)
if err != nil { if err != nil {
return return
} }
if unifiedDelay {
start = time.Now()
resp, err = client.Do(req)
if err != nil {
return
}
}
resp.Body.Close() resp.Body.Close()
t = uint16(time.Since(start) / time.Millisecond) t = uint16(time.Since(start) / time.Millisecond)
return return

View File

@@ -20,3 +20,26 @@ func NewSocket(target socks5.Addr, conn net.Conn, source C.Type) *context.ConnCo
return context.NewConnContext(conn, metadata) 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 ip, port, err := parseAddr(dst); err == nil {
metadata.DstPort = port
if host == "" {
metadata.DstIP = ip
if ip.To4() == nil {
metadata.AddrType = C.AtypIPv6
} else {
metadata.AddrType = C.AtypIPv4
}
}
}
return context.NewConnContext(conn, metadata)
}

View File

@@ -14,6 +14,7 @@ type Direct struct {
// DialContext implements C.ProxyAdapter // DialContext implements C.ProxyAdapter
func (d *Direct) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.Conn, error) { 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...)...) c, err := dialer.DialContext(ctx, "tcp", metadata.RemoteAddress(), d.Base.DialOptions(opts...)...)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -24,6 +25,7 @@ func (d *Direct) DialContext(ctx context.Context, metadata *C.Metadata, opts ...
// ListenPacketContext implements C.ProxyAdapter // ListenPacketContext implements C.ProxyAdapter
func (d *Direct) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) { 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...)...) pc, err := dialer.ListenPacket(ctx, "udp", "", d.Base.DialOptions(opts...)...)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -44,3 +46,23 @@ func NewDirect() *Direct {
}, },
} }
} }
func NewCompatible() *Direct {
return &Direct{
Base: &Base{
name: "COMPATIBLE",
tp: C.Compatible,
udp: true,
},
}
}
func NewPass() *Direct {
return &Direct{
Base: &Base{
name: "Pass",
tp: C.Pass,
udp: true,
},
}
}

View File

@@ -136,6 +136,7 @@ func NewHttp(option HttpOption) *Http {
addr: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)), addr: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)),
tp: C.Http, tp: C.Http,
iface: option.Interface, iface: option.Interface,
rmark: option.RoutingMark,
}, },
user: option.UserName, user: option.UserName,
pass: option.Password, pass: option.Password,

View File

@@ -2,7 +2,6 @@ package outbound
import ( import (
"context" "context"
"errors"
"io" "io"
"net" "net"
"time" "time"
@@ -17,12 +16,12 @@ type Reject struct {
// DialContext implements C.ProxyAdapter // DialContext implements C.ProxyAdapter
func (r *Reject) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.Conn, error) { func (r *Reject) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.Conn, error) {
return NewConn(&NopConn{}, r), nil return NewConn(&nopConn{}, r), nil
} }
// ListenPacketContext implements C.ProxyAdapter // ListenPacketContext implements C.ProxyAdapter
func (r *Reject) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) { func (r *Reject) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) {
return nil, errors.New("match reject rule") return newPacketConn(&nopPacketConn{}, r), nil
} }
func NewReject() *Reject { func NewReject() *Reject {
@@ -35,30 +34,29 @@ func NewReject() *Reject {
} }
} }
type NopConn struct{} type nopConn struct{}
func (rw *NopConn) Read(b []byte) (int, error) { func (rw *nopConn) Read(b []byte) (int, error) {
return 0, io.EOF return 0, io.EOF
} }
func (rw *NopConn) Write(b []byte) (int, error) { func (rw *nopConn) Write(b []byte) (int, error) {
return 0, io.EOF return 0, io.EOF
} }
// Close is fake function for net.Conn func (rw *nopConn) Close() error { return nil }
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 }
// LocalAddr is fake function for net.Conn type nopPacketConn struct{}
func (rw *NopConn) LocalAddr() net.Addr { return nil }
// RemoteAddr is fake function for net.Conn func (npc *nopPacketConn) WriteTo(b []byte, addr net.Addr) (n int, err error) { return len(b), nil }
func (rw *NopConn) RemoteAddr() net.Addr { return nil } func (npc *nopPacketConn) ReadFrom(b []byte) (int, net.Addr, error) { return 0, nil, io.EOF }
func (npc *nopPacketConn) Close() error { return nil }
// SetDeadline is fake function for net.Conn func (npc *nopPacketConn) LocalAddr() net.Addr { return &net.UDPAddr{IP: net.IPv4zero, Port: 0} }
func (rw *NopConn) SetDeadline(time.Time) error { return nil } func (npc *nopPacketConn) SetDeadline(time.Time) error { return nil }
func (npc *nopPacketConn) SetReadDeadline(time.Time) error { return nil }
// SetReadDeadline is fake function for net.Conn func (npc *nopPacketConn) SetWriteDeadline(time.Time) error { return nil }
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

@@ -29,14 +29,14 @@ type ShadowSocks struct {
type ShadowSocksOption struct { type ShadowSocksOption struct {
BasicOption BasicOption
Name string `proxy:"name"` Name string `proxy:"name"`
Server string `proxy:"server"` Server string `proxy:"server"`
Port int `proxy:"port"` Port int `proxy:"port"`
Password string `proxy:"password"` Password string `proxy:"password"`
Cipher string `proxy:"cipher"` Cipher string `proxy:"cipher"`
UDP bool `proxy:"udp,omitempty"` UDP bool `proxy:"udp,omitempty"`
Plugin string `proxy:"plugin,omitempty"` Plugin string `proxy:"plugin,omitempty"`
PluginOpts map[string]interface{} `proxy:"plugin-opts,omitempty"` PluginOpts map[string]any `proxy:"plugin-opts,omitempty"`
} }
type simpleObfsOption struct { type simpleObfsOption struct {
@@ -160,6 +160,7 @@ func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) {
tp: C.Shadowsocks, tp: C.Shadowsocks,
udp: option.UDP, udp: option.UDP,
iface: option.Interface, iface: option.Interface,
rmark: option.RoutingMark,
}, },
cipher: ciph, cipher: ciph,

View File

@@ -142,6 +142,7 @@ func NewShadowSocksR(option ShadowSocksROption) (*ShadowSocksR, error) {
tp: C.ShadowsocksR, tp: C.ShadowsocksR,
udp: option.UDP, udp: option.UDP,
iface: option.Interface, iface: option.Interface,
rmark: option.RoutingMark,
}, },
cipher: coreCiph, cipher: coreCiph,
obfs: obfs, obfs: obfs,

View File

@@ -23,12 +23,13 @@ type Snell struct {
type SnellOption struct { type SnellOption struct {
BasicOption BasicOption
Name string `proxy:"name"` Name string `proxy:"name"`
Server string `proxy:"server"` Server string `proxy:"server"`
Port int `proxy:"port"` Port int `proxy:"port"`
Psk string `proxy:"psk"` Psk string `proxy:"psk"`
Version int `proxy:"version,omitempty"` UDP bool `proxy:"udp,omitempty"`
ObfsOpts map[string]interface{} `proxy:"obfs-opts,omitempty"` Version int `proxy:"version,omitempty"`
ObfsOpts map[string]any `proxy:"obfs-opts,omitempty"`
} }
type streamOption struct { type streamOption struct {
@@ -52,7 +53,7 @@ func streamConn(c net.Conn, option streamOption) *snell.Snell {
// StreamConn implements C.ProxyAdapter // StreamConn implements C.ProxyAdapter
func (s *Snell) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) { func (s *Snell) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
c = streamConn(c, streamOption{s.psk, s.version, s.addr, s.obfsOption}) c = streamConn(c, streamOption{s.psk, s.version, s.addr, s.obfsOption})
port, _ := strconv.ParseInt(metadata.DstPort, 10, 16) port, _ := strconv.ParseUint(metadata.DstPort, 10, 16)
err := snell.WriteHeader(c, metadata.String(), uint(port), s.version) err := snell.WriteHeader(c, metadata.String(), uint(port), s.version)
return c, err return c, err
} }
@@ -85,6 +86,24 @@ func (s *Snell) DialContext(ctx context.Context, metadata *C.Metadata, opts ...d
return NewConn(c, s), err 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
}
func NewSnell(option SnellOption) (*Snell, error) { func NewSnell(option SnellOption) (*Snell, error) {
addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port)) addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port))
psk := []byte(option.Psk) psk := []byte(option.Psk)
@@ -106,7 +125,13 @@ func NewSnell(option SnellOption) (*Snell, error) {
if option.Version == 0 { if option.Version == 0 {
option.Version = snell.DefaultSnellVersion option.Version = snell.DefaultSnellVersion
} }
if option.Version != snell.Version1 && option.Version != snell.Version2 { 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) return nil, fmt.Errorf("snell version error: %d", option.Version)
} }
@@ -115,7 +140,9 @@ func NewSnell(option SnellOption) (*Snell, error) {
name: option.Name, name: option.Name,
addr: addr, addr: addr,
tp: C.Snell, tp: C.Snell,
udp: option.UDP,
iface: option.Interface, iface: option.Interface,
rmark: option.RoutingMark,
}, },
psk: psk, psk: psk,
obfsOption: obfsOption, obfsOption: obfsOption,

View File

@@ -154,6 +154,7 @@ func NewSocks5(option Socks5Option) *Socks5 {
tp: C.Socks5, tp: C.Socks5,
udp: option.UDP, udp: option.UDP,
iface: option.Interface, iface: option.Interface,
rmark: option.RoutingMark,
}, },
user: option.UserName, user: option.UserName,
pass: option.Password, pass: option.Password,

View File

@@ -12,6 +12,7 @@ import (
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/transport/gun" "github.com/Dreamacro/clash/transport/gun"
"github.com/Dreamacro/clash/transport/trojan" "github.com/Dreamacro/clash/transport/trojan"
"github.com/Dreamacro/clash/transport/vless"
"golang.org/x/net/http2" "golang.org/x/net/http2"
) )
@@ -40,6 +41,8 @@ type TrojanOption struct {
Network string `proxy:"network,omitempty"` Network string `proxy:"network,omitempty"`
GrpcOpts GrpcOptions `proxy:"grpc-opts,omitempty"` GrpcOpts GrpcOptions `proxy:"grpc-opts,omitempty"`
WSOpts WSOptions `proxy:"ws-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) { func (t *Trojan) plainStream(c net.Conn) (net.Conn, error) {
@@ -82,6 +85,11 @@ func (t *Trojan) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error)
return nil, fmt.Errorf("%s connect error: %w", t.addr, err) return nil, fmt.Errorf("%s connect error: %w", t.addr, err)
} }
c, err = t.instance.PresetXTLSConn(c)
if err != nil {
return nil, err
}
err = t.instance.WriteHeader(c, trojan.CommandTCP, serializesSocksAddr(metadata)) err = t.instance.WriteHeader(c, trojan.CommandTCP, serializesSocksAddr(metadata))
return c, err return c, err
} }
@@ -95,6 +103,12 @@ func (t *Trojan) DialContext(ctx context.Context, metadata *C.Metadata, opts ...
return nil, err 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 { if err = t.instance.WriteHeader(c, trojan.CommandTCP, serializesSocksAddr(metadata)); err != nil {
c.Close() c.Close()
return nil, err return nil, err
@@ -160,6 +174,17 @@ func NewTrojan(option TrojanOption) (*Trojan, error) {
ALPN: option.ALPN, ALPN: option.ALPN,
ServerName: option.Server, ServerName: option.Server,
SkipCertVerify: option.SkipCertVerify, 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 != "" { if option.SNI != "" {
@@ -173,6 +198,7 @@ func NewTrojan(option TrojanOption) (*Trojan, error) {
tp: C.Trojan, tp: C.Trojan,
udp: option.UDP, udp: option.UDP,
iface: option.Interface, iface: option.Interface,
rmark: option.RoutingMark,
}, },
instance: trojan.New(tOption), instance: trojan.New(tOption),
option: &option, option: &option,
@@ -195,7 +221,12 @@ func NewTrojan(option TrojanOption) (*Trojan, error) {
ServerName: tOption.ServerName, ServerName: tOption.ServerName,
} }
t.transport = gun.NewHTTP2Client(dialFn, tlsConfig) if t.option.Flow != "" {
t.transport = gun.NewHTTP2XTLSClient(dialFn, tlsConfig)
} else {
t.transport = gun.NewHTTP2Client(dialFn, tlsConfig)
}
t.gunTLSConfig = tlsConfig t.gunTLSConfig = tlsConfig
t.gunConfig = &gun.Config{ t.gunConfig = &gun.Config{
ServiceName: option.GrpcOpts.GrpcServiceName, ServiceName: option.GrpcOpts.GrpcServiceName,

View File

@@ -2,8 +2,11 @@ package outbound
import ( import (
"bytes" "bytes"
"crypto/tls"
xtls "github.com/xtls/go"
"net" "net"
"strconv" "strconv"
"sync"
"time" "time"
"github.com/Dreamacro/clash/component/resolver" "github.com/Dreamacro/clash/component/resolver"
@@ -11,6 +14,12 @@ import (
"github.com/Dreamacro/clash/transport/socks5" "github.com/Dreamacro/clash/transport/socks5"
) )
var (
globalClientSessionCache tls.ClientSessionCache
globalClientXSessionCache xtls.ClientSessionCache
once sync.Once
)
func tcpKeepAlive(c net.Conn) { func tcpKeepAlive(c net.Conn) {
if tcp, ok := c.(*net.TCPConn); ok { if tcp, ok := c.(*net.TCPConn); ok {
tcp.SetKeepAlive(true) tcp.SetKeepAlive(true)
@@ -18,6 +27,20 @@ func tcpKeepAlive(c net.Conn) {
} }
} }
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 { func serializesSocksAddr(metadata *C.Metadata) []byte {
var buf [][]byte var buf [][]byte
aType := uint8(metadata.AddrType) aType := uint8(metadata.AddrType)
@@ -44,7 +67,7 @@ func resolveUDPAddr(network, address string) (*net.UDPAddr, error) {
return nil, err return nil, err
} }
ip, err := resolver.ResolveIP(host) ip, err := resolver.ResolveProxyServerHost(host)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@@ -3,11 +3,14 @@ package outbound
import ( import (
"context" "context"
"crypto/tls" "crypto/tls"
"encoding/binary"
"errors" "errors"
"fmt" "fmt"
"io"
"net" "net"
"net/http" "net/http"
"strconv" "strconv"
"sync"
"github.com/Dreamacro/clash/component/dialer" "github.com/Dreamacro/clash/component/dialer"
"github.com/Dreamacro/clash/component/resolver" "github.com/Dreamacro/clash/component/resolver"
@@ -15,9 +18,15 @@ import (
"github.com/Dreamacro/clash/transport/gun" "github.com/Dreamacro/clash/transport/gun"
"github.com/Dreamacro/clash/transport/vless" "github.com/Dreamacro/clash/transport/vless"
"github.com/Dreamacro/clash/transport/vmess" "github.com/Dreamacro/clash/transport/vmess"
"golang.org/x/net/http2" "golang.org/x/net/http2"
) )
const (
// max packet length
maxLength = 1024 << 3
)
type Vless struct { type Vless struct {
*Base *Base
client *vless.Client client *vless.Client
@@ -54,12 +63,6 @@ func (v *Vless) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
var err error var err error
switch v.option.Network { switch v.option.Network {
case "ws": case "ws":
if v.option.WSOpts.Path == "" {
v.option.WSOpts.Path = v.option.WSPath
}
if len(v.option.WSOpts.Headers) == 0 {
v.option.WSOpts.Headers = v.option.WSHeaders
}
host, port, _ := net.SplitHostPort(v.addr) host, port, _ := net.SplitHostPort(v.addr)
wsOpts := &vmess.WebsocketConfig{ wsOpts := &vmess.WebsocketConfig{
@@ -78,18 +81,17 @@ func (v *Vless) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
wsOpts.Headers = header wsOpts.Headers = header
} }
if v.option.TLS { wsOpts.TLS = true
wsOpts.TLS = true wsOpts.TLSConfig = &tls.Config{
wsOpts.TLSConfig = &tls.Config{ MinVersion: tls.VersionTLS12,
ServerName: host, ServerName: host,
InsecureSkipVerify: v.option.SkipCertVerify, InsecureSkipVerify: v.option.SkipCertVerify,
NextProtos: []string{"http/1.1"}, NextProtos: []string{"http/1.1"},
} }
if v.option.ServerName != "" { if v.option.ServerName != "" {
wsOpts.TLSConfig.ServerName = v.option.ServerName wsOpts.TLSConfig.ServerName = v.option.ServerName
} else if host := wsOpts.Headers.Get("Host"); host != "" { } else if host := wsOpts.Headers.Get("Host"); host != "" {
wsOpts.TLSConfig.ServerName = host wsOpts.TLSConfig.ServerName = host
}
} }
c, err = vmess.StreamWebsocketConn(c, wsOpts) c, err = vmess.StreamWebsocketConn(c, wsOpts)
case "http": case "http":
@@ -127,8 +129,9 @@ func (v *Vless) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
c, err = gun.StreamGunWithConn(c, v.gunTLSConfig, v.gunConfig) c, err = gun.StreamGunWithConn(c, v.gunTLSConfig, v.gunConfig)
} }
default: default:
// default tcp network
// handle TLS And XTLS // handle TLS And XTLS
c, err = v.streamTLSOrXTLSConn(c, true) c, err = v.streamTLSOrXTLSConn(c, false)
} }
if err != nil { if err != nil {
@@ -212,7 +215,7 @@ func (v *Vless) DialContext(ctx context.Context, metadata *C.Metadata, opts ...d
// ListenPacketContext implements C.ProxyAdapter // ListenPacketContext implements C.ProxyAdapter
func (v *Vless) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.PacketConn, err error) { func (v *Vless) 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 // vless use stream-oriented udp with a special address, so we needs a net.UDPAddr
if !metadata.Resolved() { if !metadata.Resolved() {
ip, err := resolver.ResolveIP(metadata.Host) ip, err := resolver.ResolveIP(metadata.Host)
if err != nil { if err != nil {
@@ -268,7 +271,7 @@ func parseVlessAddr(metadata *C.Metadata) *vless.DstAddr {
copy(addr[1:], []byte(metadata.Host)) copy(addr[1:], []byte(metadata.Host))
} }
port, _ := strconv.Atoi(metadata.DstPort) port, _ := strconv.ParseUint(metadata.DstPort, 10, 16)
return &vless.DstAddr{ return &vless.DstAddr{
UDP: metadata.NetWork == C.UDP, UDP: metadata.NetWork == C.UDP,
AddrType: addrType, AddrType: addrType,
@@ -279,23 +282,97 @@ func parseVlessAddr(metadata *C.Metadata) *vless.DstAddr {
type vlessPacketConn struct { type vlessPacketConn struct {
net.Conn net.Conn
rAddr net.Addr rAddr net.Addr
remain int
mux sync.Mutex
cache [2]byte
} }
func (uc *vlessPacketConn) WriteTo(b []byte, addr net.Addr) (int, error) { func (c *vlessPacketConn) writePacket(payload []byte) (int, error) {
return uc.Conn.Write(b) 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 (uc *vlessPacketConn) ReadFrom(b []byte) (int, net.Addr, error) { func (c *vlessPacketConn) WriteTo(b []byte, addr net.Addr) (int, error) {
n, err := uc.Conn.Read(b) total := len(b)
return n, uc.rAddr, err 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) { func NewVless(option VlessOption) (*Vless, error) {
if !option.TLS && option.Network =="grpc"{
return nil, fmt.Errorf("TLS must be true with vless-grpc")
}
var addons *vless.Addons var addons *vless.Addons
if option.Network != "ws" && len(option.Flow) >= 16 { if option.Network != "ws" && len(option.Flow) >= 16 {
option.Flow = option.Flow[:16] option.Flow = option.Flow[:16]
@@ -305,7 +382,7 @@ func NewVless(option VlessOption) (*Vless, error) {
Flow: option.Flow, Flow: option.Flow,
} }
default: default:
return nil, fmt.Errorf("unsupported vless flow type: %s", option.Flow) return nil, fmt.Errorf("unsupported xtls flow type: %s", option.Flow)
} }
} }

View File

@@ -48,7 +48,7 @@ type VmessOption struct {
GrpcOpts GrpcOptions `proxy:"grpc-opts,omitempty"` GrpcOpts GrpcOptions `proxy:"grpc-opts,omitempty"`
WSOpts WSOptions `proxy:"ws-opts,omitempty"` WSOpts WSOptions `proxy:"ws-opts,omitempty"`
// TODO: remove these until 2022 // TODO: compatible with VMESS WS older version configurations
WSHeaders map[string]string `proxy:"ws-headers,omitempty"` WSHeaders map[string]string `proxy:"ws-headers,omitempty"`
WSPath string `proxy:"ws-path,omitempty"` WSPath string `proxy:"ws-path,omitempty"`
} }
@@ -291,6 +291,7 @@ func NewVmess(option VmessOption) (*Vmess, error) {
tp: C.Vmess, tp: C.Vmess,
udp: option.UDP, udp: option.UDP,
iface: option.Interface, iface: option.Interface,
rmark: option.RoutingMark,
}, },
client: client, client: client,
option: &option, option: &option,

View File

@@ -1,6 +1,8 @@
package outboundgroup package outboundgroup
import ( import (
"github.com/Dreamacro/clash/tunnel"
"github.com/dlclark/regexp2"
"time" "time"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
@@ -11,7 +13,7 @@ const (
defaultGetProxiesDuration = time.Second * 5 defaultGetProxiesDuration = time.Second * 5
) )
func getProvidersProxies(providers []provider.ProxyProvider, touch bool) []C.Proxy { func getProvidersProxies(providers []provider.ProxyProvider, touch bool, filter string) []C.Proxy {
proxies := []C.Proxy{} proxies := []C.Proxy{}
for _, provider := range providers { for _, provider := range providers {
if touch { if touch {
@@ -20,5 +22,34 @@ func getProvidersProxies(providers []provider.ProxyProvider, touch bool) []C.Pro
proxies = append(proxies, provider.Proxies()...) proxies = append(proxies, provider.Proxies()...)
} }
} }
return proxies
var filterReg *regexp2.Regexp = nil
var matchedProxies []C.Proxy
if len(filter) > 0 {
//filterReg = regexp.MustCompile(filter)
filterReg = regexp2.MustCompile(filter, 0)
for _, p := range proxies {
if p.Type() < 8 {
matchedProxies = append(matchedProxies, p)
}
//if filterReg.MatchString(p.Name()) {
if mat, _ := filterReg.FindStringMatch(p.Name()); mat != nil {
matchedProxies = append(matchedProxies, p)
}
}
if len(matchedProxies) > 0 {
return matchedProxies
} else {
return append([]C.Proxy{}, tunnel.Proxies()["COMPATIBLE"])
}
} else {
if len(proxies) == 0 {
return append(proxies, tunnel.Proxies()["COMPATIBLE"])
} else {
return proxies
}
}
} }

View File

@@ -17,6 +17,7 @@ import (
type Fallback struct { type Fallback struct {
*outbound.Base *outbound.Base
disableUDP bool disableUDP bool
filter string
single *singledo.Single single *singledo.Single
providers []provider.ProxyProvider providers []provider.ProxyProvider
failedTimes *atomic.Int32 failedTimes *atomic.Int32
@@ -34,9 +35,12 @@ func (f *Fallback) DialContext(ctx context.Context, metadata *C.Metadata, opts .
c, err := proxy.DialContext(ctx, metadata, f.Base.DialOptions(opts...)...) c, err := proxy.DialContext(ctx, metadata, f.Base.DialOptions(opts...)...)
if err == nil { if err == nil {
c.AppendToChains(f) c.AppendToChains(f)
f.failedTimes.Store(-1)
f.failedTime.Store(-1)
} else { } else {
f.onDialFailed() f.onDialFailed()
} }
return c, err return c, err
} }
@@ -46,9 +50,12 @@ func (f *Fallback) ListenPacketContext(ctx context.Context, metadata *C.Metadata
pc, err := proxy.ListenPacketContext(ctx, metadata, f.Base.DialOptions(opts...)...) pc, err := proxy.ListenPacketContext(ctx, metadata, f.Base.DialOptions(opts...)...)
if err == nil { if err == nil {
pc.AppendToChains(f) pc.AppendToChains(f)
f.failedTimes.Store(-1)
f.failedTime.Store(-1)
} else { } else {
f.onDialFailed() f.onDialFailed()
} }
return pc, err return pc, err
} }
@@ -59,18 +66,20 @@ func (f *Fallback) onDialFailed() {
f.failedTime.Store(now) f.failedTime.Store(now)
f.failedTimes.Store(1) f.failedTimes.Store(1)
} else { } else {
if f.failedTime.Load()-time.Now().UnixMilli() > 5*1000 { if f.failedTime.Load()-time.Now().UnixMilli() > 5*time.Second.Milliseconds() {
f.failedTimes.Store(-1) f.failedTimes.Store(-1)
f.failedTime.Store(-1) f.failedTime.Store(-1)
} else { } else {
f.failedTimes.Inc() failedCount := f.failedTimes.Inc()
failedCount := f.failedTimes.Load()
log.Warnln("%s failed count: %d", f.Name(), failedCount) log.Warnln("%s failed count: %d", f.Name(), failedCount)
if failedCount > 5 { if failedCount >= 5 {
log.Debugln("%s failed multiple times.", f.Name()) log.Warnln("because %s failed multiple times, active health check", f.Name())
for _, proxyProvider := range f.providers { for _, proxyProvider := range f.providers {
go proxyProvider.HealthCheck() go proxyProvider.HealthCheck()
} }
f.failedTimes.Store(-1)
f.failedTime.Store(-1)
} }
} }
} }
@@ -88,11 +97,11 @@ func (f *Fallback) SupportUDP() bool {
// MarshalJSON implements C.ProxyAdapter // MarshalJSON implements C.ProxyAdapter
func (f *Fallback) MarshalJSON() ([]byte, error) { func (f *Fallback) MarshalJSON() ([]byte, error) {
var all []string all := []string{}
for _, proxy := range f.proxies(false) { for _, proxy := range f.proxies(false) {
all = append(all, proxy.Name()) all = append(all, proxy.Name())
} }
return json.Marshal(map[string]interface{}{ return json.Marshal(map[string]any{
"type": f.Type().String(), "type": f.Type().String(),
"now": f.Now(), "now": f.Now(),
"all": all, "all": all,
@@ -106,8 +115,8 @@ func (f *Fallback) Unwrap(metadata *C.Metadata) C.Proxy {
} }
func (f *Fallback) proxies(touch bool) []C.Proxy { func (f *Fallback) proxies(touch bool) []C.Proxy {
elm, _, _ := f.single.Do(func() (interface{}, error) { elm, _, _ := f.single.Do(func() (any, error) {
return getProvidersProxies(f.providers, touch), nil return getProvidersProxies(f.providers, touch, f.filter), nil
}) })
return elm.([]C.Proxy) return elm.([]C.Proxy)
@@ -132,8 +141,11 @@ func NewFallback(option *GroupCommonOption, providers []provider.ProxyProvider)
Interface: option.Interface, Interface: option.Interface,
RoutingMark: option.RoutingMark, RoutingMark: option.RoutingMark,
}), }),
single: singledo.NewSingle(defaultGetProxiesDuration), single: singledo.NewSingle(defaultGetProxiesDuration),
providers: providers, providers: providers,
disableUDP: option.DisableUDP, disableUDP: option.DisableUDP,
filter: option.Filter,
failedTimes: atomic.NewInt32(-1),
failedTime: atomic.NewInt64(-1),
} }
} }

View File

@@ -23,13 +23,14 @@ type LoadBalance struct {
*outbound.Base *outbound.Base
disableUDP bool disableUDP bool
single *singledo.Single single *singledo.Single
filter string
providers []provider.ProxyProvider providers []provider.ProxyProvider
strategyFn strategyFn strategyFn strategyFn
} }
var errStrategy = errors.New("unsupported strategy") var errStrategy = errors.New("unsupported strategy")
func parseStrategy(config map[string]interface{}) string { func parseStrategy(config map[string]any) string {
if elm, ok := config["strategy"]; ok { if elm, ok := config["strategy"]; ok {
if strategy, ok := elm.(string); ok { if strategy, ok := elm.(string); ok {
return strategy return strategy
@@ -140,8 +141,8 @@ func (lb *LoadBalance) Unwrap(metadata *C.Metadata) C.Proxy {
} }
func (lb *LoadBalance) proxies(touch bool) []C.Proxy { func (lb *LoadBalance) proxies(touch bool) []C.Proxy {
elm, _, _ := lb.single.Do(func() (interface{}, error) { elm, _, _ := lb.single.Do(func() (any, error) {
return getProvidersProxies(lb.providers, touch), nil return getProvidersProxies(lb.providers, touch, lb.filter), nil
}) })
return elm.([]C.Proxy) return elm.([]C.Proxy)
@@ -149,11 +150,11 @@ func (lb *LoadBalance) proxies(touch bool) []C.Proxy {
// MarshalJSON implements C.ProxyAdapter // MarshalJSON implements C.ProxyAdapter
func (lb *LoadBalance) MarshalJSON() ([]byte, error) { func (lb *LoadBalance) MarshalJSON() ([]byte, error) {
var all []string all := []string{}
for _, proxy := range lb.proxies(false) { for _, proxy := range lb.proxies(false) {
all = append(all, proxy.Name()) all = append(all, proxy.Name())
} }
return json.Marshal(map[string]interface{}{ return json.Marshal(map[string]any{
"type": lb.Type().String(), "type": lb.Type().String(),
"all": all, "all": all,
}) })
@@ -180,5 +181,6 @@ func NewLoadBalance(option *GroupCommonOption, providers []provider.ProxyProvide
providers: providers, providers: providers,
strategyFn: strategyFn, strategyFn: strategyFn,
disableUDP: option.DisableUDP, disableUDP: option.DisableUDP,
filter: option.Filter,
}, nil }, nil
} }

View File

@@ -16,7 +16,7 @@ var (
errType = errors.New("unsupport type") errType = errors.New("unsupport type")
errMissProxy = errors.New("`use` or `proxies` missing") errMissProxy = errors.New("`use` or `proxies` missing")
errMissHealthCheck = errors.New("`url` or `interval` missing") errMissHealthCheck = errors.New("`url` or `interval` missing")
errDuplicateProvider = errors.New("`duplicate provider name") errDuplicateProvider = errors.New("duplicate provider name")
) )
type GroupCommonOption struct { type GroupCommonOption struct {
@@ -29,9 +29,10 @@ type GroupCommonOption struct {
Interval int `group:"interval,omitempty"` Interval int `group:"interval,omitempty"`
Lazy bool `group:"lazy,omitempty"` Lazy bool `group:"lazy,omitempty"`
DisableUDP bool `group:"disable-udp,omitempty"` DisableUDP bool `group:"disable-udp,omitempty"`
Filter string `group:"filter,omitempty"`
} }
func ParseProxyGroup(config map[string]interface{}, proxyMap map[string]C.Proxy, providersMap map[string]types.ProxyProvider) (C.ProxyAdapter, error) { 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}) decoder := structure.NewDecoder(structure.Option{TagName: "group", WeaklyTypedInput: true})
groupOption := &GroupCommonOption{ groupOption := &GroupCommonOption{
@@ -59,8 +60,12 @@ func ParseProxyGroup(config map[string]interface{}, proxyMap map[string]C.Proxy,
return nil, err return nil, err
} }
// if Use not empty, drop health check options if _, ok := providersMap[groupName]; ok {
if len(groupOption.Use) != 0 { return nil, errDuplicateProvider
}
// select don't need health check
if groupOption.Type == "select" || groupOption.Type == "relay" {
hc := provider.NewHealthCheck(ps, "", 0, true) hc := provider.NewHealthCheck(ps, "", 0, true)
pd, err := provider.NewCompatibleProvider(groupName, ps, hc) pd, err := provider.NewCompatibleProvider(groupName, ps, hc)
if err != nil { if err != nil {
@@ -68,35 +73,20 @@ func ParseProxyGroup(config map[string]interface{}, proxyMap map[string]C.Proxy,
} }
providers = append(providers, pd) providers = append(providers, pd)
providersMap[groupName] = pd
} else { } else {
if _, ok := providersMap[groupName]; ok { if groupOption.URL == "" || groupOption.Interval == 0 {
return nil, errDuplicateProvider return nil, errMissHealthCheck
} }
// select don't need health check hc := provider.NewHealthCheck(ps, groupOption.URL, uint(groupOption.Interval), groupOption.Lazy)
if groupOption.Type == "select" || groupOption.Type == "relay" { pd, err := provider.NewCompatibleProvider(groupName, ps, hc)
hc := provider.NewHealthCheck(ps, "", 0, true) if err != nil {
pd, err := provider.NewCompatibleProvider(groupName, ps, hc) return nil, err
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), groupOption.Lazy)
pd, err := provider.NewCompatibleProvider(groupName, ps, hc)
if err != nil {
return nil, err
}
providers = append(providers, pd)
providersMap[groupName] = pd
} }
providers = append(providers, pd)
providersMap[groupName] = pd
} }
} }
@@ -106,6 +96,8 @@ func ParseProxyGroup(config map[string]interface{}, proxyMap map[string]C.Proxy,
return nil, err return nil, err
} }
providers = append(providers, list...) providers = append(providers, list...)
} else {
groupOption.Filter = ""
} }
var group C.ProxyAdapter var group C.ProxyAdapter

View File

@@ -16,13 +16,14 @@ type Relay struct {
*outbound.Base *outbound.Base
single *singledo.Single single *singledo.Single
providers []provider.ProxyProvider providers []provider.ProxyProvider
filter string
} }
// DialContext implements C.ProxyAdapter // DialContext implements C.ProxyAdapter
func (r *Relay) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.Conn, error) { func (r *Relay) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.Conn, error) {
var proxies []C.Proxy var proxies []C.Proxy
for _, proxy := range r.proxies(metadata, true) { for _, proxy := range r.proxies(metadata, true) {
if proxy.Type() != C.Direct { if proxy.Type() != C.Direct && proxy.Type() != C.Compatible {
proxies = append(proxies, proxy) proxies = append(proxies, proxy)
} }
} }
@@ -68,19 +69,19 @@ func (r *Relay) DialContext(ctx context.Context, metadata *C.Metadata, opts ...d
// MarshalJSON implements C.ProxyAdapter // MarshalJSON implements C.ProxyAdapter
func (r *Relay) MarshalJSON() ([]byte, error) { func (r *Relay) MarshalJSON() ([]byte, error) {
var all []string all := []string{}
for _, proxy := range r.rawProxies(false) { for _, proxy := range r.rawProxies(false) {
all = append(all, proxy.Name()) all = append(all, proxy.Name())
} }
return json.Marshal(map[string]interface{}{ return json.Marshal(map[string]any{
"type": r.Type().String(), "type": r.Type().String(),
"all": all, "all": all,
}) })
} }
func (r *Relay) rawProxies(touch bool) []C.Proxy { func (r *Relay) rawProxies(touch bool) []C.Proxy {
elm, _, _ := r.single.Do(func() (interface{}, error) { elm, _, _ := r.single.Do(func() (any, error) {
return getProvidersProxies(r.providers, touch), nil return getProvidersProxies(r.providers, touch, r.filter), nil
}) })
return elm.([]C.Proxy) return elm.([]C.Proxy)
@@ -110,5 +111,6 @@ func NewRelay(option *GroupCommonOption, providers []provider.ProxyProvider) *Re
}), }),
single: singledo.NewSingle(defaultGetProxiesDuration), single: singledo.NewSingle(defaultGetProxiesDuration),
providers: providers, providers: providers,
filter: option.Filter,
} }
} }

View File

@@ -17,6 +17,7 @@ type Selector struct {
disableUDP bool disableUDP bool
single *singledo.Single single *singledo.Single
selected string selected string
filter string
providers []provider.ProxyProvider providers []provider.ProxyProvider
} }
@@ -49,12 +50,12 @@ func (s *Selector) SupportUDP() bool {
// MarshalJSON implements C.ProxyAdapter // MarshalJSON implements C.ProxyAdapter
func (s *Selector) MarshalJSON() ([]byte, error) { func (s *Selector) MarshalJSON() ([]byte, error) {
var all []string all := []string{}
for _, proxy := range getProvidersProxies(s.providers, false) { for _, proxy := range getProvidersProxies(s.providers, false, s.filter) {
all = append(all, proxy.Name()) all = append(all, proxy.Name())
} }
return json.Marshal(map[string]interface{}{ return json.Marshal(map[string]any{
"type": s.Type().String(), "type": s.Type().String(),
"now": s.Now(), "now": s.Now(),
"all": all, "all": all,
@@ -66,7 +67,7 @@ func (s *Selector) Now() string {
} }
func (s *Selector) Set(name string) error { func (s *Selector) Set(name string) error {
for _, proxy := range getProvidersProxies(s.providers, false) { for _, proxy := range getProvidersProxies(s.providers, false, s.filter) {
if proxy.Name() == name { if proxy.Name() == name {
s.selected = name s.selected = name
s.single.Reset() s.single.Reset()
@@ -78,13 +79,13 @@ func (s *Selector) Set(name string) error {
} }
// Unwrap implements C.ProxyAdapter // Unwrap implements C.ProxyAdapter
func (s *Selector) Unwrap(metadata *C.Metadata) C.Proxy { func (s *Selector) Unwrap(*C.Metadata) C.Proxy {
return s.selectedProxy(true) return s.selectedProxy(true)
} }
func (s *Selector) selectedProxy(touch bool) C.Proxy { func (s *Selector) selectedProxy(touch bool) C.Proxy {
elm, _, _ := s.single.Do(func() (interface{}, error) { elm, _, _ := s.single.Do(func() (any, error) {
proxies := getProvidersProxies(s.providers, touch) proxies := getProvidersProxies(s.providers, touch, s.filter)
for _, proxy := range proxies { for _, proxy := range proxies {
if proxy.Name() == s.selected { if proxy.Name() == s.selected {
return proxy, nil return proxy, nil
@@ -98,7 +99,6 @@ func (s *Selector) selectedProxy(touch bool) C.Proxy {
} }
func NewSelector(option *GroupCommonOption, providers []provider.ProxyProvider) *Selector { func NewSelector(option *GroupCommonOption, providers []provider.ProxyProvider) *Selector {
selected := providers[0].Proxies()[0].Name()
return &Selector{ return &Selector{
Base: outbound.NewBase(outbound.BaseOption{ Base: outbound.NewBase(outbound.BaseOption{
Name: option.Name, Name: option.Name,
@@ -108,7 +108,8 @@ func NewSelector(option *GroupCommonOption, providers []provider.ProxyProvider)
}), }),
single: singledo.NewSingle(defaultGetProxiesDuration), single: singledo.NewSingle(defaultGetProxiesDuration),
providers: providers, providers: providers,
selected: selected, selected: "COMPATIBLE",
disableUDP: option.DisableUDP, disableUDP: option.DisableUDP,
filter: option.Filter,
} }
} }

View File

@@ -27,6 +27,7 @@ type URLTest struct {
tolerance uint16 tolerance uint16
disableUDP bool disableUDP bool
fastNode C.Proxy fastNode C.Proxy
filter string
single *singledo.Single single *singledo.Single
fastSingle *singledo.Single fastSingle *singledo.Single
providers []provider.ProxyProvider providers []provider.ProxyProvider
@@ -43,10 +44,11 @@ func (u *URLTest) DialContext(ctx context.Context, metadata *C.Metadata, opts ..
c, err = u.fast(true).DialContext(ctx, metadata, u.Base.DialOptions(opts...)...) c, err = u.fast(true).DialContext(ctx, metadata, u.Base.DialOptions(opts...)...)
if err == nil { if err == nil {
c.AppendToChains(u) c.AppendToChains(u)
u.failedTimes.Store(-1)
u.failedTime.Store(-1)
} else { } else {
u.onDialFailed() u.onDialFailed()
} }
return c, err return c, err
} }
@@ -55,28 +57,29 @@ func (u *URLTest) ListenPacketContext(ctx context.Context, metadata *C.Metadata,
pc, err := u.fast(true).ListenPacketContext(ctx, metadata, u.Base.DialOptions(opts...)...) pc, err := u.fast(true).ListenPacketContext(ctx, metadata, u.Base.DialOptions(opts...)...)
if err == nil { if err == nil {
pc.AppendToChains(u) pc.AppendToChains(u)
u.failedTimes.Store(-1)
u.failedTime.Store(-1)
} else { } else {
u.onDialFailed() u.onDialFailed()
} }
return pc, err return pc, err
} }
// Unwrap implements C.ProxyAdapter // Unwrap implements C.ProxyAdapter
func (u *URLTest) Unwrap(metadata *C.Metadata) C.Proxy { func (u *URLTest) Unwrap(*C.Metadata) C.Proxy {
return u.fast(true) return u.fast(true)
} }
func (u *URLTest) proxies(touch bool) []C.Proxy { func (u *URLTest) proxies(touch bool) []C.Proxy {
elm, _, _ := u.single.Do(func() (interface{}, error) { elm, _, _ := u.single.Do(func() (any, error) {
return getProvidersProxies(u.providers, touch), nil return getProvidersProxies(u.providers, touch, u.filter), nil
}) })
return elm.([]C.Proxy) return elm.([]C.Proxy)
} }
func (u *URLTest) fast(touch bool) C.Proxy { func (u *URLTest) fast(touch bool) C.Proxy {
elm, _, _ := u.fastSingle.Do(func() (interface{}, error) { elm, _, _ := u.fastSingle.Do(func() (any, error) {
proxies := u.proxies(touch) proxies := u.proxies(touch)
fast := proxies[0] fast := proxies[0]
min := fast.LastDelay() min := fast.LastDelay()
@@ -120,11 +123,11 @@ func (u *URLTest) SupportUDP() bool {
// MarshalJSON implements C.ProxyAdapter // MarshalJSON implements C.ProxyAdapter
func (u *URLTest) MarshalJSON() ([]byte, error) { func (u *URLTest) MarshalJSON() ([]byte, error) {
var all []string all := []string{}
for _, proxy := range u.proxies(false) { for _, proxy := range u.proxies(false) {
all = append(all, proxy.Name()) all = append(all, proxy.Name())
} }
return json.Marshal(map[string]interface{}{ return json.Marshal(map[string]any{
"type": u.Type().String(), "type": u.Type().String(),
"now": u.Now(), "now": u.Now(),
"all": all, "all": all,
@@ -142,20 +145,22 @@ func (u *URLTest) onDialFailed() {
u.failedTimes.Store(-1) u.failedTimes.Store(-1)
u.failedTime.Store(-1) u.failedTime.Store(-1)
} else { } else {
u.failedTimes.Inc() failedCount := u.failedTimes.Inc()
failedCount := u.failedTimes.Load()
log.Warnln("%s failed count: %d", u.Name(), failedCount) log.Warnln("%s failed count: %d", u.Name(), failedCount)
if failedCount > 5 { if failedCount >= 5 {
log.Debugln("%s failed multiple times.", u.Name()) log.Warnln("because %s failed multiple times, active health check", u.Name())
for _, proxyProvider := range u.providers { for _, proxyProvider := range u.providers {
go proxyProvider.HealthCheck() go proxyProvider.HealthCheck()
} }
u.failedTimes.Store(-1)
u.failedTime.Store(-1)
} }
} }
} }
} }
func parseURLTestOption(config map[string]interface{}) []urlTestOption { func parseURLTestOption(config map[string]any) []urlTestOption {
opts := []urlTestOption{} opts := []urlTestOption{}
// tolerance // tolerance
@@ -180,6 +185,7 @@ func NewURLTest(option *GroupCommonOption, providers []provider.ProxyProvider, o
fastSingle: singledo.NewSingle(time.Second * 10), fastSingle: singledo.NewSingle(time.Second * 10),
providers: providers, providers: providers,
disableUDP: option.DisableUDP, disableUDP: option.DisableUDP,
filter: option.Filter,
failedTimes: atomic.NewInt32(-1), failedTimes: atomic.NewInt32(-1),
failedTime: atomic.NewInt64(-1), failedTime: atomic.NewInt64(-1),
} }

View File

@@ -8,7 +8,7 @@ import (
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
) )
func ParseProxy(mapping map[string]interface{}) (C.Proxy, error) { func ParseProxy(mapping map[string]any) (C.Proxy, error) {
decoder := structure.NewDecoder(structure.Option{TagName: "proxy", WeaklyTypedInput: true}) decoder := structure.NewDecoder(structure.Option{TagName: "proxy", WeaklyTypedInput: true})
proxyType, existType := mapping["type"].(string) proxyType, existType := mapping["type"].(string)
if !existType { if !existType {

View File

@@ -16,7 +16,7 @@ var (
dirMode os.FileMode = 0o755 dirMode os.FileMode = 0o755
) )
type parser = func([]byte) (interface{}, error) type parser = func([]byte) (any, error)
type fetcher struct { type fetcher struct {
name string name string
@@ -26,7 +26,7 @@ type fetcher struct {
done chan struct{} done chan struct{}
hash [16]byte hash [16]byte
parser parser parser parser
onUpdate func(interface{}) onUpdate func(any)
} }
func (f *fetcher) Name() string { func (f *fetcher) Name() string {
@@ -37,7 +37,7 @@ func (f *fetcher) VehicleType() types.VehicleType {
return f.vehicle.Type() return f.vehicle.Type()
} }
func (f *fetcher) Initial() (interface{}, error) { func (f *fetcher) Initial() (any, error) {
var ( var (
buf []byte buf []byte
err error err error
@@ -92,7 +92,7 @@ func (f *fetcher) Initial() (interface{}, error) {
return proxies, nil return proxies, nil
} }
func (f *fetcher) Update() (interface{}, bool, error) { func (f *fetcher) Update() (any, bool, error) {
buf, err := f.vehicle.Read() buf, err := f.vehicle.Read()
if err != nil { if err != nil {
return nil, false, err return nil, false, err
@@ -102,6 +102,7 @@ func (f *fetcher) Update() (interface{}, bool, error) {
hash := md5.Sum(buf) hash := md5.Sum(buf)
if bytes.Equal(f.hash[:], hash[:]) { if bytes.Equal(f.hash[:], hash[:]) {
f.updatedAt = &now f.updatedAt = &now
os.Chtimes(f.vehicle.Path(), now, now)
return nil, true, nil return nil, true, nil
} }
@@ -167,7 +168,7 @@ func safeWrite(path string, buf []byte) error {
return os.WriteFile(path, buf, fileMode) return os.WriteFile(path, buf, fileMode)
} }
func newFetcher(name string, interval time.Duration, vehicle types.Vehicle, parser parser, onUpdate func(interface{})) *fetcher { func newFetcher(name string, interval time.Duration, vehicle types.Vehicle, parser parser, onUpdate func(any)) *fetcher {
var ticker *time.Ticker var ticker *time.Ticker
if interval != 0 { if interval != 0 {
ticker = time.NewTicker(interval) ticker = time.NewTicker(interval)

View File

@@ -31,7 +31,13 @@ type HealthCheck struct {
func (hc *HealthCheck) process() { func (hc *HealthCheck) process() {
ticker := time.NewTicker(time.Duration(hc.interval) * time.Second) ticker := time.NewTicker(time.Duration(hc.interval) * time.Second)
go hc.check() go func() {
t := time.NewTicker(30 * time.Second)
<-t.C
t.Stop()
hc.check()
}()
for { for {
select { select {
case <-ticker.C: case <-ticker.C:
@@ -62,7 +68,7 @@ func (hc *HealthCheck) check() {
b, _ := batch.New(context.Background(), batch.WithConcurrencyNum(10)) b, _ := batch.New(context.Background(), batch.WithConcurrencyNum(10))
for _, proxy := range hc.proxies { for _, proxy := range hc.proxies {
p := proxy p := proxy
b.Go(p.Name(), func() (interface{}, error) { b.Go(p.Name(), func() (any, error) {
ctx, cancel := context.WithTimeout(context.Background(), defaultURLTestTimeout) ctx, cancel := context.WithTimeout(context.Background(), defaultURLTestTimeout)
defer cancel() defer cancel()
p.URLTest(ctx, hc.url) p.URLTest(ctx, hc.url)

View File

@@ -24,10 +24,11 @@ type proxyProviderSchema struct {
Path string `provider:"path"` Path string `provider:"path"`
URL string `provider:"url,omitempty"` URL string `provider:"url,omitempty"`
Interval int `provider:"interval,omitempty"` Interval int `provider:"interval,omitempty"`
Filter string `provider:"filter,omitempty"`
HealthCheck healthCheckSchema `provider:"health-check,omitempty"` HealthCheck healthCheckSchema `provider:"health-check,omitempty"`
} }
func ParseProxyProvider(name string, mapping map[string]interface{}) (types.ProxyProvider, error) { func ParseProxyProvider(name string, mapping map[string]any) (types.ProxyProvider, error) {
decoder := structure.NewDecoder(structure.Option{TagName: "provider", WeaklyTypedInput: true}) decoder := structure.NewDecoder(structure.Option{TagName: "provider", WeaklyTypedInput: true})
schema := &proxyProviderSchema{ schema := &proxyProviderSchema{
@@ -58,5 +59,6 @@ func ParseProxyProvider(name string, mapping map[string]interface{}) (types.Prox
} }
interval := time.Duration(uint(schema.Interval)) * time.Second 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

@@ -4,6 +4,7 @@ import (
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
"github.com/dlclark/regexp2"
"runtime" "runtime"
"time" "time"
@@ -19,7 +20,7 @@ const (
) )
type ProxySchema struct { type ProxySchema struct {
Proxies []map[string]interface{} `yaml:"proxies"` Proxies []map[string]any `yaml:"proxies"`
} }
// for auto gc // for auto gc
@@ -34,12 +35,13 @@ type proxySetProvider struct {
} }
func (pp *proxySetProvider) MarshalJSON() ([]byte, error) { func (pp *proxySetProvider) MarshalJSON() ([]byte, error) {
return json.Marshal(map[string]interface{}{ return json.Marshal(map[string]any{
"name": pp.Name(), "name": pp.Name(),
"type": pp.Type().String(), "type": pp.Type().String(),
"vehicleType": pp.VehicleType().String(), "vehicleType": pp.VehicleType().String(),
"proxies": pp.Proxies(), "proxies": pp.Proxies(),
"updatedAt": pp.updatedAt, //TODO maybe error because year value overflow
"updatedAt": pp.updatedAt,
}) })
} }
@@ -66,6 +68,10 @@ func (pp *proxySetProvider) Initial() error {
} }
pp.onUpdate(elm) pp.onUpdate(elm)
if pp.healthCheck.auto() {
go pp.healthCheck.process()
}
return nil return nil
} }
@@ -82,33 +88,6 @@ func (pp *proxySetProvider) ProxiesWithTouch() []C.Proxy {
return pp.Proxies() return pp.Proxies()
} }
func proxiesParse(buf []byte) (interface{}, 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 := adapter.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) { func (pp *proxySetProvider) setProxies(proxies []C.Proxy) {
pp.proxies = proxies pp.proxies = proxies
pp.healthCheck.setProxy(proxies) pp.healthCheck.setProxy(proxies)
@@ -122,9 +101,11 @@ func stopProxyProvider(pd *ProxySetProvider) {
pd.fetcher.Destroy() pd.fetcher.Destroy()
} }
func NewProxySetProvider(name string, interval time.Duration, vehicle types.Vehicle, hc *HealthCheck) *ProxySetProvider { func NewProxySetProvider(name string, interval time.Duration, filter string, vehicle types.Vehicle, hc *HealthCheck) (*ProxySetProvider, error) {
if hc.auto() { //filterReg, err := regexp.Compile(filter)
go hc.process() filterReg, err := regexp2.Compile(filter, 0)
if err != nil {
return nil, fmt.Errorf("invalid filter regex: %w", err)
} }
pd := &proxySetProvider{ pd := &proxySetProvider{
@@ -132,17 +113,52 @@ func NewProxySetProvider(name string, interval time.Duration, vehicle types.Vehi
healthCheck: hc, healthCheck: hc,
} }
onUpdate := func(elm interface{}) { onUpdate := func(elm any) {
ret := elm.([]C.Proxy) ret := elm.([]C.Proxy)
pd.setProxies(ret) pd.setProxies(ret)
} }
fetcher := newFetcher(name, interval, vehicle, proxiesParse, onUpdate) proxiesParseAndFilter := func(buf []byte) (any, 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 {
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
}
fetcher := newFetcher(name, interval, vehicle, proxiesParseAndFilter, onUpdate)
pd.fetcher = fetcher pd.fetcher = fetcher
wrapper := &ProxySetProvider{pd} wrapper := &ProxySetProvider{pd}
runtime.SetFinalizer(wrapper, stopProxyProvider) runtime.SetFinalizer(wrapper, stopProxyProvider)
return wrapper return wrapper, nil
} }
// for auto gc // for auto gc
@@ -157,7 +173,7 @@ type compatibleProvider struct {
} }
func (cp *compatibleProvider) MarshalJSON() ([]byte, error) { func (cp *compatibleProvider) MarshalJSON() ([]byte, error) {
return json.Marshal(map[string]interface{}{ return json.Marshal(map[string]any{
"name": cp.Name(), "name": cp.Name(),
"type": cp.Type().String(), "type": cp.Type().String(),
"vehicleType": cp.VehicleType().String(), "vehicleType": cp.VehicleType().String(),
@@ -178,6 +194,10 @@ func (cp *compatibleProvider) Update() error {
} }
func (cp *compatibleProvider) Initial() error { func (cp *compatibleProvider) Initial() error {
if cp.healthCheck.auto() {
go cp.healthCheck.process()
}
return nil return nil
} }
@@ -207,10 +227,6 @@ func NewCompatibleProvider(name string, proxies []C.Proxy, hc *HealthCheck) (*Co
return nil, errors.New("provider need one proxy at least") return nil, errors.New("provider need one proxy at least")
} }
if hc.auto() {
go hc.process()
}
pd := &compatibleProvider{ pd := &compatibleProvider{
name: name, name: name,
proxies: proxies, proxies: proxies,

View File

@@ -2,6 +2,8 @@ package provider
import ( import (
"context" "context"
"github.com/Dreamacro/clash/component/dialer"
"github.com/Dreamacro/clash/listener/inner"
"io" "io"
"net" "net"
"net/http" "net/http"
@@ -9,7 +11,7 @@ import (
"os" "os"
"time" "time"
"github.com/Dreamacro/clash/component/dialer" netHttp "github.com/Dreamacro/clash/common/net"
types "github.com/Dreamacro/clash/constant/provider" types "github.com/Dreamacro/clash/constant/provider"
) )
@@ -56,6 +58,8 @@ func (h *HTTPVehicle) Read() ([]byte, error) {
} }
req, err := http.NewRequest(http.MethodGet, uri.String(), nil) req, err := http.NewRequest(http.MethodGet, uri.String(), nil)
req.Header.Set("user-agent", netHttp.UA)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -74,14 +78,21 @@ func (h *HTTPVehicle) Read() ([]byte, error) {
TLSHandshakeTimeout: 10 * time.Second, TLSHandshakeTimeout: 10 * time.Second,
ExpectContinueTimeout: 1 * time.Second, ExpectContinueTimeout: 1 * time.Second,
DialContext: func(ctx context.Context, network, address string) (net.Conn, error) { DialContext: func(ctx context.Context, network, address string) (net.Conn, error) {
return dialer.DialContext(ctx, network, address) conn := inner.HandleTcp(address, uri.Hostname())
return conn, nil
}, },
} }
client := http.Client{Transport: transport} client := http.Client{Transport: transport}
resp, err := client.Do(req) resp, err := client.Do(req)
if err != nil { if err != nil {
return nil, err transport.DialContext = func(ctx context.Context, network, address string) (net.Conn, error) {
return dialer.DialContext(ctx, network, address)
}
resp, err = client.Do(req)
if err != nil {
return nil, err
}
} }
defer resp.Body.Close() defer resp.Body.Close()

View File

@@ -8,7 +8,7 @@ import (
type Option = func(b *Batch) type Option = func(b *Batch)
type Result struct { type Result struct {
Value interface{} Value any
Err error Err error
} }
@@ -38,7 +38,7 @@ type Batch struct {
cancel func() cancel func()
} }
func (b *Batch) Go(key string, fn func() (interface{}, error)) { func (b *Batch) Go(key string, fn func() (any, error)) {
b.wg.Add(1) b.wg.Add(1)
go func() { go func() {
defer b.wg.Done() defer b.wg.Done()

View File

@@ -14,11 +14,11 @@ func TestBatch(t *testing.T) {
b, _ := New(context.Background()) b, _ := New(context.Background())
now := time.Now() now := time.Now()
b.Go("foo", func() (interface{}, error) { b.Go("foo", func() (any, error) {
time.Sleep(time.Millisecond * 100) time.Sleep(time.Millisecond * 100)
return "foo", nil return "foo", nil
}) })
b.Go("bar", func() (interface{}, error) { b.Go("bar", func() (any, error) {
time.Sleep(time.Millisecond * 150) time.Sleep(time.Millisecond * 150)
return "bar", nil return "bar", nil
}) })
@@ -45,7 +45,7 @@ func TestBatchWithConcurrencyNum(t *testing.T) {
now := time.Now() now := time.Now()
for i := 0; i < 7; i++ { for i := 0; i < 7; i++ {
idx := i idx := i
b.Go(strconv.Itoa(idx), func() (interface{}, error) { b.Go(strconv.Itoa(idx), func() (any, error) {
time.Sleep(time.Millisecond * 100) time.Sleep(time.Millisecond * 100)
return strconv.Itoa(idx), nil return strconv.Itoa(idx), nil
}) })
@@ -64,12 +64,12 @@ func TestBatchWithConcurrencyNum(t *testing.T) {
func TestBatchContext(t *testing.T) { func TestBatchContext(t *testing.T) {
b, ctx := New(context.Background()) b, ctx := New(context.Background())
b.Go("error", func() (interface{}, error) { b.Go("error", func() (any, error) {
time.Sleep(time.Millisecond * 100) time.Sleep(time.Millisecond * 100)
return nil, errors.New("test error") return nil, errors.New("test error")
}) })
b.Go("ctx", func() (interface{}, error) { b.Go("ctx", func() (any, error) {
<-ctx.Done() <-ctx.Done()
return nil, ctx.Err() return nil, ctx.Err()
}) })

10
common/cache/cache.go vendored
View File

@@ -18,11 +18,11 @@ type cache struct {
type element struct { type element struct {
Expired time.Time Expired time.Time
Payload interface{} Payload any
} }
// Put element in Cache with its ttl // Put element in Cache with its ttl
func (c *cache) Put(key interface{}, payload interface{}, ttl time.Duration) { func (c *cache) Put(key any, payload any, ttl time.Duration) {
c.mapping.Store(key, &element{ c.mapping.Store(key, &element{
Payload: payload, Payload: payload,
Expired: time.Now().Add(ttl), Expired: time.Now().Add(ttl),
@@ -30,7 +30,7 @@ func (c *cache) Put(key interface{}, payload interface{}, ttl time.Duration) {
} }
// Get element in Cache, and drop when it expired // Get element in Cache, and drop when it expired
func (c *cache) Get(key interface{}) interface{} { func (c *cache) Get(key any) any {
item, exist := c.mapping.Load(key) item, exist := c.mapping.Load(key)
if !exist { if !exist {
return nil return nil
@@ -45,7 +45,7 @@ func (c *cache) Get(key interface{}) interface{} {
} }
// GetWithExpire element in Cache with Expire Time // GetWithExpire element in Cache with Expire Time
func (c *cache) GetWithExpire(key interface{}) (payload interface{}, expired time.Time) { func (c *cache) GetWithExpire(key any) (payload any, expired time.Time) {
item, exist := c.mapping.Load(key) item, exist := c.mapping.Load(key)
if !exist { if !exist {
return return
@@ -60,7 +60,7 @@ func (c *cache) GetWithExpire(key interface{}) (payload interface{}, expired tim
} }
func (c *cache) cleanup() { func (c *cache) cleanup() {
c.mapping.Range(func(k, v interface{}) bool { c.mapping.Range(func(k, v any) bool {
key := k.(string) key := k.(string)
elm := v.(*element) elm := v.(*element)
if time.Since(elm.Expired) > 0 { if time.Since(elm.Expired) > 0 {

View File

@@ -12,7 +12,7 @@ import (
type Option func(*LruCache) type Option func(*LruCache)
// EvictCallback is used to get a callback when a cache entry is evicted // EvictCallback is used to get a callback when a cache entry is evicted
type EvictCallback = func(key interface{}, value interface{}) type EvictCallback = func(key any, value any)
// WithEvict set the evict callback // WithEvict set the evict callback
func WithEvict(cb EvictCallback) Option { func WithEvict(cb EvictCallback) Option {
@@ -57,7 +57,7 @@ type LruCache struct {
maxAge int64 maxAge int64
maxSize int maxSize int
mu sync.Mutex mu sync.Mutex
cache map[interface{}]*list.Element cache map[any]*list.Element
lru *list.List // Front is least-recent lru *list.List // Front is least-recent
updateAgeOnGet bool updateAgeOnGet bool
staleReturn bool staleReturn bool
@@ -68,7 +68,7 @@ type LruCache struct {
func NewLRUCache(options ...Option) *LruCache { func NewLRUCache(options ...Option) *LruCache {
lc := &LruCache{ lc := &LruCache{
lru: list.New(), lru: list.New(),
cache: make(map[interface{}]*list.Element), cache: make(map[any]*list.Element),
} }
for _, option := range options { for _, option := range options {
@@ -78,9 +78,9 @@ func NewLRUCache(options ...Option) *LruCache {
return lc 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. // set to true if the key was found.
func (c *LruCache) Get(key interface{}) (interface{}, bool) { func (c *LruCache) Get(key any) (any, bool) {
entry := c.get(key) entry := c.get(key)
if entry == nil { if entry == nil {
return nil, false return nil, false
@@ -90,11 +90,11 @@ func (c *LruCache) Get(key interface{}) (interface{}, bool) {
return value, true return value, true
} }
// GetWithExpire returns the interface{} representation of a cached response, // GetWithExpire returns the any representation of a cached response,
// a time.Time Give expected expires, // a time.Time Give expected expires,
// and a bool set to true if the key was found. // 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. // This method will NOT check the maxAge of element and will NOT update the expires.
func (c *LruCache) GetWithExpire(key interface{}) (interface{}, time.Time, bool) { func (c *LruCache) GetWithExpire(key any) (any, time.Time, bool) {
entry := c.get(key) entry := c.get(key)
if entry == nil { if entry == nil {
return nil, time.Time{}, false return nil, time.Time{}, false
@@ -104,7 +104,7 @@ func (c *LruCache) GetWithExpire(key interface{}) (interface{}, time.Time, bool)
} }
// Exist returns if key exist in cache but not put item to the head of linked list // 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) Exist(key any) bool {
c.mu.Lock() c.mu.Lock()
defer c.mu.Unlock() defer c.mu.Unlock()
@@ -112,8 +112,8 @@ func (c *LruCache) Exist(key interface{}) bool {
return ok return ok
} }
// Set stores the interface{} representation of a response for a given key. // Set stores the any representation of a response for a given key.
func (c *LruCache) Set(key interface{}, value interface{}) { func (c *LruCache) Set(key any, value any) {
expires := int64(0) expires := int64(0)
if c.maxAge > 0 { if c.maxAge > 0 {
expires = time.Now().Unix() + c.maxAge expires = time.Now().Unix() + c.maxAge
@@ -121,9 +121,9 @@ func (c *LruCache) Set(key interface{}, value interface{}) {
c.SetWithExpire(key, value, time.Unix(expires, 0)) c.SetWithExpire(key, value, time.Unix(expires, 0))
} }
// SetWithExpire stores the interface{} representation of a response for a given key and given expires. // SetWithExpire stores the any representation of a response for a given key and given expires.
// The expires time will round to second. // The expires time will round to second.
func (c *LruCache) SetWithExpire(key interface{}, value interface{}, expires time.Time) { func (c *LruCache) SetWithExpire(key any, value any, expires time.Time) {
c.mu.Lock() c.mu.Lock()
defer c.mu.Unlock() defer c.mu.Unlock()
@@ -155,7 +155,7 @@ func (c *LruCache) CloneTo(n *LruCache) {
defer n.mu.Unlock() defer n.mu.Unlock()
n.lru = list.New() n.lru = list.New()
n.cache = make(map[interface{}]*list.Element) n.cache = make(map[any]*list.Element)
for e := c.lru.Front(); e != nil; e = e.Next() { for e := c.lru.Front(); e != nil; e = e.Next() {
elm := e.Value.(*entry) elm := e.Value.(*entry)
@@ -163,7 +163,7 @@ func (c *LruCache) CloneTo(n *LruCache) {
} }
} }
func (c *LruCache) get(key interface{}) *entry { func (c *LruCache) get(key any) *entry {
c.mu.Lock() c.mu.Lock()
defer c.mu.Unlock() defer c.mu.Unlock()
@@ -188,7 +188,7 @@ func (c *LruCache) get(key interface{}) *entry {
} }
// Delete removes the value associated with a key. // Delete removes the value associated with a key.
func (c *LruCache) Delete(key interface{}) { func (c *LruCache) Delete(key any) {
c.mu.Lock() c.mu.Lock()
if le, ok := c.cache[key]; ok { if le, ok := c.cache[key]; ok {
@@ -216,8 +216,17 @@ func (c *LruCache) deleteElement(le *list.Element) {
} }
} }
func (c *LruCache) Clear() error {
c.mu.Lock()
c.cache = make(map[any]*list.Element)
c.mu.Unlock()
return nil
}
type entry struct { type entry struct {
key interface{} key any
value interface{} value any
expires int64 expires int64
} }

View File

@@ -126,7 +126,7 @@ func TestExist(t *testing.T) {
func TestEvict(t *testing.T) { func TestEvict(t *testing.T) {
temp := 0 temp := 0
evict := func(key interface{}, value interface{}) { evict := func(key any, value any) {
temp = key.(int) + value.(int) temp = key.(int) + value.(int)
} }

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

@@ -0,0 +1,36 @@
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
}

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

@@ -0,0 +1,11 @@
//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++
}

5
common/net/http.go Normal file
View File

@@ -0,0 +1,5 @@
package net
const (
UA = "Clash"
)

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
}

View File

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

View File

@@ -9,8 +9,8 @@ import (
"go.uber.org/atomic" "go.uber.org/atomic"
) )
func iterator(item []interface{}) chan interface{} { func iterator(item []any) chan any {
ch := make(chan interface{}) ch := make(chan any)
go func() { go func() {
time.Sleep(100 * time.Millisecond) time.Sleep(100 * time.Millisecond)
for _, elm := range item { for _, elm := range item {
@@ -22,7 +22,7 @@ func iterator(item []interface{}) chan interface{} {
} }
func TestObservable(t *testing.T) { func TestObservable(t *testing.T) {
iter := iterator([]interface{}{1, 2, 3, 4, 5}) iter := iterator([]any{1, 2, 3, 4, 5})
src := NewObservable(iter) src := NewObservable(iter)
data, err := src.Subscribe() data, err := src.Subscribe()
assert.Nil(t, err) assert.Nil(t, err)
@@ -34,7 +34,7 @@ func TestObservable(t *testing.T) {
} }
func TestObservable_MultiSubscribe(t *testing.T) { func TestObservable_MultiSubscribe(t *testing.T) {
iter := iterator([]interface{}{1, 2, 3, 4, 5}) iter := iterator([]any{1, 2, 3, 4, 5})
src := NewObservable(iter) src := NewObservable(iter)
ch1, _ := src.Subscribe() ch1, _ := src.Subscribe()
ch2, _ := src.Subscribe() ch2, _ := src.Subscribe()
@@ -42,7 +42,7 @@ func TestObservable_MultiSubscribe(t *testing.T) {
var wg sync.WaitGroup var wg sync.WaitGroup
wg.Add(2) wg.Add(2)
waitCh := func(ch <-chan interface{}) { waitCh := func(ch <-chan any) {
for range ch { for range ch {
count.Inc() count.Inc()
} }
@@ -55,7 +55,7 @@ func TestObservable_MultiSubscribe(t *testing.T) {
} }
func TestObservable_UnSubscribe(t *testing.T) { func TestObservable_UnSubscribe(t *testing.T) {
iter := iterator([]interface{}{1, 2, 3, 4, 5}) iter := iterator([]any{1, 2, 3, 4, 5})
src := NewObservable(iter) src := NewObservable(iter)
data, err := src.Subscribe() data, err := src.Subscribe()
assert.Nil(t, err) assert.Nil(t, err)
@@ -65,7 +65,7 @@ func TestObservable_UnSubscribe(t *testing.T) {
} }
func TestObservable_SubscribeClosedSource(t *testing.T) { func TestObservable_SubscribeClosedSource(t *testing.T) {
iter := iterator([]interface{}{1}) iter := iterator([]any{1})
src := NewObservable(iter) src := NewObservable(iter)
data, _ := src.Subscribe() data, _ := src.Subscribe()
<-data <-data
@@ -75,14 +75,14 @@ func TestObservable_SubscribeClosedSource(t *testing.T) {
} }
func TestObservable_UnSubscribeWithNotExistSubscription(t *testing.T) { func TestObservable_UnSubscribeWithNotExistSubscription(t *testing.T) {
sub := Subscription(make(chan interface{})) sub := Subscription(make(chan any))
iter := iterator([]interface{}{1}) iter := iterator([]any{1})
src := NewObservable(iter) src := NewObservable(iter)
src.UnSubscribe(sub) src.UnSubscribe(sub)
} }
func TestObservable_SubscribeGoroutineLeak(t *testing.T) { func TestObservable_SubscribeGoroutineLeak(t *testing.T) {
iter := iterator([]interface{}{1, 2, 3, 4, 5}) iter := iterator([]any{1, 2, 3, 4, 5})
src := NewObservable(iter) src := NewObservable(iter)
max := 100 max := 100
@@ -94,7 +94,7 @@ func TestObservable_SubscribeGoroutineLeak(t *testing.T) {
var wg sync.WaitGroup var wg sync.WaitGroup
wg.Add(max) wg.Add(max)
waitCh := func(ch <-chan interface{}) { waitCh := func(ch <-chan any) {
for range ch { for range ch {
} }
wg.Done() wg.Done()
@@ -115,7 +115,7 @@ func TestObservable_SubscribeGoroutineLeak(t *testing.T) {
} }
func Benchmark_Observable_1000(b *testing.B) { func Benchmark_Observable_1000(b *testing.B) {
ch := make(chan interface{}) ch := make(chan any)
o := NewObservable(ch) o := NewObservable(ch)
num := 1000 num := 1000

View File

@@ -4,14 +4,14 @@ import (
"sync" "sync"
) )
type Subscription <-chan interface{} type Subscription <-chan any
type Subscriber struct { type Subscriber struct {
buffer chan interface{} buffer chan any
once sync.Once once sync.Once
} }
func (s *Subscriber) Emit(item interface{}) { func (s *Subscriber) Emit(item any) {
s.buffer <- item s.buffer <- item
} }
@@ -27,7 +27,7 @@ func (s *Subscriber) Close() {
func newSubscriber() *Subscriber { func newSubscriber() *Subscriber {
sub := &Subscriber{ sub := &Subscriber{
buffer: make(chan interface{}, 200), buffer: make(chan any, 200),
} }
return sub return sub
} }

View File

@@ -17,7 +17,7 @@ type Picker struct {
once sync.Once once sync.Once
errOnce sync.Once errOnce sync.Once
result interface{} result any
err error err error
} }
@@ -43,7 +43,7 @@ func WithTimeout(ctx context.Context, timeout time.Duration) (*Picker, context.C
// Wait blocks until all function calls from the Go method have returned, // Wait blocks until all function calls from the Go method have returned,
// then returns the first nil error result (if any) from them. // then returns the first nil error result (if any) from them.
func (p *Picker) Wait() interface{} { func (p *Picker) Wait() any {
p.wg.Wait() p.wg.Wait()
if p.cancel != nil { if p.cancel != nil {
p.cancel() p.cancel()
@@ -58,7 +58,7 @@ func (p *Picker) Error() error {
// Go calls the given function in a new goroutine. // 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. // 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) Go(f func() (any, error)) {
p.wg.Add(1) p.wg.Add(1)
go func() { go func() {

View File

@@ -8,8 +8,8 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
func sleepAndSend(ctx context.Context, delay int, input interface{}) func() (interface{}, error) { func sleepAndSend(ctx context.Context, delay int, input any) func() (any, error) {
return func() (interface{}, error) { return func() (any, error) {
timer := time.NewTimer(time.Millisecond * time.Duration(delay)) timer := time.NewTimer(time.Millisecond * time.Duration(delay))
select { select {
case <-timer.C: case <-timer.C:

View File

@@ -23,7 +23,7 @@ func NewAllocator() *Allocator {
alloc.buffers = make([]sync.Pool, 17) // 1B -> 64K alloc.buffers = make([]sync.Pool, 17) // 1B -> 64K
for k := range alloc.buffers { for k := range alloc.buffers {
i := k i := k
alloc.buffers[k].New = func() interface{} { alloc.buffers[k].New = func() any {
return make([]byte, 1<<uint32(i)) return make([]byte, 1<<uint32(i))
} }
} }

View File

@@ -5,7 +5,7 @@ import (
"sync" "sync"
) )
var bufferPool = sync.Pool{New: func() interface{} { return &bytes.Buffer{} }} var bufferPool = sync.Pool{New: func() any { return &bytes.Buffer{} }}
func GetBuffer() *bytes.Buffer { func GetBuffer() *bytes.Buffer {
return bufferPool.Get().(*bytes.Buffer) return bufferPool.Get().(*bytes.Buffer)

View File

@@ -6,12 +6,12 @@ import (
// Queue is a simple concurrent safe queue // Queue is a simple concurrent safe queue
type Queue struct { type Queue struct {
items []interface{} items []any
lock sync.RWMutex lock sync.RWMutex
} }
// Put add the item to the queue. // Put add the item to the queue.
func (q *Queue) Put(items ...interface{}) { func (q *Queue) Put(items ...any) {
if len(items) == 0 { if len(items) == 0 {
return return
} }
@@ -22,7 +22,7 @@ func (q *Queue) Put(items ...interface{}) {
} }
// Pop returns the head of items. // Pop returns the head of items.
func (q *Queue) Pop() interface{} { func (q *Queue) Pop() any {
if len(q.items) == 0 { if len(q.items) == 0 {
return nil return nil
} }
@@ -35,7 +35,7 @@ func (q *Queue) Pop() interface{} {
} }
// Last returns the last of item. // Last returns the last of item.
func (q *Queue) Last() interface{} { func (q *Queue) Last() any {
if len(q.items) == 0 { if len(q.items) == 0 {
return nil return nil
} }
@@ -47,8 +47,8 @@ func (q *Queue) Last() interface{} {
} }
// Copy get the copy of queue. // Copy get the copy of queue.
func (q *Queue) Copy() []interface{} { func (q *Queue) Copy() []any {
items := []interface{}{} items := []any{}
q.lock.RLock() q.lock.RLock()
items = append(items, q.items...) items = append(items, q.items...)
q.lock.RUnlock() q.lock.RUnlock()
@@ -66,6 +66,6 @@ func (q *Queue) Len() int64 {
// New is a constructor for a new concurrent safe queue. // New is a constructor for a new concurrent safe queue.
func New(hint int64) *Queue { func New(hint int64) *Queue {
return &Queue{ return &Queue{
items: make([]interface{}, 0, hint), items: make([]any, 0, hint),
} }
} }

View File

@@ -7,7 +7,7 @@ import (
type call struct { type call struct {
wg sync.WaitGroup wg sync.WaitGroup
val interface{} val any
err error err error
} }
@@ -20,13 +20,13 @@ type Single struct {
} }
type Result struct { type Result struct {
Val interface{} Val any
Err error Err error
} }
// Do single.Do likes sync.singleFlight // Do single.Do likes sync.singleFlight
//lint:ignore ST1008 it likes sync.singleFlight //lint:ignore ST1008 it likes sync.singleFlight
func (s *Single) Do(fn func() (interface{}, error)) (v interface{}, err error, shared bool) { func (s *Single) Do(fn func() (any, error)) (v any, err error, shared bool) {
s.mux.Lock() s.mux.Lock()
now := time.Now() now := time.Now()
if now.Before(s.last.Add(s.wait)) { if now.Before(s.last.Add(s.wait)) {

View File

@@ -13,7 +13,7 @@ func TestBasic(t *testing.T) {
single := NewSingle(time.Millisecond * 30) single := NewSingle(time.Millisecond * 30)
foo := 0 foo := 0
shardCount := atomic.NewInt32(0) shardCount := atomic.NewInt32(0)
call := func() (interface{}, error) { call := func() (any, error) {
foo++ foo++
time.Sleep(time.Millisecond * 5) time.Sleep(time.Millisecond * 5)
return nil, nil return nil, nil
@@ -40,7 +40,7 @@ func TestBasic(t *testing.T) {
func TestTimer(t *testing.T) { func TestTimer(t *testing.T) {
single := NewSingle(time.Millisecond * 30) single := NewSingle(time.Millisecond * 30)
foo := 0 foo := 0
call := func() (interface{}, error) { call := func() (any, error) {
foo++ foo++
return nil, nil return nil, nil
} }
@@ -56,7 +56,7 @@ func TestTimer(t *testing.T) {
func TestReset(t *testing.T) { func TestReset(t *testing.T) {
single := NewSingle(time.Millisecond * 30) single := NewSingle(time.Millisecond * 30)
foo := 0 foo := 0
call := func() (interface{}, error) { call := func() (any, error) {
foo++ foo++
return nil, nil return nil, nil
} }

View File

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

View File

@@ -28,8 +28,8 @@ func NewDecoder(option Option) *Decoder {
return &Decoder{option: &option} return &Decoder{option: &option}
} }
// Decode transform a map[string]interface{} to a struct // Decode transform a map[string]any to a struct
func (d *Decoder) Decode(src map[string]interface{}, dst interface{}) error { func (d *Decoder) Decode(src map[string]any, dst any) error {
if reflect.TypeOf(dst).Kind() != reflect.Ptr { if reflect.TypeOf(dst).Kind() != reflect.Ptr {
return fmt.Errorf("Decode must recive a ptr struct") return fmt.Errorf("Decode must recive a ptr struct")
} }
@@ -45,12 +45,8 @@ func (d *Decoder) Decode(src map[string]interface{}, dst interface{}) error {
} }
tag := field.Tag.Get(d.option.TagName) tag := field.Tag.Get(d.option.TagName)
str := strings.SplitN(tag, ",", 2) key, omitKey, found := strings.Cut(tag, ",")
key := str[0] omitempty := found && omitKey == "omitempty"
omitempty := false
if len(str) > 1 {
omitempty = str[1] == "omitempty"
}
value, ok := src[key] value, ok := src[key]
if !ok || value == nil { if !ok || value == nil {
@@ -68,7 +64,7 @@ func (d *Decoder) Decode(src map[string]interface{}, dst interface{}) error {
return nil return nil
} }
func (d *Decoder) decode(name string, data interface{}, val reflect.Value) error { func (d *Decoder) decode(name string, data any, val reflect.Value) error {
switch val.Kind() { switch val.Kind() {
case reflect.Int: case reflect.Int:
return d.decodeInt(name, data, val) return d.decodeInt(name, data, val)
@@ -89,12 +85,14 @@ func (d *Decoder) decode(name string, data interface{}, val reflect.Value) error
} }
} }
func (d *Decoder) decodeInt(name string, data interface{}, val reflect.Value) (err error) { func (d *Decoder) decodeInt(name string, data any, val reflect.Value) (err error) {
dataVal := reflect.ValueOf(data) dataVal := reflect.ValueOf(data)
kind := dataVal.Kind() kind := dataVal.Kind()
switch { switch {
case kind == reflect.Int: case kind == reflect.Int:
val.SetInt(dataVal.Int()) val.SetInt(dataVal.Int())
case kind == reflect.Float64 && d.option.WeaklyTypedInput:
val.SetInt(int64(dataVal.Float()))
case kind == reflect.String && d.option.WeaklyTypedInput: case kind == reflect.String && d.option.WeaklyTypedInput:
var i int64 var i int64
i, err = strconv.ParseInt(dataVal.String(), 0, val.Type().Bits()) i, err = strconv.ParseInt(dataVal.String(), 0, val.Type().Bits())
@@ -112,7 +110,7 @@ func (d *Decoder) decodeInt(name string, data interface{}, val reflect.Value) (e
return err return err
} }
func (d *Decoder) decodeString(name string, data interface{}, val reflect.Value) (err error) { func (d *Decoder) decodeString(name string, data any, val reflect.Value) (err error) {
dataVal := reflect.ValueOf(data) dataVal := reflect.ValueOf(data)
kind := dataVal.Kind() kind := dataVal.Kind()
switch { switch {
@@ -129,7 +127,7 @@ func (d *Decoder) decodeString(name string, data interface{}, val reflect.Value)
return err return err
} }
func (d *Decoder) decodeBool(name string, data interface{}, val reflect.Value) (err error) { func (d *Decoder) decodeBool(name string, data any, val reflect.Value) (err error) {
dataVal := reflect.ValueOf(data) dataVal := reflect.ValueOf(data)
kind := dataVal.Kind() kind := dataVal.Kind()
switch { switch {
@@ -146,7 +144,7 @@ func (d *Decoder) decodeBool(name string, data interface{}, val reflect.Value) (
return err return err
} }
func (d *Decoder) decodeSlice(name string, data interface{}, val reflect.Value) error { func (d *Decoder) decodeSlice(name string, data any, val reflect.Value) error {
dataVal := reflect.Indirect(reflect.ValueOf(data)) dataVal := reflect.Indirect(reflect.ValueOf(data))
valType := val.Type() valType := val.Type()
valElemType := valType.Elem() valElemType := valType.Elem()
@@ -173,7 +171,7 @@ func (d *Decoder) decodeSlice(name string, data interface{}, val reflect.Value)
return nil return nil
} }
func (d *Decoder) decodeMap(name string, data interface{}, val reflect.Value) error { func (d *Decoder) decodeMap(name string, data any, val reflect.Value) error {
valType := val.Type() valType := val.Type()
valKeyType := valType.Key() valKeyType := valType.Key()
valElemType := valType.Elem() valElemType := valType.Elem()
@@ -245,7 +243,7 @@ func (d *Decoder) decodeMapFromMap(name string, dataVal reflect.Value, val refle
return nil return nil
} }
func (d *Decoder) decodeStruct(name string, data interface{}, val reflect.Value) error { func (d *Decoder) decodeStruct(name string, data any, val reflect.Value) error {
dataVal := reflect.Indirect(reflect.ValueOf(data)) dataVal := reflect.Indirect(reflect.ValueOf(data))
// If the type of the value to write to and the data match directly, // If the type of the value to write to and the data match directly,
@@ -273,7 +271,7 @@ func (d *Decoder) decodeStructFromMap(name string, dataVal, val reflect.Value) e
} }
dataValKeys := make(map[reflect.Value]struct{}) dataValKeys := make(map[reflect.Value]struct{})
dataValKeysUnused := make(map[interface{}]struct{}) dataValKeysUnused := make(map[any]struct{})
for _, dataValKey := range dataVal.MapKeys() { for _, dataValKey := range dataVal.MapKeys() {
dataValKeys[dataValKey] = struct{}{} dataValKeys[dataValKey] = struct{}{}
dataValKeysUnused[dataValKey.Interface()] = struct{}{} dataValKeysUnused[dataValKey.Interface()] = struct{}{}
@@ -398,7 +396,7 @@ func (d *Decoder) decodeStructFromMap(name string, dataVal, val reflect.Value) e
return nil return nil
} }
func (d *Decoder) setInterface(name string, data interface{}, val reflect.Value) (err error) { func (d *Decoder) setInterface(name string, data any, val reflect.Value) (err error) {
dataVal := reflect.ValueOf(data) dataVal := reflect.ValueOf(data)
val.Set(dataVal) val.Set(dataVal)
return nil return nil

View File

@@ -27,7 +27,7 @@ type BazOptional struct {
} }
func TestStructure_Basic(t *testing.T) { func TestStructure_Basic(t *testing.T) {
rawMap := map[string]interface{}{ rawMap := map[string]any{
"foo": 1, "foo": 1,
"bar": "test", "bar": "test",
"extra": false, "extra": false,
@@ -45,7 +45,7 @@ func TestStructure_Basic(t *testing.T) {
} }
func TestStructure_Slice(t *testing.T) { func TestStructure_Slice(t *testing.T) {
rawMap := map[string]interface{}{ rawMap := map[string]any{
"foo": 1, "foo": 1,
"bar": []string{"one", "two"}, "bar": []string{"one", "two"},
} }
@@ -62,7 +62,7 @@ func TestStructure_Slice(t *testing.T) {
} }
func TestStructure_Optional(t *testing.T) { func TestStructure_Optional(t *testing.T) {
rawMap := map[string]interface{}{ rawMap := map[string]any{
"foo": 1, "foo": 1,
} }
@@ -77,7 +77,7 @@ func TestStructure_Optional(t *testing.T) {
} }
func TestStructure_MissingKey(t *testing.T) { func TestStructure_MissingKey(t *testing.T) {
rawMap := map[string]interface{}{ rawMap := map[string]any{
"foo": 1, "foo": 1,
} }
@@ -87,14 +87,14 @@ func TestStructure_MissingKey(t *testing.T) {
} }
func TestStructure_ParamError(t *testing.T) { func TestStructure_ParamError(t *testing.T) {
rawMap := map[string]interface{}{} rawMap := map[string]any{}
s := Baz{} s := Baz{}
err := decoder.Decode(rawMap, s) err := decoder.Decode(rawMap, s)
assert.NotNilf(t, err, "should throw error: %#v", s) assert.NotNilf(t, err, "should throw error: %#v", s)
} }
func TestStructure_SliceTypeError(t *testing.T) { func TestStructure_SliceTypeError(t *testing.T) {
rawMap := map[string]interface{}{ rawMap := map[string]any{
"foo": 1, "foo": 1,
"bar": []int{1, 2}, "bar": []int{1, 2},
} }
@@ -105,7 +105,7 @@ func TestStructure_SliceTypeError(t *testing.T) {
} }
func TestStructure_WeakType(t *testing.T) { func TestStructure_WeakType(t *testing.T) {
rawMap := map[string]interface{}{ rawMap := map[string]any{
"foo": "1", "foo": "1",
"bar": []int{1}, "bar": []int{1},
} }
@@ -122,7 +122,7 @@ func TestStructure_WeakType(t *testing.T) {
} }
func TestStructure_Nest(t *testing.T) { func TestStructure_Nest(t *testing.T) {
rawMap := map[string]interface{}{ rawMap := map[string]any{
"foo": 1, "foo": 1,
} }

View File

@@ -36,7 +36,7 @@ func NewAuthenticator(users []AuthUser) Authenticator {
au.storage.Store(user.User, user.Pass) au.storage.Store(user.User, user.Pass)
} }
usernames := make([]string, 0, len(users)) usernames := make([]string, 0, len(users))
au.storage.Range(func(key, value interface{}) bool { au.storage.Range(func(key, value any) bool {
usernames = append(usernames, key.(string)) usernames = append(usernames, key.(string))
return true return true
}) })

View File

@@ -4,9 +4,9 @@ import (
"net" "net"
"syscall" "syscall"
"golang.org/x/sys/unix"
"github.com/Dreamacro/clash/component/iface" "github.com/Dreamacro/clash/component/iface"
"golang.org/x/sys/unix"
) )
type controlFn = func(network, address string, c syscall.RawConn) error type controlFn = func(network, address string, c syscall.RawConn) error

View File

@@ -1,5 +1,4 @@
//go:build !linux && !darwin //go:build !linux && !darwin
// +build !linux,!darwin
package dialer package dialer
@@ -58,11 +57,11 @@ func bindIfaceToDialer(ifaceName string, dialer *net.Dialer, network string, des
return nil return nil
} }
local := int64(0) local := uint64(0)
if dialer.LocalAddr != nil { if dialer.LocalAddr != nil {
_, port, err := net.SplitHostPort(dialer.LocalAddr.String()) _, port, err := net.SplitHostPort(dialer.LocalAddr.String())
if err == nil { if err == nil {
local, _ = strconv.ParseInt(port, 10, 16) local, _ = strconv.ParseUint(port, 10, 16)
} }
} }
@@ -82,7 +81,7 @@ func bindIfaceToListenConfig(ifaceName string, _ *net.ListenConfig, network, add
port = "0" port = "0"
} }
local, _ := strconv.ParseInt(port, 10, 16) local, _ := strconv.ParseUint(port, 10, 16)
addr, err := lookupLocalAddr(ifaceName, network, nil, int(local)) addr, err := lookupLocalAddr(ifaceName, network, nil, int(local))
if err != nil { if err != nil {

View File

@@ -11,6 +11,7 @@ import (
func DialContext(ctx context.Context, network, address string, options ...Option) (net.Conn, error) { func DialContext(ctx context.Context, network, address string, options ...Option) (net.Conn, error) {
opt := &option{ opt := &option{
interfaceName: DefaultInterface.Load(), interfaceName: DefaultInterface.Load(),
routingMark: int(DefaultRoutingMark.Load()),
} }
for _, o := range DefaultOptions { for _, o := range DefaultOptions {
@@ -31,14 +32,14 @@ func DialContext(ctx context.Context, network, address string, options ...Option
var ip net.IP var ip net.IP
switch network { switch network {
case "tcp4", "udp4": case "tcp4", "udp4":
if opt.interfaceName != "" { if !opt.direct {
ip, err = resolver.ResolveIPv4WithMain(host) ip, err = resolver.ResolveIPv4ProxyServerHost(host)
} else { } else {
ip, err = resolver.ResolveIPv4(host) ip, err = resolver.ResolveIPv4(host)
} }
default: default:
if opt.interfaceName != "" { if !opt.direct {
ip, err = resolver.ResolveIPv6WithMain(host) ip, err = resolver.ResolveIPv6ProxyServerHost(host)
} else { } else {
ip, err = resolver.ResolveIPv6(host) ip, err = resolver.ResolveIPv6(host)
} }
@@ -58,6 +59,7 @@ func DialContext(ctx context.Context, network, address string, options ...Option
func ListenPacket(ctx context.Context, network, address string, options ...Option) (net.PacketConn, error) { func ListenPacket(ctx context.Context, network, address string, options ...Option) (net.PacketConn, error) {
cfg := &option{ cfg := &option{
interfaceName: DefaultInterface.Load(), interfaceName: DefaultInterface.Load(),
routingMark: int(DefaultRoutingMark.Load()),
} }
for _, o := range DefaultOptions { for _, o := range DefaultOptions {
@@ -119,7 +121,7 @@ func dualStackDialContext(ctx context.Context, network, address string, opt *opt
results := make(chan dialResult) results := make(chan dialResult)
var primary, fallback dialResult var primary, fallback dialResult
startRacer := func(ctx context.Context, network, host string, ipv6 bool) { startRacer := func(ctx context.Context, network, host string, direct bool, ipv6 bool) {
result := dialResult{ipv6: ipv6, done: true} result := dialResult{ipv6: ipv6, done: true}
defer func() { defer func() {
select { select {
@@ -133,14 +135,14 @@ func dualStackDialContext(ctx context.Context, network, address string, opt *opt
var ip net.IP var ip net.IP
if ipv6 { if ipv6 {
if opt.interfaceName != "" { if !direct {
ip, result.error = resolver.ResolveIPv6WithMain(host) ip, result.error = resolver.ResolveIPv6ProxyServerHost(host)
} else { } else {
ip, result.error = resolver.ResolveIPv6(host) ip, result.error = resolver.ResolveIPv6(host)
} }
} else { } else {
if opt.interfaceName != "" { if !direct {
ip, result.error = resolver.ResolveIPv4WithMain(host) ip, result.error = resolver.ResolveIPv4ProxyServerHost(host)
} else { } else {
ip, result.error = resolver.ResolveIPv4(host) ip, result.error = resolver.ResolveIPv4(host)
} }
@@ -153,8 +155,8 @@ func dualStackDialContext(ctx context.Context, network, address string, opt *opt
result.Conn, result.error = dialContext(ctx, network, ip, port, opt) result.Conn, result.error = dialContext(ctx, network, ip, port, opt)
} }
go startRacer(ctx, network+"4", host, false) go startRacer(ctx, network+"4", host, opt.direct, false)
go startRacer(ctx, network+"6", host, true) go startRacer(ctx, network+"6", host, opt.direct, true)
for res := range results { for res := range results {
if res.error == nil { if res.error == nil {

View File

@@ -1,5 +1,4 @@
//go:build linux //go:build linux
// +build linux
package dialer package dialer

View File

@@ -1,5 +1,4 @@
//go:build !linux //go:build !linux
// +build !linux
package dialer package dialer

View File

@@ -3,14 +3,16 @@ package dialer
import "go.uber.org/atomic" import "go.uber.org/atomic"
var ( var (
DefaultOptions []Option DefaultOptions []Option
DefaultInterface = atomic.NewString("") DefaultInterface = atomic.NewString("")
DefaultRoutingMark = atomic.NewInt32(0)
) )
type option struct { type option struct {
interfaceName string interfaceName string
addrReuse bool addrReuse bool
routingMark int routingMark int
direct bool
} }
type Option func(opt *option) type Option func(opt *option)
@@ -32,3 +34,9 @@ func WithRoutingMark(mark int) Option {
opt.routingMark = mark opt.routingMark = mark
} }
} }
func WithDirect() Option {
return func(opt *option) {
opt.direct = true
}
}

View File

@@ -0,0 +1,28 @@
package dialer
import (
"context"
"net"
)
func init() {
// We must use this DialContext to query DNS
// when using net default resolver.
net.DefaultResolver.PreferGo = true
net.DefaultResolver.Dial = resolverDialContext
}
func resolverDialContext(ctx context.Context, network, address string) (net.Conn, error) {
d := &net.Dialer{}
interfaceName := DefaultInterface.Load()
if interfaceName != "" {
dstIP := net.ParseIP(address)
if dstIP != nil {
bindIfaceToDialer(interfaceName, d, network, dstIP)
}
}
return d.DialContext(ctx, network, address)
}

View File

@@ -1,5 +1,4 @@
//go:build !darwin && !dragonfly && !freebsd && !linux && !netbsd && !openbsd && !solaris && !windows //go:build !darwin && !dragonfly && !freebsd && !linux && !netbsd && !openbsd && !solaris && !windows
// +build !darwin,!dragonfly,!freebsd,!linux,!netbsd,!openbsd,!solaris,!windows
package dialer package dialer

View File

@@ -1,5 +1,4 @@
//go:build darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris //go:build darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris
// +build darwin dragonfly freebsd linux netbsd openbsd solaris
package dialer package dialer

View File

@@ -38,6 +38,12 @@ func (c *cachefileStore) PutByIP(ip net.IP, host string) {
c.cache.PutFakeip(ip.To4(), []byte(host)) c.cache.PutFakeip(ip.To4(), []byte(host))
} }
// DelByIP implements store.DelByIP
func (c *cachefileStore) DelByIP(ip net.IP) {
ip = ip.To4()
c.cache.DelFakeipPair(ip, c.cache.GetFakeip(ip.To4()))
}
// Exist implements store.Exist // Exist implements store.Exist
func (c *cachefileStore) Exist(ip net.IP) bool { func (c *cachefileStore) Exist(ip net.IP) bool {
_, exist := c.GetByIP(ip) _, exist := c.GetByIP(ip)
@@ -47,3 +53,8 @@ func (c *cachefileStore) Exist(ip net.IP) bool {
// CloneTo implements store.CloneTo // CloneTo implements store.CloneTo
// already persistence // already persistence
func (c *cachefileStore) CloneTo(store store) {} func (c *cachefileStore) CloneTo(store store) {}
// FlushFakeIP implements store.FlushFakeIP
func (c *cachefileStore) FlushFakeIP() error {
return c.cache.FlushFakeIP()
}

View File

@@ -46,6 +46,15 @@ func (m *memoryStore) PutByIP(ip net.IP, host string) {
m.cache.Set(ipToUint(ip.To4()), host) m.cache.Set(ipToUint(ip.To4()), host)
} }
// DelByIP implements store.DelByIP
func (m *memoryStore) DelByIP(ip net.IP) {
ipNum := ipToUint(ip.To4())
if elm, exist := m.cache.Get(ipNum); exist {
m.cache.Delete(elm.(string))
}
m.cache.Delete(ipNum)
}
// Exist implements store.Exist // Exist implements store.Exist
func (m *memoryStore) Exist(ip net.IP) bool { func (m *memoryStore) Exist(ip net.IP) bool {
return m.cache.Exist(ipToUint(ip.To4())) return m.cache.Exist(ipToUint(ip.To4()))
@@ -58,3 +67,8 @@ func (m *memoryStore) CloneTo(store store) {
m.cache.CloneTo(ms.cache) m.cache.CloneTo(ms.cache)
} }
} }
// FlushFakeIP implements store.FlushFakeIP
func (m *memoryStore) FlushFakeIP() error {
return m.cache.Clear()
}

View File

@@ -15,20 +15,23 @@ type store interface {
PutByHost(host string, ip net.IP) PutByHost(host string, ip net.IP)
GetByIP(ip net.IP) (string, bool) GetByIP(ip net.IP) (string, bool)
PutByIP(ip net.IP, host string) PutByIP(ip net.IP, host string)
DelByIP(ip net.IP)
Exist(ip net.IP) bool Exist(ip net.IP) bool
CloneTo(store) CloneTo(store)
FlushFakeIP() error
} }
// Pool is a implementation about fake ip generator without storage // Pool is a implementation about fake ip generator without storage
type Pool struct { type Pool struct {
max uint32 max uint32
min uint32 min uint32
gateway uint32 gateway uint32
offset uint32 broadcast uint32
mux sync.Mutex offset uint32
host *trie.DomainTrie mux sync.Mutex
ipnet *net.IPNet host *trie.DomainTrie
store store ipnet *net.IPNet
store store
} }
// Lookup return a fake ip with host // Lookup return a fake ip with host
@@ -81,6 +84,11 @@ func (p *Pool) Gateway() net.IP {
return uintToIP(p.gateway) return uintToIP(p.gateway)
} }
// Broadcast return broadcast ip
func (p *Pool) Broadcast() net.IP {
return uintToIP(p.broadcast)
}
// IPNet return raw ipnet // IPNet return raw ipnet
func (p *Pool) IPNet() *net.IPNet { func (p *Pool) IPNet() *net.IPNet {
return p.ipnet return p.ipnet
@@ -97,6 +105,9 @@ func (p *Pool) get(host string) net.IP {
p.offset = (p.offset + 1) % (p.max - p.min) p.offset = (p.offset + 1) % (p.max - p.min)
// Avoid infinite loops // Avoid infinite loops
if p.offset == current { if p.offset == current {
p.offset = (p.offset + 1) % (p.max - p.min)
ip := uintToIP(p.min + p.offset - 1)
p.store.DelByIP(ip)
break break
} }
@@ -110,6 +121,10 @@ func (p *Pool) get(host string) net.IP {
return ip return ip
} }
func (p *Pool) FlushFakeIP() error {
return p.store.FlushFakeIP()
}
func ipToUint(ip net.IP) uint32 { func ipToUint(ip net.IP) uint32 {
v := uint32(ip[0]) << 24 v := uint32(ip[0]) << 24
v += uint32(ip[1]) << 16 v += uint32(ip[1]) << 16
@@ -137,10 +152,10 @@ type Options struct {
// New return Pool instance // New return Pool instance
func New(options Options) (*Pool, error) { func New(options Options) (*Pool, error) {
min := ipToUint(options.IPNet.IP) + 2 min := ipToUint(options.IPNet.IP) + 3
ones, bits := options.IPNet.Mask.Size() ones, bits := options.IPNet.Mask.Size()
total := 1<<uint(bits-ones) - 2 total := 1<<uint(bits-ones) - 4
if total <= 0 { if total <= 0 {
return nil, errors.New("ipnet don't have valid ip") return nil, errors.New("ipnet don't have valid ip")
@@ -148,11 +163,12 @@ func New(options Options) (*Pool, error) {
max := min + uint32(total) - 1 max := min + uint32(total) - 1
pool := &Pool{ pool := &Pool{
min: min, min: min,
max: max, max: max,
gateway: min - 1, gateway: min - 2,
host: options.Host, broadcast: max + 1,
ipnet: options.IPNet, host: options.Host,
ipnet: options.IPNet,
} }
if options.Persistence { if options.Persistence {
pool.store = &cachefileStore{ pool.store = &cachefileStore{

View File

@@ -1,6 +1,7 @@
package fakeip package fakeip
import ( import (
"fmt"
"net" "net"
"os" "os"
"testing" "testing"
@@ -48,7 +49,7 @@ func createCachefileStore(options Options) (*Pool, string, error) {
} }
func TestPool_Basic(t *testing.T) { func TestPool_Basic(t *testing.T) {
_, ipnet, _ := net.ParseCIDR("192.168.0.1/29") _, ipnet, _ := net.ParseCIDR("192.168.0.0/28")
pools, tempfile, err := createPools(Options{ pools, tempfile, err := createPools(Options{
IPNet: ipnet, IPNet: ipnet,
Size: 10, Size: 10,
@@ -61,21 +62,22 @@ func TestPool_Basic(t *testing.T) {
last := pool.Lookup("bar.com") last := pool.Lookup("bar.com")
bar, exist := pool.LookBack(last) bar, exist := pool.LookBack(last)
assert.True(t, first.Equal(net.IP{192, 168, 0, 2})) assert.True(t, first.Equal(net.IP{192, 168, 0, 3}))
assert.Equal(t, pool.Lookup("foo.com"), net.IP{192, 168, 0, 2}) assert.Equal(t, pool.Lookup("foo.com"), net.IP{192, 168, 0, 3})
assert.True(t, last.Equal(net.IP{192, 168, 0, 3})) assert.True(t, last.Equal(net.IP{192, 168, 0, 4}))
assert.True(t, exist) assert.True(t, exist)
assert.Equal(t, bar, "bar.com") assert.Equal(t, bar, "bar.com")
assert.Equal(t, pool.Gateway(), net.IP{192, 168, 0, 1}) assert.Equal(t, pool.Gateway(), net.IP{192, 168, 0, 1})
assert.Equal(t, pool.Broadcast(), net.IP{192, 168, 0, 15})
assert.Equal(t, pool.IPNet().String(), ipnet.String()) assert.Equal(t, pool.IPNet().String(), ipnet.String())
assert.True(t, pool.Exist(net.IP{192, 168, 0, 3})) assert.True(t, pool.Exist(net.IP{192, 168, 0, 4}))
assert.False(t, pool.Exist(net.IP{192, 168, 0, 4})) assert.False(t, pool.Exist(net.IP{192, 168, 0, 5}))
assert.False(t, pool.Exist(net.ParseIP("::1"))) assert.False(t, pool.Exist(net.ParseIP("::1")))
} }
} }
func TestPool_CycleUsed(t *testing.T) { func TestPool_CycleUsed(t *testing.T) {
_, ipnet, _ := net.ParseCIDR("192.168.0.1/30") _, ipnet, _ := net.ParseCIDR("192.168.0.16/28")
pools, tempfile, err := createPools(Options{ pools, tempfile, err := createPools(Options{
IPNet: ipnet, IPNet: ipnet,
Size: 10, Size: 10,
@@ -84,14 +86,20 @@ func TestPool_CycleUsed(t *testing.T) {
defer os.Remove(tempfile) defer os.Remove(tempfile)
for _, pool := range pools { for _, pool := range pools {
first := pool.Lookup("foo.com") foo := pool.Lookup("foo.com")
same := pool.Lookup("baz.com") bar := pool.Lookup("bar.com")
assert.True(t, first.Equal(same)) for i := 0; i < 9; i++ {
pool.Lookup(fmt.Sprintf("%d.com", i))
}
baz := pool.Lookup("baz.com")
next := pool.Lookup("foo.com")
assert.True(t, foo.Equal(baz))
assert.True(t, next.Equal(bar))
} }
} }
func TestPool_Skip(t *testing.T) { func TestPool_Skip(t *testing.T) {
_, ipnet, _ := net.ParseCIDR("192.168.0.1/30") _, ipnet, _ := net.ParseCIDR("192.168.0.1/29")
tree := trie.New() tree := trie.New()
tree.Insert("example.com", tree) tree.Insert("example.com", tree)
pools, tempfile, err := createPools(Options{ pools, tempfile, err := createPools(Options{
@@ -162,8 +170,8 @@ func TestPool_Clone(t *testing.T) {
first := pool.Lookup("foo.com") first := pool.Lookup("foo.com")
last := pool.Lookup("bar.com") last := pool.Lookup("bar.com")
assert.True(t, first.Equal(net.IP{192, 168, 0, 2})) assert.True(t, first.Equal(net.IP{192, 168, 0, 3}))
assert.True(t, last.Equal(net.IP{192, 168, 0, 3})) assert.True(t, last.Equal(net.IP{192, 168, 0, 4}))
newPool, _ := New(Options{ newPool, _ := New(Options{
IPNet: ipnet, IPNet: ipnet,
@@ -185,3 +193,59 @@ func TestPool_Error(t *testing.T) {
assert.Error(t, err) assert.Error(t, err)
} }
func TestPool_FlushFileCache(t *testing.T) {
_, ipnet, _ := net.ParseCIDR("192.168.0.1/28")
pools, tempfile, err := createPools(Options{
IPNet: ipnet,
Size: 10,
})
assert.Nil(t, err)
defer os.Remove(tempfile)
for _, pool := range pools {
foo := pool.Lookup("foo.com")
bar := pool.Lookup("baz.com")
bax := pool.Lookup("baz.com")
fox := pool.Lookup("foo.com")
err = pool.FlushFakeIP()
assert.Nil(t, err)
baz := pool.Lookup("foo.com")
next := pool.Lookup("baz.com")
nero := pool.Lookup("foo.com")
assert.Equal(t, foo, fox)
assert.NotEqual(t, foo, baz)
assert.Equal(t, bar, bax)
assert.NotEqual(t, bar, next)
assert.Equal(t, baz, nero)
}
}
func TestPool_FlushMemoryCache(t *testing.T) {
_, ipnet, _ := net.ParseCIDR("192.168.0.1/28")
pool, _ := New(Options{
IPNet: ipnet,
Size: 10,
})
foo := pool.Lookup("foo.com")
bar := pool.Lookup("baz.com")
bax := pool.Lookup("baz.com")
fox := pool.Lookup("foo.com")
err := pool.FlushFakeIP()
assert.Nil(t, err)
baz := pool.Lookup("foo.com")
next := pool.Lookup("baz.com")
nero := pool.Lookup("foo.com")
assert.Equal(t, foo, fox)
assert.NotEqual(t, foo, baz)
assert.Equal(t, bar, bax)
assert.NotEqual(t, bar, next)
assert.Equal(t, baz, nero)
}

View File

@@ -3,6 +3,7 @@ package geodata
import ( import (
"errors" "errors"
"fmt" "fmt"
C "github.com/Dreamacro/clash/constant"
"strings" "strings"
"github.com/Dreamacro/clash/component/geodata/router" "github.com/Dreamacro/clash/component/geodata/router"
@@ -14,7 +15,7 @@ type loader struct {
} }
func (l *loader) LoadGeoSite(list string) ([]*router.Domain, error) { func (l *loader) LoadGeoSite(list string) ([]*router.Domain, error) {
return l.LoadGeoSiteWithAttr("geosite.dat", list) return l.LoadGeoSiteWithAttr(C.GeositeName, list)
} }
func (l *loader) LoadGeoSiteWithAttr(file string, siteWithAttr string) ([]*router.Domain, error) { func (l *loader) LoadGeoSiteWithAttr(file string, siteWithAttr string) ([]*router.Domain, error) {
@@ -58,7 +59,7 @@ func (l *loader) LoadGeoSiteWithAttr(file string, siteWithAttr string) ([]*route
} }
func (l *loader) LoadGeoIP(country string) ([]*router.CIDR, error) { func (l *loader) LoadGeoIP(country string) ([]*router.CIDR, error) {
return l.LoadIP("geoip.dat", country) return l.LoadIP(C.GeoipName, country)
} }
var loaders map[string]func() LoaderImplementation var loaders map[string]func() LoaderImplementation

View File

@@ -1,7 +1,10 @@
package router package router
import ( import (
"encoding/binary"
"fmt" "fmt"
"net"
"sort"
"strings" "strings"
"github.com/Dreamacro/clash/component/geodata/strmatcher" "github.com/Dreamacro/clash/component/geodata/strmatcher"
@@ -69,3 +72,279 @@ func NewDomainMatcher(domains []*Domain) (*DomainMatcher, error) {
func (m *DomainMatcher) ApplyDomain(domain string) bool { func (m *DomainMatcher) ApplyDomain(domain string) bool {
return len(m.matchers.Match(strings.ToLower(domain))) > 0 return len(m.matchers.Match(strings.ToLower(domain))) > 0
} }
// CIDRList is an alias of []*CIDR to provide sort.Interface.
type CIDRList []*CIDR
// Len implements sort.Interface.
func (l *CIDRList) Len() int {
return len(*l)
}
// Less implements sort.Interface.
func (l *CIDRList) Less(i int, j int) bool {
ci := (*l)[i]
cj := (*l)[j]
if len(ci.Ip) < len(cj.Ip) {
return true
}
if len(ci.Ip) > len(cj.Ip) {
return false
}
for k := 0; k < len(ci.Ip); k++ {
if ci.Ip[k] < cj.Ip[k] {
return true
}
if ci.Ip[k] > cj.Ip[k] {
return false
}
}
return ci.Prefix < cj.Prefix
}
// Swap implements sort.Interface.
func (l *CIDRList) Swap(i int, j int) {
(*l)[i], (*l)[j] = (*l)[j], (*l)[i]
}
type ipv6 struct {
a uint64
b uint64
}
type GeoIPMatcher struct {
countryCode string
reverseMatch bool
ip4 []uint32
prefix4 []uint8
ip6 []ipv6
prefix6 []uint8
}
func normalize4(ip uint32, prefix uint8) uint32 {
return (ip >> (32 - prefix)) << (32 - prefix)
}
func normalize6(ip ipv6, prefix uint8) ipv6 {
if prefix <= 64 {
ip.a = (ip.a >> (64 - prefix)) << (64 - prefix)
ip.b = 0
} else {
ip.b = (ip.b >> (128 - prefix)) << (128 - prefix)
}
return ip
}
func (m *GeoIPMatcher) Init(cidrs []*CIDR) error {
ip4Count := 0
ip6Count := 0
for _, cidr := range cidrs {
ip := cidr.Ip
switch len(ip) {
case 4:
ip4Count++
case 16:
ip6Count++
default:
return fmt.Errorf("unexpect ip length: %d", len(ip))
}
}
cidrList := CIDRList(cidrs)
sort.Sort(&cidrList)
m.ip4 = make([]uint32, 0, ip4Count)
m.prefix4 = make([]uint8, 0, ip4Count)
m.ip6 = make([]ipv6, 0, ip6Count)
m.prefix6 = make([]uint8, 0, ip6Count)
for _, cidr := range cidrs {
ip := cidr.Ip
prefix := uint8(cidr.Prefix)
switch len(ip) {
case 4:
m.ip4 = append(m.ip4, normalize4(binary.BigEndian.Uint32(ip), prefix))
m.prefix4 = append(m.prefix4, prefix)
case 16:
ip6 := ipv6{
a: binary.BigEndian.Uint64(ip[0:8]),
b: binary.BigEndian.Uint64(ip[8:16]),
}
ip6 = normalize6(ip6, prefix)
m.ip6 = append(m.ip6, ip6)
m.prefix6 = append(m.prefix6, prefix)
}
}
return nil
}
func (m *GeoIPMatcher) SetReverseMatch(isReverseMatch bool) {
m.reverseMatch = isReverseMatch
}
func (m *GeoIPMatcher) match4(ip uint32) bool {
if len(m.ip4) == 0 {
return false
}
if ip < m.ip4[0] {
return false
}
size := uint32(len(m.ip4))
l := uint32(0)
r := size
for l < r {
x := ((l + r) >> 1)
if ip < m.ip4[x] {
r = x
continue
}
nip := normalize4(ip, m.prefix4[x])
if nip == m.ip4[x] {
return true
}
l = x + 1
}
return l > 0 && normalize4(ip, m.prefix4[l-1]) == m.ip4[l-1]
}
func less6(a ipv6, b ipv6) bool {
return a.a < b.a || (a.a == b.a && a.b < b.b)
}
func (m *GeoIPMatcher) match6(ip ipv6) bool {
if len(m.ip6) == 0 {
return false
}
if less6(ip, m.ip6[0]) {
return false
}
size := uint32(len(m.ip6))
l := uint32(0)
r := size
for l < r {
x := (l + r) / 2
if less6(ip, m.ip6[x]) {
r = x
continue
}
if normalize6(ip, m.prefix6[x]) == m.ip6[x] {
return true
}
l = x + 1
}
return l > 0 && normalize6(ip, m.prefix6[l-1]) == m.ip6[l-1]
}
// Match returns true if the given ip is included by the GeoIP.
func (m *GeoIPMatcher) Match(ip net.IP) bool {
switch len(ip) {
case 4:
if m.reverseMatch {
return !m.match4(binary.BigEndian.Uint32(ip))
}
return m.match4(binary.BigEndian.Uint32(ip))
case 16:
if m.reverseMatch {
return !m.match6(ipv6{
a: binary.BigEndian.Uint64(ip[0:8]),
b: binary.BigEndian.Uint64(ip[8:16]),
})
}
return m.match6(ipv6{
a: binary.BigEndian.Uint64(ip[0:8]),
b: binary.BigEndian.Uint64(ip[8:16]),
})
default:
return false
}
}
// GeoIPMatcherContainer is a container for GeoIPMatchers. It keeps unique copies of GeoIPMatcher by country code.
type GeoIPMatcherContainer struct {
matchers []*GeoIPMatcher
}
// Add adds a new GeoIP set into the container.
// If the country code of GeoIP is not empty, GeoIPMatcherContainer will try to find an existing one, instead of adding a new one.
func (c *GeoIPMatcherContainer) Add(geoip *GeoIP) (*GeoIPMatcher, error) {
if len(geoip.CountryCode) > 0 {
for _, m := range c.matchers {
if m.countryCode == geoip.CountryCode && m.reverseMatch == geoip.ReverseMatch {
return m, nil
}
}
}
m := &GeoIPMatcher{
countryCode: geoip.CountryCode,
reverseMatch: geoip.ReverseMatch,
}
if err := m.Init(geoip.Cidr); err != nil {
return nil, err
}
if len(geoip.CountryCode) > 0 {
c.matchers = append(c.matchers, m)
}
return m, nil
}
var globalGeoIPContainer GeoIPMatcherContainer
type MultiGeoIPMatcher struct {
matchers []*GeoIPMatcher
}
func NewGeoIPMatcher(geoip *GeoIP) (*GeoIPMatcher, error) {
matcher, err := globalGeoIPContainer.Add(geoip)
if err != nil {
return nil, err
}
return matcher, nil
}
func (m *MultiGeoIPMatcher) ApplyIp(ip net.IP) bool {
for _, matcher := range m.matchers {
if matcher.Match(ip) {
return true
}
}
return false
}
func NewMultiGeoIPMatcher(geoips []*GeoIP) (*MultiGeoIPMatcher, error) {
var matchers []*GeoIPMatcher
for _, geoip := range geoips {
matcher, err := globalGeoIPContainer.Add(geoip)
if err != nil {
return nil, err
}
matchers = append(matchers, matcher)
}
matcher := &MultiGeoIPMatcher{
matchers: matchers,
}
return matcher, nil
}

View File

@@ -1,7 +1,7 @@
// Code generated by protoc-gen-go. DO NOT EDIT. // Code generated by protoc-gen-go. DO NOT EDIT.
// versions: // versions:
// protoc-gen-go v1.27.1 // protoc-gen-go v1.28.0
// protoc v3.17.3 // protoc v3.19.1
// source: component/geodata/router/config.proto // source: component/geodata/router/config.proto
package router package router

View File

@@ -9,6 +9,7 @@ import (
"github.com/Dreamacro/clash/component/geodata" "github.com/Dreamacro/clash/component/geodata"
"github.com/Dreamacro/clash/component/geodata/router" "github.com/Dreamacro/clash/component/geodata/router"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
"google.golang.org/protobuf/proto" "google.golang.org/protobuf/proto"
) )

View File

@@ -2,10 +2,36 @@ package geodata
import ( import (
"github.com/Dreamacro/clash/component/geodata/router" "github.com/Dreamacro/clash/component/geodata/router"
C "github.com/Dreamacro/clash/constant"
"strings"
) )
var geoLoaderName = "memconservative"
// geoLoaderName = "standard"
func LoaderName() string {
return geoLoaderName
}
func SetLoader(newLoader string) {
geoLoaderName = newLoader
}
func Verify(name string) bool {
switch name {
case C.GeositeName:
_, _, err := LoadGeoSiteMatcher("CN")
return err == nil
case C.GeoipName:
_, _, err := LoadGeoIPMatcher("CN")
return err == nil
default:
return false
}
}
func LoadGeoSiteMatcher(countryCode string) (*router.DomainMatcher, int, error) { func LoadGeoSiteMatcher(countryCode string) (*router.DomainMatcher, int, error) {
geoLoaderName := "standard"
geoLoader, err := GetGeoDataLoader(geoLoaderName) geoLoader, err := GetGeoDataLoader(geoLoaderName)
if err != nil { if err != nil {
return nil, 0, err return nil, 0, err
@@ -28,3 +54,28 @@ func LoadGeoSiteMatcher(countryCode string) (*router.DomainMatcher, int, error)
return matcher, len(domains), nil return matcher, len(domains), nil
} }
func LoadGeoIPMatcher(country string) (*router.GeoIPMatcher, int, error) {
geoLoader, err := GetGeoDataLoader(geoLoaderName)
if err != nil {
return nil, 0, err
}
records, err := geoLoader.LoadGeoIP(strings.ReplaceAll(country, "!", ""))
if err != nil {
return nil, 0, err
}
geoIP := &router.GeoIP{
CountryCode: country,
Cidr: records,
ReverseMatch: strings.Contains(country, "!"),
}
matcher, err := router.NewGeoIPMatcher(geoIP)
if err != nil {
return nil, 0, err
}
return matcher, len(records), nil
}

View File

@@ -23,7 +23,7 @@ var (
var interfaces = singledo.NewSingle(time.Second * 20) var interfaces = singledo.NewSingle(time.Second * 20)
func ResolveInterface(name string) (*Interface, error) { func ResolveInterface(name string) (*Interface, error) {
value, err, _ := interfaces.Do(func() (interface{}, error) { value, err, _ := interfaces.Do(func() (any, error) {
ifaces, err := net.Interfaces() ifaces, err := net.Interfaces()
if err != nil { if err != nil {
return nil, err return nil, err

View File

@@ -1,12 +1,11 @@
package mmdb package mmdb
import ( import (
"github.com/oschwald/geoip2-golang"
"sync" "sync"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/log" "github.com/Dreamacro/clash/log"
"github.com/oschwald/geoip2-golang"
) )
var ( var (

View File

@@ -6,17 +6,17 @@ import (
"time" "time"
) )
type Factory = func(context.Context) (interface{}, error) type Factory = func(context.Context) (any, error)
type entry struct { type entry struct {
elm interface{} elm any
time time.Time time time.Time
} }
type Option func(*pool) type Option func(*pool)
// WithEvict set the evict callback // WithEvict set the evict callback
func WithEvict(cb func(interface{})) Option { func WithEvict(cb func(any)) Option {
return func(p *pool) { return func(p *pool) {
p.evict = cb p.evict = cb
} }
@@ -32,7 +32,7 @@ func WithAge(maxAge int64) Option {
// WithSize defined max size of Pool // WithSize defined max size of Pool
func WithSize(maxSize int) Option { func WithSize(maxSize int) Option {
return func(p *pool) { return func(p *pool) {
p.ch = make(chan interface{}, maxSize) p.ch = make(chan any, maxSize)
} }
} }
@@ -42,13 +42,13 @@ type Pool struct {
} }
type pool struct { type pool struct {
ch chan interface{} ch chan any
factory Factory factory Factory
evict func(interface{}) evict func(any)
maxAge int64 maxAge int64
} }
func (p *pool) GetContext(ctx context.Context) (interface{}, error) { func (p *pool) GetContext(ctx context.Context) (any, error) {
now := time.Now() now := time.Now()
for { for {
select { select {
@@ -68,11 +68,11 @@ func (p *pool) GetContext(ctx context.Context) (interface{}, error) {
} }
} }
func (p *pool) Get() (interface{}, error) { func (p *pool) Get() (any, error) {
return p.GetContext(context.Background()) return p.GetContext(context.Background())
} }
func (p *pool) Put(item interface{}) { func (p *pool) Put(item any) {
e := &entry{ e := &entry{
elm: item, elm: item,
time: time.Now(), time: time.Now(),
@@ -100,7 +100,7 @@ func recycle(p *Pool) {
func New(factory Factory, options ...Option) *Pool { func New(factory Factory, options ...Option) *Pool {
p := &pool{ p := &pool{
ch: make(chan interface{}, 10), ch: make(chan any, 10),
factory: factory, factory: factory,
} }

View File

@@ -10,7 +10,7 @@ import (
func lg() Factory { func lg() Factory {
initial := -1 initial := -1
return func(context.Context) (interface{}, error) { return func(context.Context) (any, error) {
initial++ initial++
return initial, nil return initial, nil
} }
@@ -34,7 +34,7 @@ func TestPool_MaxSize(t *testing.T) {
size := 5 size := 5
pool := New(g, WithSize(size)) pool := New(g, WithSize(size))
items := []interface{}{} var items []any
for i := 0; i < size; i++ { for i := 0; i < size; i++ {
item, _ := pool.Get() item, _ := pool.Get()

View File

@@ -3,6 +3,8 @@ package process
import ( import (
"errors" "errors"
"net" "net"
C "github.com/Dreamacro/clash/constant"
) )
var ( var (
@@ -19,3 +21,49 @@ const (
func FindProcessName(network string, srcIP net.IP, srcPort int) (string, error) { func FindProcessName(network string, srcIP net.IP, srcPort int) (string, error) {
return findProcessName(network, srcIP, srcPort) return findProcessName(network, srcIP, srcPort)
} }
func ShouldFindProcess(metadata *C.Metadata) bool {
if metadata.Process != "" {
return false
}
for _, ip := range localIPs {
if ip.Equal(metadata.SrcIP) {
return true
}
}
return false
}
func AppendLocalIPs(ip ...net.IP) {
localIPs = append(ip, localIPs...)
}
func getLocalIPs() []net.IP {
ips := []net.IP{net.IPv4zero, net.IPv6zero}
netInterfaces, err := net.Interfaces()
if err != nil {
ips = append(ips, net.IPv4(127, 0, 0, 1), net.IPv6loopback)
return ips
}
for i := 0; i < len(netInterfaces); i++ {
if (netInterfaces[i].Flags & net.FlagUp) != 0 {
adds, _ := netInterfaces[i].Addrs()
for _, address := range adds {
if ipNet, ok := address.(*net.IPNet); ok {
ips = append(ips, ipNet.IP)
}
}
}
}
return ips
}
var localIPs []net.IP
func init() {
localIPs = getLocalIPs()
}

View File

@@ -0,0 +1,230 @@
package process
import (
"bytes"
"encoding/binary"
"fmt"
"net"
"os"
"path"
"path/filepath"
"strings"
"syscall"
"unicode"
"unsafe"
"github.com/Dreamacro/clash/common/pool"
)
// from https://github.com/vishvananda/netlink/blob/bca67dfc8220b44ef582c9da4e9172bf1c9ec973/nl/nl_linux.go#L52-L62
var nativeEndian = func() binary.ByteOrder {
var x uint32 = 0x01020304
if *(*byte)(unsafe.Pointer(&x)) == 0x01 {
return binary.BigEndian
}
return binary.LittleEndian
}()
const (
sizeOfSocketDiagRequest = syscall.SizeofNlMsghdr + 8 + 48
socketDiagByFamily = 20
pathProc = "/proc"
)
func findProcessName(network string, ip net.IP, srcPort int) (string, error) {
inode, uid, err := resolveSocketByNetlink(network, ip, srcPort)
if err != nil {
return "", err
}
return resolveProcessNameByProcSearch(inode, uid)
}
func resolveSocketByNetlink(network string, ip net.IP, srcPort int) (int32, int32, error) {
var family byte
var protocol byte
switch network {
case TCP:
protocol = syscall.IPPROTO_TCP
case UDP:
protocol = syscall.IPPROTO_UDP
default:
return 0, 0, ErrInvalidNetwork
}
if ip.To4() != nil {
family = syscall.AF_INET
} else {
family = syscall.AF_INET6
}
req := packSocketDiagRequest(family, protocol, ip, uint16(srcPort))
socket, err := syscall.Socket(syscall.AF_NETLINK, syscall.SOCK_DGRAM, syscall.NETLINK_INET_DIAG)
if err != nil {
return 0, 0, fmt.Errorf("dial netlink: %w", err)
}
defer syscall.Close(socket)
syscall.SetsockoptTimeval(socket, syscall.SOL_SOCKET, syscall.SO_SNDTIMEO, &syscall.Timeval{Usec: 100})
syscall.SetsockoptTimeval(socket, syscall.SOL_SOCKET, syscall.SO_RCVTIMEO, &syscall.Timeval{Usec: 100})
if err := syscall.Connect(socket, &syscall.SockaddrNetlink{
Family: syscall.AF_NETLINK,
Pad: 0,
Pid: 0,
Groups: 0,
}); err != nil {
return 0, 0, err
}
if _, err := syscall.Write(socket, req); err != nil {
return 0, 0, fmt.Errorf("write request: %w", err)
}
rb := pool.Get(pool.RelayBufferSize)
defer pool.Put(rb)
n, err := syscall.Read(socket, rb)
if err != nil {
return 0, 0, fmt.Errorf("read response: %w", err)
}
messages, err := syscall.ParseNetlinkMessage(rb[:n])
if err != nil {
return 0, 0, fmt.Errorf("parse netlink message: %w", err)
} else if len(messages) == 0 {
return 0, 0, fmt.Errorf("unexcepted netlink response")
}
message := messages[0]
if message.Header.Type&syscall.NLMSG_ERROR != 0 {
return 0, 0, fmt.Errorf("netlink message: NLMSG_ERROR")
}
uid, inode := unpackSocketDiagResponse(&messages[0])
if uid < 0 || inode < 0 {
return 0, 0, fmt.Errorf("invalid uid(%d) or inode(%d)", uid, inode)
}
return uid, inode, nil
}
func packSocketDiagRequest(family, protocol byte, source net.IP, sourcePort uint16) []byte {
s := make([]byte, 16)
if v4 := source.To4(); v4 != nil {
copy(s, v4)
} else {
copy(s, source)
}
buf := make([]byte, sizeOfSocketDiagRequest)
nativeEndian.PutUint32(buf[0:4], sizeOfSocketDiagRequest)
nativeEndian.PutUint16(buf[4:6], socketDiagByFamily)
nativeEndian.PutUint16(buf[6:8], syscall.NLM_F_REQUEST|syscall.NLM_F_DUMP)
nativeEndian.PutUint32(buf[8:12], 0)
nativeEndian.PutUint32(buf[12:16], 0)
buf[16] = family
buf[17] = protocol
buf[18] = 0
buf[19] = 0
nativeEndian.PutUint32(buf[20:24], 0xFFFFFFFF)
binary.BigEndian.PutUint16(buf[24:26], sourcePort)
binary.BigEndian.PutUint16(buf[26:28], 0)
copy(buf[28:44], s)
copy(buf[44:60], net.IPv6zero)
nativeEndian.PutUint32(buf[60:64], 0)
nativeEndian.PutUint64(buf[64:72], 0xFFFFFFFFFFFFFFFF)
return buf
}
func unpackSocketDiagResponse(msg *syscall.NetlinkMessage) (inode, uid int32) {
if len(msg.Data) < 72 {
return 0, 0
}
data := msg.Data
uid = int32(nativeEndian.Uint32(data[64:68]))
inode = int32(nativeEndian.Uint32(data[68:72]))
return
}
func resolveProcessNameByProcSearch(inode, uid int32) (string, error) {
files, err := os.ReadDir(pathProc)
if err != nil {
return "", err
}
buffer := make([]byte, syscall.PathMax)
socket := []byte(fmt.Sprintf("socket:[%d]", inode))
for _, f := range files {
if !f.IsDir() || !isPid(f.Name()) {
continue
}
info, err := f.Info()
if err != nil {
return "", err
}
if info.Sys().(*syscall.Stat_t).Uid != uint32(uid) {
continue
}
processPath := path.Join(pathProc, f.Name())
fdPath := path.Join(processPath, "fd")
fds, err := os.ReadDir(fdPath)
if err != nil {
continue
}
for _, fd := range fds {
n, err := syscall.Readlink(path.Join(fdPath, fd.Name()), buffer)
if err != nil {
continue
}
if bytes.Equal(buffer[:n], socket) {
cmdline, err := os.ReadFile(path.Join(processPath, "cmdline"))
if err != nil {
return "", err
}
return splitCmdline(cmdline), nil
}
}
}
return "", fmt.Errorf("process of uid(%d),inode(%d) not found", uid, inode)
}
func splitCmdline(cmdline []byte) string {
cmdline = bytes.Trim(cmdline, " ")
idx := bytes.IndexFunc(cmdline, func(r rune) bool {
return unicode.IsControl(r) || unicode.IsSpace(r)
})
if idx == -1 {
return filepath.Base(string(cmdline))
}
return filepath.Base(string(cmdline[:idx]))
}
func isPid(s string) bool {
return strings.IndexFunc(s, func(r rune) bool {
return !unicode.IsDigit(r)
}) == -1
}

View File

@@ -3,7 +3,6 @@ package process
import ( import (
"encoding/binary" "encoding/binary"
"net" "net"
"path/filepath"
"syscall" "syscall"
"unsafe" "unsafe"
@@ -70,7 +69,7 @@ func findProcessName(network string, ip net.IP, port int) (string, error) {
continue continue
} }
if !ip.Equal(srcIP) { if !ip.Equal(srcIP) && (network == TCP || !srcIP.IsUnspecified()) {
continue continue
} }
@@ -96,7 +95,7 @@ func getExecPathFromPID(pid uint32) (string, error) {
return "", errno return "", errno
} }
return filepath.Base(unix.ByteSliceToString(buf)), nil return unix.ByteSliceToString(buf), nil
} }
func readNativeUint32(b []byte) uint32 { func readNativeUint32(b []byte) uint32 {

View File

@@ -4,7 +4,6 @@ import (
"encoding/binary" "encoding/binary"
"fmt" "fmt"
"net" "net"
"path/filepath"
"strconv" "strconv"
"strings" "strings"
"sync" "sync"
@@ -77,7 +76,7 @@ func getExecPathFromPID(pid uint32) (string, error) {
return "", errno return "", errno
} }
return filepath.Base(string(buf[:size-1])), nil return string(buf[:size-1]), nil
} }
func readNativeUint32(b []byte) uint32 { func readNativeUint32(b []byte) uint32 {

View File

@@ -1,15 +1,17 @@
//go:build !android
package process package process
import ( import (
"bytes" "bytes"
"encoding/binary" "encoding/binary"
"fmt" "fmt"
"io"
"net" "net"
"os" "os"
"path" "path"
"path/filepath" "strings"
"syscall" "syscall"
"unicode"
"unsafe" "unsafe"
"github.com/Dreamacro/clash/common/pool" "github.com/Dreamacro/clash/common/pool"
@@ -25,17 +27,6 @@ var nativeEndian = func() binary.ByteOrder {
return binary.LittleEndian return binary.LittleEndian
}() }()
type (
SocketResolver func(network string, ip net.IP, srcPort int) (inode, uid int, err error)
ProcessNameResolver func(inode, uid int) (name string, err error)
)
// export for android
var (
DefaultSocketResolver SocketResolver = resolveSocketByNetlink
DefaultProcessNameResolver ProcessNameResolver = resolveProcessNameByProcSearch
)
const ( const (
sizeOfSocketDiagRequest = syscall.SizeofNlMsghdr + 8 + 48 sizeOfSocketDiagRequest = syscall.SizeofNlMsghdr + 8 + 48
socketDiagByFamily = 20 socketDiagByFamily = 20
@@ -43,15 +34,15 @@ const (
) )
func findProcessName(network string, ip net.IP, srcPort int) (string, error) { func findProcessName(network string, ip net.IP, srcPort int) (string, error) {
inode, uid, err := DefaultSocketResolver(network, ip, srcPort) inode, uid, err := resolveSocketByNetlink(network, ip, srcPort)
if err != nil { if err != nil {
return "", err return "", err
} }
return DefaultProcessNameResolver(inode, uid) return resolveProcessNameByProcSearch(inode, uid)
} }
func resolveSocketByNetlink(network string, ip net.IP, srcPort int) (int, int, error) { func resolveSocketByNetlink(network string, ip net.IP, srcPort int) (int32, int32, error) {
var family byte var family byte
var protocol byte var protocol byte
@@ -74,13 +65,12 @@ func resolveSocketByNetlink(network string, ip net.IP, srcPort int) (int, int, e
socket, err := syscall.Socket(syscall.AF_NETLINK, syscall.SOCK_DGRAM, syscall.NETLINK_INET_DIAG) socket, err := syscall.Socket(syscall.AF_NETLINK, syscall.SOCK_DGRAM, syscall.NETLINK_INET_DIAG)
if err != nil { if err != nil {
return 0, 0, err return 0, 0, fmt.Errorf("dial netlink: %w", err)
} }
defer syscall.Close(socket) defer syscall.Close(socket)
syscall.SetNonblock(socket, true) syscall.SetsockoptTimeval(socket, syscall.SOL_SOCKET, syscall.SO_SNDTIMEO, &syscall.Timeval{Usec: 100})
syscall.SetsockoptTimeval(socket, syscall.SOL_SOCKET, syscall.SO_SNDTIMEO, &syscall.Timeval{Usec: 50}) syscall.SetsockoptTimeval(socket, syscall.SOL_SOCKET, syscall.SO_RCVTIMEO, &syscall.Timeval{Usec: 100})
syscall.SetsockoptTimeval(socket, syscall.SOL_SOCKET, syscall.SO_RCVTIMEO, &syscall.Timeval{Usec: 50})
if err := syscall.Connect(socket, &syscall.SockaddrNetlink{ if err := syscall.Connect(socket, &syscall.SockaddrNetlink{
Family: syscall.AF_NETLINK, Family: syscall.AF_NETLINK,
@@ -92,7 +82,7 @@ func resolveSocketByNetlink(network string, ip net.IP, srcPort int) (int, int, e
} }
if _, err := syscall.Write(socket, req); err != nil { if _, err := syscall.Write(socket, req); err != nil {
return 0, 0, err return 0, 0, fmt.Errorf("write request: %w", err)
} }
rb := pool.Get(pool.RelayBufferSize) rb := pool.Get(pool.RelayBufferSize)
@@ -100,24 +90,27 @@ func resolveSocketByNetlink(network string, ip net.IP, srcPort int) (int, int, e
n, err := syscall.Read(socket, rb) n, err := syscall.Read(socket, rb)
if err != nil { if err != nil {
return 0, 0, err return 0, 0, fmt.Errorf("read response: %w", err)
} }
messages, err := syscall.ParseNetlinkMessage(rb[:n]) messages, err := syscall.ParseNetlinkMessage(rb[:n])
if err != nil { if err != nil {
return 0, 0, err return 0, 0, fmt.Errorf("parse netlink message: %w", err)
} else if len(messages) == 0 { } else if len(messages) == 0 {
return 0, 0, io.ErrUnexpectedEOF return 0, 0, fmt.Errorf("unexcepted netlink response")
} }
message := messages[0] message := messages[0]
if message.Header.Type&syscall.NLMSG_ERROR != 0 { if message.Header.Type&syscall.NLMSG_ERROR != 0 {
return 0, 0, syscall.ESRCH return 0, 0, fmt.Errorf("netlink message: NLMSG_ERROR")
} }
uid, inode := unpackSocketDiagResponse(&messages[0]) inode, uid := unpackSocketDiagResponse(&messages[0])
if inode < 0 || uid < 0 {
return 0, 0, fmt.Errorf("invalid inode(%d) or uid(%d)", inode, uid)
}
return int(uid), int(inode), nil return inode, uid, nil
} }
func packSocketDiagRequest(family, protocol byte, source net.IP, sourcePort uint16) []byte { func packSocketDiagRequest(family, protocol byte, source net.IP, sourcePort uint16) []byte {
@@ -155,20 +148,20 @@ func packSocketDiagRequest(family, protocol byte, source net.IP, sourcePort uint
return buf return buf
} }
func unpackSocketDiagResponse(msg *syscall.NetlinkMessage) (inode, uid uint32) { func unpackSocketDiagResponse(msg *syscall.NetlinkMessage) (inode, uid int32) {
if len(msg.Data) < 72 { if len(msg.Data) < 72 {
return 0, 0 return 0, 0
} }
data := msg.Data data := msg.Data
uid = nativeEndian.Uint32(data[64:68]) uid = int32(nativeEndian.Uint32(data[64:68]))
inode = nativeEndian.Uint32(data[68:72]) inode = int32(nativeEndian.Uint32(data[68:72]))
return return
} }
func resolveProcessNameByProcSearch(inode, uid int) (string, error) { func resolveProcessNameByProcSearch(inode, uid int32) (string, error) {
files, err := os.ReadDir(pathProc) files, err := os.ReadDir(pathProc)
if err != nil { if err != nil {
return "", err return "", err
@@ -205,38 +198,16 @@ func resolveProcessNameByProcSearch(inode, uid int) (string, error) {
} }
if bytes.Equal(buffer[:n], socket) { if bytes.Equal(buffer[:n], socket) {
cmdline, err := os.ReadFile(path.Join(processPath, "cmdline")) return os.Readlink(path.Join(processPath, "exe"))
if err != nil {
return "", err
}
return splitCmdline(cmdline), nil
} }
} }
} }
return "", syscall.ESRCH return "", fmt.Errorf("process of uid(%d),inode(%d) not found", uid, inode)
}
func splitCmdline(cmdline []byte) string {
indexOfEndOfString := len(cmdline)
for i, c := range cmdline {
if c == 0 {
indexOfEndOfString = i
break
}
}
return filepath.Base(string(cmdline[:indexOfEndOfString]))
} }
func isPid(s string) bool { func isPid(s string) bool {
for _, s := range s { return strings.IndexFunc(s, func(r rune) bool {
if s < '0' || s > '9' { return !unicode.IsDigit(r)
return false }) == -1
}
}
return true
} }

View File

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

View File

@@ -3,7 +3,6 @@ package process
import ( import (
"fmt" "fmt"
"net" "net"
"path/filepath"
"sync" "sync"
"syscall" "syscall"
"unsafe" "unsafe"
@@ -175,7 +174,7 @@ func newSearcher(isV4, isTCP bool) *searcher {
func getTransportTable(fn uintptr, family int, class int) ([]byte, error) { func getTransportTable(fn uintptr, family int, class int) ([]byte, error) {
for size, buf := uint32(8), make([]byte, 8); ; { for size, buf := uint32(8), make([]byte, 8); ; {
ptr := unsafe.Pointer(&buf[0]) ptr := unsafe.Pointer(&buf[0])
err, _, _ := syscall.Syscall6(fn, 6, uintptr(ptr), uintptr(unsafe.Pointer(&size)), 0, uintptr(family), uintptr(class), 0) err, _, _ := syscall.SyscallN(fn, uintptr(ptr), uintptr(unsafe.Pointer(&size)), 0, uintptr(family), uintptr(class), 0)
switch err { switch err {
case 0: case 0:
@@ -210,8 +209,8 @@ func getExecPathFromPID(pid uint32) (string, error) {
buf := make([]uint16, syscall.MAX_LONG_PATH) buf := make([]uint16, syscall.MAX_LONG_PATH)
size := uint32(len(buf)) size := uint32(len(buf))
r1, _, err := syscall.Syscall6( r1, _, err := syscall.SyscallN(
queryProcName, 4, queryProcName,
uintptr(h), uintptr(h),
uintptr(1), uintptr(1),
uintptr(unsafe.Pointer(&buf[0])), uintptr(unsafe.Pointer(&buf[0])),
@@ -220,5 +219,5 @@ func getExecPathFromPID(pid uint32) (string, error) {
if r1 == 0 { if r1 == 0 {
return "", err return "", err
} }
return filepath.Base(syscall.UTF16ToString(buf[:size])), nil return syscall.UTF16ToString(buf[:size]), nil
} }

View File

@@ -1,8 +1,6 @@
package cachefile package cachefile
import ( import (
"bytes"
"encoding/gob"
"os" "os"
"sync" "sync"
"time" "time"
@@ -90,6 +88,31 @@ func (c *CacheFile) PutFakeip(key, value []byte) error {
return err return err
} }
func (c *CacheFile) DelFakeipPair(ip, host []byte) error {
if c.DB == nil {
return nil
}
err := c.DB.Batch(func(t *bbolt.Tx) error {
bucket, err := t.CreateBucketIfNotExists(bucketFakeip)
if err != nil {
return err
}
err = bucket.Delete(ip)
if len(host) > 0 {
if err := bucket.Delete(host); err != nil {
return err
}
}
return err
})
if err != nil {
log.Warnln("[CacheFile] write cache to %s failed: %s", c.DB.Path(), err.Error())
}
return err
}
func (c *CacheFile) GetFakeip(key []byte) []byte { func (c *CacheFile) GetFakeip(key []byte) []byte {
if c.DB == nil { if c.DB == nil {
return nil return nil
@@ -109,73 +132,45 @@ func (c *CacheFile) GetFakeip(key []byte) []byte {
return bucket.Get(key) return bucket.Get(key)
} }
func (c *CacheFile) FlushFakeIP() error {
err := c.DB.Batch(func(t *bbolt.Tx) error {
bucket := t.Bucket(bucketFakeip)
if bucket == nil {
return nil
}
return t.DeleteBucket(bucketFakeip)
})
return err
}
func (c *CacheFile) Close() error { func (c *CacheFile) Close() error {
return c.DB.Close() return c.DB.Close()
} }
// TODO: remove migrateCache until 2022 func initCache() {
func migrateCache() { options := bbolt.Options{Timeout: time.Second}
defer func() { db, err := bbolt.Open(C.Path.Cache(), fileMode, &options)
options := bbolt.Options{Timeout: time.Second} switch err {
db, err := bbolt.Open(C.Path.Cache(), fileMode, &options) case bbolt.ErrInvalid, bbolt.ErrChecksum, bbolt.ErrVersionMismatch:
switch err { if err = os.Remove(C.Path.Cache()); err != nil {
case bbolt.ErrInvalid, bbolt.ErrChecksum, bbolt.ErrVersionMismatch: log.Warnln("[CacheFile] remove invalid cache file error: %s", err.Error())
if err = os.Remove(C.Path.Cache()); err != nil { break
log.Warnln("[CacheFile] remove invalid cache file error: %s", err.Error())
break
}
log.Infoln("[CacheFile] remove invalid cache file and create new one")
db, err = bbolt.Open(C.Path.Cache(), fileMode, &options)
} }
if err != nil { log.Infoln("[CacheFile] remove invalid cache file and create new one")
log.Warnln("[CacheFile] can't open cache file: %s", err.Error()) db, err = bbolt.Open(C.Path.Cache(), fileMode, &options)
} }
defaultCache = &CacheFile{
DB: db,
}
}()
buf, err := os.ReadFile(C.Path.OldCache())
if err != nil { if err != nil {
return log.Warnln("[CacheFile] can't open cache file: %s", err.Error())
} }
defer os.Remove(C.Path.OldCache())
// read old cache file defaultCache = &CacheFile{
type cache struct { DB: db,
Selected map[string]string
} }
model := &cache{
Selected: map[string]string{},
}
bufReader := bytes.NewBuffer(buf)
gob.NewDecoder(bufReader).Decode(model)
// write to new cache file
db, err := bbolt.Open(C.Path.Cache(), fileMode, nil)
if err != nil {
return
}
defer db.Close()
db.Batch(func(t *bbolt.Tx) error {
bucket, err := t.CreateBucketIfNotExists(bucketSelected)
if err != nil {
return err
}
for group, selected := range model.Selected {
if err := bucket.Put([]byte(group), []byte(selected)); err != nil {
return err
}
}
return nil
})
} }
// Cache return singleton of CacheFile // Cache return singleton of CacheFile
func Cache() *CacheFile { func Cache() *CacheFile {
initOnce.Do(migrateCache) initOnce.Do(initCache)
return defaultCache return defaultCache
} }

View File

@@ -10,8 +10,10 @@ type Enhancer interface {
FakeIPEnabled() bool FakeIPEnabled() bool
MappingEnabled() bool MappingEnabled() bool
IsFakeIP(net.IP) bool IsFakeIP(net.IP) bool
IsFakeBroadcastIP(net.IP) bool
IsExistFakeIP(net.IP) bool IsExistFakeIP(net.IP) bool
FindHostByIP(net.IP) (string, bool) FindHostByIP(net.IP) (string, bool)
FlushFakeIP() error
} }
func FakeIPEnabled() bool { func FakeIPEnabled() bool {
@@ -38,6 +40,14 @@ func IsFakeIP(ip net.IP) bool {
return false return false
} }
func IsFakeBroadcastIP(ip net.IP) bool {
if mapper := DefaultHostMapper; mapper != nil {
return mapper.IsFakeBroadcastIP(ip)
}
return false
}
func IsExistFakeIP(ip net.IP) bool { func IsExistFakeIP(ip net.IP) bool {
if mapper := DefaultHostMapper; mapper != nil { if mapper := DefaultHostMapper; mapper != nil {
return mapper.IsExistFakeIP(ip) return mapper.IsExistFakeIP(ip)
@@ -53,3 +63,10 @@ func FindHostByIP(ip net.IP) (string, bool) {
return "", false return "", false
} }
func FlushFakeIP() error {
if mapper := DefaultHostMapper; mapper != nil {
return mapper.FlushFakeIP()
}
return nil
}

View File

@@ -15,8 +15,8 @@ var (
// DefaultResolver aim to resolve ip // DefaultResolver aim to resolve ip
DefaultResolver Resolver DefaultResolver Resolver
// MainResolver resolve ip with main domain server // ProxyServerHostResolver resolve ip to proxies server host
MainResolver Resolver ProxyServerHostResolver Resolver
// DisableIPv6 means don't resolve ipv6 host // DisableIPv6 means don't resolve ipv6 host
// default value is true // default value is true
@@ -46,10 +46,6 @@ func ResolveIPv4(host string) (net.IP, error) {
return ResolveIPv4WithResolver(host, DefaultResolver) return ResolveIPv4WithResolver(host, DefaultResolver)
} }
func ResolveIPv4WithMain(host string) (net.IP, error) {
return ResolveIPv4WithResolver(host, MainResolver)
}
func ResolveIPv4WithResolver(host string, r Resolver) (net.IP, error) { func ResolveIPv4WithResolver(host string, r Resolver) (net.IP, error) {
if node := DefaultHosts.Search(host); node != nil { if node := DefaultHosts.Search(host); node != nil {
if ip := node.Data.(net.IP).To4(); ip != nil { if ip := node.Data.(net.IP).To4(); ip != nil {
@@ -69,16 +65,20 @@ func ResolveIPv4WithResolver(host string, r Resolver) (net.IP, error) {
return r.ResolveIPv4(host) return r.ResolveIPv4(host)
} }
ctx, cancel := context.WithTimeout(context.Background(), DefaultDNSTimeout) if DefaultResolver == nil {
defer cancel() ctx, cancel := context.WithTimeout(context.Background(), DefaultDNSTimeout)
ipAddrs, err := net.DefaultResolver.LookupIP(ctx, "ip4", host) defer cancel()
if err != nil { ipAddrs, err := net.DefaultResolver.LookupIP(ctx, "ip4", host)
return nil, err if err != nil {
} else if len(ipAddrs) == 0 { return nil, err
return nil, ErrIPNotFound } else if len(ipAddrs) == 0 {
return nil, ErrIPNotFound
}
return ipAddrs[rand.Intn(len(ipAddrs))], nil
} }
return ipAddrs[rand.Intn(len(ipAddrs))], nil return nil, ErrIPNotFound
} }
// ResolveIPv6 with a host, return ipv6 // ResolveIPv6 with a host, return ipv6
@@ -86,10 +86,6 @@ func ResolveIPv6(host string) (net.IP, error) {
return ResolveIPv6WithResolver(host, DefaultResolver) return ResolveIPv6WithResolver(host, DefaultResolver)
} }
func ResolveIPv6WithMain(host string) (net.IP, error) {
return ResolveIPv6WithResolver(host, MainResolver)
}
func ResolveIPv6WithResolver(host string, r Resolver) (net.IP, error) { func ResolveIPv6WithResolver(host string, r Resolver) (net.IP, error) {
if DisableIPv6 { if DisableIPv6 {
return nil, ErrIPv6Disabled return nil, ErrIPv6Disabled
@@ -113,16 +109,20 @@ func ResolveIPv6WithResolver(host string, r Resolver) (net.IP, error) {
return r.ResolveIPv6(host) return r.ResolveIPv6(host)
} }
ctx, cancel := context.WithTimeout(context.Background(), DefaultDNSTimeout) if DefaultResolver == nil {
defer cancel() ctx, cancel := context.WithTimeout(context.Background(), DefaultDNSTimeout)
ipAddrs, err := net.DefaultResolver.LookupIP(ctx, "ip6", host) defer cancel()
if err != nil { ipAddrs, err := net.DefaultResolver.LookupIP(ctx, "ip6", host)
return nil, err if err != nil {
} else if len(ipAddrs) == 0 { return nil, err
return nil, ErrIPNotFound } else if len(ipAddrs) == 0 {
return nil, ErrIPNotFound
}
return ipAddrs[rand.Intn(len(ipAddrs))], nil
} }
return ipAddrs[rand.Intn(len(ipAddrs))], nil return nil, ErrIPNotFound
} }
// ResolveIPWithResolver same as ResolveIP, but with a resolver // ResolveIPWithResolver same as ResolveIP, but with a resolver
@@ -145,12 +145,16 @@ func ResolveIPWithResolver(host string, r Resolver) (net.IP, error) {
return ip, nil return ip, nil
} }
ipAddr, err := net.ResolveIPAddr("ip", host) if DefaultResolver == nil {
if err != nil { ipAddr, err := net.ResolveIPAddr("ip", host)
return nil, err if err != nil {
return nil, err
}
return ipAddr.IP, nil
} }
return ipAddr.IP, nil return nil, ErrIPNotFound
} }
// ResolveIP with a host, return ip // ResolveIP with a host, return ip
@@ -158,7 +162,26 @@ func ResolveIP(host string) (net.IP, error) {
return ResolveIPWithResolver(host, DefaultResolver) return ResolveIPWithResolver(host, DefaultResolver)
} }
// ResolveIPWithMainResolver with a host, use main resolver, return ip // ResolveIPv4ProxyServerHost proxies server host only
func ResolveIPWithMainResolver(host string) (net.IP, error) { func ResolveIPv4ProxyServerHost(host string) (net.IP, error) {
return ResolveIPWithResolver(host, MainResolver) if ProxyServerHostResolver != nil {
return ResolveIPv4WithResolver(host, ProxyServerHostResolver)
}
return ResolveIPv4(host)
}
// ResolveIPv6ProxyServerHost proxies server host only
func ResolveIPv6ProxyServerHost(host string) (net.IP, error) {
if ProxyServerHostResolver != nil {
return ResolveIPv6WithResolver(host, ProxyServerHostResolver)
}
return ResolveIPv6(host)
}
// ResolveProxyServerHost proxies server host only
func ResolveProxyServerHost(host string) (net.IP, error) {
if ProxyServerHostResolver != nil {
return ResolveIPWithResolver(host, ProxyServerHostResolver)
}
return ResolveIP(host)
} }

View File

@@ -51,7 +51,7 @@ func ValidAndSplitDomain(domain string) ([]string, bool) {
// 3. subdomain.*.example.com // 3. subdomain.*.example.com
// 4. .example.com // 4. .example.com
// 5. +.example.com // 5. +.example.com
func (t *DomainTrie) Insert(domain string, data interface{}) error { func (t *DomainTrie) Insert(domain string, data any) error {
parts, valid := ValidAndSplitDomain(domain) parts, valid := ValidAndSplitDomain(domain)
if !valid { if !valid {
return ErrInvalidDomain return ErrInvalidDomain
@@ -68,7 +68,7 @@ func (t *DomainTrie) Insert(domain string, data interface{}) error {
return nil return nil
} }
func (t *DomainTrie) insert(parts []string, data interface{}) { func (t *DomainTrie) insert(parts []string, data any) {
node := t.root node := t.root
// reverse storage domain part to save space // reverse storage domain part to save space
for i := len(parts) - 1; i >= 0; i-- { for i := len(parts) - 1; i >= 0; i-- {
@@ -109,13 +109,13 @@ func (t *DomainTrie) search(node *Node, parts []string) *Node {
} }
if c := node.getChild(parts[len(parts)-1]); c != nil { if c := node.getChild(parts[len(parts)-1]); c != nil {
if n := t.search(c, parts[:len(parts)-1]); n != nil { if n := t.search(c, parts[:len(parts)-1]); n != nil && n.Data != nil {
return n return n
} }
} }
if c := node.getChild(wildcard); c != nil { if c := node.getChild(wildcard); c != nil {
if n := t.search(c, parts[:len(parts)-1]); n != nil { if n := t.search(c, parts[:len(parts)-1]); n != nil && n.Data != nil {
return n return n
} }
} }

View File

@@ -97,3 +97,11 @@ func TestTrie_Boundary(t *testing.T) {
assert.NotNil(t, tree.Insert("..dev", localIP)) assert.NotNil(t, tree.Insert("..dev", localIP))
assert.Nil(t, tree.Search("dev")) assert.Nil(t, tree.Search("dev"))
} }
func TestTrie_WildcardBoundary(t *testing.T) {
tree := New()
tree.Insert("+.*", localIP)
tree.Insert("stun.*.*.*", localIP)
assert.NotNil(t, tree.Search("example.com"))
}

View File

@@ -103,7 +103,7 @@ func subIpCidr(ip net.IP, maskSize int, isIpv4 bool) ([]net.IP, int, error) {
lastByteMaskSize := maskSize % 8 lastByteMaskSize := maskSize % 8
lastByteMaskIndex := maskSize / 8 lastByteMaskIndex := maskSize / 8
subIpCidrNum := 0xFF >> lastByteMaskSize subIpCidrNum := 0xFF >> lastByteMaskSize
for i := 0; i < subIpCidrNum; i++ { for i := 0; i <= subIpCidrNum; i++ {
subIpCidr := make([]byte, len(ip)) subIpCidr := make([]byte, len(ip))
copy(subIpCidr, ip) copy(subIpCidr, ip)
subIpCidr[lastByteMaskIndex] += byte(i) subIpCidr[lastByteMaskIndex] += byte(i)

View File

@@ -2,8 +2,8 @@ package trie
// Node is the trie's node // Node is the trie's node
type Node struct { type Node struct {
Data interface{}
children map[string]*Node children map[string]*Node
Data any
} }
func (n *Node) getChild(s string) *Node { func (n *Node) getChild(s string) *Node {
@@ -18,7 +18,7 @@ func (n *Node) addChild(s string, child *Node) {
n.children[s] = child n.children[s] = child
} }
func newNode(data interface{}) *Node { func newNode(data any) *Node {
return &Node{ return &Node{
Data: data, Data: data,
children: map[string]*Node{}, children: map[string]*Node{},

View File

@@ -26,12 +26,20 @@ func TestIpv4AddFail(t *testing.T) {
func TestIpv4Search(t *testing.T) { func TestIpv4Search(t *testing.T) {
trie := NewIpCidrTrie() trie := NewIpCidrTrie()
// Boundary testing
assert.NoError(t, trie.AddIpCidrForString("149.154.160.0/20"))
assert.Equal(t, true, trie.IsContainForString("149.154.160.0"))
assert.Equal(t, true, trie.IsContainForString("149.154.175.255"))
assert.Equal(t, false, trie.IsContainForString("149.154.176.0"))
assert.Equal(t, false, trie.IsContainForString("149.154.159.255"))
assert.NoError(t, trie.AddIpCidrForString("129.2.36.0/16")) assert.NoError(t, trie.AddIpCidrForString("129.2.36.0/16"))
assert.NoError(t, trie.AddIpCidrForString("10.2.36.0/18")) assert.NoError(t, trie.AddIpCidrForString("10.2.36.0/18"))
assert.NoError(t, trie.AddIpCidrForString("16.2.23.0/24")) assert.NoError(t, trie.AddIpCidrForString("16.2.23.0/24"))
assert.NoError(t, trie.AddIpCidrForString("11.2.13.2/26")) assert.NoError(t, trie.AddIpCidrForString("11.2.13.2/26"))
assert.NoError(t, trie.AddIpCidrForString("55.5.6.3/8")) assert.NoError(t, trie.AddIpCidrForString("55.5.6.3/8"))
assert.NoError(t, trie.AddIpCidrForString("66.23.25.4/6")) assert.NoError(t, trie.AddIpCidrForString("66.23.25.4/6"))
assert.Equal(t, true, trie.IsContainForString("129.2.3.65")) assert.Equal(t, true, trie.IsContainForString("129.2.3.65"))
assert.Equal(t, false, trie.IsContainForString("15.2.3.1")) assert.Equal(t, false, trie.IsContainForString("15.2.3.1"))
assert.Equal(t, true, trie.IsContainForString("11.2.13.1")) assert.Equal(t, true, trie.IsContainForString("11.2.13.1"))
@@ -41,6 +49,7 @@ func TestIpv4Search(t *testing.T) {
assert.Equal(t, false, trie.IsContain(net.ParseIP("22"))) assert.Equal(t, false, trie.IsContain(net.ParseIP("22")))
assert.Equal(t, false, trie.IsContain(net.ParseIP(""))) assert.Equal(t, false, trie.IsContain(net.ParseIP("")))
} }
func TestIpv6AddSuccess(t *testing.T) { func TestIpv6AddSuccess(t *testing.T) {
@@ -66,6 +75,14 @@ func TestIpv6AddFail(t *testing.T) {
func TestIpv6Search(t *testing.T) { func TestIpv6Search(t *testing.T) {
trie := NewIpCidrTrie() trie := NewIpCidrTrie()
// Boundary testing
assert.NoError(t, trie.AddIpCidrForString("2a0a:f280::/32"))
assert.Equal(t, true, trie.IsContainForString("2a0a:f280:0000:0000:0000:0000:0000:0000"))
assert.Equal(t, true, trie.IsContainForString("2a0a:f280:ffff:ffff:ffff:ffff:ffff:ffff"))
assert.Equal(t, false, trie.IsContainForString("2a0a:f279:ffff:ffff:ffff:ffff:ffff:ffff"))
assert.Equal(t, false, trie.IsContainForString("2a0a:f281:0000:0000:0000:0000:0000:0000"))
assert.NoError(t, trie.AddIpCidrForString("2001:b28:f23d:f001::e/128")) assert.NoError(t, trie.AddIpCidrForString("2001:b28:f23d:f001::e/128"))
assert.NoError(t, trie.AddIpCidrForString("2001:67c:4e8:f002::e/12")) assert.NoError(t, trie.AddIpCidrForString("2001:67c:4e8:f002::e/12"))
assert.NoError(t, trie.AddIpCidrForString("2001:b28:f23d:f003::e/96")) assert.NoError(t, trie.AddIpCidrForString("2001:b28:f23d:f003::e/96"))
@@ -79,4 +96,5 @@ func TestIpv6Search(t *testing.T) {
assert.Equal(t, true, trie.IsContainForString("2001:67c:4e8:9666::1213")) assert.Equal(t, true, trie.IsContainForString("2001:67c:4e8:9666::1213"))
assert.Equal(t, false, trie.IsContain(net.ParseIP("22233:22"))) assert.Equal(t, false, trie.IsContain(net.ParseIP("22233:22")))
} }

View File

@@ -4,18 +4,22 @@ import (
"container/list" "container/list"
"errors" "errors"
"fmt" "fmt"
R "github.com/Dreamacro/clash/rule"
RP "github.com/Dreamacro/clash/rule/provider"
"net" "net"
"net/netip"
"net/url" "net/url"
"os" "os"
"regexp"
"runtime" "runtime"
"strings" "strings"
"time"
"github.com/Dreamacro/clash/adapter" "github.com/Dreamacro/clash/adapter"
"github.com/Dreamacro/clash/adapter/outbound" "github.com/Dreamacro/clash/adapter/outbound"
"github.com/Dreamacro/clash/adapter/outboundgroup" "github.com/Dreamacro/clash/adapter/outboundgroup"
"github.com/Dreamacro/clash/adapter/provider" "github.com/Dreamacro/clash/adapter/provider"
"github.com/Dreamacro/clash/component/auth" "github.com/Dreamacro/clash/component/auth"
"github.com/Dreamacro/clash/component/dialer"
"github.com/Dreamacro/clash/component/fakeip" "github.com/Dreamacro/clash/component/fakeip"
"github.com/Dreamacro/clash/component/geodata" "github.com/Dreamacro/clash/component/geodata"
"github.com/Dreamacro/clash/component/geodata/router" "github.com/Dreamacro/clash/component/geodata/router"
@@ -23,8 +27,8 @@ import (
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
providerTypes "github.com/Dreamacro/clash/constant/provider" providerTypes "github.com/Dreamacro/clash/constant/provider"
"github.com/Dreamacro/clash/dns" "github.com/Dreamacro/clash/dns"
"github.com/Dreamacro/clash/listener/tun/ipstack/commons"
"github.com/Dreamacro/clash/log" "github.com/Dreamacro/clash/log"
R "github.com/Dreamacro/clash/rule"
T "github.com/Dreamacro/clash/tunnel" T "github.com/Dreamacro/clash/tunnel"
"gopkg.in/yaml.v2" "gopkg.in/yaml.v2"
@@ -34,26 +38,29 @@ import (
type General struct { type General struct {
Inbound Inbound
Controller Controller
Mode T.TunnelMode `json:"mode"` Mode T.TunnelMode `json:"mode"`
LogLevel log.LogLevel `json:"log-level"` UnifiedDelay bool
IPv6 bool `json:"ipv6"` LogLevel log.LogLevel `json:"log-level"`
Interface string `json:"-"` IPv6 bool `json:"ipv6"`
Interface string `json:"-"`
RoutingMark int `json:"-"`
GeodataMode bool `json:"geodata-mode"`
GeodataLoader string `json:"geodata-loader"`
} }
// Inbound // Inbound config
type Inbound struct { type Inbound struct {
Port int `json:"port"` Port int `json:"port"`
SocksPort int `json:"socks-port"` SocksPort int `json:"socks-port"`
RedirPort int `json:"redir-port"` RedirPort int `json:"redir-port"`
TProxyPort int `json:"tproxy-port"` TProxyPort int `json:"tproxy-port"`
MixedPort int `json:"mixed-port"` MixedPort int `json:"mixed-port"`
Tun Tun `json:"tun"`
Authentication []string `json:"authentication"` Authentication []string `json:"authentication"`
AllowLan bool `json:"allow-lan"` AllowLan bool `json:"allow-lan"`
BindAddress string `json:"bind-address"` BindAddress string `json:"bind-address"`
} }
// Controller // Controller config
type Controller struct { type Controller struct {
ExternalController string `json:"-"` ExternalController string `json:"-"`
ExternalUI string `json:"-"` ExternalUI string `json:"-"`
@@ -62,17 +69,18 @@ type Controller struct {
// DNS config // DNS config
type DNS struct { type DNS struct {
Enable bool `yaml:"enable"` Enable bool `yaml:"enable"`
IPv6 bool `yaml:"ipv6"` IPv6 bool `yaml:"ipv6"`
NameServer []dns.NameServer `yaml:"nameserver"` NameServer []dns.NameServer `yaml:"nameserver"`
Fallback []dns.NameServer `yaml:"fallback"` Fallback []dns.NameServer `yaml:"fallback"`
FallbackFilter FallbackFilter `yaml:"fallback-filter"` FallbackFilter FallbackFilter `yaml:"fallback-filter"`
Listen string `yaml:"listen"` Listen string `yaml:"listen"`
EnhancedMode C.DNSMode `yaml:"enhanced-mode"` EnhancedMode C.DNSMode `yaml:"enhanced-mode"`
DefaultNameserver []dns.NameServer `yaml:"default-nameserver"` DefaultNameserver []dns.NameServer `yaml:"default-nameserver"`
FakeIPRange *fakeip.Pool FakeIPRange *fakeip.Pool
Hosts *trie.DomainTrie Hosts *trie.DomainTrie
NameServerPolicy map[string]dns.NameServer NameServerPolicy map[string]dns.NameServer
ProxyServerNameserver []dns.NameServer
} }
// FallbackFilter config // FallbackFilter config
@@ -98,10 +106,11 @@ type Profile struct {
// Tun config // Tun config
type Tun struct { type Tun struct {
Enable bool `yaml:"enable" json:"enable"` Enable bool `yaml:"enable" json:"enable"`
Stack string `yaml:"stack" json:"stack"` Device string `yaml:"device" json:"device"`
DNSListen string `yaml:"dns-listen" json:"dns-listen"` Stack C.TUNStack `yaml:"stack" json:"stack"`
AutoRoute bool `yaml:"auto-route" json:"auto-route"` DNSHijack []netip.AddrPort `yaml:"dns-hijack" json:"dns-hijack"`
AutoRoute bool `yaml:"auto-route" json:"auto-route"`
} }
// Script config // Script config
@@ -110,6 +119,13 @@ type Script struct {
ShortcutsCode map[string]string `yaml:"shortcuts" json:"shortcuts"` ShortcutsCode map[string]string `yaml:"shortcuts" json:"shortcuts"`
} }
// IPTables config
type IPTables struct {
Enable bool `yaml:"enable" json:"enable"`
InboundInterface string `yaml:"inbound-interface" json:"inbound-interface"`
Bypass []string `yaml:"bypass" json:"bypass"`
}
// Experimental config // Experimental config
type Experimental struct{} type Experimental struct{}
@@ -117,6 +133,7 @@ type Experimental struct{}
type Config struct { type Config struct {
General *General General *General
Tun *Tun Tun *Tun
IPTables *IPTables
DNS *DNS DNS *DNS
Experimental *Experimental Experimental *Experimental
Hosts *trie.DomainTrie Hosts *trie.DomainTrie
@@ -129,18 +146,19 @@ type Config struct {
} }
type RawDNS struct { type RawDNS struct {
Enable bool `yaml:"enable"` Enable bool `yaml:"enable"`
IPv6 bool `yaml:"ipv6"` IPv6 bool `yaml:"ipv6"`
UseHosts bool `yaml:"use-hosts"` UseHosts bool `yaml:"use-hosts"`
NameServer []string `yaml:"nameserver"` NameServer []string `yaml:"nameserver"`
Fallback []string `yaml:"fallback"` Fallback []string `yaml:"fallback"`
FallbackFilter RawFallbackFilter `yaml:"fallback-filter"` FallbackFilter RawFallbackFilter `yaml:"fallback-filter"`
Listen string `yaml:"listen"` Listen string `yaml:"listen"`
EnhancedMode C.DNSMode `yaml:"enhanced-mode"` EnhancedMode C.DNSMode `yaml:"enhanced-mode"`
FakeIPRange string `yaml:"fake-ip-range"` FakeIPRange string `yaml:"fake-ip-range"`
FakeIPFilter []string `yaml:"fake-ip-filter"` FakeIPFilter []string `yaml:"fake-ip-filter"`
DefaultNameserver []string `yaml:"default-nameserver"` DefaultNameserver []string `yaml:"default-nameserver"`
NameServerPolicy map[string]string `yaml:"nameserver-policy"` NameServerPolicy map[string]string `yaml:"nameserver-policy"`
ProxyServerNameserver []string `yaml:"proxy-server-nameserver"`
} }
type RawFallbackFilter struct { type RawFallbackFilter struct {
@@ -151,6 +169,14 @@ type RawFallbackFilter struct {
GeoSite []string `yaml:"geosite"` GeoSite []string `yaml:"geosite"`
} }
type RawTun struct {
Enable bool `yaml:"enable" json:"enable"`
Device string `yaml:"device" json:"device"`
Stack C.TUNStack `yaml:"stack" json:"stack"`
DNSHijack []string `yaml:"dns-hijack" json:"dns-hijack"`
AutoRoute bool `yaml:"auto-route" json:"auto-route"`
}
type RawConfig struct { type RawConfig struct {
Port int `yaml:"port"` Port int `yaml:"port"`
SocksPort int `yaml:"socks-port"` SocksPort int `yaml:"socks-port"`
@@ -161,24 +187,28 @@ type RawConfig struct {
AllowLan bool `yaml:"allow-lan"` AllowLan bool `yaml:"allow-lan"`
BindAddress string `yaml:"bind-address"` BindAddress string `yaml:"bind-address"`
Mode T.TunnelMode `yaml:"mode"` Mode T.TunnelMode `yaml:"mode"`
UnifiedDelay bool `yaml:"unified-delay"`
LogLevel log.LogLevel `yaml:"log-level"` LogLevel log.LogLevel `yaml:"log-level"`
IPv6 bool `yaml:"ipv6"` IPv6 bool `yaml:"ipv6"`
ExternalController string `yaml:"external-controller"` ExternalController string `yaml:"external-controller"`
ExternalUI string `yaml:"external-ui"` ExternalUI string `yaml:"external-ui"`
Secret string `yaml:"secret"` Secret string `yaml:"secret"`
Interface string `yaml:"interface-name"` Interface string `yaml:"interface-name"`
RoutingMark int `yaml:"routing-mark"`
GeodataMode bool `yaml:"geodata-mode"`
GeodataLoader string `yaml:"geodata-loader"`
ProxyProvider map[string]map[string]interface{} `yaml:"proxy-providers"` ProxyProvider map[string]map[string]any `yaml:"proxy-providers"`
RuleProvider map[string]map[string]interface{} `yaml:"rule-providers"` RuleProvider map[string]map[string]any `yaml:"rule-providers"`
Hosts map[string]string `yaml:"hosts"` Hosts map[string]string `yaml:"hosts"`
DNS RawDNS `yaml:"dns"` DNS RawDNS `yaml:"dns"`
Tun Tun `yaml:"tun"` Tun RawTun `yaml:"tun"`
Experimental Experimental `yaml:"experimental"` IPTables IPTables `yaml:"iptables"`
Profile Profile `yaml:"profile"` Experimental Experimental `yaml:"experimental"`
Proxy []map[string]interface{} `yaml:"proxies"` Profile Profile `yaml:"profile"`
ProxyGroup []map[string]interface{} `yaml:"proxy-groups"` Proxy []map[string]any `yaml:"proxies"`
Rule []string `yaml:"rules"` ProxyGroup []map[string]any `yaml:"proxy-groups"`
Script Script `yaml:"script"` Rule []string `yaml:"rules"`
} }
// Parse config // Parse config
@@ -197,22 +227,32 @@ func UnmarshalRawConfig(buf []byte) (*RawConfig, error) {
AllowLan: false, AllowLan: false,
BindAddress: "*", BindAddress: "*",
Mode: T.Rule, Mode: T.Rule,
GeodataMode: C.GeodataMode,
GeodataLoader: "memconservative",
UnifiedDelay: false,
Authentication: []string{}, Authentication: []string{},
LogLevel: log.INFO, LogLevel: log.INFO,
Hosts: map[string]string{}, Hosts: map[string]string{},
Rule: []string{}, Rule: []string{},
Proxy: []map[string]interface{}{}, Proxy: []map[string]any{},
ProxyGroup: []map[string]interface{}{}, ProxyGroup: []map[string]any{},
Tun: Tun{ Tun: RawTun{
Enable: false, Enable: false,
Stack: "lwip", Device: "",
DNSListen: "0.0.0.0:53", Stack: C.TunGvisor,
DNSHijack: []string{"0.0.0.0:53"}, // default hijack all dns query
AutoRoute: true, AutoRoute: true,
}, },
IPTables: IPTables{
Enable: false,
InboundInterface: "lo",
Bypass: []string{},
},
DNS: RawDNS{ DNS: RawDNS{
Enable: false, Enable: false,
UseHosts: true, UseHosts: true,
FakeIPRange: "198.18.0.1/16", EnhancedMode: C.DNSMapping,
FakeIPRange: "198.18.0.1/16",
FallbackFilter: RawFallbackFilter{ FallbackFilter: RawFallbackFilter{
GeoIP: true, GeoIP: true,
GeoIPCode: "CN", GeoIPCode: "CN",
@@ -222,15 +262,22 @@ func UnmarshalRawConfig(buf []byte) (*RawConfig, error) {
DefaultNameserver: []string{ DefaultNameserver: []string{
"114.114.114.114", "114.114.114.114",
"223.5.5.5", "223.5.5.5",
"8.8.8.8",
"1.0.0.1",
},
NameServer: []string{
"https://doh.pub/dns-query",
"tls://223.5.5.5:853",
},
FakeIPFilter: []string{
"dns.msftnsci.com",
"www.msftnsci.com",
"www.msftconnecttest.com",
}, },
}, },
Profile: Profile{ Profile: Profile{
StoreSelected: true, StoreSelected: true,
}, },
Script: Script{
MainCode: "",
ShortcutsCode: map[string]string{},
},
} }
if err := yaml.Unmarshal(buf, rawCfg); err != nil { if err := yaml.Unmarshal(buf, rawCfg); err != nil {
@@ -242,9 +289,11 @@ func UnmarshalRawConfig(buf []byte) (*RawConfig, error) {
func ParseRawConfig(rawCfg *RawConfig) (*Config, error) { func ParseRawConfig(rawCfg *RawConfig) (*Config, error) {
config := &Config{} config := &Config{}
log.Infoln("Start initial configuration in progress") //Segment finished in xxm
startTime := time.Now()
config.Experimental = &rawCfg.Experimental config.Experimental = &rawCfg.Experimental
config.Profile = &rawCfg.Profile config.Profile = &rawCfg.Profile
config.IPTables = &rawCfg.IPTables
general, err := parseGeneral(rawCfg) general, err := parseGeneral(rawCfg)
if err != nil { if err != nil {
@@ -252,6 +301,14 @@ func ParseRawConfig(rawCfg *RawConfig) (*Config, error) {
} }
config.General = general config.General = general
tunCfg, err := parseTun(rawCfg.Tun, config.General)
if err != nil {
return nil, err
}
config.Tun = tunCfg
dialer.DefaultInterface.Store(config.General.Interface)
proxies, providers, err := parseProxies(rawCfg) proxies, providers, err := parseProxies(rawCfg)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -259,11 +316,6 @@ func ParseRawConfig(rawCfg *RawConfig) (*Config, error) {
config.Proxies = proxies config.Proxies = proxies
config.Providers = providers config.Providers = providers
err = parseScript(rawCfg)
if err != nil {
return nil, err
}
rules, ruleProviders, err := parseRules(rawCfg, proxies) rules, ruleProviders, err := parseRules(rawCfg, proxies)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -285,12 +337,14 @@ func ParseRawConfig(rawCfg *RawConfig) (*Config, error) {
config.Users = parseAuthentication(rawCfg.Authentication) config.Users = parseAuthentication(rawCfg.Authentication)
elapsedTime := time.Since(startTime) / time.Millisecond // duration in ms
log.Infoln("Initial configuration complete, total time: %dms", elapsedTime) //Segment finished in xxm
return config, nil return config, nil
} }
func parseGeneral(cfg *RawConfig) (*General, error) { func parseGeneral(cfg *RawConfig) (*General, error) {
externalUI := cfg.ExternalUI externalUI := cfg.ExternalUI
geodata.SetLoader(cfg.GeodataLoader)
// checkout externalUI exist // checkout externalUI exist
if externalUI != "" { if externalUI != "" {
externalUI = C.Path.Resolve(externalUI) externalUI = C.Path.Resolve(externalUI)
@@ -307,7 +361,6 @@ func parseGeneral(cfg *RawConfig) (*General, error) {
RedirPort: cfg.RedirPort, RedirPort: cfg.RedirPort,
TProxyPort: cfg.TProxyPort, TProxyPort: cfg.TProxyPort,
MixedPort: cfg.MixedPort, MixedPort: cfg.MixedPort,
Tun: cfg.Tun,
AllowLan: cfg.AllowLan, AllowLan: cfg.AllowLan,
BindAddress: cfg.BindAddress, BindAddress: cfg.BindAddress,
}, },
@@ -316,25 +369,32 @@ func parseGeneral(cfg *RawConfig) (*General, error) {
ExternalUI: cfg.ExternalUI, ExternalUI: cfg.ExternalUI,
Secret: cfg.Secret, Secret: cfg.Secret,
}, },
Mode: cfg.Mode, UnifiedDelay: cfg.UnifiedDelay,
LogLevel: cfg.LogLevel, Mode: cfg.Mode,
IPv6: cfg.IPv6, LogLevel: cfg.LogLevel,
Interface: cfg.Interface, IPv6: cfg.IPv6,
Interface: cfg.Interface,
RoutingMark: cfg.RoutingMark,
GeodataMode: cfg.GeodataMode,
GeodataLoader: cfg.GeodataLoader,
}, nil }, nil
} }
func parseProxies(cfg *RawConfig) (proxies map[string]C.Proxy, providersMap map[string]providerTypes.ProxyProvider, err error) { func parseProxies(cfg *RawConfig) (proxies map[string]C.Proxy, providersMap map[string]providerTypes.ProxyProvider, err error) {
proxies = make(map[string]C.Proxy) proxies = make(map[string]C.Proxy)
providersMap = make(map[string]providerTypes.ProxyProvider) providersMap = make(map[string]providerTypes.ProxyProvider)
proxyList := []string{}
_proxiesList := list.New()
_groupsList := list.New()
proxiesConfig := cfg.Proxy proxiesConfig := cfg.Proxy
groupsConfig := cfg.ProxyGroup groupsConfig := cfg.ProxyGroup
providersConfig := cfg.ProxyProvider providersConfig := cfg.ProxyProvider
var proxyList []string
_proxiesList := list.New()
_groupsList := list.New()
proxies["DIRECT"] = adapter.NewProxy(outbound.NewDirect()) proxies["DIRECT"] = adapter.NewProxy(outbound.NewDirect())
proxies["REJECT"] = adapter.NewProxy(outbound.NewReject()) proxies["REJECT"] = adapter.NewProxy(outbound.NewReject())
proxies["COMPATIBLE"] = adapter.NewProxy(outbound.NewCompatible())
proxies["PASS"] = adapter.NewProxy(outbound.NewPass())
proxyList = append(proxyList, "DIRECT", "REJECT") proxyList = append(proxyList, "DIRECT", "REJECT")
// parse proxy // parse proxy
@@ -381,13 +441,6 @@ func parseProxies(cfg *RawConfig) (proxies map[string]C.Proxy, providersMap map[
providersMap[name] = pd providersMap[name] = pd
} }
for _, rp := range providersMap {
log.Infoln("Start initial provider %s", rp.Name())
if err := rp.Initial(); err != nil {
return nil, nil, fmt.Errorf("initial proxy provider %s error: %w", rp.Name(), err)
}
}
// parse proxy group // parse proxy group
for idx, mapping := range groupsConfig { for idx, mapping := range groupsConfig {
group, err := outboundgroup.ParseProxyGroup(mapping, proxies, providersMap) group, err := outboundgroup.ParseProxyGroup(mapping, proxies, providersMap)
@@ -403,20 +456,11 @@ func parseProxies(cfg *RawConfig) (proxies map[string]C.Proxy, providersMap map[
proxies[groupName] = adapter.NewProxy(group) proxies[groupName] = adapter.NewProxy(group)
} }
// initial compatible provider var ps []C.Proxy
for _, pd := range providersMap { for _, v := range proxyList {
if pd.VehicleType() != providerTypes.Compatible { if proxies[v].Type() == C.Pass {
continue continue
} }
log.Infoln("Start initial compatible provider %s", pd.Name())
if err := pd.Initial(); err != nil {
return nil, nil, err
}
}
ps := []C.Proxy{}
for _, v := range proxyList {
ps = append(ps, proxies[v]) ps = append(ps, proxies[v])
} }
hc := provider.NewHealthCheck(ps, "", 0, true) hc := provider.NewHealthCheck(ps, "", 0, true)
@@ -439,71 +483,21 @@ func parseProxies(cfg *RawConfig) (proxies map[string]C.Proxy, providersMap map[
return proxies, providersMap, nil return proxies, providersMap, nil
} }
func parseScript(cfg *RawConfig) error {
mode := cfg.Mode
script := cfg.Script
mainCode := cleanPyKeywords(script.MainCode)
shortcutsCode := script.ShortcutsCode
if mode != T.Script && len(shortcutsCode) == 0 {
return nil
} else if mode == T.Script && len(mainCode) == 0 {
return fmt.Errorf("initialized script module failure, can't find script code in the config file")
}
content :=
`# -*- coding: UTF-8 -*-
from datetime import datetime as whatever
class ClashTime:
def now(self):
return whatever.now()
def unix(self):
return int(whatever.now().timestamp())
def unix_nano(self):
return int(round(whatever.now().timestamp() * 1000))
time = ClashTime()
`
for k, v := range shortcutsCode {
v = cleanPyKeywords(v)
v = strings.TrimSpace(v)
if len(v) == 0 {
return fmt.Errorf("initialized rule SCRIPT failure, shortcut [%s] code invalid syntax", k)
}
content += "def " + strings.ToLower(k) + "(ctx, network, process_name, host, src_ip, src_port, dst_ip, dst_port):\n return " + v + "\n\n"
}
return nil
}
func parseRules(cfg *RawConfig, proxies map[string]C.Proxy) ([]C.Rule, map[string]*providerTypes.RuleProvider, error) { func parseRules(cfg *RawConfig, proxies map[string]C.Proxy) ([]C.Rule, map[string]*providerTypes.RuleProvider, error) {
ruleProviders := map[string]*providerTypes.RuleProvider{} ruleProviders := map[string]*providerTypes.RuleProvider{}
log.Infoln("Geodata Loader mode: %s", geodata.LoaderName())
// parse rule provider // parse rule provider
for name, mapping := range cfg.RuleProvider { for name, mapping := range cfg.RuleProvider {
rp, err := R.ParseRuleProvider(name, mapping) rp, err := RP.ParseRuleProvider(name, mapping)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
ruleProviders[name] = &rp ruleProviders[name] = &rp
R.SetRuleProvider(&rp) RP.SetRuleProvider(rp)
} }
for _, provider := range ruleProviders { var rules []C.Rule
log.Infoln("Start initial provider %s", (*provider).Name())
if err := (*provider).Initial(); err != nil {
return nil, nil, fmt.Errorf("initial rule provider %s error: %w", (*provider).Name(), err)
}
}
rules := []C.Rule{}
rulesConfig := cfg.Rule rulesConfig := cfg.Rule
mode := cfg.Mode mode := cfg.Mode
@@ -513,7 +507,7 @@ func parseRules(cfg *RawConfig, proxies map[string]C.Proxy) ([]C.Rule, map[strin
var ( var (
payload string payload string
target string target string
params = []string{} params []string
ruleName = strings.ToUpper(rule[0]) ruleName = strings.ToUpper(rule[0])
) )
@@ -521,24 +515,27 @@ func parseRules(cfg *RawConfig, proxies map[string]C.Proxy) ([]C.Rule, map[strin
continue continue
} }
switch l := len(rule); { l := len(rule)
case l == 2:
target = rule[1] if ruleName == "NOT" || ruleName == "OR" || ruleName == "AND" {
case l == 3: target = rule[l-1]
if ruleName == "MATCH" { payload = strings.Join(rule[1:l-1], ",")
payload = "" } else {
target = rule[1] if l < 2 {
params = rule[2:] return nil, nil, fmt.Errorf("rules[%d] [%s] error: format invalid", idx, line)
break
} }
payload = rule[1] if l < 4 {
target = rule[2] rule = append(rule, make([]string, 4-l)...)
case l >= 4: }
payload = rule[1] if ruleName == "MATCH" {
target = rule[2] l = 2
params = rule[3:] }
default: if l >= 3 {
return nil, nil, fmt.Errorf("rules[%d] [%s] error: format invalid", idx, line) l = 3
payload = rule[1]
}
target = rule[l-1]
params = rule[l:]
} }
if _, ok := proxies[target]; mode != T.Script && !ok { if _, ok := proxies[target]; mode != T.Script && !ok {
@@ -547,6 +544,12 @@ func parseRules(cfg *RawConfig, proxies map[string]C.Proxy) ([]C.Rule, map[strin
params = trimArr(params) params = trimArr(params)
if ruleName == "GEOSITE" {
if err := initGeoSite(); err != nil {
return nil, nil, fmt.Errorf("can't initial GeoSite: %s", err)
}
initMode = false
}
parsed, parseErr := R.ParseRule(ruleName, payload, target, params) parsed, parseErr := R.ParseRule(ruleName, payload, target, params)
if parseErr != nil { if parseErr != nil {
return nil, nil, fmt.Errorf("rules[%d] [%s] error: %s", idx, line, parseErr.Error()) return nil, nil, fmt.Errorf("rules[%d] [%s] error: %s", idx, line, parseErr.Error())
@@ -601,7 +604,7 @@ func hostWithDefaultPort(host string, defPort string) (string, error) {
} }
func parseNameServer(servers []string) ([]dns.NameServer, error) { func parseNameServer(servers []string) ([]dns.NameServer, error) {
nameservers := []dns.NameServer{} var nameservers []dns.NameServer
for idx, server := range servers { for idx, server := range servers {
// parse without scheme .e.g 8.8.8.8:53 // parse without scheme .e.g 8.8.8.8:53
@@ -631,6 +634,9 @@ func parseNameServer(servers []string) ([]dns.NameServer, error) {
case "dhcp": case "dhcp":
addr = u.Host addr = u.Host
dnsNetType = "dhcp" // UDP from DHCP dnsNetType = "dhcp" // UDP from DHCP
case "quic":
addr, err = hostWithDefaultPort(u.Host, "784")
dnsNetType = "quic" // DNS over QUIC
default: default:
return nil, fmt.Errorf("DNS NameServer[%d] unsupport scheme: %s", idx, u.Scheme) return nil, fmt.Errorf("DNS NameServer[%d] unsupport scheme: %s", idx, u.Scheme)
} }
@@ -645,6 +651,7 @@ func parseNameServer(servers []string) ([]dns.NameServer, error) {
Net: dnsNetType, Net: dnsNetType,
Addr: addr, Addr: addr,
ProxyAdapter: u.Fragment, ProxyAdapter: u.Fragment,
Interface: dialer.DefaultInterface.Load(),
}, },
) )
} }
@@ -669,7 +676,7 @@ func parseNameServerPolicy(nsPolicy map[string]string) (map[string]dns.NameServe
} }
func parseFallbackIPCIDR(ips []string) ([]*net.IPNet, error) { func parseFallbackIPCIDR(ips []string) ([]*net.IPNet, error) {
ipNets := []*net.IPNet{} var ipNets []*net.IPNet
for idx, ip := range ips { for idx, ip := range ips {
_, ipnet, err := net.ParseCIDR(ip) _, ipnet, err := net.ParseCIDR(ip)
@@ -683,7 +690,12 @@ func parseFallbackIPCIDR(ips []string) ([]*net.IPNet, error) {
} }
func parseFallbackGeoSite(countries []string, rules []C.Rule) ([]*router.DomainMatcher, error) { func parseFallbackGeoSite(countries []string, rules []C.Rule) ([]*router.DomainMatcher, error) {
sites := []*router.DomainMatcher{} var sites []*router.DomainMatcher
if len(countries) > 0 {
if err := initGeoSite(); err != nil {
return nil, fmt.Errorf("can't initial GeoSite: %s", err)
}
}
for _, country := range countries { for _, country := range countries {
found := false found := false
@@ -741,6 +753,10 @@ func parseDNS(rawCfg *RawConfig, hosts *trie.DomainTrie, rules []C.Rule) (*DNS,
return nil, err return nil, err
} }
if dnsCfg.ProxyServerNameserver, err = parseNameServer(cfg.ProxyServerNameserver); err != nil {
return nil, err
}
if len(cfg.DefaultNameserver) == 0 { if len(cfg.DefaultNameserver) == 0 {
return nil, errors.New("default nameserver should have at least one nameserver") return nil, errors.New("default nameserver should have at least one nameserver")
} }
@@ -778,7 +794,7 @@ func parseDNS(rawCfg *RawConfig, hosts *trie.DomainTrie, rules []C.Rule) (*DNS,
if net.ParseIP(fb.Addr) != nil { if net.ParseIP(fb.Addr) != nil {
continue continue
} }
host.Insert(fb.Addr, true) _ = host.Insert(fb.Addr, true)
} }
} }
@@ -817,25 +833,47 @@ func parseDNS(rawCfg *RawConfig, hosts *trie.DomainTrie, rules []C.Rule) (*DNS,
} }
func parseAuthentication(rawRecords []string) []auth.AuthUser { func parseAuthentication(rawRecords []string) []auth.AuthUser {
users := make([]auth.AuthUser, 0) var users []auth.AuthUser
for _, line := range rawRecords { for _, line := range rawRecords {
userData := strings.SplitN(line, ":", 2) if user, pass, found := strings.Cut(line, ":"); found {
if len(userData) == 2 { users = append(users, auth.AuthUser{User: user, Pass: pass})
users = append(users, auth.AuthUser{User: userData[0], Pass: userData[1]})
} }
} }
return users return users
} }
func cleanPyKeywords(code string) string { func parseTun(rawTun RawTun, general *General) (*Tun, error) {
if len(code) == 0 { if (rawTun.Enable || general.TProxyPort != 0) && general.Interface == "" {
return code autoDetectInterfaceName, err := commons.GetAutoDetectInterface()
} if err != nil || autoDetectInterfaceName == "" {
keywords := []string{"import", "print"} log.Warnln("Can not find auto detect interface.[%s]", err)
} else {
log.Warnln("Auto detect interface: %s", autoDetectInterfaceName)
}
for _, kw := range keywords { general.Interface = autoDetectInterfaceName
reg := regexp.MustCompile("(?m)[\r\n]+^.*" + kw + ".*$")
code = reg.ReplaceAllString(code, "")
} }
return code
var dnsHijack []netip.AddrPort
for _, d := range rawTun.DNSHijack {
if _, after, ok := strings.Cut(d, "://"); ok {
d = after
}
addrPort, err := netip.ParseAddrPort(d)
if err != nil {
return nil, fmt.Errorf("parse dns-hijack url error: %w", err)
}
dnsHijack = append(dnsHijack, addrPort)
}
return &Tun{
Enable: rawTun.Enable,
Device: rawTun.Device,
Stack: rawTun.Stack,
DNSHijack: dnsHijack,
AutoRoute: rawTun.AutoRoute,
}, nil
} }

View File

@@ -2,17 +2,20 @@ package config
import ( import (
"fmt" "fmt"
"github.com/Dreamacro/clash/component/geodata"
"github.com/Dreamacro/clash/component/mmdb"
"io" "io"
"net/http" "net/http"
"os" "os"
"github.com/Dreamacro/clash/component/mmdb"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/log" "github.com/Dreamacro/clash/log"
) )
var initMode = true
func downloadMMDB(path string) (err error) { func downloadMMDB(path string) (err error) {
resp, err := http.Get("https://cdn.jsdelivr.net/gh/Loyalsoldier/geoip@release/Country.mmdb") resp, err := http.Get("https://raw.githubusercontents.com/Loyalsoldier/geoip/release/Country.mmdb")
if err != nil { if err != nil {
return return
} }
@@ -28,7 +31,84 @@ func downloadMMDB(path string) (err error) {
return err return err
} }
func initMMDB() error { func downloadGeoIP(path string) (err error) {
resp, err := http.Get("https://raw.githubusercontents.com/Loyalsoldier/v2ray-rules-dat/release/geoip.dat")
if err != nil {
return
}
defer resp.Body.Close()
f, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY, 0o644)
if err != nil {
return err
}
defer f.Close()
_, err = io.Copy(f, resp.Body)
return err
}
func downloadGeoSite(path string) (err error) {
resp, err := http.Get("https://raw.githubusercontents.com/Loyalsoldier/v2ray-rules-dat/release/geosite.dat")
if err != nil {
return
}
defer resp.Body.Close()
f, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY, 0o644)
if err != nil {
return err
}
defer f.Close()
_, err = io.Copy(f, resp.Body)
return err
}
func initGeoSite() error {
if _, err := os.Stat(C.Path.GeoSite()); os.IsNotExist(err) {
log.Infoln("Can't find GeoSite.dat, start download")
if err := downloadGeoSite(C.Path.GeoSite()); err != nil {
return fmt.Errorf("can't download GeoSite.dat: %s", err.Error())
}
log.Infoln("Download GeoSite.dat finish")
}
if initMode {
if !geodata.Verify(C.GeositeName) {
log.Warnln("GeoSite.dat invalid, remove and download")
if err := os.Remove(C.Path.GeoSite()); err != nil {
return fmt.Errorf("can't remove invalid GeoSite.dat: %s", err.Error())
}
if err := downloadGeoSite(C.Path.GeoSite()); err != nil {
return fmt.Errorf("can't download GeoSite.dat: %s", err.Error())
}
}
}
return nil
}
func initGeoIP() error {
if C.GeodataMode {
if _, err := os.Stat(C.Path.GeoIP()); os.IsNotExist(err) {
log.Infoln("Can't find GeoIP.dat, start download")
if err := downloadGeoIP(C.Path.GeoIP()); err != nil {
return fmt.Errorf("can't download GeoIP.dat: %s", err.Error())
}
log.Infoln("Download GeoIP.dat finish")
}
if !geodata.Verify(C.GeoipName) {
log.Warnln("GeoIP.dat invalid, remove and download")
if err := os.Remove(C.Path.GeoIP()); err != nil {
return fmt.Errorf("can't remove invalid GeoIP.dat: %s", err.Error())
}
if err := downloadGeoIP(C.Path.GeoIP()); err != nil {
return fmt.Errorf("can't download GeoIP.dat: %s", err.Error())
}
}
return nil
}
if _, err := os.Stat(C.Path.MMDB()); os.IsNotExist(err) { if _, err := os.Stat(C.Path.MMDB()); os.IsNotExist(err) {
log.Infoln("Can't find MMDB, start download") log.Infoln("Can't find MMDB, start download")
if err := downloadMMDB(C.Path.MMDB()); err != nil { if err := downloadMMDB(C.Path.MMDB()); err != nil {
@@ -50,65 +130,6 @@ func initMMDB() error {
return nil return nil
} }
//func downloadGeoIP(path string) (err error) {
// resp, err := http.Get("https://cdn.jsdelivr.net/gh/Loyalsoldier/v2ray-rules-dat@release/geoip.dat")
// if err != nil {
// return
// }
// defer resp.Body.Close()
//
// f, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY, 0644)
// if err != nil {
// return err
// }
// defer f.Close()
// _, err = io.Copy(f, resp.Body)
//
// return err
//}
func downloadGeoSite(path string) (err error) {
resp, err := http.Get("https://cdn.jsdelivr.net/gh/Loyalsoldier/v2ray-rules-dat@release/geosite.dat")
if err != nil {
return
}
defer resp.Body.Close()
f, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY, 0o644)
if err != nil {
return err
}
defer f.Close()
_, err = io.Copy(f, resp.Body)
return err
}
//
//func initGeoIP() error {
// if _, err := os.Stat(C.Path.GeoIP()); os.IsNotExist(err) {
// log.Infoln("Can't find GeoIP.dat, start download")
// if err := downloadGeoIP(C.Path.GeoIP()); err != nil {
// return fmt.Errorf("can't download GeoIP.dat: %s", err.Error())
// }
// log.Infoln("Download GeoIP.dat finish")
// }
//
// return nil
//}
func initGeoSite() error {
if _, err := os.Stat(C.Path.GeoSite()); os.IsNotExist(err) {
log.Infoln("Can't find GeoSite.dat, start download")
if err := downloadGeoSite(C.Path.GeoSite()); err != nil {
return fmt.Errorf("can't download GeoSite.dat: %s", err.Error())
}
log.Infoln("Download GeoSite.dat finish")
}
return nil
}
// Init prepare necessary files // Init prepare necessary files
func Init(dir string) error { func Init(dir string) error {
// initial homedir // initial homedir
@@ -128,20 +149,20 @@ func Init(dir string) error {
f.Write([]byte(`mixed-port: 7890`)) f.Write([]byte(`mixed-port: 7890`))
f.Close() f.Close()
} }
buf, _ := os.ReadFile(C.Path.Config())
//// initial GeoIP rawCfg, err := UnmarshalRawConfig(buf)
//if err := initGeoIP(); err != nil { if err != nil {
// return fmt.Errorf("can't initial GeoIP: %w", err) log.Errorln(err.Error())
//} fmt.Printf("configuration file %s test failed\n", C.Path.Config())
os.Exit(1)
// initial mmdb }
if err := initMMDB(); err != nil { if !C.GeodataMode {
return fmt.Errorf("can't initial MMDB: %w", err) C.GeodataMode = rawCfg.GeodataMode
}
// initial GeoIP
if err := initGeoIP(); err != nil {
return fmt.Errorf("can't initial GeoIP: %w", err)
} }
// initial GeoSite
if err := initGeoSite(); err != nil {
return fmt.Errorf("can't initial GeoSite: %w", err)
}
return nil return nil
} }

View File

@@ -18,13 +18,13 @@ func trimArr(arr []string) (r []string) {
// Check if ProxyGroups form DAG(Directed Acyclic Graph), and sort all ProxyGroups by dependency order. // Check if ProxyGroups form DAG(Directed Acyclic Graph), and sort all ProxyGroups by dependency order.
// Meanwhile, record the original index in the config file. // Meanwhile, record the original index in the config file.
// If loop is detected, return an error with location of loop. // If loop is detected, return an error with location of loop.
func proxyGroupsDagSort(groupsConfig []map[string]interface{}) error { func proxyGroupsDagSort(groupsConfig []map[string]any) error {
type graphNode struct { type graphNode struct {
indegree int indegree int
// topological order // topological order
topo int topo int
// the original data in `groupsConfig` // the original data in `groupsConfig`
data map[string]interface{} data map[string]any
// `outdegree` and `from` are used in loop locating // `outdegree` and `from` are used in loop locating
outdegree int outdegree int
option *outboundgroup.GroupCommonOption option *outboundgroup.GroupCommonOption

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