Compare commits

...

334 Commits

Author SHA1 Message Date
be8eb7c17c feat: add port whitelist, empty is all port 2022-04-21 06:50:20 -07:00
58cd8f9ac1 fix:force-domain invalid 2022-04-17 21:17:21 +08:00
ea0d236259 chore: change comments 2022-04-17 20:03:53 +08:00
48a01adb7a refactor: sniffer param force and reverses deprecated, will be removed when release version, replace force-domain and skip-sni,
force-domain add '+' equivalent to force is true
sniffer:
  enable: true
  force-domain:
    - "google.com"
  skip-sni:
    - www.baidu.com
  sniffing:
    - tls
2022-04-17 20:02:13 +08:00
f8d7f29856 fix: PASS policy inconsistent names 2022-04-17 14:11:58 +08:00
1cf9321aa0 fix: domain tree match failed 2022-04-16 11:55:49 +08:00
71a1f5dfbd fix: domain type fix Mapping 2022-04-16 09:51:31 +08:00
25426cba33 chore: log style 2022-04-16 09:04:43 +08:00
9d364f66e9 fix: reverse error when force is false 2022-04-16 08:53:31 +08:00
7c23fa2bd4 fix: sniffer npe 2022-04-16 08:45:18 +08:00
0658ecadd3 fix: adjust loading timing 2022-04-16 08:29:38 +08:00
efbc334b3b Merge branch 'logic-rule' into Alpha 2022-04-16 08:22:08 +08:00
80764217c2 feat: add domain list for sniffer, reverse force logic
when force is false, if domain in the list, will force replace
when force is true, if sniff domain in the list, will skip it
2022-04-16 08:21:31 +08:00
45fe6e996b fix: npe when parse rule 2022-04-16 00:21:08 +08:00
36a719e2f8 feat: support http headers 2022-04-14 13:07:39 +08:00
1b6b0052c2 chore:adjust sniffer debuglog info 2022-04-13 08:38:55 +08:00
c981ef0f28 chore: update dependencies 2022-04-13 02:32:55 +08:00
1bf291d240 chore: update dependencies 2022-04-13 02:24:21 +08:00
b179d09efb Chore: adjust ipstack 2022-04-13 02:20:53 +08:00
4be17653e0 Fix: fakeip pool cycle used 2022-04-13 02:19:42 +08:00
21446ba5d4 chore: adjust code 2022-04-12 21:39:31 +08:00
75ce6b59bf Refactor: fakeip pool use netip.Prefix, supports ipv6 range 2022-04-12 20:32:08 +08:00
ce96ac35fb chore:merge & adjust code 2022-04-12 20:20:04 +08:00
173e10abe6 Chore: fix typos 2022-04-12 19:08:13 +08:00
a6eb11ce18 Refactor: DomainTrie use generics 2022-04-12 18:45:47 +08:00
0c65f6962a Refactor: queue use generics 2022-04-12 18:44:13 +08:00
baa9e02af6 Refactor: cache use generics 2022-04-12 18:44:10 +08:00
673541e2a8 Refactor: lrucache use generics 2022-04-12 18:44:07 +08:00
14878b37f6 fix: trojan fail may panic 2022-04-12 18:43:55 +08:00
83e0abaa8c chore: adjust code 2022-04-11 13:23:59 +08:00
7166db2ac9 fix: code logic error 2022-04-10 20:01:35 +08:00
815a060309 Update metadata.go
revet commit 13012a9
2022-04-10 00:47:22 +08:00
544e0f137d feat: sniffer support
sniffer:
  enable: true
  force: false # Overwrite domain
  sniffing:
    - tls
2022-04-09 22:30:36 +08:00
07906c0aa5 fix: parse logic rule error 2022-04-09 22:25:39 +08:00
b2981f921c chore: reduce a little memory 2022-04-09 22:24:48 +08:00
7be3e617ab disable process name on android 2022-04-09 17:54:01 +08:00
9a3bc8ef9e fix: auto detect interface add param[auto-detect-interface], default is true, only use it when tun is enabled 2022-04-07 21:36:19 +08:00
e083d1c57d Revert "Add docker workflow"
This reverts commit ce4902e5e6.
2022-04-07 10:24:23 +08:00
ce4902e5e6 Add docker workflow 2022-04-06 08:31:34 +08:00
4b9edc3b66 revert:the name of tun device on mac 2022-04-05 23:04:59 +08:00
91e48b707b Merge remote-tracking branch 'yaling888/with-tun' into Alpha 2022-04-05 14:44:40 +08:00
7a8af90b86 feat: add SMTPS/POP3S/IMAPS port to sni detect 2022-04-05 03:26:23 +08:00
93d2cfa091 fix: when ssh connect to a ip, if this ip map to a domain in clash, change ip to host may redirect to a diffrent ip 2022-04-05 03:26:23 +08:00
5719b9d22f fix: npe panic 2022-04-04 22:28:47 +08:00
b553dd749b refactor: Some adjustments 2022-04-03 19:15:16 +08:00
9461bcd44e fix: default-nameserver allow DOT and DOH with host is ip 2022-04-03 19:14:21 +08:00
6548dc90fa Merge remote-tracking branch 'Plus/with-tun' into Alpha 2022-04-02 20:48:11 +08:00
908ca20afa fix: dns over proxy may due to cancel request, but proxy live status is fine 2022-04-02 18:24:11 +08:00
13012a9f89 fix: dns over proxy may due to cancel request, but proxy live status is fine 2022-04-02 17:32:37 +08:00
afdcb6cfc7 fix: log level ajust and lint fix 2022-03-31 21:27:25 +08:00
c495d314d4 feat: 添加tls sni 嗅探
# Conflicts:
#	tunnel/statistic/tracker.go
#	tunnel/tunnel.go
2022-03-31 21:27:25 +08:00
e877b68179 Chore: revert "Feature: add tls SNI sniffing (#68)"
This reverts commit 24ce6622a2.
2022-03-31 21:20:46 +08:00
24ce6622a2 Feature: add tls SNI sniffing (#68) 2022-03-31 19:34:40 +08:00
88e4b3575e [Chore] fallback dependency 2022-03-31 00:26:01 +08:00
559b3ff9f3 [Fix] VLESS http conn with tls false
[Chore] Upgrade Dependencies
2022-03-31 00:08:43 +08:00
127634028d Merge remote-tracking branch 'Meta/Alpha' into Alpha 2022-03-30 13:19:05 +08:00
81c5a65f23 Merge remote-tracking branch 'Pro-Plus/with-tun' into Alpha
# Conflicts:
#	README.md
#	adapter/outbound/trojan.go
#	adapter/outbound/vless.go
#	transport/trojan/trojan.go
2022-03-30 13:15:45 +08:00
591ee119c2 docs: warning 2022-03-30 13:05:46 +08:00
5b03cc56e7 Merge remote-tracking branch 'Clash-dev/dev' into Alpha 2022-03-30 12:41:16 +08:00
9ff1f5530e Feature: Trojan XTLS 2022-03-30 00:15:39 +08:00
b3ea2ff8b6 Chore: adjust VLESS 2022-03-29 23:50:41 +08:00
c4216218c8 Merge pull request #24 from MarksonHon/patch-2
Fix systemd service
2022-03-29 14:53:02 +08:00
63840b3358 Fix systemd service 2022-03-29 14:50:12 +08:00
131e9d38b6 Fix: Vless UDP 2022-03-29 07:24:11 +08:00
56e2c172e1 Chore: adjust tun_wireguard cache buffer 2022-03-29 07:24:11 +08:00
b3b7a393f8 Chore: merge branch 'ogn-dev' into with-tun 2022-03-29 07:22:52 +08:00
045dd0589b fix: classical missing count 2022-03-28 21:04:50 +08:00
705311b70e [Chore]修改workflows 2022-03-28 20:52:09 +08:00
55ce40fbd1 [Chore]升级项目依赖
[Chore]隐藏TUN模式在system堆栈启动时弹窗
2022-03-28 20:44:52 +08:00
07fda93111 [Chore]升级项目依赖
[Chore]隐藏TUN模式在system堆栈启动时弹窗
2022-03-28 19:48:32 +08:00
012e044c54 [Chore]完成调试workflows 2022-03-28 19:02:51 +08:00
b323315583 [Chore]调试workflows 2022-03-28 18:58:23 +08:00
4c10d6e212 [Chore]调试workflows 2022-03-28 18:54:00 +08:00
ece3bb360a [Chore]调试workflows 2022-03-28 18:52:19 +08:00
5a7b9bdf45 [Chore]调试workflows 2022-03-28 18:49:24 +08:00
028ecb70c5 [Chore]调整workflows流程2 2022-03-28 18:44:27 +08:00
4e0b22f42d [Chore]调整workflows流程2 2022-03-28 18:41:30 +08:00
dbd27ef910 [Chore]调整workflows流程 2022-03-28 17:07:11 +08:00
ffff1418f2 [Fixed]尝试修复PASS空指针问题
[Chore]调整workflows测试
2022-03-28 16:36:34 +08:00
dd9bdf4e2f Fix: convert size to unit32 in getoridst to solve some mips64 devices cannot get redirect origin dst (#2041)
Change-Id: I40aa73dcea692132e38db980320a8a07ed427fe6

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

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

View File

@ -1,7 +1,7 @@
name: Dev
name: Alpha
on: [push]
jobs:
dev-build:
Feature-build:
if: ${{ !contains(github.event.head_commit.message, '[Skip CI]') }}
runs-on: ubuntu-latest
steps:
@ -15,7 +15,7 @@ jobs:
go-version: ${{ steps.version.outputs.go_version }}
- name: Check out code into the Go module directory
uses: actions/checkout@v2
uses: actions/checkout@v3
- name: Cache go module
uses: actions/cache@v2
@ -24,9 +24,9 @@ jobs:
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go-
# - name: Get dependencies, run test
# run: |
# go test ./...
# - name: Get dependencies, run test
# run: |
# go test ./...
- name: Build
if: success()
env:
@ -34,11 +34,38 @@ jobs:
BINDIR: bin
run: make -j releases
- name: Upload Dev
- 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: alpha
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Upload Alpha
uses: softprops/action-gh-release@v1
if: ${{ env.GIT_BRANCH != 'Meta' && success() }}
with:
tag_name: develop
tag: ${{ github.ref }}
tag_name: alpha
files: bin/*
prerelease: true
- 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}}

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

View File

@ -1,76 +0,0 @@
name: Publish Docker Image
on:
push:
branches:
- dev
tags:
- '*'
jobs:
build:
name: Build
runs-on: ubuntu-latest
steps:
- name: Check out code into the Go module directory
uses: actions/checkout@v2
with:
fetch-depth: 0
- name: Set up QEMU
uses: docker/setup-qemu-action@v1
with:
platforms: all
- name: Set up docker buildx
id: buildx
uses: docker/setup-buildx-action@v1
with:
version: latest
- name: Login to DockerHub
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Login to Github Package
uses: docker/login-action@v1
with:
registry: ghcr.io
username: Dreamacro
password: ${{ secrets.PACKAGE_TOKEN }}
- name: Build dev branch and push
if: github.ref == 'refs/heads/dev'
uses: docker/build-push-action@v2
with:
context: .
platforms: linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64
push: true
tags: 'dreamacro/clash:dev,ghcr.io/dreamacro/clash:dev'
- name: Get all docker tags
if: startsWith(github.ref, 'refs/tags/')
uses: actions/github-script@v4
id: tags
with:
script: |
const ref = `${context.payload.ref.replace(/\/?refs\/tags\//, '')}`
const tags = [
'dreamacro/clash:latest',
`dreamacro/clash:${ref}`,
'ghcr.io/dreamacro/clash:latest',
`ghcr.io/dreamacro/clash:${ref}`
]
return tags.join(',')
result-encoding: string
- name: Build release and push
if: startsWith(github.ref, 'refs/tags/')
uses: docker/build-push-action@v2
with:
context: .
platforms: linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64
push: true
tags: ${{steps.tags.outputs.result}}

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
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 /clash /
ENTRYPOINT ["/clash"]

View File

@ -1,58 +1,58 @@
NAME=Clash.Meta
BINDIR=bin
BRANCH=$(shell git rev-parse --abbrev-ref HEAD)
VERSION=$(shell git describe --tags || echo "unknown version" )
ifeq ($(BRANCH),Dev)
VERSION=develop-$(shell git rev-parse --short HEAD)
endif
VERSION=alpha-$(shell git rev-parse --short HEAD)
BUILDTIME=$(shell date -u)
AUTOIPTABLES=Enable
GOBUILD=CGO_ENABLED=0 go build -trimpath -ldflags '-X "github.com/Dreamacro/clash/constant.Version=$(VERSION)" \
-X "github.com/Dreamacro/clash/constant.BuildTime=$(BUILDTIME)" \
-w -s -buildid='
GOBUILDOP=CGO_ENABLED=0 go build -trimpath -ldflags '-X "github.com/Dreamacro/clash/constant.Version=$(VERSION)" \
-X "github.com/Dreamacro/clash/constant.BuildTime=$(BUILDTIME)" \
-X "github.com/Dreamacro/clash/constant.AutoIptables=$(AUTOIPTABLES)" \
-w -s -buildid='
PLATFORM_LIST = \
darwin-amd64 \
darwin-amd64v1 \
darwin-amd64v2 \
darwin-amd64v3 \
darwin-arm64 \
linux-amd64 \
linux-amd64v1 \
linux-amd64v2 \
linux-amd64v3 \
linux-armv5 \
linux-armv6 \
linux-armv7 \
linux-armv8 \
linux-arm64 \
linux-mips64 \
linux-mips64le \
linux-mips-softfloat \
linux-mips-hardfloat \
linux-mipsle-softfloat \
linux-mipsle-hardfloat \
android-arm64 \
freebsd-386 \
freebsd-amd64 \
freebsd-arm64
WINDOWS_ARCH_LIST = \
windows-386 \
windows-amd64 \
windows-amd64v1 \
windows-amd64v2 \
windows-amd64v3 \
windows-arm64 \
windows-arm32v7
all:linux-amd64-AutoIptables linux-amd64\
linux-arm64 linux-arm64-AutoIptables linux-armv7\
all:linux-amd64 linux-arm64\
darwin-amd64 darwin-arm64\
windows-amd64 windows-386 \
linux-mips-hardfloat linux-mips-softfloat linux-mips64 linux-mips64le linux-mipsle-hardfloat linux-mipsle-softfloat# Most used
windows-amd64 windows-arm64\
docker:
$(GOBUILD) -o $(BINDIR)/$(NAME)-$@
darwin-amd64:
GOARCH=amd64 GOOS=darwin $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
darwin-amd64v3:
GOARCH=amd64 GOOS=darwin GOAMD64=v3 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
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:
GOARCH=arm64 GOOS=darwin $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
@ -60,42 +60,27 @@ darwin-arm64:
linux-386:
GOARCH=386 GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
linux-amd64:
GOARCH=amd64 GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
linux-amd64v3:
GOARCH=amd64 GOOS=linux GOAMD64=v3 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
linux-amd64-AutoIptables:
GOARCH=amd64 GOOS=linux $(GOBUILDOP) -o $(BINDIR)/$(NAME)-$@
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:
GOARCH=arm64 GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
linux-arm64-AutoIptables:
GOARCH=arm64 GOOS=linux $(GOBUILDOP) -o $(BINDIR)/$(NAME)-$@
linux-armv5:
GOARCH=arm GOOS=linux GOARM=5 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
linux-armv5-AutoIptables:
GOARCH=arm GOOS=linux GOARM=5 $(GOBUILDOP) -o $(BINDIR)/$(NAME)-$@
linux-armv6:
GOARCH=arm GOOS=linux GOARM=6 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
linux-armv6-AutoIptables:
GOARCH=arm GOOS=linux GOARM=6 $(GOBUILDOP) -o $(BINDIR)/$(NAME)-$@
linux-armv7:
GOARCH=arm GOOS=linux GOARM=7 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
linux-armv7-AutoIptables:
GOARCH=arm GOOS=linux GOARM=7 $(GOBUILDOP) -o $(BINDIR)/$(NAME)-$@
linux-armv8:
GOARCH=arm64 GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
linux-armv8-AutoIptables:
GOARCH=arm64 GOOS=linux $(GOBUILDOP) -o $(BINDIR)/$(NAME)-$@
linux-mips-softfloat:
GOARCH=mips GOMIPS=softfloat GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
@ -114,11 +99,14 @@ linux-mips64:
linux-mips64le:
GOARCH=mips64le GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
android-arm64:
GOARCH=arm64 GOOS=android $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
freebsd-386:
GOARCH=386 GOOS=freebsd $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
freebsd-amd64:
GOARCH=amd64 GOOS=freebsd $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
GOARCH=amd64 GOOS=freebsd GOAMD64=v3 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
freebsd-arm64:
GOARCH=arm64 GOOS=freebsd $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
@ -126,8 +114,14 @@ freebsd-arm64:
windows-386:
GOARCH=386 GOOS=windows $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.exe
windows-amd64:
GOARCH=amd64 GOOS=windows $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.exe
windows-amd64v3:
GOARCH=amd64 GOOS=windows GOAMD64=v3 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.exe
windows-amd64v2:
GOARCH=amd64 GOOS=windows GOAMD64=v2 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.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
@ -148,5 +142,12 @@ $(zip_releases): %.zip : %
all-arch: $(PLATFORM_LIST) $(WINDOWS_ARCH_LIST)
releases: $(gz_releases) $(zip_releases)
vet:
go test ./...
lint:
golangci-lint run ./...
clean:
rm $(BINDIR)/*
rm $(BINDIR)/*

View File

@ -172,15 +172,14 @@ proxy-providers:
Support outbound transport protocol `VLESS`.
The XTLS support TCP/UDP by the XRAY-CORE.
The XTLS support (TCP/UDP) transport by the XRAY-CORE.
```yaml
proxies:
- name: "vless-tcp"
- name: "vless"
type: vless
server: server
port: 443
uuid: uuid
network: tcp
servername: example.com # AKA SNI
# flow: xtls-rprx-direct # xtls-rprx-origin # enable XTLS
# skip-cert-verify: true
@ -190,57 +189,85 @@ proxies:
server: server
port: 443
uuid: uuid
tls: true
udp: true
network: ws
servername: example.com # priority over wss host
# skip-cert-verify: true
ws-path: /path
ws-headers:
Host: example.com
ws-opts:
path: /path
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
Only work on Linux OS who support `iptables`, Clash will auto-configuration iptables for tproxy listener when `tproxy-port` value isn't zero.
### IPTABLES configuration
Work on Linux OS who's supported `iptables`
If `TPROXY` is enabled, the `TUN` must be disabled.
```yaml
# Enable the TPROXY listener
tproxy-port: 9898
# Disable the TUN listener
tun:
enable: false
iptables:
enable: true # default is false
inbound-interface: eth0 # detect the inbound interface, default is 'lo'
```
Create user given name `Clash.Meta`.
Run Meta Kernel by user `Clash.Meta` 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]
Description=Clash.Meta Daemon, Another Clash Kernel.
After=network.target
Description=Clash-Meta Daemon, Another Clash Kernel.
After=network.target NetworkManager.service systemd-networkd.service iwd.service
[Service]
Type=simple
User=Clash.Meta
Group=Clash.Meta
User=clash-meta
Group=clash-meta
LimitNPROC=500
LimitNOFILE=1000000
CapabilityBoundingSet=cap_net_admin
AmbientCapabilities=cap_net_admin
Restart=always
ExecStart=/usr/local/bin/Clash.Meta -d /etc/Clash.Meta
ExecStartPre=/usr/bin/sleep 1s
ExecStart=/usr/local/bin/Clash-Meta -d /etc/Clash-Meta
[Install]
WantedBy=multi-user.target
```
Launch clashd on system startup with:
```shell
$ systemctl enable Clash.Meta
$ systemctl enable Clash-Meta
```
Launch clashd immediately with:
```shell
$ systemctl start Clash.Meta
$ systemctl start Clash-Meta
```
### Display Process name

View File

@ -7,6 +7,7 @@ import (
"net"
"net/http"
"net/url"
"strings"
"time"
"github.com/Dreamacro/clash/common/queue"
@ -20,7 +21,7 @@ var UnifiedDelay = atomic.NewBool(false)
type Proxy struct {
C.ProxyAdapter
history *queue.Queue
history *queue.Queue[C.DelayHistory]
alive *atomic.Bool
}
@ -39,7 +40,11 @@ func (p *Proxy) Dial(metadata *C.Metadata) (C.Conn, error) {
// DialContext implements C.ProxyAdapter
func (p *Proxy) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.Conn, error) {
conn, err := p.ProxyAdapter.DialContext(ctx, metadata, opts...)
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
}
@ -62,7 +67,7 @@ func (p *Proxy) DelayHistory() []C.DelayHistory {
queue := p.history.Copy()
histories := []C.DelayHistory{}
for _, item := range queue {
histories = append(histories, item.(C.DelayHistory))
histories = append(histories, item)
}
return histories
}
@ -75,11 +80,7 @@ func (p *Proxy) LastDelay() (delay uint16) {
return max
}
last := p.history.Last()
if last == nil {
return max
}
history := last.(C.DelayHistory)
history := p.history.Last()
if history.Delay == 0 {
return max
}
@ -93,7 +94,7 @@ func (p *Proxy) MarshalJSON() ([]byte, error) {
return inner, err
}
mapping := map[string]interface{}{}
mapping := map[string]any{}
json.Unmarshal(inner, &mapping)
mapping["history"] = p.DelayHistory()
mapping["name"] = p.Name()
@ -173,7 +174,7 @@ func (p *Proxy) URLTest(ctx context.Context, url string) (t uint16, err error) {
}
func NewProxy(adapter C.ProxyAdapter) *Proxy {
return &Proxy{adapter, queue.New(10), atomic.NewBool(true)}
return &Proxy{adapter, queue.New[C.DelayHistory](10), atomic.NewBool(true)}
}
func urlToMetadata(rawURL string) (addr C.Metadata, err error) {

View File

@ -13,9 +13,13 @@ func NewSocket(target socks5.Addr, conn net.Conn, source C.Type) *context.ConnCo
metadata := parseSocksAddr(target)
metadata.NetWork = C.TCP
metadata.Type = source
if ip, port, err := parseAddr(conn.RemoteAddr().String()); err == nil {
metadata.SrcIP = ip
metadata.SrcPort = port
remoteAddr := conn.RemoteAddr()
// Filter when net.Addr interface is nil
if remoteAddr != nil {
if ip, port, err := parseAddr(remoteAddr.String()); err == nil {
metadata.SrcIP = ip
metadata.SrcPort = port
}
}
return context.NewConnContext(conn, metadata)

View File

@ -14,6 +14,7 @@ type Direct struct {
// DialContext implements C.ProxyAdapter
func (d *Direct) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.Conn, error) {
opts = append(opts, dialer.WithDirect())
c, err := dialer.DialContext(ctx, "tcp", metadata.RemoteAddress(), d.Base.DialOptions(opts...)...)
if err != nil {
return nil, err
@ -24,6 +25,7 @@ func (d *Direct) DialContext(ctx context.Context, metadata *C.Metadata, opts ...
// ListenPacketContext implements C.ProxyAdapter
func (d *Direct) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) {
opts = append(opts, dialer.WithDirect())
pc, err := dialer.ListenPacket(ctx, "udp", "", d.Base.DialOptions(opts...)...)
if err != nil {
return nil, err
@ -54,3 +56,13 @@ func NewCompatible() *Direct {
},
}
}
func NewPass() *Direct {
return &Direct{
Base: &Base{
name: "PASS",
tp: C.Pass,
udp: true,
},
}
}

View File

@ -22,18 +22,20 @@ type Http struct {
user string
pass string
tlsConfig *tls.Config
option *HttpOption
}
type HttpOption struct {
BasicOption
Name string `proxy:"name"`
Server string `proxy:"server"`
Port int `proxy:"port"`
UserName string `proxy:"username,omitempty"`
Password string `proxy:"password,omitempty"`
TLS bool `proxy:"tls,omitempty"`
SNI string `proxy:"sni,omitempty"`
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
Name string `proxy:"name"`
Server string `proxy:"server"`
Port int `proxy:"port"`
UserName string `proxy:"username,omitempty"`
Password string `proxy:"password,omitempty"`
TLS bool `proxy:"tls,omitempty"`
SNI string `proxy:"sni,omitempty"`
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
Headers map[string]string `proxy:"headers,omitempty"`
}
// StreamConn implements C.ProxyAdapter
@ -84,6 +86,13 @@ func (h *Http) shakeHand(metadata *C.Metadata, rw io.ReadWriter) error {
},
}
//增加headers
if len(h.option.Headers) != 0 {
for key, value := range h.option.Headers {
req.Header.Add(key, value)
}
}
if h.user != "" && h.pass != "" {
auth := h.user + ":" + h.pass
req.Header.Add("Proxy-Authorization", "Basic "+base64.StdEncoding.EncodeToString([]byte(auth)))
@ -136,9 +145,11 @@ func NewHttp(option HttpOption) *Http {
addr: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)),
tp: C.Http,
iface: option.Interface,
rmark: option.RoutingMark,
},
user: option.UserName,
pass: option.Password,
tlsConfig: tlsConfig,
option: &option,
}
}

View File

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

View File

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

View File

@ -23,13 +23,13 @@ type Snell struct {
type SnellOption struct {
BasicOption
Name string `proxy:"name"`
Server string `proxy:"server"`
Port int `proxy:"port"`
Psk string `proxy:"psk"`
UDP bool `proxy:"udp,omitempty"`
Version int `proxy:"version,omitempty"`
ObfsOpts map[string]interface{} `proxy:"obfs-opts,omitempty"`
Name string `proxy:"name"`
Server string `proxy:"server"`
Port int `proxy:"port"`
Psk string `proxy:"psk"`
UDP bool `proxy:"udp,omitempty"`
Version int `proxy:"version,omitempty"`
ObfsOpts map[string]any `proxy:"obfs-opts,omitempty"`
}
type streamOption struct {
@ -142,6 +142,7 @@ func NewSnell(option SnellOption) (*Snell, error) {
tp: C.Snell,
udp: option.UDP,
iface: option.Interface,
rmark: option.RoutingMark,
},
psk: psk,
obfsOption: obfsOption,

View File

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

View File

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

View File

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

View File

@ -6,6 +6,7 @@ import (
"encoding/binary"
"errors"
"fmt"
"io"
"net"
"net/http"
"strconv"
@ -17,12 +18,13 @@ import (
"github.com/Dreamacro/clash/transport/gun"
"github.com/Dreamacro/clash/transport/vless"
"github.com/Dreamacro/clash/transport/vmess"
"golang.org/x/net/http2"
)
const (
// max packet length
maxLength = 8192
maxLength = 1024 << 3
)
type Vless struct {
@ -61,12 +63,6 @@ func (v *Vless) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
var err error
switch v.option.Network {
case "ws":
if v.option.WSOpts.Path == "" {
v.option.WSOpts.Path = v.option.WSPath
}
if len(v.option.WSOpts.Headers) == 0 {
v.option.WSOpts.Headers = v.option.WSHeaders
}
host, port, _ := net.SplitHostPort(v.addr)
wsOpts := &vmess.WebsocketConfig{
@ -85,18 +81,17 @@ func (v *Vless) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
wsOpts.Headers = header
}
if v.option.TLS {
wsOpts.TLS = true
wsOpts.TLSConfig = &tls.Config{
ServerName: host,
InsecureSkipVerify: v.option.SkipCertVerify,
NextProtos: []string{"http/1.1"},
}
if v.option.ServerName != "" {
wsOpts.TLSConfig.ServerName = v.option.ServerName
} else if host := wsOpts.Headers.Get("Host"); host != "" {
wsOpts.TLSConfig.ServerName = host
}
wsOpts.TLS = true
wsOpts.TLSConfig = &tls.Config{
MinVersion: tls.VersionTLS12,
ServerName: host,
InsecureSkipVerify: v.option.SkipCertVerify,
NextProtos: []string{"http/1.1"},
}
if v.option.ServerName != "" {
wsOpts.TLSConfig.ServerName = v.option.ServerName
} else if host := wsOpts.Headers.Get("Host"); host != "" {
wsOpts.TLSConfig.ServerName = host
}
c, err = vmess.StreamWebsocketConn(c, wsOpts)
case "http":
@ -134,6 +129,7 @@ func (v *Vless) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
c, err = gun.StreamGunWithConn(c, v.gunTLSConfig, v.gunConfig)
}
default:
// default tcp network
// handle TLS And XTLS
c, err = v.streamTLSOrXTLSConn(c, false)
}
@ -219,7 +215,7 @@ func (v *Vless) DialContext(ctx context.Context, metadata *C.Metadata, opts ...d
// ListenPacketContext implements C.ProxyAdapter
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() {
ip, err := resolver.ResolveIP(metadata.Host)
if err != nil {
@ -275,7 +271,7 @@ func parseVlessAddr(metadata *C.Metadata) *vless.DstAddr {
copy(addr[1:], []byte(metadata.Host))
}
port, _ := strconv.Atoi(metadata.DstPort)
port, _ := strconv.ParseUint(metadata.DstPort, 10, 16)
return &vless.DstAddr{
UDP: metadata.NetWork == C.UDP,
AddrType: addrType,
@ -286,91 +282,97 @@ func parseVlessAddr(metadata *C.Metadata) *vless.DstAddr {
type vlessPacketConn struct {
net.Conn
rAddr net.Addr
rAddr net.Addr
remain int
mux sync.Mutex
cache []byte
cache [2]byte
}
func (c *vlessPacketConn) writePacket(b []byte, addr net.Addr) (int, error) {
length := len(b)
defer func() {
c.cache = c.cache[:0]
}()
c.cache = append(c.cache, byte(length>>8), byte(length))
c.cache = append(c.cache, b...)
n, err := c.Conn.Write(c.cache)
if n > 2 {
return n - 2, err
func (c *vlessPacketConn) writePacket(payload []byte) (int, error) {
binary.BigEndian.PutUint16(c.cache[:], uint16(len(payload)))
if _, err := c.Conn.Write(c.cache[:]); err != nil {
return 0, err
}
return 0, err
return c.Conn.Write(payload)
}
func (c *vlessPacketConn) WriteTo(b []byte, addr net.Addr) (int, error) {
if len(b) <= maxLength {
return c.writePacket(b, addr)
total := len(b)
if total == 0 {
return 0, nil
}
if total <= maxLength {
return c.writePacket(b)
}
offset := 0
total := len(b)
for offset < total {
cursor := offset + maxLength
if cursor > total {
cursor = total
}
n, err := c.writePacket(b[offset:cursor], addr)
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()
length := len(b)
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, nil, err
return 0, c.rAddr, err
}
c.remain -= n
return n, c.rAddr, nil
}
var packetLength uint16
if err := binary.Read(c.Conn, binary.BigEndian, &packetLength); err != nil {
return 0, nil, err
if _, err := c.Conn.Read(b[:2]); err != nil {
return 0, c.rAddr, err
}
remain := int(packetLength)
n, err := c.Conn.Read(b[:length])
remain -= n
if remain > 0 {
c.remain = remain
total := int(binary.BigEndian.Uint16(b[:2]))
if total == 0 {
return 0, c.rAddr, nil
}
return n, c.rAddr, err
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) {
if !option.TLS && option.Network == "grpc" {
return nil, fmt.Errorf("TLS must be true with vless-grpc")
}
var addons *vless.Addons
if option.Network != "ws" && len(option.Flow) >= 16 {
option.Flow = option.Flow[:16]
@ -380,7 +382,7 @@ func NewVless(option VlessOption) (*Vless, error) {
Flow: option.Flow,
}
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

@ -47,6 +47,10 @@ type VmessOption struct {
HTTP2Opts HTTP2Options `proxy:"h2-opts,omitempty"`
GrpcOpts GrpcOptions `proxy:"grpc-opts,omitempty"`
WSOpts WSOptions `proxy:"ws-opts,omitempty"`
// TODO: compatible with VMESS WS older version configurations
WSHeaders map[string]string `proxy:"ws-headers,omitempty"`
WSPath string `proxy:"ws-path,omitempty"`
}
type HTTPOptions struct {
@ -76,6 +80,13 @@ func (v *Vmess) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
var err error
switch v.option.Network {
case "ws":
if v.option.WSOpts.Path == "" {
v.option.WSOpts.Path = v.option.WSPath
}
if len(v.option.WSOpts.Headers) == 0 {
v.option.WSOpts.Headers = v.option.WSHeaders
}
host, port, _ := net.SplitHostPort(v.addr)
wsOpts := &vmess.WebsocketConfig{
Host: host,
@ -280,6 +291,7 @@ func NewVmess(option VmessOption) (*Vmess, error) {
tp: C.Vmess,
udp: option.UDP,
iface: option.Interface,
rmark: option.RoutingMark,
},
client: client,
option: &option,

View File

@ -2,7 +2,7 @@ package outboundgroup
import (
"github.com/Dreamacro/clash/tunnel"
"regexp"
"github.com/dlclark/regexp2"
"time"
C "github.com/Dreamacro/clash/constant"
@ -23,12 +23,18 @@ func getProvidersProxies(providers []provider.ProxyProvider, touch bool, filter
}
}
var filterReg *regexp.Regexp = nil
matchedProxies := []C.Proxy{}
var filterReg *regexp2.Regexp = nil
var matchedProxies []C.Proxy
if len(filter) > 0 {
filterReg = regexp.MustCompile(filter)
//filterReg = regexp.MustCompile(filter)
filterReg = regexp2.MustCompile(filter, 0)
for _, p := range proxies {
if filterReg.MatchString(p.Name()) {
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)
}
}

View File

@ -97,12 +97,11 @@ func (f *Fallback) SupportUDP() bool {
// MarshalJSON implements C.ProxyAdapter
func (f *Fallback) MarshalJSON() ([]byte, error) {
all := make([]string, 0)
all := []string{}
for _, proxy := range f.proxies(false) {
all = append(all, proxy.Name())
}
return json.Marshal(map[string]interface{}{
return json.Marshal(map[string]any{
"type": f.Type().String(),
"now": f.Now(),
"all": all,
@ -116,7 +115,7 @@ func (f *Fallback) Unwrap(metadata *C.Metadata) 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, f.filter), nil
})

View File

@ -30,7 +30,7 @@ type LoadBalance struct {
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 strategy, ok := elm.(string); ok {
return strategy
@ -141,7 +141,7 @@ func (lb *LoadBalance) Unwrap(metadata *C.Metadata) 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, lb.filter), nil
})
@ -150,13 +150,11 @@ func (lb *LoadBalance) proxies(touch bool) []C.Proxy {
// MarshalJSON implements C.ProxyAdapter
func (lb *LoadBalance) MarshalJSON() ([]byte, error) {
all := make([]string, 0)
all := []string{}
for _, proxy := range lb.proxies(false) {
all = append(all, proxy.Name())
}
return json.Marshal(map[string]interface{}{
return json.Marshal(map[string]any{
"type": lb.Type().String(),
"all": all,
})

View File

@ -32,7 +32,7 @@ type GroupCommonOption struct {
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})
groupOption := &GroupCommonOption{

View File

@ -69,20 +69,18 @@ func (r *Relay) DialContext(ctx context.Context, metadata *C.Metadata, opts ...d
// MarshalJSON implements C.ProxyAdapter
func (r *Relay) MarshalJSON() ([]byte, error) {
all := make([]string, 0)
all := []string{}
for _, proxy := range r.rawProxies(false) {
all = append(all, proxy.Name())
}
return json.Marshal(map[string]interface{}{
return json.Marshal(map[string]any{
"type": r.Type().String(),
"all": all,
})
}
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, r.filter), nil
})

View File

@ -50,13 +50,12 @@ func (s *Selector) SupportUDP() bool {
// MarshalJSON implements C.ProxyAdapter
func (s *Selector) MarshalJSON() ([]byte, error) {
all := make([]string, 0)
all := []string{}
for _, proxy := range getProvidersProxies(s.providers, false, s.filter) {
all = append(all, proxy.Name())
}
return json.Marshal(map[string]interface{}{
return json.Marshal(map[string]any{
"type": s.Type().String(),
"now": s.Now(),
"all": all,
@ -80,12 +79,12 @@ func (s *Selector) Set(name string) error {
}
// 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)
}
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, s.filter)
for _, proxy := range proxies {
if proxy.Name() == s.selected {

View File

@ -49,7 +49,6 @@ func (u *URLTest) DialContext(ctx context.Context, metadata *C.Metadata, opts ..
} else {
u.onDialFailed()
}
return c, err
}
@ -63,17 +62,16 @@ func (u *URLTest) ListenPacketContext(ctx context.Context, metadata *C.Metadata,
} else {
u.onDialFailed()
}
return pc, err
}
// 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)
}
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, u.filter), nil
})
@ -81,7 +79,7 @@ func (u *URLTest) proxies(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)
fast := proxies[0]
min := fast.LastDelay()
@ -125,13 +123,11 @@ func (u *URLTest) SupportUDP() bool {
// MarshalJSON implements C.ProxyAdapter
func (u *URLTest) MarshalJSON() ([]byte, error) {
all := make([]string, 0)
all := []string{}
for _, proxy := range u.proxies(false) {
all = append(all, proxy.Name())
}
return json.Marshal(map[string]interface{}{
return json.Marshal(map[string]any{
"type": u.Type().String(),
"now": u.Now(),
"all": all,
@ -164,7 +160,7 @@ func (u *URLTest) onDialFailed() {
}
}
func parseURLTestOption(config map[string]interface{}) []urlTestOption {
func parseURLTestOption(config map[string]any) []urlTestOption {
opts := []urlTestOption{}
// tolerance

View File

@ -8,7 +8,7 @@ import (
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})
proxyType, existType := mapping["type"].(string)
if !existType {

View File

@ -16,7 +16,7 @@ var (
dirMode os.FileMode = 0o755
)
type parser = func([]byte) (interface{}, error)
type parser = func([]byte) (any, error)
type fetcher struct {
name string
@ -26,7 +26,7 @@ type fetcher struct {
done chan struct{}
hash [16]byte
parser parser
onUpdate func(interface{})
onUpdate func(any)
}
func (f *fetcher) Name() string {
@ -37,7 +37,7 @@ func (f *fetcher) VehicleType() types.VehicleType {
return f.vehicle.Type()
}
func (f *fetcher) Initial() (interface{}, error) {
func (f *fetcher) Initial() (any, error) {
var (
buf []byte
err error
@ -92,7 +92,7 @@ func (f *fetcher) Initial() (interface{}, error) {
return proxies, nil
}
func (f *fetcher) Update() (interface{}, bool, error) {
func (f *fetcher) Update() (any, bool, error) {
buf, err := f.vehicle.Read()
if err != nil {
return nil, false, err
@ -168,7 +168,7 @@ func safeWrite(path string, buf []byte) error {
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
if interval != 0 {
ticker = time.NewTicker(interval)

View File

@ -31,7 +31,13 @@ type HealthCheck struct {
func (hc *HealthCheck) process() {
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 {
select {
case <-ticker.C:
@ -62,7 +68,7 @@ func (hc *HealthCheck) check() {
b, _ := batch.New(context.Background(), batch.WithConcurrencyNum(10))
for _, proxy := range hc.proxies {
p := proxy
b.Go(p.Name(), func() (interface{}, error) {
b.Go(p.Name(), func() (any, error) {
ctx, cancel := context.WithTimeout(context.Background(), defaultURLTestTimeout)
defer cancel()
p.URLTest(ctx, hc.url)

View File

@ -28,7 +28,7 @@ type proxyProviderSchema struct {
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})
schema := &proxyProviderSchema{

View File

@ -4,7 +4,7 @@ import (
"encoding/json"
"errors"
"fmt"
"regexp"
"github.com/dlclark/regexp2"
"runtime"
"time"
@ -20,7 +20,7 @@ const (
)
type ProxySchema struct {
Proxies []map[string]interface{} `yaml:"proxies"`
Proxies []map[string]any `yaml:"proxies"`
}
// for auto gc
@ -35,12 +35,13 @@ type proxySetProvider struct {
}
func (pp *proxySetProvider) MarshalJSON() ([]byte, error) {
return json.Marshal(map[string]interface{}{
return json.Marshal(map[string]any{
"name": pp.Name(),
"type": pp.Type().String(),
"vehicleType": pp.VehicleType().String(),
"proxies": pp.Proxies(),
"updatedAt": pp.updatedAt,
//TODO maybe error because year value overflow
"updatedAt": pp.updatedAt,
})
}
@ -101,7 +102,8 @@ func stopProxyProvider(pd *ProxySetProvider) {
}
func NewProxySetProvider(name string, interval time.Duration, filter string, vehicle types.Vehicle, hc *HealthCheck) (*ProxySetProvider, error) {
filterReg, err := regexp.Compile(filter)
//filterReg, err := regexp.Compile(filter)
filterReg, err := regexp2.Compile(filter, 0)
if err != nil {
return nil, fmt.Errorf("invalid filter regex: %w", err)
}
@ -111,12 +113,12 @@ func NewProxySetProvider(name string, interval time.Duration, filter string, veh
healthCheck: hc,
}
onUpdate := func(elm interface{}) {
onUpdate := func(elm any) {
ret := elm.([]C.Proxy)
pd.setProxies(ret)
}
proxiesParseAndFilter := func(buf []byte) (interface{}, error) {
proxiesParseAndFilter := func(buf []byte) (any, error) {
schema := &ProxySchema{}
if err := yaml.Unmarshal(buf, schema); err != nil {
@ -129,7 +131,9 @@ func NewProxySetProvider(name string, interval time.Duration, filter string, veh
proxies := []C.Proxy{}
for idx, mapping := range schema.Proxies {
if name, ok := mapping["name"]; ok && len(filter) > 0 && !filterReg.MatchString(name.(string)) {
name, ok := mapping["name"]
mat, _ := filterReg.FindStringMatch(name.(string))
if ok && len(filter) > 0 && mat == nil {
continue
}
proxy, err := adapter.ParseProxy(mapping)
@ -169,7 +173,7 @@ type compatibleProvider struct {
}
func (cp *compatibleProvider) MarshalJSON() ([]byte, error) {
return json.Marshal(map[string]interface{}{
return json.Marshal(map[string]any{
"name": cp.Name(),
"type": cp.Type().String(),
"vehicleType": cp.VehicleType().String(),

View File

@ -2,6 +2,7 @@ package provider
import (
"context"
"github.com/Dreamacro/clash/component/dialer"
"github.com/Dreamacro/clash/listener/inner"
"io"
"net"
@ -57,7 +58,7 @@ func (h *HTTPVehicle) Read() ([]byte, error) {
}
req, err := http.NewRequest(http.MethodGet, uri.String(), nil)
req.Header.Set("user-agent", netHttp.UA)
req.Header.Set("User-Agent", netHttp.UA)
if err != nil {
return nil, err
@ -85,7 +86,13 @@ func (h *HTTPVehicle) Read() ([]byte, error) {
client := http.Client{Transport: transport}
resp, err := client.Do(req)
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()

View File

@ -8,7 +8,7 @@ import (
type Option = func(b *Batch)
type Result struct {
Value interface{}
Value any
Err error
}
@ -38,7 +38,7 @@ type Batch struct {
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)
go func() {
defer b.wg.Done()

View File

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

50
common/cache/cache.go vendored
View File

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

View File

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

View File

@ -9,43 +9,43 @@ import (
)
// Option is part of Functional Options Pattern
type Option func(*LruCache)
type Option[K comparable, V any] func(*LruCache[K, V])
// EvictCallback is used to get a callback when a cache entry is evicted
type EvictCallback = func(key interface{}, value interface{})
type EvictCallback = func(key any, value any)
// WithEvict set the evict callback
func WithEvict(cb EvictCallback) Option {
return func(l *LruCache) {
func WithEvict[K comparable, V any](cb EvictCallback) Option[K, V] {
return func(l *LruCache[K, V]) {
l.onEvict = cb
}
}
// WithUpdateAgeOnGet update expires when Get element
func WithUpdateAgeOnGet() Option {
return func(l *LruCache) {
func WithUpdateAgeOnGet[K comparable, V any]() Option[K, V] {
return func(l *LruCache[K, V]) {
l.updateAgeOnGet = true
}
}
// WithAge defined element max age (second)
func WithAge(maxAge int64) Option {
return func(l *LruCache) {
func WithAge[K comparable, V any](maxAge int64) Option[K, V] {
return func(l *LruCache[K, V]) {
l.maxAge = maxAge
}
}
// WithSize defined max length of LruCache
func WithSize(maxSize int) Option {
return func(l *LruCache) {
func WithSize[K comparable, V any](maxSize int) Option[K, V] {
return func(l *LruCache[K, V]) {
l.maxSize = maxSize
}
}
// WithStale decide whether Stale return is enabled.
// If this feature is enabled, element will not get Evicted according to `WithAge`.
func WithStale(stale bool) Option {
return func(l *LruCache) {
func WithStale[K comparable, V any](stale bool) Option[K, V] {
return func(l *LruCache[K, V]) {
l.staleReturn = stale
}
}
@ -53,11 +53,11 @@ func WithStale(stale bool) Option {
// LruCache is a thread-safe, in-memory lru-cache that evicts the
// least recently used entries from memory when (if set) the entries are
// older than maxAge (in seconds). Use the New constructor to create one.
type LruCache struct {
type LruCache[K comparable, V any] struct {
maxAge int64
maxSize int
mu sync.Mutex
cache map[interface{}]*list.Element
cache map[any]*list.Element
lru *list.List // Front is least-recent
updateAgeOnGet bool
staleReturn bool
@ -65,10 +65,10 @@ type LruCache struct {
}
// NewLRUCache creates an LruCache
func NewLRUCache(options ...Option) *LruCache {
lc := &LruCache{
func NewLRUCache[K comparable, V any](options ...Option[K, V]) *LruCache[K, V] {
lc := &LruCache[K, V]{
lru: list.New(),
cache: make(map[interface{}]*list.Element),
cache: make(map[any]*list.Element),
}
for _, option := range options {
@ -78,33 +78,33 @@ func NewLRUCache(options ...Option) *LruCache {
return lc
}
// Get returns the interface{} representation of a cached response and a bool
// Get returns the any representation of a cached response and a bool
// set to true if the key was found.
func (c *LruCache) Get(key interface{}) (interface{}, bool) {
entry := c.get(key)
if entry == nil {
return nil, false
func (c *LruCache[K, V]) Get(key K) (V, bool) {
el := c.get(key)
if el == nil {
return getZero[V](), false
}
value := entry.value
value := el.value
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,
// and a bool set to true if the key was found.
// This method will NOT check the maxAge of element and will NOT update the expires.
func (c *LruCache) GetWithExpire(key interface{}) (interface{}, time.Time, bool) {
entry := c.get(key)
if entry == nil {
return nil, time.Time{}, false
func (c *LruCache[K, V]) GetWithExpire(key K) (V, time.Time, bool) {
el := c.get(key)
if el == nil {
return getZero[V](), time.Time{}, false
}
return entry.value, time.Unix(entry.expires, 0), true
return el.value, time.Unix(el.expires, 0), true
}
// Exist returns if key exist in cache but not put item to the head of linked list
func (c *LruCache) Exist(key interface{}) bool {
func (c *LruCache[K, V]) Exist(key K) bool {
c.mu.Lock()
defer c.mu.Unlock()
@ -112,8 +112,8 @@ func (c *LruCache) Exist(key interface{}) bool {
return ok
}
// Set stores the interface{} representation of a response for a given key.
func (c *LruCache) Set(key interface{}, value interface{}) {
// Set stores the any representation of a response for a given key.
func (c *LruCache[K, V]) Set(key K, value V) {
expires := int64(0)
if c.maxAge > 0 {
expires = time.Now().Unix() + c.maxAge
@ -121,23 +121,23 @@ func (c *LruCache) Set(key interface{}, value interface{}) {
c.SetWithExpire(key, value, time.Unix(expires, 0))
}
// SetWithExpire stores the interface{} representation of a response for a given key and given expires.
// SetWithExpire stores the any representation of a response for a given key and given expires.
// The expires time will round to second.
func (c *LruCache) SetWithExpire(key interface{}, value interface{}, expires time.Time) {
func (c *LruCache[K, V]) SetWithExpire(key K, value V, expires time.Time) {
c.mu.Lock()
defer c.mu.Unlock()
if le, ok := c.cache[key]; ok {
c.lru.MoveToBack(le)
e := le.Value.(*entry)
e := le.Value.(*entry[K, V])
e.value = value
e.expires = expires.Unix()
} else {
e := &entry{key: key, value: value, expires: expires.Unix()}
e := &entry[K, V]{key: key, value: value, expires: expires.Unix()}
c.cache[key] = c.lru.PushBack(e)
if c.maxSize > 0 {
if len := c.lru.Len(); len > c.maxSize {
if elLen := c.lru.Len(); elLen > c.maxSize {
c.deleteElement(c.lru.Front())
}
}
@ -147,7 +147,7 @@ func (c *LruCache) SetWithExpire(key interface{}, value interface{}, expires tim
}
// CloneTo clone and overwrite elements to another LruCache
func (c *LruCache) CloneTo(n *LruCache) {
func (c *LruCache[K, V]) CloneTo(n *LruCache[K, V]) {
c.mu.Lock()
defer c.mu.Unlock()
@ -155,15 +155,15 @@ func (c *LruCache) CloneTo(n *LruCache) {
defer n.mu.Unlock()
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() {
elm := e.Value.(*entry)
elm := e.Value.(*entry[K, V])
n.cache[elm.key] = n.lru.PushBack(elm)
}
}
func (c *LruCache) get(key interface{}) *entry {
func (c *LruCache[K, V]) get(key K) *entry[K, V] {
c.mu.Lock()
defer c.mu.Unlock()
@ -172,7 +172,7 @@ func (c *LruCache) get(key interface{}) *entry {
return nil
}
if !c.staleReturn && c.maxAge > 0 && le.Value.(*entry).expires <= time.Now().Unix() {
if !c.staleReturn && c.maxAge > 0 && le.Value.(*entry[K, V]).expires <= time.Now().Unix() {
c.deleteElement(le)
c.maybeDeleteOldest()
@ -180,15 +180,15 @@ func (c *LruCache) get(key interface{}) *entry {
}
c.lru.MoveToBack(le)
entry := le.Value.(*entry)
el := le.Value.(*entry[K, V])
if c.maxAge > 0 && c.updateAgeOnGet {
entry.expires = time.Now().Unix() + c.maxAge
el.expires = time.Now().Unix() + c.maxAge
}
return entry
return el
}
// Delete removes the value associated with a key.
func (c *LruCache) Delete(key interface{}) {
func (c *LruCache[K, V]) Delete(key K) {
c.mu.Lock()
if le, ok := c.cache[key]; ok {
@ -198,26 +198,40 @@ func (c *LruCache) Delete(key interface{}) {
c.mu.Unlock()
}
func (c *LruCache) maybeDeleteOldest() {
func (c *LruCache[K, V]) maybeDeleteOldest() {
if !c.staleReturn && c.maxAge > 0 {
now := time.Now().Unix()
for le := c.lru.Front(); le != nil && le.Value.(*entry).expires <= now; le = c.lru.Front() {
for le := c.lru.Front(); le != nil && le.Value.(*entry[K, V]).expires <= now; le = c.lru.Front() {
c.deleteElement(le)
}
}
}
func (c *LruCache) deleteElement(le *list.Element) {
func (c *LruCache[K, V]) deleteElement(le *list.Element) {
c.lru.Remove(le)
e := le.Value.(*entry)
e := le.Value.(*entry[K, V])
delete(c.cache, e.key)
if c.onEvict != nil {
c.onEvict(e.key, e.value)
}
}
type entry struct {
key interface{}
value interface{}
func (c *LruCache[K, V]) Clear() error {
c.mu.Lock()
c.cache = make(map[any]*list.Element)
c.mu.Unlock()
return nil
}
type entry[K comparable, V any] struct {
key K
value V
expires int64
}
func getZero[T any]() T {
var result T
return result
}

View File

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

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

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

View File

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

View File

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

View File

@ -17,7 +17,7 @@ type Picker struct {
once sync.Once
errOnce sync.Once
result interface{}
result any
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,
// then returns the first nil error result (if any) from them.
func (p *Picker) Wait() interface{} {
func (p *Picker) Wait() any {
p.wg.Wait()
if p.cancel != nil {
p.cancel()
@ -58,7 +58,7 @@ func (p *Picker) Error() error {
// Go calls the given function in a new goroutine.
// The first call to return a nil error cancels the group; its result will be returned by Wait.
func (p *Picker) Go(f func() (interface{}, error)) {
func (p *Picker) Go(f func() (any, error)) {
p.wg.Add(1)
go func() {

View File

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

View File

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

View File

@ -5,7 +5,7 @@ import (
"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 {
return bufferPool.Get().(*bytes.Buffer)

View File

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

View File

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

View File

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

View File

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

View File

@ -28,8 +28,8 @@ func NewDecoder(option Option) *Decoder {
return &Decoder{option: &option}
}
// Decode transform a map[string]interface{} to a struct
func (d *Decoder) Decode(src map[string]interface{}, dst interface{}) error {
// Decode transform a map[string]any to a struct
func (d *Decoder) Decode(src map[string]any, dst any) error {
if reflect.TypeOf(dst).Kind() != reflect.Ptr {
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)
str := strings.SplitN(tag, ",", 2)
key := str[0]
omitempty := false
if len(str) > 1 {
omitempty = str[1] == "omitempty"
}
key, omitKey, found := strings.Cut(tag, ",")
omitempty := found && omitKey == "omitempty"
value, ok := src[key]
if !ok || value == nil {
@ -68,7 +64,7 @@ func (d *Decoder) Decode(src map[string]interface{}, dst interface{}) error {
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() {
case reflect.Int:
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)
kind := dataVal.Kind()
switch {
case kind == reflect.Int:
val.SetInt(dataVal.Int())
case kind == reflect.Float64 && d.option.WeaklyTypedInput:
val.SetInt(int64(dataVal.Float()))
case kind == reflect.String && d.option.WeaklyTypedInput:
var i int64
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
}
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)
kind := dataVal.Kind()
switch {
@ -129,7 +127,7 @@ func (d *Decoder) decodeString(name string, data interface{}, val reflect.Value)
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)
kind := dataVal.Kind()
switch {
@ -146,7 +144,7 @@ func (d *Decoder) decodeBool(name string, data interface{}, val reflect.Value) (
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))
valType := val.Type()
valElemType := valType.Elem()
@ -173,7 +171,7 @@ func (d *Decoder) decodeSlice(name string, data interface{}, val reflect.Value)
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()
valKeyType := valType.Key()
valElemType := valType.Elem()
@ -245,7 +243,7 @@ func (d *Decoder) decodeMapFromMap(name string, dataVal reflect.Value, val refle
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))
// 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{})
dataValKeysUnused := make(map[interface{}]struct{})
dataValKeysUnused := make(map[any]struct{})
for _, dataValKey := range dataVal.MapKeys() {
dataValKeys[dataValKey] = struct{}{}
dataValKeysUnused[dataValKey.Interface()] = struct{}{}
@ -398,7 +396,7 @@ func (d *Decoder) decodeStructFromMap(name string, dataVal, val reflect.Value) e
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)
val.Set(dataVal)
return nil

View File

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

44
common/utils/range.go Normal file
View File

@ -0,0 +1,44 @@
package utils
import (
"golang.org/x/exp/constraints"
)
type Range[T constraints.Ordered] struct {
start T
end T
}
func NewRange[T constraints.Ordered](start, end T) *Range[T] {
if start > end {
return &Range[T]{
start: end,
end: start,
}
}
return &Range[T]{
start: start,
end: end,
}
}
func (r *Range[T]) Contains(t T) bool {
return t >= r.start && t <= r.end
}
func (r *Range[T]) LeftContains(t T) bool {
return t >= r.start && t < r.end
}
func (r *Range[T]) RightContains(t T) bool {
return t > r.start && t <= r.end
}
func (r *Range[T]) Start() T {
return r.start
}
func (r *Range[T]) End() T {
return r.end
}

View File

@ -36,7 +36,7 @@ func NewAuthenticator(users []AuthUser) Authenticator {
au.storage.Store(user.User, user.Pass)
}
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))
return true
})

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -3,14 +3,16 @@ package dialer
import "go.uber.org/atomic"
var (
DefaultOptions []Option
DefaultInterface = atomic.NewString("")
DefaultOptions []Option
DefaultInterface = atomic.NewString("")
DefaultRoutingMark = atomic.NewInt32(0)
)
type option struct {
interfaceName string
addrReuse bool
routingMark int
direct bool
}
type Option func(opt *option)
@ -32,3 +34,9 @@ func WithRoutingMark(mark int) Option {
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
// +build !darwin,!dragonfly,!freebsd,!linux,!netbsd,!openbsd,!solaris,!windows
package dialer

View File

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

View File

@ -1,7 +1,7 @@
package fakeip
import (
"net"
"net/netip"
"github.com/Dreamacro/clash/component/profile/cachefile"
)
@ -11,22 +11,27 @@ type cachefileStore struct {
}
// GetByHost implements store.GetByHost
func (c *cachefileStore) GetByHost(host string) (net.IP, bool) {
func (c *cachefileStore) GetByHost(host string) (netip.Addr, bool) {
elm := c.cache.GetFakeip([]byte(host))
if elm == nil {
return nil, false
return netip.Addr{}, false
}
if len(elm) == 4 {
return netip.AddrFrom4(*(*[4]byte)(elm)), true
} else {
return netip.AddrFrom16(*(*[16]byte)(elm)), true
}
return net.IP(elm), true
}
// PutByHost implements store.PutByHost
func (c *cachefileStore) PutByHost(host string, ip net.IP) {
c.cache.PutFakeip([]byte(host), ip)
func (c *cachefileStore) PutByHost(host string, ip netip.Addr) {
c.cache.PutFakeip([]byte(host), ip.AsSlice())
}
// GetByIP implements store.GetByIP
func (c *cachefileStore) GetByIP(ip net.IP) (string, bool) {
elm := c.cache.GetFakeip(ip.To4())
func (c *cachefileStore) GetByIP(ip netip.Addr) (string, bool) {
elm := c.cache.GetFakeip(ip.AsSlice())
if elm == nil {
return "", false
}
@ -34,18 +39,18 @@ func (c *cachefileStore) GetByIP(ip net.IP) (string, bool) {
}
// PutByIP implements store.PutByIP
func (c *cachefileStore) PutByIP(ip net.IP, host string) {
c.cache.PutFakeip(ip.To4(), []byte(host))
func (c *cachefileStore) PutByIP(ip netip.Addr, host string) {
c.cache.PutFakeip(ip.AsSlice(), []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()))
func (c *cachefileStore) DelByIP(ip netip.Addr) {
addr := ip.AsSlice()
c.cache.DelFakeipPair(addr, c.cache.GetFakeip(addr))
}
// Exist implements store.Exist
func (c *cachefileStore) Exist(ip net.IP) bool {
func (c *cachefileStore) Exist(ip netip.Addr) bool {
_, exist := c.GetByIP(ip)
return exist
}
@ -53,3 +58,8 @@ func (c *cachefileStore) Exist(ip net.IP) bool {
// CloneTo implements store.CloneTo
// already persistence
func (c *cachefileStore) CloneTo(store store) {}
// FlushFakeIP implements store.FlushFakeIP
func (c *cachefileStore) FlushFakeIP() error {
return c.cache.FlushFakeIP()
}

View File

@ -1,40 +1,37 @@
package fakeip
import (
"net"
"net/netip"
"github.com/Dreamacro/clash/common/cache"
)
type memoryStore struct {
cache *cache.LruCache
cacheIP *cache.LruCache[string, netip.Addr]
cacheHost *cache.LruCache[netip.Addr, string]
}
// GetByHost implements store.GetByHost
func (m *memoryStore) GetByHost(host string) (net.IP, bool) {
if elm, exist := m.cache.Get(host); exist {
ip := elm.(net.IP)
func (m *memoryStore) GetByHost(host string) (netip.Addr, bool) {
if ip, exist := m.cacheIP.Get(host); exist {
// ensure ip --> host on head of linked list
m.cache.Get(ipToUint(ip.To4()))
m.cacheHost.Get(ip)
return ip, true
}
return nil, false
return netip.Addr{}, false
}
// PutByHost implements store.PutByHost
func (m *memoryStore) PutByHost(host string, ip net.IP) {
m.cache.Set(host, ip)
func (m *memoryStore) PutByHost(host string, ip netip.Addr) {
m.cacheIP.Set(host, ip)
}
// GetByIP implements store.GetByIP
func (m *memoryStore) GetByIP(ip net.IP) (string, bool) {
if elm, exist := m.cache.Get(ipToUint(ip.To4())); exist {
host := elm.(string)
func (m *memoryStore) GetByIP(ip netip.Addr) (string, bool) {
if host, exist := m.cacheHost.Get(ip); exist {
// ensure host --> ip on head of linked list
m.cache.Get(host)
m.cacheIP.Get(host)
return host, true
}
@ -42,28 +39,41 @@ func (m *memoryStore) GetByIP(ip net.IP) (string, bool) {
}
// PutByIP implements store.PutByIP
func (m *memoryStore) PutByIP(ip net.IP, host string) {
m.cache.Set(ipToUint(ip.To4()), host)
func (m *memoryStore) PutByIP(ip netip.Addr, host string) {
m.cacheHost.Set(ip, 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))
func (m *memoryStore) DelByIP(ip netip.Addr) {
if host, exist := m.cacheHost.Get(ip); exist {
m.cacheIP.Delete(host)
}
m.cache.Delete(ipNum)
m.cacheHost.Delete(ip)
}
// Exist implements store.Exist
func (m *memoryStore) Exist(ip net.IP) bool {
return m.cache.Exist(ipToUint(ip.To4()))
func (m *memoryStore) Exist(ip netip.Addr) bool {
return m.cacheHost.Exist(ip)
}
// CloneTo implements store.CloneTo
// only for memoryStore to memoryStore
func (m *memoryStore) CloneTo(store store) {
if ms, ok := store.(*memoryStore); ok {
m.cache.CloneTo(ms.cache)
m.cacheIP.CloneTo(ms.cacheIP)
m.cacheHost.CloneTo(ms.cacheHost)
}
}
// FlushFakeIP implements store.FlushFakeIP
func (m *memoryStore) FlushFakeIP() error {
_ = m.cacheIP.Clear()
return m.cacheHost.Clear()
}
func newMemoryStore(size int) *memoryStore {
return &memoryStore{
cacheIP: cache.NewLRUCache[string, netip.Addr](cache.WithSize[string, netip.Addr](size)),
cacheHost: cache.NewLRUCache[netip.Addr, string](cache.WithSize[netip.Addr, string](size)),
}
}

View File

@ -1,39 +1,47 @@
package fakeip
import (
"encoding/binary"
"errors"
"net"
"math/bits"
"net/netip"
"sync"
"github.com/Dreamacro/clash/common/cache"
"github.com/Dreamacro/clash/component/profile/cachefile"
"github.com/Dreamacro/clash/component/trie"
)
type uint128 struct {
hi uint64
lo uint64
}
type store interface {
GetByHost(host string) (net.IP, bool)
PutByHost(host string, ip net.IP)
GetByIP(ip net.IP) (string, bool)
PutByIP(ip net.IP, host string)
DelByIP(ip net.IP)
Exist(ip net.IP) bool
GetByHost(host string) (netip.Addr, bool)
PutByHost(host string, ip netip.Addr)
GetByIP(ip netip.Addr) (string, bool)
PutByIP(ip netip.Addr, host string)
DelByIP(ip netip.Addr)
Exist(ip netip.Addr) bool
CloneTo(store)
FlushFakeIP() error
}
// Pool is a implementation about fake ip generator without storage
type Pool struct {
max uint32
min uint32
gateway uint32
offset uint32
gateway netip.Addr
first netip.Addr
last netip.Addr
offset netip.Addr
cycle bool
mux sync.Mutex
host *trie.DomainTrie
ipnet *net.IPNet
host *trie.DomainTrie[bool]
ipnet *netip.Prefix
store store
}
// Lookup return a fake ip with host
func (p *Pool) Lookup(host string) net.IP {
func (p *Pool) Lookup(host string) netip.Addr {
p.mux.Lock()
defer p.mux.Unlock()
if ip, exist := p.store.GetByHost(host); exist {
@ -46,14 +54,10 @@ func (p *Pool) Lookup(host string) net.IP {
}
// LookBack return host with the fake ip
func (p *Pool) LookBack(ip net.IP) (string, bool) {
func (p *Pool) LookBack(ip netip.Addr) (string, bool) {
p.mux.Lock()
defer p.mux.Unlock()
if ip = ip.To4(); ip == nil {
return "", false
}
return p.store.GetByIP(ip)
}
@ -66,24 +70,25 @@ func (p *Pool) ShouldSkipped(domain string) bool {
}
// Exist returns if given ip exists in fake-ip pool
func (p *Pool) Exist(ip net.IP) bool {
func (p *Pool) Exist(ip netip.Addr) bool {
p.mux.Lock()
defer p.mux.Unlock()
if ip = ip.To4(); ip == nil {
return false
}
return p.store.Exist(ip)
}
// Gateway return gateway ip
func (p *Pool) Gateway() net.IP {
return uintToIP(p.gateway)
func (p *Pool) Gateway() netip.Addr {
return p.gateway
}
// Broadcast return the last ip
func (p *Pool) Broadcast() netip.Addr {
return p.last
}
// IPNet return raw ipnet
func (p *Pool) IPNet() *net.IPNet {
func (p *Pool) IPNet() *netip.Prefix {
return p.ipnet
}
@ -92,43 +97,36 @@ func (p *Pool) CloneFrom(o *Pool) {
o.store.CloneTo(p.store)
}
func (p *Pool) get(host string) net.IP {
current := p.offset
func (p *Pool) get(host string) netip.Addr {
for {
p.offset = (p.offset + 1) % (p.max - p.min)
// Avoid infinite loops
if p.offset == current {
p.offset = (p.offset + 1) % (p.max - p.min)
ip := uintToIP(p.min + p.offset - 1)
p.store.DelByIP(ip)
p.offset = p.offset.Next()
if !p.offset.Less(p.last) {
p.cycle = true
p.offset = p.first
}
if p.cycle {
p.store.DelByIP(p.offset)
break
}
ip := uintToIP(p.min + p.offset - 1)
if !p.store.Exist(ip) {
if !p.store.Exist(p.offset) {
break
}
}
ip := uintToIP(p.min + p.offset - 1)
p.store.PutByIP(ip, host)
return ip
p.store.PutByIP(p.offset, host)
return p.offset
}
func ipToUint(ip net.IP) uint32 {
v := uint32(ip[0]) << 24
v += uint32(ip[1]) << 16
v += uint32(ip[2]) << 8
v += uint32(ip[3])
return v
}
func uintToIP(v uint32) net.IP {
return net.IP{byte(v >> 24), byte(v >> 16), byte(v >> 8), byte(v)}
func (p *Pool) FlushFakeIP() error {
return p.store.FlushFakeIP()
}
type Options struct {
IPNet *net.IPNet
Host *trie.DomainTrie
IPNet *netip.Prefix
Host *trie.DomainTrie[bool]
// Size sets the maximum number of entries in memory
// and does not work if Persistence is true
@ -141,20 +139,23 @@ type Options struct {
// New return Pool instance
func New(options Options) (*Pool, error) {
min := ipToUint(options.IPNet.IP) + 2
var (
hostAddr = options.IPNet.Masked().Addr()
gateway = hostAddr.Next()
first = gateway.Next().Next()
last = add(hostAddr, 1<<uint64(hostAddr.BitLen()-options.IPNet.Bits())-1)
)
ones, bits := options.IPNet.Mask.Size()
total := 1<<uint(bits-ones) - 2
if total <= 0 {
if !options.IPNet.IsValid() || !first.Less(last) || !options.IPNet.Contains(last) {
return nil, errors.New("ipnet don't have valid ip")
}
max := min + uint32(total) - 1
pool := &Pool{
min: min,
max: max,
gateway: min - 1,
gateway: gateway,
first: first,
last: last,
offset: first.Prev(),
cycle: false,
host: options.Host,
ipnet: options.IPNet,
}
@ -163,10 +164,34 @@ func New(options Options) (*Pool, error) {
cache: cachefile.Cache(),
}
} else {
pool.store = &memoryStore{
cache: cache.NewLRUCache(cache.WithSize(options.Size * 2)),
}
pool.store = newMemoryStore(options.Size)
}
return pool, nil
}
// add returns addr + n.
func add(addr netip.Addr, n uint64) netip.Addr {
buf := addr.As16()
u := uint128{
binary.BigEndian.Uint64(buf[:8]),
binary.BigEndian.Uint64(buf[8:]),
}
lo, carry := bits.Add64(u.lo, n, 0)
u.hi = u.hi + carry
u.lo = lo
binary.BigEndian.PutUint64(buf[:8], u.hi)
binary.BigEndian.PutUint64(buf[8:], u.lo)
a := netip.AddrFrom16(buf)
if addr.Is4() {
return a.Unmap()
}
return a
}

View File

@ -2,7 +2,7 @@ package fakeip
import (
"fmt"
"net"
"net/netip"
"os"
"testing"
"time"
@ -49,9 +49,9 @@ func createCachefileStore(options Options) (*Pool, string, error) {
}
func TestPool_Basic(t *testing.T) {
_, ipnet, _ := net.ParseCIDR("192.168.0.1/29")
ipnet := netip.MustParsePrefix("192.168.0.0/28")
pools, tempfile, err := createPools(Options{
IPNet: ipnet,
IPNet: &ipnet,
Size: 10,
})
assert.Nil(t, err)
@ -62,23 +62,52 @@ func TestPool_Basic(t *testing.T) {
last := pool.Lookup("bar.com")
bar, exist := pool.LookBack(last)
assert.True(t, first.Equal(net.IP{192, 168, 0, 2}))
assert.Equal(t, pool.Lookup("foo.com"), net.IP{192, 168, 0, 2})
assert.True(t, last.Equal(net.IP{192, 168, 0, 3}))
assert.True(t, first == netip.AddrFrom4([4]byte{192, 168, 0, 3}))
assert.True(t, pool.Lookup("foo.com") == netip.AddrFrom4([4]byte{192, 168, 0, 3}))
assert.True(t, last == netip.AddrFrom4([4]byte{192, 168, 0, 4}))
assert.True(t, exist)
assert.Equal(t, bar, "bar.com")
assert.Equal(t, pool.Gateway(), net.IP{192, 168, 0, 1})
assert.True(t, pool.Gateway() == netip.AddrFrom4([4]byte{192, 168, 0, 1}))
assert.True(t, pool.Broadcast() == netip.AddrFrom4([4]byte{192, 168, 0, 15}))
assert.Equal(t, pool.IPNet().String(), ipnet.String())
assert.True(t, pool.Exist(net.IP{192, 168, 0, 3}))
assert.False(t, pool.Exist(net.IP{192, 168, 0, 4}))
assert.False(t, pool.Exist(net.ParseIP("::1")))
assert.True(t, pool.Exist(netip.AddrFrom4([4]byte{192, 168, 0, 4})))
assert.False(t, pool.Exist(netip.AddrFrom4([4]byte{192, 168, 0, 5})))
assert.False(t, pool.Exist(netip.MustParseAddr("::1")))
}
}
func TestPool_BasicV6(t *testing.T) {
ipnet := netip.MustParsePrefix("2001:4860:4860::8888/118")
pools, tempfile, err := createPools(Options{
IPNet: &ipnet,
Size: 10,
})
assert.Nil(t, err)
defer os.Remove(tempfile)
for _, pool := range pools {
first := pool.Lookup("foo.com")
last := pool.Lookup("bar.com")
bar, exist := pool.LookBack(last)
assert.True(t, first == netip.MustParseAddr("2001:4860:4860:0000:0000:0000:0000:8803"))
assert.True(t, pool.Lookup("foo.com") == netip.MustParseAddr("2001:4860:4860:0000:0000:0000:0000:8803"))
assert.True(t, last == netip.MustParseAddr("2001:4860:4860:0000:0000:0000:0000:8804"))
assert.True(t, exist)
assert.Equal(t, bar, "bar.com")
assert.True(t, pool.Gateway() == netip.MustParseAddr("2001:4860:4860:0000:0000:0000:0000:8801"))
assert.True(t, pool.Broadcast() == netip.MustParseAddr("2001:4860:4860:0000:0000:0000:0000:8bff"))
assert.Equal(t, pool.IPNet().String(), ipnet.String())
assert.True(t, pool.Exist(netip.MustParseAddr("2001:4860:4860:0000:0000:0000:0000:8804")))
assert.False(t, pool.Exist(netip.MustParseAddr("2001:4860:4860:0000:0000:0000:0000:8805")))
assert.False(t, pool.Exist(netip.MustParseAddr("127.0.0.1")))
}
}
func TestPool_CycleUsed(t *testing.T) {
_, ipnet, _ := net.ParseCIDR("192.168.0.1/29")
ipnet := netip.MustParsePrefix("192.168.0.16/28")
pools, tempfile, err := createPools(Options{
IPNet: ipnet,
IPNet: &ipnet,
Size: 10,
})
assert.Nil(t, err)
@ -87,22 +116,22 @@ func TestPool_CycleUsed(t *testing.T) {
for _, pool := range pools {
foo := pool.Lookup("foo.com")
bar := pool.Lookup("bar.com")
for i := 0; i < 3; i++ {
for i := 0; i < 10; 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))
assert.True(t, foo == baz)
assert.True(t, next == bar)
}
}
func TestPool_Skip(t *testing.T) {
_, ipnet, _ := net.ParseCIDR("192.168.0.1/30")
tree := trie.New()
tree.Insert("example.com", tree)
ipnet := netip.MustParsePrefix("192.168.0.1/29")
tree := trie.New[bool]()
tree.Insert("example.com", true)
pools, tempfile, err := createPools(Options{
IPNet: ipnet,
IPNet: &ipnet,
Size: 10,
Host: tree,
})
@ -116,9 +145,9 @@ func TestPool_Skip(t *testing.T) {
}
func TestPool_MaxCacheSize(t *testing.T) {
_, ipnet, _ := net.ParseCIDR("192.168.0.1/24")
ipnet := netip.MustParsePrefix("192.168.0.1/24")
pool, _ := New(Options{
IPNet: ipnet,
IPNet: &ipnet,
Size: 2,
})
@ -127,13 +156,13 @@ func TestPool_MaxCacheSize(t *testing.T) {
pool.Lookup("baz.com")
next := pool.Lookup("foo.com")
assert.False(t, first.Equal(next))
assert.False(t, first == next)
}
func TestPool_DoubleMapping(t *testing.T) {
_, ipnet, _ := net.ParseCIDR("192.168.0.1/24")
ipnet := netip.MustParsePrefix("192.168.0.1/24")
pool, _ := New(Options{
IPNet: ipnet,
IPNet: &ipnet,
Size: 2,
})
@ -157,23 +186,23 @@ func TestPool_DoubleMapping(t *testing.T) {
assert.False(t, bazExist)
assert.True(t, barExist)
assert.False(t, bazIP.Equal(newBazIP))
assert.False(t, bazIP == newBazIP)
}
func TestPool_Clone(t *testing.T) {
_, ipnet, _ := net.ParseCIDR("192.168.0.1/24")
ipnet := netip.MustParsePrefix("192.168.0.1/24")
pool, _ := New(Options{
IPNet: ipnet,
IPNet: &ipnet,
Size: 2,
})
first := pool.Lookup("foo.com")
last := pool.Lookup("bar.com")
assert.True(t, first.Equal(net.IP{192, 168, 0, 2}))
assert.True(t, last.Equal(net.IP{192, 168, 0, 3}))
assert.True(t, first == netip.AddrFrom4([4]byte{192, 168, 0, 3}))
assert.True(t, last == netip.AddrFrom4([4]byte{192, 168, 0, 4}))
newPool, _ := New(Options{
IPNet: ipnet,
IPNet: &ipnet,
Size: 2,
})
newPool.CloneFrom(pool)
@ -184,11 +213,67 @@ func TestPool_Clone(t *testing.T) {
}
func TestPool_Error(t *testing.T) {
_, ipnet, _ := net.ParseCIDR("192.168.0.1/31")
ipnet := netip.MustParsePrefix("192.168.0.1/31")
_, err := New(Options{
IPNet: ipnet,
IPNet: &ipnet,
Size: 10,
})
assert.Error(t, err)
}
func TestPool_FlushFileCache(t *testing.T) {
ipnet := netip.MustParsePrefix("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.True(t, foo == fox)
assert.False(t, foo == baz)
assert.True(t, bar == bax)
assert.False(t, bar == next)
assert.True(t, baz == nero)
}
}
func TestPool_FlushMemoryCache(t *testing.T) {
ipnet := netip.MustParsePrefix("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.True(t, foo == fox)
assert.False(t, foo == baz)
assert.True(t, bar == bax)
assert.False(t, bar == next)
assert.True(t, baz == nero)
}

View File

@ -3,6 +3,7 @@ package geodata
import (
"errors"
"fmt"
C "github.com/Dreamacro/clash/constant"
"strings"
"github.com/Dreamacro/clash/component/geodata/router"
@ -14,7 +15,7 @@ type loader struct {
}
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) {
@ -58,7 +59,7 @@ func (l *loader) LoadGeoSiteWithAttr(file string, siteWithAttr string) ([]*route
}
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

View File

@ -1,7 +1,10 @@
package router
import (
"encoding/binary"
"fmt"
"net"
"sort"
"strings"
"github.com/Dreamacro/clash/component/geodata/strmatcher"
@ -69,3 +72,279 @@ func NewDomainMatcher(domains []*Domain) (*DomainMatcher, error) {
func (m *DomainMatcher) ApplyDomain(domain string) bool {
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.
// versions:
// protoc-gen-go v1.27.1
// protoc v3.17.3
// protoc-gen-go v1.28.0
// protoc v3.19.1
// source: component/geodata/router/config.proto
package router

View File

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

View File

@ -2,10 +2,39 @@ package geodata
import (
"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) {
if newLoader == "memc" {
newLoader = "memconservative"
}
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) {
geoLoaderName := "standard"
geoLoader, err := GetGeoDataLoader(geoLoaderName)
if err != nil {
return nil, 0, err
@ -28,3 +57,28 @@ func LoadGeoSiteMatcher(countryCode string) (*router.DomainMatcher, int, error)
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)
func ResolveInterface(name string) (*Interface, error) {
value, err, _ := interfaces.Do(func() (interface{}, error) {
value, err, _ := interfaces.Do(func() (any, error) {
ifaces, err := net.Interfaces()
if err != nil {
return nil, err

View File

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

View File

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

View File

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

View File

@ -3,6 +3,9 @@ package process
import (
"errors"
"net"
"runtime"
C "github.com/Dreamacro/clash/constant"
)
var (
@ -19,3 +22,52 @@ const (
func FindProcessName(network string, srcIP net.IP, srcPort int) (string, error) {
return findProcessName(network, srcIP, srcPort)
}
func ShouldFindProcess(metadata *C.Metadata) bool {
if runtime.GOOS == "android" {
return false
}
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

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

View File

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

View File

@ -4,12 +4,12 @@ import (
"bytes"
"encoding/binary"
"fmt"
"io"
"net"
"os"
"path"
"path/filepath"
"strings"
"syscall"
"unicode"
"unsafe"
"github.com/Dreamacro/clash/common/pool"
@ -25,17 +25,6 @@ var nativeEndian = func() binary.ByteOrder {
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 (
sizeOfSocketDiagRequest = syscall.SizeofNlMsghdr + 8 + 48
socketDiagByFamily = 20
@ -43,15 +32,15 @@ const (
)
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 {
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 protocol byte
@ -74,13 +63,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)
if err != nil {
return 0, 0, err
return 0, 0, fmt.Errorf("dial netlink: %w", err)
}
defer syscall.Close(socket)
syscall.SetNonblock(socket, true)
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: 50})
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,
@ -92,7 +80,7 @@ func resolveSocketByNetlink(network string, ip net.IP, srcPort int) (int, int, e
}
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)
@ -100,24 +88,27 @@ func resolveSocketByNetlink(network string, ip net.IP, srcPort int) (int, int, e
n, err := syscall.Read(socket, rb)
if err != nil {
return 0, 0, err
return 0, 0, fmt.Errorf("read response: %w", err)
}
messages, err := syscall.ParseNetlinkMessage(rb[:n])
if err != nil {
return 0, 0, err
return 0, 0, fmt.Errorf("parse netlink message: %w", err)
} else if len(messages) == 0 {
return 0, 0, io.ErrUnexpectedEOF
return 0, 0, fmt.Errorf("unexcepted netlink response")
}
message := messages[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 {
@ -155,20 +146,20 @@ func packSocketDiagRequest(family, protocol byte, source net.IP, sourcePort uint
return buf
}
func unpackSocketDiagResponse(msg *syscall.NetlinkMessage) (inode, uid uint32) {
func unpackSocketDiagResponse(msg *syscall.NetlinkMessage) (inode, uid int32) {
if len(msg.Data) < 72 {
return 0, 0
}
data := msg.Data
uid = nativeEndian.Uint32(data[64:68])
inode = nativeEndian.Uint32(data[68:72])
uid = int32(nativeEndian.Uint32(data[64:68]))
inode = int32(nativeEndian.Uint32(data[68:72]))
return
}
func resolveProcessNameByProcSearch(inode, uid int) (string, error) {
func resolveProcessNameByProcSearch(inode, uid int32) (string, error) {
files, err := os.ReadDir(pathProc)
if err != nil {
return "", err
@ -205,38 +196,16 @@ func resolveProcessNameByProcSearch(inode, uid int) (string, error) {
}
if bytes.Equal(buffer[:n], socket) {
cmdline, err := os.ReadFile(path.Join(processPath, "cmdline"))
if err != nil {
return "", err
}
return splitCmdline(cmdline), nil
return os.Readlink(path.Join(processPath, "exe"))
}
}
}
return "", syscall.ESRCH
}
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]))
return "", fmt.Errorf("process of uid(%d),inode(%d) not found", uid, inode)
}
func isPid(s string) bool {
for _, s := range s {
if s < '0' || s > '9' {
return false
}
}
return true
return strings.IndexFunc(s, func(r rune) bool {
return !unicode.IsDigit(r)
}) == -1
}

View File

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

View File

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

View File

@ -132,6 +132,17 @@ func (c *CacheFile) GetFakeip(key []byte) []byte {
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 {
return c.DB.Close()
}

View File

@ -10,8 +10,11 @@ type Enhancer interface {
FakeIPEnabled() bool
MappingEnabled() bool
IsFakeIP(net.IP) bool
IsFakeBroadcastIP(net.IP) bool
IsExistFakeIP(net.IP) bool
FindHostByIP(net.IP) (string, bool)
FlushFakeIP() error
InsertHostByIP(net.IP, string)
}
func FakeIPEnabled() bool {
@ -38,6 +41,14 @@ func IsFakeIP(ip net.IP) bool {
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 {
if mapper := DefaultHostMapper; mapper != nil {
return mapper.IsExistFakeIP(ip)
@ -46,6 +57,12 @@ func IsExistFakeIP(ip net.IP) bool {
return false
}
func InsertHostByIP(ip net.IP, host string) {
if mapper := DefaultHostMapper; mapper != nil {
mapper.InsertHostByIP(ip, host)
}
}
func FindHostByIP(ip net.IP) (string, bool) {
if mapper := DefaultHostMapper; mapper != nil {
return mapper.FindHostByIP(ip)
@ -53,3 +70,10 @@ func FindHostByIP(ip net.IP) (string, bool) {
return "", false
}
func FlushFakeIP() error {
if mapper := DefaultHostMapper; mapper != nil {
return mapper.FlushFakeIP()
}
return nil
}

View File

@ -5,6 +5,7 @@ import (
"errors"
"math/rand"
"net"
"net/netip"
"strings"
"time"
@ -15,15 +16,15 @@ var (
// DefaultResolver aim to resolve ip
DefaultResolver Resolver
// MainResolver resolve ip with main domain server
MainResolver Resolver
// ProxyServerHostResolver resolve ip to proxies server host
ProxyServerHostResolver Resolver
// DisableIPv6 means don't resolve ipv6 host
// default value is true
DisableIPv6 = true
// DefaultHosts aim to resolve hosts
DefaultHosts = trie.New()
DefaultHosts = trie.New[netip.Addr]()
// DefaultDNSTimeout defined the default dns request timeout
DefaultDNSTimeout = time.Second * 5
@ -46,14 +47,10 @@ func ResolveIPv4(host string) (net.IP, error) {
return ResolveIPv4WithResolver(host, DefaultResolver)
}
func ResolveIPv4WithMain(host string) (net.IP, error) {
return ResolveIPv4WithResolver(host, MainResolver)
}
func ResolveIPv4WithResolver(host string, r Resolver) (net.IP, error) {
if node := DefaultHosts.Search(host); node != nil {
if ip := node.Data.(net.IP).To4(); ip != nil {
return ip, nil
if ip := node.Data; ip.Is4() {
return ip.AsSlice(), nil
}
}
@ -69,16 +66,20 @@ func ResolveIPv4WithResolver(host string, r Resolver) (net.IP, error) {
return r.ResolveIPv4(host)
}
ctx, cancel := context.WithTimeout(context.Background(), DefaultDNSTimeout)
defer cancel()
ipAddrs, err := net.DefaultResolver.LookupIP(ctx, "ip4", host)
if err != nil {
return nil, err
} else if len(ipAddrs) == 0 {
return nil, ErrIPNotFound
if DefaultResolver == nil {
ctx, cancel := context.WithTimeout(context.Background(), DefaultDNSTimeout)
defer cancel()
ipAddrs, err := net.DefaultResolver.LookupIP(ctx, "ip4", host)
if err != nil {
return nil, err
} else if len(ipAddrs) == 0 {
return nil, ErrIPNotFound
}
return ipAddrs[rand.Intn(len(ipAddrs))], nil
}
return ipAddrs[rand.Intn(len(ipAddrs))], nil
return nil, ErrIPNotFound
}
// ResolveIPv6 with a host, return ipv6
@ -86,18 +87,14 @@ func ResolveIPv6(host string) (net.IP, error) {
return ResolveIPv6WithResolver(host, DefaultResolver)
}
func ResolveIPv6WithMain(host string) (net.IP, error) {
return ResolveIPv6WithResolver(host, MainResolver)
}
func ResolveIPv6WithResolver(host string, r Resolver) (net.IP, error) {
if DisableIPv6 {
return nil, ErrIPv6Disabled
}
if node := DefaultHosts.Search(host); node != nil {
if ip := node.Data.(net.IP).To16(); ip != nil {
return ip, nil
if ip := node.Data; ip.Is6() {
return ip.AsSlice(), nil
}
}
@ -113,22 +110,27 @@ func ResolveIPv6WithResolver(host string, r Resolver) (net.IP, error) {
return r.ResolveIPv6(host)
}
ctx, cancel := context.WithTimeout(context.Background(), DefaultDNSTimeout)
defer cancel()
ipAddrs, err := net.DefaultResolver.LookupIP(ctx, "ip6", host)
if err != nil {
return nil, err
} else if len(ipAddrs) == 0 {
return nil, ErrIPNotFound
if DefaultResolver == nil {
ctx, cancel := context.WithTimeout(context.Background(), DefaultDNSTimeout)
defer cancel()
ipAddrs, err := net.DefaultResolver.LookupIP(ctx, "ip6", host)
if err != nil {
return nil, err
} else if len(ipAddrs) == 0 {
return nil, ErrIPNotFound
}
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
func ResolveIPWithResolver(host string, r Resolver) (net.IP, error) {
if node := DefaultHosts.Search(host); node != nil {
return node.Data.(net.IP), nil
ip := node.Data
return ip.Unmap().AsSlice(), nil
}
if r != nil {
@ -145,12 +147,16 @@ func ResolveIPWithResolver(host string, r Resolver) (net.IP, error) {
return ip, nil
}
ipAddr, err := net.ResolveIPAddr("ip", host)
if err != nil {
return nil, err
if DefaultResolver == nil {
ipAddr, err := net.ResolveIPAddr("ip", host)
if err != nil {
return nil, err
}
return ipAddr.IP, nil
}
return ipAddr.IP, nil
return nil, ErrIPNotFound
}
// ResolveIP with a host, return ip
@ -158,7 +164,26 @@ func ResolveIP(host string) (net.IP, error) {
return ResolveIPWithResolver(host, DefaultResolver)
}
// ResolveIPWithMainResolver with a host, use main resolver, return ip
func ResolveIPWithMainResolver(host string) (net.IP, error) {
return ResolveIPWithResolver(host, MainResolver)
// ResolveIPv4ProxyServerHost proxies server host only
func ResolveIPv4ProxyServerHost(host string) (net.IP, error) {
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

@ -0,0 +1,149 @@
package sniffer
import (
"errors"
"net"
"strconv"
"github.com/Dreamacro/clash/component/trie"
CN "github.com/Dreamacro/clash/common/net"
"github.com/Dreamacro/clash/common/utils"
"github.com/Dreamacro/clash/component/resolver"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/log"
)
var (
ErrorUnsupportedSniffer = errors.New("unsupported sniffer")
ErrorSniffFailed = errors.New("all sniffer failed")
)
var Dispatcher SnifferDispatcher
type SnifferDispatcher struct {
enable bool
sniffers []C.Sniffer
foreDomain *trie.DomainTrie[bool]
skipSNI *trie.DomainTrie[bool]
portRanges *[]utils.Range[uint16]
}
func (sd *SnifferDispatcher) TCPSniff(conn net.Conn, metadata *C.Metadata) {
bufConn, ok := conn.(*CN.BufferedConn)
if !ok {
return
}
if metadata.Host == "" || sd.foreDomain.Search(metadata.Host) != nil {
port, err := strconv.ParseUint(metadata.DstPort, 10, 16)
if err != nil {
log.Debugln("[Sniffer] Dst port is error")
return
}
for _, portRange := range *sd.portRanges {
if !portRange.Contains(uint16(port)) {
return
}
}
if host, err := sd.sniffDomain(bufConn, metadata); err != nil {
log.Debugln("[Sniffer] All sniffing sniff failed with from [%s:%s] to [%s:%s]", metadata.SrcIP, metadata.SrcPort, metadata.String(), metadata.DstPort)
return
} else {
if sd.skipSNI.Search(host) != nil {
log.Debugln("[Sniffer] Skip sni[%s]", host)
return
}
sd.replaceDomain(metadata, host)
}
}
}
func (sd *SnifferDispatcher) replaceDomain(metadata *C.Metadata, host string) {
log.Debugln("[Sniffer] Sniff TCP [%s:%s]-->[%s:%s] success, replace domain [%s]-->[%s]",
metadata.SrcIP, metadata.SrcPort,
metadata.DstIP, metadata.DstPort,
metadata.Host, host)
metadata.AddrType = C.AtypDomainName
metadata.Host = host
metadata.DNSMode = C.DNSMapping
resolver.InsertHostByIP(metadata.DstIP, host)
metadata.DstIP = nil
}
func (sd *SnifferDispatcher) Enable() bool {
return sd.enable
}
func (sd *SnifferDispatcher) sniffDomain(conn *CN.BufferedConn, metadata *C.Metadata) (string, error) {
for _, sniffer := range sd.sniffers {
if sniffer.SupportNetwork() == C.TCP {
_, err := conn.Peek(1)
if err != nil {
return "", err
}
bufferedLen := conn.Buffered()
bytes, err := conn.Peek(bufferedLen)
if err != nil {
log.Debugln("[Sniffer] the data length not enough")
continue
}
host, err := sniffer.SniffTCP(bytes)
if err != nil {
log.Debugln("[Sniffer] [%s] Sniff data failed %s", sniffer.Protocol(), metadata.DstIP)
continue
}
return host, nil
}
}
return "", ErrorSniffFailed
}
func NewCloseSnifferDispatcher() (*SnifferDispatcher, error) {
dispatcher := SnifferDispatcher{
enable: false,
}
return &dispatcher, nil
}
func NewSnifferDispatcher(needSniffer []C.SnifferType, forceDomain *trie.DomainTrie[bool],
skipSNI *trie.DomainTrie[bool], ports *[]utils.Range[uint16]) (*SnifferDispatcher, error) {
dispatcher := SnifferDispatcher{
enable: true,
foreDomain: forceDomain,
skipSNI: skipSNI,
portRanges: ports,
}
for _, snifferName := range needSniffer {
sniffer, err := NewSniffer(snifferName)
if err != nil {
log.Errorln("Sniffer name[%s] is error", snifferName)
return &SnifferDispatcher{enable: false}, err
}
dispatcher.sniffers = append(dispatcher.sniffers, sniffer)
}
return &dispatcher, nil
}
func NewSniffer(name C.SnifferType) (C.Sniffer, error) {
switch name {
case C.TLS:
return &TLSSniffer{}, nil
default:
return nil, ErrorUnsupportedSniffer
}
}

View File

@ -0,0 +1,159 @@
package sniffer
import (
"testing"
)
func TestTLSHeaders(t *testing.T) {
cases := []struct {
input []byte
domain string
err bool
}{
{
input: []byte{
0x16, 0x03, 0x01, 0x00, 0xc8, 0x01, 0x00, 0x00,
0xc4, 0x03, 0x03, 0x1a, 0xac, 0xb2, 0xa8, 0xfe,
0xb4, 0x96, 0x04, 0x5b, 0xca, 0xf7, 0xc1, 0xf4,
0x2e, 0x53, 0x24, 0x6e, 0x34, 0x0c, 0x58, 0x36,
0x71, 0x97, 0x59, 0xe9, 0x41, 0x66, 0xe2, 0x43,
0xa0, 0x13, 0xb6, 0x00, 0x00, 0x20, 0x1a, 0x1a,
0xc0, 0x2b, 0xc0, 0x2f, 0xc0, 0x2c, 0xc0, 0x30,
0xcc, 0xa9, 0xcc, 0xa8, 0xcc, 0x14, 0xcc, 0x13,
0xc0, 0x13, 0xc0, 0x14, 0x00, 0x9c, 0x00, 0x9d,
0x00, 0x2f, 0x00, 0x35, 0x00, 0x0a, 0x01, 0x00,
0x00, 0x7b, 0xba, 0xba, 0x00, 0x00, 0xff, 0x01,
0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x16, 0x00,
0x14, 0x00, 0x00, 0x11, 0x63, 0x2e, 0x73, 0x2d,
0x6d, 0x69, 0x63, 0x72, 0x6f, 0x73, 0x6f, 0x66,
0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x00, 0x17, 0x00,
0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x0d, 0x00,
0x14, 0x00, 0x12, 0x04, 0x03, 0x08, 0x04, 0x04,
0x01, 0x05, 0x03, 0x08, 0x05, 0x05, 0x01, 0x08,
0x06, 0x06, 0x01, 0x02, 0x01, 0x00, 0x05, 0x00,
0x05, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12,
0x00, 0x00, 0x00, 0x10, 0x00, 0x0e, 0x00, 0x0c,
0x02, 0x68, 0x32, 0x08, 0x68, 0x74, 0x74, 0x70,
0x2f, 0x31, 0x2e, 0x31, 0x00, 0x0b, 0x00, 0x02,
0x01, 0x00, 0x00, 0x0a, 0x00, 0x0a, 0x00, 0x08,
0xaa, 0xaa, 0x00, 0x1d, 0x00, 0x17, 0x00, 0x18,
0xaa, 0xaa, 0x00, 0x01, 0x00,
},
domain: "c.s-microsoft.com",
err: false,
},
{
input: []byte{
0x16, 0x03, 0x01, 0x00, 0xee, 0x01, 0x00, 0x00,
0xea, 0x03, 0x03, 0xe7, 0x91, 0x9e, 0x93, 0xca,
0x78, 0x1b, 0x3c, 0xe0, 0x65, 0x25, 0x58, 0xb5,
0x93, 0xe1, 0x0f, 0x85, 0xec, 0x9a, 0x66, 0x8e,
0x61, 0x82, 0x88, 0xc8, 0xfc, 0xae, 0x1e, 0xca,
0xd7, 0xa5, 0x63, 0x20, 0xbd, 0x1c, 0x00, 0x00,
0x8b, 0xee, 0x09, 0xe3, 0x47, 0x6a, 0x0e, 0x74,
0xb0, 0xbc, 0xa3, 0x02, 0xa7, 0x35, 0xe8, 0x85,
0x70, 0x7c, 0x7a, 0xf0, 0x00, 0xdf, 0x4a, 0xea,
0x87, 0x01, 0x14, 0x91, 0x00, 0x20, 0xea, 0xea,
0xc0, 0x2b, 0xc0, 0x2f, 0xc0, 0x2c, 0xc0, 0x30,
0xcc, 0xa9, 0xcc, 0xa8, 0xcc, 0x14, 0xcc, 0x13,
0xc0, 0x13, 0xc0, 0x14, 0x00, 0x9c, 0x00, 0x9d,
0x00, 0x2f, 0x00, 0x35, 0x00, 0x0a, 0x01, 0x00,
0x00, 0x81, 0x9a, 0x9a, 0x00, 0x00, 0xff, 0x01,
0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00,
0x16, 0x00, 0x00, 0x13, 0x77, 0x77, 0x77, 0x30,
0x37, 0x2e, 0x63, 0x6c, 0x69, 0x63, 0x6b, 0x74,
0x61, 0x6c, 0x65, 0x2e, 0x6e, 0x65, 0x74, 0x00,
0x17, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00,
0x0d, 0x00, 0x14, 0x00, 0x12, 0x04, 0x03, 0x08,
0x04, 0x04, 0x01, 0x05, 0x03, 0x08, 0x05, 0x05,
0x01, 0x08, 0x06, 0x06, 0x01, 0x02, 0x01, 0x00,
0x05, 0x00, 0x05, 0x01, 0x00, 0x00, 0x00, 0x00,
0x00, 0x12, 0x00, 0x00, 0x00, 0x10, 0x00, 0x0e,
0x00, 0x0c, 0x02, 0x68, 0x32, 0x08, 0x68, 0x74,
0x74, 0x70, 0x2f, 0x31, 0x2e, 0x31, 0x75, 0x50,
0x00, 0x00, 0x00, 0x0b, 0x00, 0x02, 0x01, 0x00,
0x00, 0x0a, 0x00, 0x0a, 0x00, 0x08, 0x9a, 0x9a,
0x00, 0x1d, 0x00, 0x17, 0x00, 0x18, 0x8a, 0x8a,
0x00, 0x01, 0x00,
},
domain: "www07.clicktale.net",
err: false,
},
{
input: []byte{
0x16, 0x03, 0x01, 0x00, 0xe6, 0x01, 0x00, 0x00, 0xe2, 0x03, 0x03, 0x81, 0x47, 0xc1,
0x66, 0xd5, 0x1b, 0xfa, 0x4b, 0xb5, 0xe0, 0x2a, 0xe1, 0xa7, 0x87, 0x13, 0x1d, 0x11, 0xaa, 0xc6,
0xce, 0xfc, 0x7f, 0xab, 0x94, 0xc8, 0x62, 0xad, 0xc8, 0xab, 0x0c, 0xdd, 0xcb, 0x20, 0x6f, 0x9d,
0x07, 0xf1, 0x95, 0x3e, 0x99, 0xd8, 0xf3, 0x6d, 0x97, 0xee, 0x19, 0x0b, 0x06, 0x1b, 0xf4, 0x84,
0x0b, 0xb6, 0x8f, 0xcc, 0xde, 0xe2, 0xd0, 0x2d, 0x6b, 0x0c, 0x1f, 0x52, 0x53, 0x13, 0x00, 0x08,
0x13, 0x02, 0x13, 0x03, 0x13, 0x01, 0x00, 0xff, 0x01, 0x00, 0x00, 0x91, 0x00, 0x00, 0x00, 0x0c,
0x00, 0x0a, 0x00, 0x00, 0x07, 0x64, 0x6f, 0x67, 0x66, 0x69, 0x73, 0x68, 0x00, 0x0b, 0x00, 0x04,
0x03, 0x00, 0x01, 0x02, 0x00, 0x0a, 0x00, 0x0c, 0x00, 0x0a, 0x00, 0x1d, 0x00, 0x17, 0x00, 0x1e,
0x00, 0x19, 0x00, 0x18, 0x00, 0x23, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00,
0x00, 0x0d, 0x00, 0x1e, 0x00, 0x1c, 0x04, 0x03, 0x05, 0x03, 0x06, 0x03, 0x08, 0x07, 0x08, 0x08,
0x08, 0x09, 0x08, 0x0a, 0x08, 0x0b, 0x08, 0x04, 0x08, 0x05, 0x08, 0x06, 0x04, 0x01, 0x05, 0x01,
0x06, 0x01, 0x00, 0x2b, 0x00, 0x07, 0x06, 0x7f, 0x1c, 0x7f, 0x1b, 0x7f, 0x1a, 0x00, 0x2d, 0x00,
0x02, 0x01, 0x01, 0x00, 0x33, 0x00, 0x26, 0x00, 0x24, 0x00, 0x1d, 0x00, 0x20, 0x2f, 0x35, 0x0c,
0xb6, 0x90, 0x0a, 0xb7, 0xd5, 0xc4, 0x1b, 0x2f, 0x60, 0xaa, 0x56, 0x7b, 0x3f, 0x71, 0xc8, 0x01,
0x7e, 0x86, 0xd3, 0xb7, 0x0c, 0x29, 0x1a, 0x9e, 0x5b, 0x38, 0x3f, 0x01, 0x72,
},
domain: "dogfish",
err: false,
},
{
input: []byte{
0x16, 0x03, 0x01, 0x01, 0x03, 0x01, 0x00, 0x00,
0xff, 0x03, 0x03, 0x3d, 0x89, 0x52, 0x9e, 0xee,
0xbe, 0x17, 0x63, 0x75, 0xef, 0x29, 0xbd, 0x14,
0x6a, 0x49, 0xe0, 0x2c, 0x37, 0x57, 0x71, 0x62,
0x82, 0x44, 0x94, 0x8f, 0x6e, 0x94, 0x08, 0x45,
0x7f, 0xdb, 0xc1, 0x00, 0x00, 0x3e, 0xc0, 0x2c,
0xc0, 0x30, 0x00, 0x9f, 0xcc, 0xa9, 0xcc, 0xa8,
0xcc, 0xaa, 0xc0, 0x2b, 0xc0, 0x2f, 0x00, 0x9e,
0xc0, 0x24, 0xc0, 0x28, 0x00, 0x6b, 0xc0, 0x23,
0xc0, 0x27, 0x00, 0x67, 0xc0, 0x0a, 0xc0, 0x14,
0x00, 0x39, 0xc0, 0x09, 0xc0, 0x13, 0x00, 0x33,
0x00, 0x9d, 0x00, 0x9c, 0x13, 0x02, 0x13, 0x03,
0x13, 0x01, 0x00, 0x3d, 0x00, 0x3c, 0x00, 0x35,
0x00, 0x2f, 0x00, 0xff, 0x01, 0x00, 0x00, 0x98,
0x00, 0x00, 0x00, 0x10, 0x00, 0x0e, 0x00, 0x00,
0x0b, 0x31, 0x30, 0x2e, 0x34, 0x32, 0x2e, 0x30,
0x2e, 0x32, 0x34, 0x33, 0x00, 0x0b, 0x00, 0x04,
0x03, 0x00, 0x01, 0x02, 0x00, 0x0a, 0x00, 0x0a,
0x00, 0x08, 0x00, 0x1d, 0x00, 0x17, 0x00, 0x19,
0x00, 0x18, 0x00, 0x23, 0x00, 0x00, 0x00, 0x0d,
0x00, 0x20, 0x00, 0x1e, 0x04, 0x03, 0x05, 0x03,
0x06, 0x03, 0x08, 0x04, 0x08, 0x05, 0x08, 0x06,
0x04, 0x01, 0x05, 0x01, 0x06, 0x01, 0x02, 0x03,
0x02, 0x01, 0x02, 0x02, 0x04, 0x02, 0x05, 0x02,
0x06, 0x02, 0x00, 0x16, 0x00, 0x00, 0x00, 0x17,
0x00, 0x00, 0x00, 0x2b, 0x00, 0x09, 0x08, 0x7f,
0x14, 0x03, 0x03, 0x03, 0x02, 0x03, 0x01, 0x00,
0x2d, 0x00, 0x03, 0x02, 0x01, 0x00, 0x00, 0x28,
0x00, 0x26, 0x00, 0x24, 0x00, 0x1d, 0x00, 0x20,
0x13, 0x7c, 0x6e, 0x97, 0xc4, 0xfd, 0x09, 0x2e,
0x70, 0x2f, 0x73, 0x5a, 0x9b, 0x57, 0x4d, 0x5f,
0x2b, 0x73, 0x2c, 0xa5, 0x4a, 0x98, 0x40, 0x3d,
0x75, 0x6e, 0xb4, 0x76, 0xf9, 0x48, 0x8f, 0x36,
},
domain: "10.42.0.243",
err: false,
},
}
for _, test := range cases {
domain, err := SniffTLS(test.input)
if test.err {
if err == nil {
t.Errorf("Exepct error but nil in test %v", test)
}
} else {
if err != nil {
t.Errorf("Expect no error but actually %s in test %v", err.Error(), test)
}
if *domain != test.domain {
t.Error("expect domain ", test.domain, " but got ", domain)
}
}
}
}

View File

@ -0,0 +1,157 @@
package sniffer
import (
"encoding/binary"
"errors"
"strings"
C "github.com/Dreamacro/clash/constant"
)
var (
errNotTLS = errors.New("not TLS header")
errNotClientHello = errors.New("not client hello")
ErrNoClue = errors.New("not enough information for making a decision")
)
type TLSSniffer struct {
}
func (tls *TLSSniffer) Protocol() string {
return "tls"
}
func (tls *TLSSniffer) SupportNetwork() C.NetWork {
return C.TCP
}
func (tls *TLSSniffer) SniffTCP(bytes []byte) (string, error) {
domain, err := SniffTLS(bytes)
if err == nil {
return *domain, nil
} else {
return "", err
}
}
func IsValidTLSVersion(major, minor byte) bool {
return major == 3
}
// ReadClientHello returns server name (if any) from TLS client hello message.
// https://github.com/golang/go/blob/master/src/crypto/tls/handshake_messages.go#L300
func ReadClientHello(data []byte) (*string, error) {
if len(data) < 42 {
return nil, ErrNoClue
}
sessionIDLen := int(data[38])
if sessionIDLen > 32 || len(data) < 39+sessionIDLen {
return nil, ErrNoClue
}
data = data[39+sessionIDLen:]
if len(data) < 2 {
return nil, ErrNoClue
}
// cipherSuiteLen is the number of bytes of cipher suite numbers. Since
// they are uint16s, the number must be even.
cipherSuiteLen := int(data[0])<<8 | int(data[1])
if cipherSuiteLen%2 == 1 || len(data) < 2+cipherSuiteLen {
return nil, errNotClientHello
}
data = data[2+cipherSuiteLen:]
if len(data) < 1 {
return nil, ErrNoClue
}
compressionMethodsLen := int(data[0])
if len(data) < 1+compressionMethodsLen {
return nil, ErrNoClue
}
data = data[1+compressionMethodsLen:]
if len(data) == 0 {
return nil, errNotClientHello
}
if len(data) < 2 {
return nil, errNotClientHello
}
extensionsLength := int(data[0])<<8 | int(data[1])
data = data[2:]
if extensionsLength != len(data) {
return nil, errNotClientHello
}
for len(data) != 0 {
if len(data) < 4 {
return nil, errNotClientHello
}
extension := uint16(data[0])<<8 | uint16(data[1])
length := int(data[2])<<8 | int(data[3])
data = data[4:]
if len(data) < length {
return nil, errNotClientHello
}
if extension == 0x00 { /* extensionServerName */
d := data[:length]
if len(d) < 2 {
return nil, errNotClientHello
}
namesLen := int(d[0])<<8 | int(d[1])
d = d[2:]
if len(d) != namesLen {
return nil, errNotClientHello
}
for len(d) > 0 {
if len(d) < 3 {
return nil, errNotClientHello
}
nameType := d[0]
nameLen := int(d[1])<<8 | int(d[2])
d = d[3:]
if len(d) < nameLen {
return nil, errNotClientHello
}
if nameType == 0 {
serverName := string(d[:nameLen])
// An SNI value may not include a
// trailing dot. See
// https://tools.ietf.org/html/rfc6066#section-3.
if strings.HasSuffix(serverName, ".") {
return nil, errNotClientHello
}
return &serverName, nil
}
d = d[nameLen:]
}
}
data = data[length:]
}
return nil, errNotTLS
}
func SniffTLS(b []byte) (*string, error) {
if len(b) < 5 {
return nil, ErrNoClue
}
if b[0] != 0x16 /* TLS Handshake */ {
return nil, errNotTLS
}
if !IsValidTLSVersion(b[1], b[2]) {
return nil, errNotTLS
}
headerLen := int(binary.BigEndian.Uint16(b[3:5]))
if 5+headerLen > len(b) {
return nil, ErrNoClue
}
domain, err := ReadClientHello(b[5 : 5+headerLen])
if err == nil {
return domain, nil
}
return nil, err
}

View File

@ -17,8 +17,8 @@ var ErrInvalidDomain = errors.New("invalid domain")
// DomainTrie contains the main logic for adding and searching nodes for domain segments.
// support wildcard domain (e.g *.google.com)
type DomainTrie struct {
root *Node
type DomainTrie[T comparable] struct {
root *Node[T]
}
func ValidAndSplitDomain(domain string) ([]string, bool) {
@ -51,7 +51,7 @@ func ValidAndSplitDomain(domain string) ([]string, bool) {
// 3. subdomain.*.example.com
// 4. .example.com
// 5. +.example.com
func (t *DomainTrie) Insert(domain string, data interface{}) error {
func (t *DomainTrie[T]) Insert(domain string, data T) error {
parts, valid := ValidAndSplitDomain(domain)
if !valid {
return ErrInvalidDomain
@ -68,13 +68,13 @@ func (t *DomainTrie) Insert(domain string, data interface{}) error {
return nil
}
func (t *DomainTrie) insert(parts []string, data interface{}) {
func (t *DomainTrie[T]) insert(parts []string, data T) {
node := t.root
// reverse storage domain part to save space
for i := len(parts) - 1; i >= 0; i-- {
part := parts[i]
if !node.hasChild(part) {
node.addChild(part, newNode(nil))
node.addChild(part, newNode(getZero[T]()))
}
node = node.getChild(part)
@ -88,7 +88,7 @@ func (t *DomainTrie) insert(parts []string, data interface{}) {
// 1. static part
// 2. wildcard domain
// 2. dot wildcard domain
func (t *DomainTrie) Search(domain string) *Node {
func (t *DomainTrie[T]) Search(domain string) *Node[T] {
parts, valid := ValidAndSplitDomain(domain)
if !valid || parts[0] == "" {
return nil
@ -96,26 +96,26 @@ func (t *DomainTrie) Search(domain string) *Node {
n := t.search(t.root, parts)
if n == nil || n.Data == nil {
if n == nil || n.Data == getZero[T]() {
return nil
}
return n
}
func (t *DomainTrie) search(node *Node, parts []string) *Node {
func (t *DomainTrie[T]) search(node *Node[T], parts []string) *Node[T] {
if len(parts) == 0 {
return node
}
if c := node.getChild(parts[len(parts)-1]); c != nil {
if n := t.search(c, parts[:len(parts)-1]); n != nil && n.Data != nil {
if n := t.search(c, parts[:len(parts)-1]); n != nil && n.Data != getZero[T]() {
return n
}
}
if c := node.getChild(wildcard); c != nil {
if n := t.search(c, parts[:len(parts)-1]); n != nil && n.Data != nil {
if n := t.search(c, parts[:len(parts)-1]); n != nil && n.Data != getZero[T]() {
return n
}
}
@ -124,6 +124,6 @@ func (t *DomainTrie) search(node *Node, parts []string) *Node {
}
// New returns a new, empty Trie.
func New() *DomainTrie {
return &DomainTrie{root: newNode(nil)}
func New[T comparable]() *DomainTrie[T] {
return &DomainTrie[T]{root: newNode[T](getZero[T]())}
}

View File

@ -1,16 +1,16 @@
package trie
import (
"net"
"net/netip"
"testing"
"github.com/stretchr/testify/assert"
)
var localIP = net.IP{127, 0, 0, 1}
var localIP = netip.AddrFrom4([4]byte{127, 0, 0, 1})
func TestTrie_Basic(t *testing.T) {
tree := New()
tree := New[netip.Addr]()
domains := []string{
"example.com",
"google.com",
@ -23,7 +23,7 @@ func TestTrie_Basic(t *testing.T) {
node := tree.Search("example.com")
assert.NotNil(t, node)
assert.True(t, node.Data.(net.IP).Equal(localIP))
assert.True(t, node.Data == localIP)
assert.NotNil(t, tree.Insert("", localIP))
assert.Nil(t, tree.Search(""))
assert.NotNil(t, tree.Search("localhost"))
@ -31,7 +31,7 @@ func TestTrie_Basic(t *testing.T) {
}
func TestTrie_Wildcard(t *testing.T) {
tree := New()
tree := New[netip.Addr]()
domains := []string{
"*.example.com",
"sub.*.example.com",
@ -64,7 +64,7 @@ func TestTrie_Wildcard(t *testing.T) {
}
func TestTrie_Priority(t *testing.T) {
tree := New()
tree := New[int]()
domains := []string{
".dev",
"example.dev",
@ -79,18 +79,18 @@ func TestTrie_Priority(t *testing.T) {
}
for idx, domain := range domains {
tree.Insert(domain, idx)
tree.Insert(domain, idx+1)
}
assertFn("test.dev", 0)
assertFn("foo.bar.dev", 0)
assertFn("example.dev", 1)
assertFn("foo.example.dev", 2)
assertFn("test.example.dev", 3)
assertFn("test.dev", 1)
assertFn("foo.bar.dev", 1)
assertFn("example.dev", 2)
assertFn("foo.example.dev", 3)
assertFn("test.example.dev", 4)
}
func TestTrie_Boundary(t *testing.T) {
tree := New()
tree := New[netip.Addr]()
tree.Insert("*.dev", localIP)
assert.NotNil(t, tree.Insert(".", localIP))
@ -99,7 +99,7 @@ func TestTrie_Boundary(t *testing.T) {
}
func TestTrie_WildcardBoundary(t *testing.T) {
tree := New()
tree := New[netip.Addr]()
tree.Insert("+.*", localIP)
tree.Insert("stun.*.*.*", localIP)

View File

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

View File

@ -1,26 +1,31 @@
package trie
// Node is the trie's node
type Node struct {
children map[string]*Node
Data interface{}
type Node[T comparable] struct {
children map[string]*Node[T]
Data T
}
func (n *Node) getChild(s string) *Node {
func (n *Node[T]) getChild(s string) *Node[T] {
return n.children[s]
}
func (n *Node) hasChild(s string) bool {
func (n *Node[T]) hasChild(s string) bool {
return n.getChild(s) != nil
}
func (n *Node) addChild(s string, child *Node) {
func (n *Node[T]) addChild(s string, child *Node[T]) {
n.children[s] = child
}
func newNode(data interface{}) *Node {
return &Node{
func newNode[T comparable](data T) *Node[T] {
return &Node[T]{
Data: data,
children: map[string]*Node{},
children: map[string]*Node[T]{},
}
}
func getZero[T comparable]() T {
var result T
return result
}

View File

@ -26,12 +26,20 @@ func TestIpv4AddFail(t *testing.T) {
func TestIpv4Search(t *testing.T) {
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("10.2.36.0/18"))
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("55.5.6.3/8"))
assert.NoError(t, trie.AddIpCidrForString("66.23.25.4/6"))
assert.Equal(t, true, trie.IsContainForString("129.2.3.65"))
assert.Equal(t, false, trie.IsContainForString("15.2.3.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("")))
}
func TestIpv6AddSuccess(t *testing.T) {
@ -66,6 +75,14 @@ func TestIpv6AddFail(t *testing.T) {
func TestIpv6Search(t *testing.T) {
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:67c:4e8:f002::e/12"))
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, false, trie.IsContain(net.ParseIP("22233:22")))
}

View File

@ -4,20 +4,25 @@ import (
"container/list"
"errors"
"fmt"
R "github.com/Dreamacro/clash/rule"
RP "github.com/Dreamacro/clash/rule/provider"
"net"
"net/netip"
"net/url"
"os"
"regexp"
"runtime"
"strconv"
"strings"
"time"
"github.com/Dreamacro/clash/common/utils"
R "github.com/Dreamacro/clash/rule"
RP "github.com/Dreamacro/clash/rule/provider"
"github.com/Dreamacro/clash/adapter"
"github.com/Dreamacro/clash/adapter/outbound"
"github.com/Dreamacro/clash/adapter/outboundgroup"
"github.com/Dreamacro/clash/adapter/provider"
"github.com/Dreamacro/clash/component/auth"
"github.com/Dreamacro/clash/component/dialer"
"github.com/Dreamacro/clash/component/fakeip"
"github.com/Dreamacro/clash/component/geodata"
"github.com/Dreamacro/clash/component/geodata/router"
@ -25,6 +30,7 @@ import (
C "github.com/Dreamacro/clash/constant"
providerTypes "github.com/Dreamacro/clash/constant/provider"
"github.com/Dreamacro/clash/dns"
"github.com/Dreamacro/clash/listener/tun/ipstack/commons"
"github.com/Dreamacro/clash/log"
T "github.com/Dreamacro/clash/tunnel"
@ -35,14 +41,17 @@ import (
type General struct {
Inbound
Controller
Mode T.TunnelMode `json:"mode"`
UnifiedDelay bool
LogLevel log.LogLevel `json:"log-level"`
IPv6 bool `json:"ipv6"`
Interface string `json:"-"`
Mode T.TunnelMode `json:"mode"`
UnifiedDelay bool
LogLevel log.LogLevel `json:"log-level"`
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 {
Port int `json:"port"`
SocksPort int `json:"socks-port"`
@ -54,7 +63,7 @@ type Inbound struct {
BindAddress string `json:"bind-address"`
}
// Controller
// Controller config
type Controller struct {
ExternalController string `json:"-"`
ExternalUI string `json:"-"`
@ -63,17 +72,18 @@ type Controller struct {
// DNS config
type DNS struct {
Enable bool `yaml:"enable"`
IPv6 bool `yaml:"ipv6"`
NameServer []dns.NameServer `yaml:"nameserver"`
Fallback []dns.NameServer `yaml:"fallback"`
FallbackFilter FallbackFilter `yaml:"fallback-filter"`
Listen string `yaml:"listen"`
EnhancedMode C.DNSMode `yaml:"enhanced-mode"`
DefaultNameserver []dns.NameServer `yaml:"default-nameserver"`
FakeIPRange *fakeip.Pool
Hosts *trie.DomainTrie
NameServerPolicy map[string]dns.NameServer
Enable bool `yaml:"enable"`
IPv6 bool `yaml:"ipv6"`
NameServer []dns.NameServer `yaml:"nameserver"`
Fallback []dns.NameServer `yaml:"fallback"`
FallbackFilter FallbackFilter `yaml:"fallback-filter"`
Listen string `yaml:"listen"`
EnhancedMode C.DNSMode `yaml:"enhanced-mode"`
DefaultNameserver []dns.NameServer `yaml:"default-nameserver"`
FakeIPRange *fakeip.Pool
Hosts *trie.DomainTrie[netip.Addr]
NameServerPolicy map[string]dns.NameServer
ProxyServerNameserver []dns.NameServer
}
// FallbackFilter config
@ -99,16 +109,28 @@ type Profile struct {
// Tun config
type Tun struct {
Enable bool `yaml:"enable" json:"enable"`
Stack string `yaml:"stack" json:"stack"`
DnsHijack []string `yaml:"dns-hijack" json:"dns-hijack"`
AutoRoute bool `yaml:"auto-route" json:"auto-route"`
Enable bool `yaml:"enable" json:"enable"`
Device string `yaml:"device" json:"device"`
Stack C.TUNStack `yaml:"stack" json:"stack"`
DNSHijack []netip.AddrPort `yaml:"dns-hijack" json:"dns-hijack"`
AutoRoute bool `yaml:"auto-route" json:"auto-route"`
}
// Script config
type Script struct {
MainCode string `yaml:"code" json:"code"`
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"`
}
type Sniffer struct {
Enable bool
Force bool
Sniffers []C.SnifferType
Reverses *trie.DomainTrie[bool]
ForceDomain *trie.DomainTrie[bool]
SkipSNI *trie.DomainTrie[bool]
Ports *[]utils.Range[uint16]
}
// Experimental config
@ -118,30 +140,33 @@ type Experimental struct{}
type Config struct {
General *General
Tun *Tun
IPTables *IPTables
DNS *DNS
Experimental *Experimental
Hosts *trie.DomainTrie
Hosts *trie.DomainTrie[netip.Addr]
Profile *Profile
Rules []C.Rule
Users []auth.AuthUser
Proxies map[string]C.Proxy
Providers map[string]providerTypes.ProxyProvider
RuleProviders map[string]*providerTypes.RuleProvider
Sniffer *Sniffer
}
type RawDNS struct {
Enable bool `yaml:"enable"`
IPv6 bool `yaml:"ipv6"`
UseHosts bool `yaml:"use-hosts"`
NameServer []string `yaml:"nameserver"`
Fallback []string `yaml:"fallback"`
FallbackFilter RawFallbackFilter `yaml:"fallback-filter"`
Listen string `yaml:"listen"`
EnhancedMode C.DNSMode `yaml:"enhanced-mode"`
FakeIPRange string `yaml:"fake-ip-range"`
FakeIPFilter []string `yaml:"fake-ip-filter"`
DefaultNameserver []string `yaml:"default-nameserver"`
NameServerPolicy map[string]string `yaml:"nameserver-policy"`
Enable bool `yaml:"enable"`
IPv6 bool `yaml:"ipv6"`
UseHosts bool `yaml:"use-hosts"`
NameServer []string `yaml:"nameserver"`
Fallback []string `yaml:"fallback"`
FallbackFilter RawFallbackFilter `yaml:"fallback-filter"`
Listen string `yaml:"listen"`
EnhancedMode C.DNSMode `yaml:"enhanced-mode"`
FakeIPRange string `yaml:"fake-ip-range"`
FakeIPFilter []string `yaml:"fake-ip-filter"`
DefaultNameserver []string `yaml:"default-nameserver"`
NameServerPolicy map[string]string `yaml:"nameserver-policy"`
ProxyServerNameserver []string `yaml:"proxy-server-nameserver"`
}
type RawFallbackFilter struct {
@ -152,6 +177,15 @@ type RawFallbackFilter struct {
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"`
AutoDetectInterface bool `yaml:"auto-detect-interface"`
}
type RawConfig struct {
Port int `yaml:"port"`
SocksPort int `yaml:"socks-port"`
@ -169,18 +203,32 @@ type RawConfig struct {
ExternalUI string `yaml:"external-ui"`
Secret string `yaml:"secret"`
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"`
RuleProvider map[string]map[string]interface{} `yaml:"rule-providers"`
Hosts map[string]string `yaml:"hosts"`
DNS RawDNS `yaml:"dns"`
Tun Tun `yaml:"tun"`
Experimental Experimental `yaml:"experimental"`
Profile Profile `yaml:"profile"`
Proxy []map[string]interface{} `yaml:"proxies"`
ProxyGroup []map[string]interface{} `yaml:"proxy-groups"`
Rule []string `yaml:"rules"`
Script Script `yaml:"script"`
Sniffer SnifferRaw `yaml:"sniffer"`
ProxyProvider map[string]map[string]any `yaml:"proxy-providers"`
RuleProvider map[string]map[string]any `yaml:"rule-providers"`
Hosts map[string]string `yaml:"hosts"`
DNS RawDNS `yaml:"dns"`
Tun RawTun `yaml:"tun"`
IPTables IPTables `yaml:"iptables"`
Experimental Experimental `yaml:"experimental"`
Profile Profile `yaml:"profile"`
Proxy []map[string]any `yaml:"proxies"`
ProxyGroup []map[string]any `yaml:"proxy-groups"`
Rule []string `yaml:"rules"`
}
type SnifferRaw struct {
Enable bool `yaml:"enable" json:"enable"`
Sniffing []string `yaml:"sniffing" json:"sniffing"`
Force bool `yaml:"force" json:"force"`
Reverse []string `yaml:"reverses" json:"reverses"`
ForceDomain []string `yaml:"force-domain" json:"force-domain"`
SkipSNI []string `yaml:"skip-sni" json:"skip-sni"`
Ports []string `yaml:"port-whitelist" json:"port-whitelist"`
}
// Parse config
@ -199,23 +247,33 @@ func UnmarshalRawConfig(buf []byte) (*RawConfig, error) {
AllowLan: false,
BindAddress: "*",
Mode: T.Rule,
GeodataMode: C.GeodataMode,
GeodataLoader: "memconservative",
UnifiedDelay: false,
Authentication: []string{},
LogLevel: log.INFO,
Hosts: map[string]string{},
Rule: []string{},
Proxy: []map[string]interface{}{},
ProxyGroup: []map[string]interface{}{},
Tun: Tun{
Enable: false,
Stack: "gvisor",
DnsHijack: []string{"198.18.0.2:53"},
AutoRoute: false,
Proxy: []map[string]any{},
ProxyGroup: []map[string]any{},
Tun: RawTun{
Enable: false,
Device: "",
AutoDetectInterface: true,
Stack: C.TunGvisor,
DNSHijack: []string{"0.0.0.0:53"}, // default hijack all dns query
AutoRoute: true,
},
IPTables: IPTables{
Enable: false,
InboundInterface: "lo",
Bypass: []string{},
},
DNS: RawDNS{
Enable: false,
UseHosts: true,
FakeIPRange: "198.18.0.1/16",
Enable: false,
UseHosts: true,
EnhancedMode: C.DNSMapping,
FakeIPRange: "198.18.0.1/16",
FallbackFilter: RawFallbackFilter{
GeoIP: true,
GeoIPCode: "CN",
@ -229,8 +287,8 @@ func UnmarshalRawConfig(buf []byte) (*RawConfig, error) {
"1.0.0.1",
},
NameServer: []string{
"223.5.5.5",
"119.29.29",
"https://doh.pub/dns-query",
"tls://223.5.5.5:853",
},
FakeIPFilter: []string{
"dns.msftnsci.com",
@ -238,13 +296,18 @@ func UnmarshalRawConfig(buf []byte) (*RawConfig, error) {
"www.msftconnecttest.com",
},
},
Sniffer: SnifferRaw{
Enable: false,
Force: false,
Sniffing: []string{},
Reverse: []string{},
ForceDomain: []string{},
SkipSNI: []string{},
Ports: []string{},
},
Profile: Profile{
StoreSelected: true,
},
Script: Script{
MainCode: "",
ShortcutsCode: map[string]string{},
},
}
if err := yaml.Unmarshal(buf, rawCfg); err != nil {
@ -256,9 +319,11 @@ func UnmarshalRawConfig(buf []byte) (*RawConfig, error) {
func ParseRawConfig(rawCfg *RawConfig) (*Config, error) {
config := &Config{}
log.Infoln("Start initial configuration in progress") //Segment finished in xxm
startTime := time.Now()
config.Experimental = &rawCfg.Experimental
config.Profile = &rawCfg.Profile
config.IPTables = &rawCfg.IPTables
general, err := parseGeneral(rawCfg)
if err != nil {
@ -266,7 +331,13 @@ func ParseRawConfig(rawCfg *RawConfig) (*Config, error) {
}
config.General = general
config.Tun = &rawCfg.Tun
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)
if err != nil {
@ -275,11 +346,6 @@ func ParseRawConfig(rawCfg *RawConfig) (*Config, error) {
config.Proxies = proxies
config.Providers = providers
err = parseScript(rawCfg)
if err != nil {
return nil, err
}
rules, ruleProviders, err := parseRules(rawCfg, proxies)
if err != nil {
return nil, err
@ -301,12 +367,19 @@ func ParseRawConfig(rawCfg *RawConfig) (*Config, error) {
config.Users = parseAuthentication(rawCfg.Authentication)
config.Sniffer, err = parseSniffer(rawCfg.Sniffer)
if err != nil {
return nil, err
}
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
}
func parseGeneral(cfg *RawConfig) (*General, error) {
externalUI := cfg.ExternalUI
geodata.SetLoader(cfg.GeodataLoader)
// checkout externalUI exist
if externalUI != "" {
externalUI = C.Path.Resolve(externalUI)
@ -331,27 +404,32 @@ func parseGeneral(cfg *RawConfig) (*General, error) {
ExternalUI: cfg.ExternalUI,
Secret: cfg.Secret,
},
UnifiedDelay: cfg.UnifiedDelay,
Mode: cfg.Mode,
LogLevel: cfg.LogLevel,
IPv6: cfg.IPv6,
Interface: cfg.Interface,
UnifiedDelay: cfg.UnifiedDelay,
Mode: cfg.Mode,
LogLevel: cfg.LogLevel,
IPv6: cfg.IPv6,
Interface: cfg.Interface,
RoutingMark: cfg.RoutingMark,
GeodataMode: cfg.GeodataMode,
GeodataLoader: cfg.GeodataLoader,
}, nil
}
func parseProxies(cfg *RawConfig) (proxies map[string]C.Proxy, providersMap map[string]providerTypes.ProxyProvider, err error) {
proxies = make(map[string]C.Proxy)
providersMap = make(map[string]providerTypes.ProxyProvider)
var proxyList []string
_proxiesList := list.New()
_groupsList := list.New()
proxiesConfig := cfg.Proxy
groupsConfig := cfg.ProxyGroup
providersConfig := cfg.ProxyProvider
var proxyList []string
_proxiesList := list.New()
_groupsList := list.New()
proxies["DIRECT"] = adapter.NewProxy(outbound.NewDirect())
proxies["REJECT"] = adapter.NewProxy(outbound.NewReject())
proxies["COMPATIBLE"] = adapter.NewProxy(outbound.NewCompatible())
proxies["PASS"] = adapter.NewProxy(outbound.NewPass())
proxyList = append(proxyList, "DIRECT", "REJECT")
// parse proxy
@ -415,6 +493,9 @@ func parseProxies(cfg *RawConfig) (proxies map[string]C.Proxy, providersMap map[
var ps []C.Proxy
for _, v := range proxyList {
if proxies[v].Type() == C.Pass {
continue
}
ps = append(ps, proxies[v])
}
hc := provider.NewHealthCheck(ps, "", 0, true)
@ -437,52 +518,9 @@ func parseProxies(cfg *RawConfig) (proxies map[string]C.Proxy, providersMap map[
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) {
ruleProviders := map[string]*providerTypes.RuleProvider{}
log.Infoln("Geodata Loader mode: %s", geodata.LoaderName())
// parse rule provider
for name, mapping := range cfg.RuleProvider {
rp, err := RP.ParseRuleProvider(name, mapping)
@ -512,32 +550,30 @@ func parseRules(cfg *RawConfig, proxies map[string]C.Proxy) ([]C.Rule, map[strin
continue
}
l := len(rule)
if ruleName == "NOT" || ruleName == "OR" || ruleName == "AND" {
payload = strings.Join(rule[1:len(rule)-1], ",")
target = rule[len(rule)-1]
target = rule[l-1]
payload = strings.Join(rule[1:l-1], ",")
} else {
switch l := len(rule); {
case l == 2:
target = rule[1]
case l == 3:
if ruleName == "MATCH" {
payload = ""
target = rule[1]
params = rule[2:]
break
}
payload = rule[1]
target = rule[2]
case l >= 4:
payload = rule[1]
target = rule[2]
params = rule[3:]
default:
if l < 2 {
return nil, nil, fmt.Errorf("rules[%d] [%s] error: format invalid", idx, line)
}
if l < 4 {
rule = append(rule, make([]string, 4-l)...)
}
if ruleName == "MATCH" {
l = 2
}
if l >= 3 {
l = 3
payload = rule[1]
}
target = rule[l-1]
params = rule[l:]
}
if _, ok := proxies[target]; mode != T.Script && !ok {
if _, ok := proxies[target]; !ok {
return nil, nil, fmt.Errorf("rules[%d] [%s] error: proxy [%s] not found", idx, line, target)
}
@ -545,17 +581,16 @@ func parseRules(cfg *RawConfig, proxies map[string]C.Proxy) ([]C.Rule, map[strin
if ruleName == "GEOSITE" {
if err := initGeoSite(); err != nil {
return nil, nil, fmt.Errorf("can't initial GeoSite: %w", err)
return nil, nil, fmt.Errorf("can't initial GeoSite: %s", err)
}
initMode = false
}
parsed, parseErr := R.ParseRule(ruleName, payload, target, params)
if parseErr != nil {
return nil, nil, fmt.Errorf("rules[%d] [%s] error: %s", idx, line, parseErr.Error())
}
if mode != T.Script {
rules = append(rules, parsed)
}
rules = append(rules, parsed)
}
runtime.GC()
@ -563,18 +598,18 @@ func parseRules(cfg *RawConfig, proxies map[string]C.Proxy) ([]C.Rule, map[strin
return rules, ruleProviders, nil
}
func parseHosts(cfg *RawConfig) (*trie.DomainTrie, error) {
tree := trie.New()
func parseHosts(cfg *RawConfig) (*trie.DomainTrie[netip.Addr], error) {
tree := trie.New[netip.Addr]()
// add default hosts
if err := tree.Insert("localhost", net.IP{127, 0, 0, 1}); err != nil {
if err := tree.Insert("localhost", netip.AddrFrom4([4]byte{127, 0, 0, 1})); err != nil {
log.Errorln("insert localhost to host error: %s", err.Error())
}
if len(cfg.Hosts) != 0 {
for domain, ipStr := range cfg.Hosts {
ip := net.ParseIP(ipStr)
if ip == nil {
ip, err := netip.ParseAddr(ipStr)
if err != nil {
return nil, fmt.Errorf("%s is not a valid IP", ipStr)
}
_ = tree.Insert(domain, ip)
@ -632,6 +667,9 @@ func parseNameServer(servers []string) ([]dns.NameServer, error) {
case "dhcp":
addr = u.Host
dnsNetType = "dhcp" // UDP from DHCP
case "quic":
addr, err = hostWithDefaultPort(u.Host, "784")
dnsNetType = "quic" // DNS over QUIC
default:
return nil, fmt.Errorf("DNS NameServer[%d] unsupport scheme: %s", idx, u.Scheme)
}
@ -646,6 +684,7 @@ func parseNameServer(servers []string) ([]dns.NameServer, error) {
Net: dnsNetType,
Addr: addr,
ProxyAdapter: u.Fragment,
Interface: dialer.DefaultInterface.Load(),
},
)
}
@ -687,7 +726,7 @@ func parseFallbackGeoSite(countries []string, rules []C.Rule) ([]*router.DomainM
var sites []*router.DomainMatcher
if len(countries) > 0 {
if err := initGeoSite(); err != nil {
return nil, fmt.Errorf("can't initial GeoSite: %w", err)
return nil, fmt.Errorf("can't initial GeoSite: %s", err)
}
}
@ -718,7 +757,7 @@ func parseFallbackGeoSite(countries []string, rules []C.Rule) ([]*router.DomainM
return sites, nil
}
func parseDNS(rawCfg *RawConfig, hosts *trie.DomainTrie, rules []C.Rule) (*DNS, error) {
func parseDNS(rawCfg *RawConfig, hosts *trie.DomainTrie[netip.Addr], rules []C.Rule) (*DNS, error) {
cfg := rawCfg.DNS
if cfg.Enable && len(cfg.NameServer) == 0 {
return nil, fmt.Errorf("if DNS configuration is turned on, NameServer cannot be empty")
@ -747,6 +786,10 @@ func parseDNS(rawCfg *RawConfig, hosts *trie.DomainTrie, rules []C.Rule) (*DNS,
return nil, err
}
if dnsCfg.ProxyServerNameserver, err = parseNameServer(cfg.ProxyServerNameserver); err != nil {
return nil, err
}
if len(cfg.DefaultNameserver) == 0 {
return nil, errors.New("default nameserver should have at least one nameserver")
}
@ -757,20 +800,23 @@ func parseDNS(rawCfg *RawConfig, hosts *trie.DomainTrie, rules []C.Rule) (*DNS,
for _, ns := range dnsCfg.DefaultNameserver {
host, _, err := net.SplitHostPort(ns.Addr)
if err != nil || net.ParseIP(host) == nil {
return nil, errors.New("default nameserver should be pure IP")
u, err := url.Parse(ns.Addr)
if err != nil || net.ParseIP(u.Host) == nil {
return nil, errors.New("default nameserver should be pure IP")
}
}
}
if cfg.EnhancedMode == C.DNSFakeIP {
_, ipnet, err := net.ParseCIDR(cfg.FakeIPRange)
ipnet, err := netip.ParsePrefix(cfg.FakeIPRange)
if err != nil {
return nil, err
}
var host *trie.DomainTrie
var host *trie.DomainTrie[bool]
// fake ip skip host filter
if len(cfg.FakeIPFilter) != 0 {
host = trie.New()
host = trie.New[bool]()
for _, domain := range cfg.FakeIPFilter {
_ = host.Insert(domain, true)
}
@ -778,7 +824,7 @@ func parseDNS(rawCfg *RawConfig, hosts *trie.DomainTrie, rules []C.Rule) (*DNS,
if len(dnsCfg.Fallback) != 0 {
if host == nil {
host = trie.New()
host = trie.New[bool]()
}
for _, fb := range dnsCfg.Fallback {
if net.ParseIP(fb.Addr) != nil {
@ -789,7 +835,7 @@ func parseDNS(rawCfg *RawConfig, hosts *trie.DomainTrie, rules []C.Rule) (*DNS,
}
pool, err := fakeip.New(fakeip.Options{
IPNet: ipnet,
IPNet: &ipnet,
Size: 1000,
Host: host,
Persistence: rawCfg.Profile.StoreFakeIP,
@ -823,25 +869,141 @@ func parseDNS(rawCfg *RawConfig, hosts *trie.DomainTrie, rules []C.Rule) (*DNS,
}
func parseAuthentication(rawRecords []string) []auth.AuthUser {
users := make([]auth.AuthUser, 0)
var users []auth.AuthUser
for _, line := range rawRecords {
userData := strings.SplitN(line, ":", 2)
if len(userData) == 2 {
users = append(users, auth.AuthUser{User: userData[0], Pass: userData[1]})
if user, pass, found := strings.Cut(line, ":"); found {
users = append(users, auth.AuthUser{User: user, Pass: pass})
}
}
return users
}
func cleanPyKeywords(code string) string {
if len(code) == 0 {
return code
}
keywords := []string{"import", "print"}
func parseTun(rawTun RawTun, general *General) (*Tun, error) {
if rawTun.Enable && rawTun.AutoDetectInterface {
autoDetectInterfaceName, err := commons.GetAutoDetectInterface()
if err != nil || autoDetectInterfaceName == "" {
log.Warnln("Can not find auto detect interface.[%s]", err)
} else {
log.Warnln("Auto detect interface: %s", autoDetectInterfaceName)
}
for _, kw := range keywords {
reg := regexp.MustCompile("(?m)[\r\n]+^.*" + kw + ".*$")
code = reg.ReplaceAllString(code, "")
general.Interface = autoDetectInterfaceName
}
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
}
func parseSniffer(snifferRaw SnifferRaw) (*Sniffer, error) {
sniffer := &Sniffer{
Enable: snifferRaw.Enable,
Force: snifferRaw.Force,
}
ports := []utils.Range[uint16]{}
if len(snifferRaw.Ports) == 0 {
ports = append(ports, *utils.NewRange[uint16](0, 65535))
} else {
for _, portRange := range snifferRaw.Ports {
portRaws := strings.Split(portRange, "-")
if len(portRaws) > 1 {
p, err := strconv.ParseUint(portRaws[0], 10, 16)
if err != nil {
return nil, fmt.Errorf("%s format error", portRange)
}
start := uint16(p)
p, err = strconv.ParseUint(portRaws[0], 10, 16)
if err != nil {
return nil, fmt.Errorf("%s format error", portRange)
}
end := uint16(p)
ports = append(ports, *utils.NewRange(start, end))
}
}
}
sniffer.Ports = &ports
loadSniffer := make(map[C.SnifferType]struct{})
for _, snifferName := range snifferRaw.Sniffing {
find := false
for _, snifferType := range C.SnifferList {
if snifferType.String() == strings.ToUpper(snifferName) {
find = true
loadSniffer[snifferType] = struct{}{}
}
}
if !find {
return nil, fmt.Errorf("not find the sniffer[%s]", snifferName)
}
}
for st := range loadSniffer {
sniffer.Sniffers = append(sniffer.Sniffers, st)
}
sniffer.ForceDomain = trie.New[bool]()
for _, domain := range snifferRaw.ForceDomain {
err := sniffer.ForceDomain.Insert(domain, true)
if err != nil {
return nil, fmt.Errorf("error domian[%s] in force-domain, error:%v", domain, err)
}
}
sniffer.SkipSNI = trie.New[bool]()
for _, domain := range snifferRaw.SkipSNI {
err := sniffer.SkipSNI.Insert(domain, true)
if err != nil {
return nil, fmt.Errorf("error domian[%s] in force-domain, error:%v", domain, err)
}
}
// Compatibility, remove it when release
if strings.Contains(C.Version, "alpha") || strings.Contains(C.Version, "develop") || strings.Contains(C.Version, "1.10.0") {
log.Warnln("Sniffer param force and reverses deprecated, will be removed in the release version, see https://github.com/MetaCubeX/Clash.Meta/commit/48a01adb7a4f38974b9d9639f931d0d245aebf28")
if snifferRaw.Force {
// match all domain
sniffer.ForceDomain.Insert("+", true)
for _, domain := range snifferRaw.Reverse {
err := sniffer.SkipSNI.Insert(domain, true)
if err != nil {
return nil, fmt.Errorf("error domian[%s], error:%v", domain, err)
}
}
} else {
for _, domain := range snifferRaw.Reverse {
err := sniffer.ForceDomain.Insert(domain, true)
if err != nil {
return nil, fmt.Errorf("error domian[%s], error:%v", domain, err)
}
}
}
}
return sniffer, nil
}

View File

@ -2,17 +2,20 @@ package config
import (
"fmt"
"github.com/Dreamacro/clash/component/geodata"
"github.com/Dreamacro/clash/component/mmdb"
"io"
"net/http"
"os"
"github.com/Dreamacro/clash/component/mmdb"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/log"
)
var initMode = true
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 {
return
}
@ -28,30 +31,25 @@ func downloadMMDB(path string) (err error) {
return err
}
func initMMDB() error {
if _, err := os.Stat(C.Path.MMDB()); os.IsNotExist(err) {
log.Infoln("Can't find MMDB, start download")
if err := downloadMMDB(C.Path.MMDB()); err != nil {
return fmt.Errorf("can't download MMDB: %s", err.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()
if !mmdb.Verify() {
log.Warnln("MMDB invalid, remove and download")
if err := os.Remove(C.Path.MMDB()); err != nil {
return fmt.Errorf("can't remove invalid MMDB: %s", err.Error())
}
if err := downloadMMDB(C.Path.MMDB()); err != nil {
return fmt.Errorf("can't download MMDB: %s", err.Error())
}
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 nil
return err
}
func downloadGeoSite(path string) (err error) {
resp, err := http.Get("https://cdn.jsdelivr.net/gh/Loyalsoldier/v2ray-rules-dat@release/geosite.dat")
resp, err := http.Get("https://raw.githubusercontents.com/Loyalsoldier/v2ray-rules-dat/release/geosite.dat")
if err != nil {
return
}
@ -69,12 +67,65 @@ func downloadGeoSite(path string) (err error) {
func initGeoSite() error {
if _, err := os.Stat(C.Path.GeoSite()); os.IsNotExist(err) {
log.Infoln("Need GeoSite but can't find GeoSite.dat, start download")
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) {
log.Infoln("Can't find MMDB, start download")
if err := downloadMMDB(C.Path.MMDB()); err != nil {
return fmt.Errorf("can't download MMDB: %s", err.Error())
}
}
if !mmdb.Verify() {
log.Warnln("MMDB invalid, remove and download")
if err := os.Remove(C.Path.MMDB()); err != nil {
return fmt.Errorf("can't remove invalid MMDB: %s", err.Error())
}
if err := downloadMMDB(C.Path.MMDB()); err != nil {
return fmt.Errorf("can't download MMDB: %s", err.Error())
}
}
return nil
}
@ -98,15 +149,19 @@ func Init(dir string) error {
f.Write([]byte(`mixed-port: 7890`))
f.Close()
}
//// initial GeoIP
//if err := initGeoIP(); err != nil {
// return fmt.Errorf("can't initial GeoIP: %w", err)
//}
// initial mmdb
if err := initMMDB(); err != nil {
return fmt.Errorf("can't initial MMDB: %w", err)
buf, _ := os.ReadFile(C.Path.Config())
rawCfg, err := UnmarshalRawConfig(buf)
if err != nil {
log.Errorln(err.Error())
fmt.Printf("configuration file %s test failed\n", C.Path.Config())
os.Exit(1)
}
if !C.GeodataMode {
C.GeodataMode = rawCfg.GeodataMode
}
// initial GeoIP
if err := initGeoIP(); err != nil {
return fmt.Errorf("can't initial GeoIP: %w", err)
}
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.
// Meanwhile, record the original index in the config file.
// 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 {
indegree int
// topological order
topo int
// the original data in `groupsConfig`
data map[string]interface{}
data map[string]any
// `outdegree` and `from` are used in loop locating
outdegree int
option *outboundgroup.GroupCommonOption

View File

@ -14,6 +14,14 @@ const (
Direct AdapterType = iota
Reject
Compatible
Pass
Relay
Selector
Fallback
URLTest
LoadBalance
Shadowsocks
ShadowsocksR
Snell
@ -22,12 +30,6 @@ const (
Vmess
Vless
Trojan
Relay
Selector
Fallback
URLTest
LoadBalance
)
const (
@ -131,6 +133,8 @@ func (at AdapterType) String() string {
return "Reject"
case Compatible:
return "Compatible"
case Pass:
return "Pass"
case Shadowsocks:
return "Shadowsocks"
case ShadowsocksR:

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