Compare commits

..

136 Commits

Author SHA1 Message Date
wwqgtxx
bda71dbfa1 Merge branch 'Alpha' into Meta 2023-12-03 08:44:30 +08:00
Larvan2
1a0932c210 feat: support ARC for DNS cache 2023-12-03 08:37:05 +08:00
snakem982
bc74c943b8 [fix] append tuic to proxies 2023-12-02 13:02:20 +08:00
wzdnzd
cc6429722a return expected status through Rest API and clean useless code 2023-12-01 23:16:55 +08:00
H1JK
3b57a923fd fix: Pool panic when putting small buffer 2023-12-01 23:13:14 +08:00
H1JK
7efd692bbc Revert "Revert "chore: Shrink allocator pool range""
This reverts commit 8f61b0e180.
2023-12-01 23:06:29 +08:00
wwqgtxx
d773d335a2 chore: Update quic-go to v0.40.0 2023-11-30 22:22:45 +08:00
xishang0128
78ae8815c2 chore: modify some fields 2023-11-30 21:12:30 +08:00
wwqgtxx
8f61b0e180 Revert "chore: Shrink allocator pool range"
This reverts commit 3c088b33a2.
2023-11-30 20:30:05 +08:00
wwqgtxx
a974e810c2 fix: build error 2023-11-30 20:20:45 +08:00
wwqgtxx
599ce784d2 chore: simplify fast open code 2023-11-30 20:16:55 +08:00
Larvan2
db973de7bd chore: update dependencies 2023-11-30 20:04:41 +08:00
H1JK
5f7053c519 feat: Add v2ray httpupgrade fast open support 2023-11-24 13:02:00 +08:00
wwqgtxx
84a334dd3a chore: reorder atomic TypedValue
see: https://gfw.go101.org/article/unofficial-faq.html#final-zero-size-field
2023-11-23 22:39:47 +08:00
wwqgtxx
7d15ce2b33 chore: add some warning log 2023-11-23 10:39:29 +08:00
wwqgtxx
37791acb59 chore: upgrade xsync to v3 2023-11-23 10:24:01 +08:00
wwqgtxx
96f0254a48 chore: listeners can set mux-option 2023-11-23 08:20:26 +08:00
Larvan2
8b4499e461 Revert "chore: reduce memory alloc"
This reverts commit a6b816b1c6.
2023-11-22 19:22:15 +08:00
Larvan2
6a3e28c384 chore: print colored log 2023-11-21 22:35:24 +08:00
H1JK
b05cf14b98 chore: Replace stack collection with list 2023-11-20 23:48:30 +08:00
Larvan2
a6b816b1c6 chore: reduce memory alloc 2023-11-20 23:48:30 +08:00
H1JK
bb9ad6cac0 fix: Trojan websocket header panic 2023-11-20 23:36:22 +08:00
Larvan2
b9d48f4115 fix: parsing override 2023-11-20 23:36:22 +08:00
Steve Johnson
84299606f4 chore: revert default global ua 2023-11-19 18:23:48 +08:00
Larvan2
8efb699231 chore: temporary seal 2023-11-19 13:26:53 +08:00
H1JK
3c088b33a2 chore: Shrink allocator pool range 2023-11-18 21:40:50 +08:00
H1JK
4362dfacc9 fix: Mux missing sing logger & initializing race 2023-11-18 15:30:35 +08:00
H1JK
05b9071ca6 chore: Pool allocate arrays instead of slices
This is inspired by https://go-review.googlesource.com/c/net/+/539915
2023-11-18 13:52:03 +08:00
Larvan2
117228fa8c feat: support REJECT-DROP 2023-11-18 13:17:15 +08:00
H1JK
3a3d88c668 chore: Update dependencies 2023-11-18 11:26:27 +08:00
H1JK
54a7f52fe3 feat: Add outbound sing-mux tcp-brutal support 2023-11-18 00:07:07 +08:00
H1JK
1479b449df chore: Cleanup code 2023-11-17 23:12:10 +08:00
Steve Johnson
fef5ad780d action: remove code about android branches 2023-11-17 19:49:55 +08:00
Steve Johnson
aa3c1ac623 fix: fix package name rules match 2023-11-17 19:39:57 +08:00
Steve Johnson
b5a8f0fce1 fix: improve feature check and add missing patches 2023-11-17 19:10:17 +08:00
Steve Johnson
d9cfdc3242 chore: add android feature and patch 2023-11-17 13:19:24 +08:00
Steve Johnson
b73382f60a fix: fix android-arm64 build 2023-11-17 10:53:57 +08:00
Steve Johnson
9e96d70840 feat: share more code from android branch 2023-11-17 01:21:02 +08:00
Larvan2
d28c3b50e3 ci: push to ghcr.io instead 2023-11-17 00:39:36 +08:00
Larvan2
2f203330e4 feat: add override to proxy-providers
Co-authored-by: xishang0128 <xishang02@gmail.com>
2023-11-17 00:37:54 +08:00
Larvan2
7d222b1b71 fix: health check available for 'selector' if configured 2023-11-15 19:06:20 +08:00
Skyxim
d85d8ac13f fix: only force health check compatible providers 2023-11-13 08:06:51 +00:00
Skyxim
7979eb654f fix: health check at startup 2023-11-13 15:42:31 +08:00
xishang0128
2577dd3af4 chore: fix subscription_info 2023-11-12 03:17:37 +08:00
xishang0128
daa332e7b0 chore: modify ua 2023-11-12 02:44:55 +08:00
xishang0128
288c0c27d6 feat: add include-all-providers to proxy-groups 2023-11-11 22:15:57 +08:00
wwqgtxx
832dae3421 chore: direct append data to bufio.Reader's internal buffer as much as possible 2023-11-09 22:19:29 +08:00
wwqgtxx
fe7c1a2cdb chore: using wk8/go-ordered-map/v2 replace internal StringMapSlice 2023-11-09 08:47:44 +08:00
wwqgtxx
e8e4288d85 action: test_author.yml 2023-11-08 23:05:27 +08:00
Larvan2
6901afb406 ci: fix android build 2023-11-08 22:15:29 +08:00
wwqgtxx
f260d8cf01 chore: share dnsClient in NewResolver 2023-11-08 20:19:48 +08:00
wwqgtxx
575c1d4129 chore: NameServerPolicy will match inorder 2023-11-08 19:29:26 +08:00
wwqgtxx
253b023442 Merge branch 'Alpha' into Meta 2023-11-03 21:59:44 +08:00
wwqgtxx
17c9d507be chore: hello mihomo 2023-11-03 21:58:21 +08:00
wwqgtxx
8c3557e96b chore: support v2ray http upgrade server too 2023-11-03 13:58:53 +08:00
wwqgtxx
228990472d fix: avoid tls panic 2023-11-03 12:04:22 +08:00
wwqgtxx
09e7866a5c fix: gvisor panic 2023-11-03 11:50:25 +08:00
wwqgtxx
665ba7f9f1 chore: do websocket client upgrade directly instead of gobwas/ws 2023-11-03 11:50:25 +08:00
wwqgtxx
ee3038d5e4 chore: add SetupContextForConn for common/net 2023-11-03 11:50:25 +08:00
wwqgtxx
885ee7a820 fix: v2ray http upgrade Hosts header not working 2023-11-03 11:50:25 +08:00
Steve Johnson
ef303b11f2 action: trigger CMFA PR update in every commit 2023-11-02 16:01:35 +08:00
wwqgtxx
a82ce85707 chore: add route exclude support 2023-11-02 11:37:40 +08:00
wwqgtxx
5bfe7ba169 chore: better tls handshake 2023-11-02 11:22:01 +08:00
wwqgtxx
ceac5bfaa4 feat: add v2ray-http-upgrade support 2023-11-02 11:11:35 +08:00
wwqgtxx
b0638cfc49 chore: better bufio.Reader warp 2023-11-02 11:11:35 +08:00
Skyxim
96220aa8ea feat: cancel RULE-SET nested SUB-RULE restrictions 2023-10-31 11:10:38 +00:00
HolgerHuo
8ff476a3a1 fix: remote logic rules cannot be parsed (#837) 2023-10-31 19:07:01 +08:00
Steve Johnson
261b6e8dce action: small fix to cmfa core-update trigger 2023-10-30 20:00:15 +08:00
xishang0128
2b9141e0e5 chore: geo link replaced with github 2023-10-30 19:46:56 +08:00
xishang0128
55255faa52 chore: modify configuration fields 2023-10-27 17:49:12 +08:00
Steve Johnson
d42e3f74ad action: add question issue guidance 2023-10-26 19:08:42 +08:00
wwqgtxx
81a8a63861 build: more go120 build 2023-10-26 11:39:54 +08:00
wwqgtxx
c3a61e2db5 build: add go120 build for win7/8.1 2023-10-26 11:09:19 +08:00
wwqgtxx
bffe47a974 chore: netip.Prefix should not using pointer 2023-10-26 11:02:53 +08:00
wwqgtxx
4314b37d04 fix: dhcp not working on windows 2023-10-26 10:27:38 +08:00
wwqgtxx
cf93f69f40 chore: cleanup error using of dialer.DefaultInterface 2023-10-26 09:07:49 +08:00
wwqgtxx
55f626424f chore: better dns batchExchange 2023-10-25 20:16:44 +08:00
wwqgtxx
431d52f250 chore: system resolver can autoupdate 2023-10-25 19:21:20 +08:00
wwqgtxx
c1f24d8f0e chore: code cleanup 2023-10-25 18:07:45 +08:00
Steve Johnson
fc5a3cf80c action: ban black issues 2023-10-25 18:06:10 +08:00
wwqgtxx
e1e999180a chore: inMemoryAuthenticator unneed sync map 2023-10-24 21:25:03 +08:00
wwqgtxx
8755618910 fix: reality panic 2023-10-23 23:33:59 +08:00
Steve Johnson
aede97571f Merge branch 'Alpha' of https://github.com/MetaCubeX/Clash.Meta into Alpha 2023-10-23 17:02:08 +08:00
Steve Johnson
01bc84db02 chore: add labels to issue template 2023-10-23 17:02:04 +08:00
wwqgtxx
3564e96a00 chore: share some code 2023-10-23 16:45:22 +08:00
wwqgtxx
f6f8f27668 action: update sync 2023-10-23 15:39:56 +08:00
Steve Johnson
dff54464c6 Add auto sync Alpha rebase android-open -> android-real (#817)
* chore: add android branch auto sync

* chore: fix

* chore: fix missing

* chore: fix actions

* chore: write branch auto sync
2023-10-23 15:39:56 +08:00
Steve Johnson
e987cdaaae chore: add CMFA auto update-dependencies trigger 2023-10-23 15:39:56 +08:00
wwqgtxx
6cd0e58fd0 fix: ssr panic 2023-10-23 15:39:56 +08:00
wwqgtxx
f794c090a5 chore: update sing-tun 2023-10-23 15:39:56 +08:00
xishang0128
0d3197e437 chore: fix sniffer log error 2023-10-20 22:36:29 +08:00
wwqgtxx
150bf7fc65 chore: decrease memory copy in sing listener 2023-10-20 08:39:04 +08:00
Larvan2
51004b14d9 docs: update readme.md 2023-10-20 00:34:10 +08:00
wwqgtxx
ea7e15b447 chore: decrease memory copy in quic sniffer 2023-10-19 23:51:37 +08:00
wwqgtxx
8e637a2ec7 chore: code cleanup 2023-10-19 20:44:49 +08:00
Larvan2
96d886380a Merge pull request #810 from 5aaee9/Alpha
feat: add quic sniffer
2023-10-19 19:34:45 +08:00
5aaee9
981c69040f docs: update about quic sniffer 2023-10-19 19:09:13 +08:00
5aaee9
de90c276af feat(sniffer): add quic sniffer 2023-10-19 18:30:20 +08:00
wwqgtxx
0129a8579f chore: merge some quic-go fix 2023-10-19 11:08:14 +08:00
wwqgtxx
11ed4a56bd chore: code cleanup 2023-10-17 12:46:41 +08:00
wwqgtxx
d75a0e69a0 chore: Update dependencies 2023-10-16 09:56:41 +08:00
Dreamacro
1faad73381 fix: socks5 udp associate 2023-10-16 09:27:55 +08:00
septs
d2499cd69d feature: add xdg base support (#2913) 2023-10-16 09:23:31 +08:00
septs
98df77439c feature: add environs startup option support (#2909) 2023-10-16 09:22:16 +08:00
Jiahao Lu
81bbbe4eec fix: DNS NCACHE TTL and OPT RRs (#2900)
* Fix: DNS NCACHE TTL and OPT RRs

1. DNS NCACHE was not correctly implemented.
2. OPT RRs must not be cached or forwarded.

Closes #2889.
2023-10-16 09:21:06 +08:00
sduoduo233
9f530525d7 fix: method in vmess http-opts is not used 2023-10-16 09:16:36 +08:00
wwqgtxx
129283066f chore: code cleanup 2023-10-11 22:54:19 +08:00
wwqgtxx
0dc6a726c1 fix: unmap 4in6 ip 2023-10-11 18:17:39 +08:00
wwqgtxx
4636499439 chore: support reject proxy type 2023-10-11 13:01:14 +08:00
wwqgtxx
9a16eb2895 fix: BBR memory leak
from: 7c46e845a6
2023-10-11 11:01:17 +08:00
wwqgtxx
270a080b55 fix: sing listener panic 2023-10-11 10:55:12 +08:00
wwqgtxx
1cf9a55e3e chore: code cleanup 2023-10-10 21:29:12 +08:00
wwqgtxx
6bcd91a801 feat: add skip-auth-prefixes 2023-10-10 21:29:12 +08:00
wwqgtxx
7ed25ddc74 chore: better atomic using 2023-10-10 21:28:46 +08:00
wwqgtxx
ae557c30d3 fix: quic-go min MTU 2023-10-08 13:15:17 +08:00
wwqgtxx
5a1800d642 fix: BBR bandwidth estimation edge case
from 89429598bf
2023-10-08 07:26:28 +08:00
wwqgtxx
d8fe7a52d6 feat: add certificate and private-key to vmess listener 2023-10-08 07:26:28 +08:00
wwqgtxx
791ecfbb32 feat: add ws-path to vmess listener 2023-10-08 07:26:28 +08:00
wwqgtxx
5ff4473083 chore: migrate from gorilla/websocket to gobwas/ws 2023-10-06 17:44:36 +08:00
wwqgtxx
d1e88a30cb fix: gVisor UDP 6to4 check 2023-10-03 16:00:03 +08:00
wwqgtxx
7eae7756f5 chore: update gvisor 2023-10-01 19:15:26 +08:00
wwqgtxx
4e3cd01aad chore: merge some quic-go fix 2023-10-01 13:44:56 +08:00
Larvan2
dbaee284e4 fix: hy2/tuic inbound cert isn't path
Co-authored-by: wwqgtxx <wwqgtxx@gmail.com>
2023-10-01 12:04:34 +08:00
wwqgtxx
8253bfe2e0 add quic-go-disable-ecn to experimental 2023-10-01 09:10:11 +08:00
wwqgtxx
828b5ad8bb chore: add new bbr implementation 2023-10-01 00:01:32 +08:00
Larvan2
fedad26c13 chore: support relative path for hy2/tuic inbound cert 2023-10-01 00:01:32 +08:00
wwqgtxx
a526bb70ea chore: fix bbr bugs 2023-09-30 13:40:07 +08:00
yaling888
5f6de610e1 Fix: should check all ips need to fallback (#2915) 2023-09-29 13:42:22 +08:00
Larvan2
02397868fc docs: support reload in service 2023-09-29 13:26:59 +08:00
Kiva
265a6b9b68 chore: reduce string split immediately after string concat (#773) 2023-09-29 08:51:13 +08:00
NyaMisty
10e7c533d7 feat: support clash premium's structured log stream (#735)
* feat: support clash premium's structured log stream

New version of Clash for Windows uses `ws://external-controller/logs?token=&level=info&format=structured` to get real time log. When Clash Premium Core reveices `format=structured`, it returns a different form of JSON log entry. Supporting this feature will allow better Clash for Windows integration

Signed-off-by: Misty <gyc990326@gmail.com>
2023-09-29 08:50:50 +08:00
septs
0ed3c5a5ec chore: improve subscription userinfo parsing (#781)
do not use regex parsing for `Subscription-UserInfo` header field
2023-09-29 08:42:57 +08:00
Andrei Shevchuk
c2b06a02bf feat: add reload signal support (#780)
Backport Clash feature by @septs, see Dreamacro/clash#2908
2023-09-29 08:36:25 +08:00
wwqgtxx
e0458a8fde chore: decrease goroutine used in core tunnel 2023-09-28 18:59:31 +08:00
wwqgtxx
21fb5f75b8 fix: gvisor panic 2023-09-26 09:06:00 +08:00
wwqgtxx
fb99412193 chore: update quic-go to 0.39.0 2023-09-26 08:51:25 +08:00
395 changed files with 7930 additions and 4021 deletions

View File

@@ -1,6 +1,7 @@
name: Bug report name: Bug report
description: Create a report to help us improve description: Create a report to help us improve
title: "[Bug] " title: "[Bug] "
labels: ["bug"]
body: body:
- type: checkboxes - type: checkboxes
id: ensure id: ensure
@@ -12,8 +13,8 @@ Please verify that you've followed these steps
" "
options: options:
- label: " - label: "
确保你使用的是**本仓库**最新的的 clash 或 clash Alpha 版本 确保你使用的是**本仓库**最新的的 mihomo 或 mihomo Alpha 版本
Ensure you are using the latest version of Clash or Clash Premium from **this repository**. Ensure you are using the latest version of Mihomo or Mihomo Alpha from **this repository**.
" "
required: true required: true
- label: " - label: "
@@ -37,14 +38,14 @@ I have read the [documentation](https://wiki.metacubex.one/) and was unable to s
" "
required: true required: true
- label: " - label: "
这是 Clash 核心的问题,并非我所使用的 Clash 衍生版本(如 OpenClash、KoolClash 等)的特定问题 这是 Mihomo 核心的问题,并非我所使用的 Mihomo 衍生版本(如 OpenMihomo、KoolMihomo 等)的特定问题
This is an issue of the Clash core *per se*, not to the derivatives of Clash, like OpenClash or KoolClash. This is an issue of the Mihomo core *per se*, not to the derivatives of Mihomo, like OpenMihomo or KoolMihomo.
" "
required: true required: true
- type: input - type: input
attributes: attributes:
label: Clash version label: Mihomo version
description: "use `clash -v`" description: "use `mihomo -v`"
validations: validations:
required: true required: true
- type: dropdown - type: dropdown
@@ -60,23 +61,23 @@ This is an issue of the Clash core *per se*, not to the derivatives of Clash, li
- type: textarea - type: textarea
attributes: attributes:
render: yaml render: yaml
label: "Clash config" label: "Mihomo config"
description: " description: "
在下方附上 Clash core 配置文件,请确保配置文件中没有敏感信息(比如:服务器地址,密码,端口等) 在下方附上 Mihomo core 配置文件,请确保配置文件中没有敏感信息(比如:服务器地址,密码,端口等)
Paste the Clash core configuration file below, please make sure that there is no sensitive information in the configuration file (e.g., server address/url, password, port) Paste the Mihomo core configuration file below, please make sure that there is no sensitive information in the configuration file (e.g., server address/url, password, port)
" "
validations: validations:
required: true required: true
- type: textarea - type: textarea
attributes: attributes:
render: shell render: shell
label: Clash log label: Mihomo log
description: " description: "
在下方附上 Clash Core 的日志log level 使用 DEBUG 在下方附上 Mihomo Core 的日志log level 使用 DEBUG
Paste the Clash core log below with the log level set to `DEBUG`. Paste the Mihomo core log below with the log level set to `DEBUG`.
" "
- type: textarea - type: textarea
attributes: attributes:
label: Description label: Description
validations: validations:
required: true required: true

5
.github/ISSUE_TEMPLATE/config.yml vendored Normal file
View File

@@ -0,0 +1,5 @@
blank_issues_enabled: false
contact_links:
- name: mihomo Community Support
url: https://github.com/MetaCubeX/mihomo/discussions
about: Please ask and answer questions about mihomo here.

View File

@@ -1,6 +1,7 @@
name: Feature request name: Feature request
description: Suggest an idea for this project description: Suggest an idea for this project
title: "[Feature] " title: "[Feature] "
labels: ["enhancement"]
body: body:
- type: checkboxes - type: checkboxes
id: ensure id: ensure
@@ -24,7 +25,7 @@ I have read the [documentation](https://wiki.metacubex.one/) and was unable to s
- type: textarea - type: textarea
attributes: attributes:
label: Description label: Description
description: 请详细、清晰地表达你要提出的论述,例如这个问题如何影响到你?你想实现什么功能?目前 Clash Core 的行为是什麽? description: 请详细、清晰地表达你要提出的论述,例如这个问题如何影响到你?你想实现什么功能?目前 Mihomo Core 的行为是什麽?
validations: validations:
required: true required: true
- type: textarea - type: textarea

View File

@@ -1,32 +0,0 @@
#!/bin/bash
while getopts "v:" opt; do
case $opt in
v)
version_range=$OPTARG
;;
\?)
echo "Invalid option: -$OPTARG" >&2
exit 1
;;
esac
done
if [ -z "$version_range" ]; then
echo "Please provide the version range using -v option. Example: ./genReleashNote.sh -v v1.14.1...v1.14.2"
exit 1
fi
echo "## What's Changed" > release.md
git log --pretty=format:"* %s by @%an" --grep="^feat" -i $version_range | sort -f | uniq >> release.md
echo "" >> release.md
echo "## BUG & Fix" >> release.md
git log --pretty=format:"* %s by @%an" --grep="^fix" -i $version_range | sort -f | uniq >> release.md
echo "" >> release.md
echo "## Maintenance" >> release.md
git log --pretty=format:"* %s by @%an" --grep="^chore\|^docs\|^refactor" -i $version_range | sort -f | uniq >> release.md
echo "" >> release.md
echo "**Full Changelog**: https://github.com/MetaCubeX/Clash.Meta/compare/$version_range" >> release.md

26
.github/rename-cgo.sh vendored
View File

@@ -5,25 +5,25 @@ for FILENAME in $FILENAMES
do do
if [[ $FILENAME =~ "darwin-10.16-arm64" ]];then if [[ $FILENAME =~ "darwin-10.16-arm64" ]];then
echo "rename darwin-10.16-arm64 $FILENAME" echo "rename darwin-10.16-arm64 $FILENAME"
mv $FILENAME clash.meta-darwin-arm64-cgo mv $FILENAME mihomo-darwin-arm64-cgo
elif [[ $FILENAME =~ "darwin-10.16-amd64" ]];then elif [[ $FILENAME =~ "darwin-10.16-amd64" ]];then
echo "rename darwin-10.16-amd64 $FILENAME" echo "rename darwin-10.16-amd64 $FILENAME"
mv $FILENAME clash.meta-darwin-amd64-cgo mv $FILENAME mihomo-darwin-amd64-cgo
elif [[ $FILENAME =~ "windows-4.0-386" ]];then elif [[ $FILENAME =~ "windows-4.0-386" ]];then
echo "rename windows 386 $FILENAME" echo "rename windows 386 $FILENAME"
mv $FILENAME clash.meta-windows-386-cgo.exe mv $FILENAME mihomo-windows-386-cgo.exe
elif [[ $FILENAME =~ "windows-4.0-amd64" ]];then elif [[ $FILENAME =~ "windows-4.0-amd64" ]];then
echo "rename windows amd64 $FILENAME" echo "rename windows amd64 $FILENAME"
mv $FILENAME clash.meta-windows-amd64-cgo.exe mv $FILENAME mihomo-windows-amd64-cgo.exe
elif [[ $FILENAME =~ "clash.meta-linux-arm-5" ]];then elif [[ $FILENAME =~ "mihomo-linux-arm-5" ]];then
echo "rename clash.meta-linux-arm-5 $FILENAME" echo "rename mihomo-linux-arm-5 $FILENAME"
mv $FILENAME clash.meta-linux-armv5-cgo mv $FILENAME mihomo-linux-armv5-cgo
elif [[ $FILENAME =~ "clash.meta-linux-arm-6" ]];then elif [[ $FILENAME =~ "mihomo-linux-arm-6" ]];then
echo "rename clash.meta-linux-arm-6 $FILENAME" echo "rename mihomo-linux-arm-6 $FILENAME"
mv $FILENAME clash.meta-linux-armv6-cgo mv $FILENAME mihomo-linux-armv6-cgo
elif [[ $FILENAME =~ "clash.meta-linux-arm-7" ]];then elif [[ $FILENAME =~ "mihomo-linux-arm-7" ]];then
echo "rename clash.meta-linux-arm-7 $FILENAME" echo "rename mihomo-linux-arm-7 $FILENAME"
mv $FILENAME clash.meta-linux-armv7-cgo mv $FILENAME mihomo-linux-armv7-cgo
elif [[ $FILENAME =~ "linux" ]];then elif [[ $FILENAME =~ "linux" ]];then
echo "rename linux $FILENAME" echo "rename linux $FILENAME"
mv $FILENAME $FILENAME-cgo mv $FILENAME $FILENAME-cgo

12
.github/rename-go120.sh vendored Normal file
View File

@@ -0,0 +1,12 @@
#!/bin/bash
FILENAMES=$(ls)
for FILENAME in $FILENAMES
do
if [[ ! ($FILENAME =~ ".exe" || $FILENAME =~ ".sh")]];then
mv $FILENAME ${FILENAME}-go120
elif [[ $FILENAME =~ ".exe" ]];then
mv $FILENAME ${FILENAME%.*}-go120.exe
else echo "skip $FILENAME"
fi
done

View File

@@ -1,7 +1,8 @@
name: Delete old workflow runs name: Delete old workflow runs
on: on:
schedule: schedule:
- cron: "0 0 * * SUN" - cron: '0 0 1 * *'
# Run monthly, at 00:00 on the 1st day of month.
jobs: jobs:
del_runs: del_runs:

View File

@@ -19,7 +19,7 @@ concurrency:
cancel-in-progress: true cancel-in-progress: true
env: env:
REGISTRY: docker.io REGISTRY: ghcr.io
jobs: jobs:
Build: Build:
permissions: write-all permissions: write-all
@@ -69,6 +69,12 @@ jobs:
target: "darwin-amd64 darwin-arm64 android-arm64", target: "darwin-amd64 darwin-arm64 android-arm64",
id: "9", id: "9",
} }
# only for test
- { type: "WithoutCGO-GO120", target: "linux-amd64 linux-amd64-compatible",id: "1" }
# Go 1.20 is the last release that will run on any release of Windows 7, 8, Server 2008 and Server 2012. Go 1.21 will require at least Windows 10 or Server 2016.
- { type: "WithoutCGO-GO120", target: "windows-amd64-compatible windows-amd64 windows-386",id: "2" }
# Go 1.20 is the last release that will run on macOS 10.13 High Sierra or 10.14 Mojave. Go 1.21 will require macOS 10.15 Catalina or later.
- { type: "WithoutCGO-GO120", target: "darwin-amd64 darwin-arm64 android-arm64",id: "3" }
- { type: "WithCGO", target: "windows/*", id: "1" } - { type: "WithCGO", target: "windows/*", id: "1" }
- { type: "WithCGO", target: "linux/386", id: "2" } - { type: "WithCGO", target: "linux/386", id: "2" }
- { type: "WithCGO", target: "linux/amd64", id: "3" } - { type: "WithCGO", target: "linux/amd64", id: "3" }
@@ -94,6 +100,11 @@ jobs:
run: echo "VERSION=alpha-$(git rev-parse --short HEAD)" >> $GITHUB_ENV run: echo "VERSION=alpha-$(git rev-parse --short HEAD)" >> $GITHUB_ENV
shell: bash shell: bash
- name: Set variables
if: ${{github.ref_name=='Beta'}}
run: echo "VERSION=beta-$(git rev-parse --short HEAD)" >> $GITHUB_ENV
shell: bash
- name: Set variables - name: Set variables
if: ${{github.ref_name=='Meta'}} if: ${{github.ref_name=='Meta'}}
run: echo "VERSION=meta-$(git rev-parse --short HEAD)" >> $GITHUB_ENV run: echo "VERSION=meta-$(git rev-parse --short HEAD)" >> $GITHUB_ENV
@@ -107,7 +118,7 @@ jobs:
- name: Set ENV - name: Set ENV
run: | run: |
sudo timedatectl set-timezone "Asia/Shanghai" sudo timedatectl set-timezone "Asia/Shanghai"
echo "NAME=clash.meta" >> $GITHUB_ENV echo "NAME=mihomo" >> $GITHUB_ENV
echo "REPO=${{ github.repository }}" >> $GITHUB_ENV echo "REPO=${{ github.repository }}" >> $GITHUB_ENV
echo "ShortSHA=$(git rev-parse --short ${{ github.sha }})" >> $GITHUB_ENV echo "ShortSHA=$(git rev-parse --short ${{ github.sha }})" >> $GITHUB_ENV
echo "BUILDTIME=$(date)" >> $GITHUB_ENV echo "BUILDTIME=$(date)" >> $GITHUB_ENV
@@ -117,24 +128,32 @@ jobs:
- name: Set ENV - name: Set ENV
run: | run: |
echo "TAGS=with_gvisor,with_lwip" >> $GITHUB_ENV echo "TAGS=with_gvisor,with_lwip" >> $GITHUB_ENV
echo "LDFLAGS=-X 'github.com/Dreamacro/clash/constant.Version=${VERSION}' -X 'github.com/Dreamacro/clash/constant.BuildTime=${BUILDTIME}' -w -s -buildid=" >> $GITHUB_ENV echo "LDFLAGS=-X 'github.com/metacubex/mihomo/constant.Version=${VERSION}' -X 'github.com/metacubex/mihomo/constant.BuildTime=${BUILDTIME}' -w -s -buildid=" >> $GITHUB_ENV
shell: bash shell: bash
- name: Setup Go - name: Setup Go
if: ${{ matrix.job.type!='WithoutCGO-GO120' }}
uses: actions/setup-go@v4 uses: actions/setup-go@v4
with: with:
go-version: "1.21" go-version: "1.21"
check-latest: true check-latest: true
- name: Setup Go
if: ${{ matrix.job.type=='WithoutCGO-GO120' }}
uses: actions/setup-go@v4
with:
go-version: "1.20"
check-latest: true
- name: Test - name: Test
if: ${{ matrix.job.id=='1' && matrix.job.type=='WithoutCGO' }} if: ${{ matrix.job.id=='1' && matrix.job.type!='WithCGO' }}
run: | run: |
go test ./... go test ./...
- name: Build WithoutCGO - name: Build WithoutCGO
if: ${{ matrix.job.type=='WithoutCGO' }} if: ${{ matrix.job.type!='WithCGO' }}
env: env:
NAME: Clash.Meta NAME: mihomo
BINDIR: bin BINDIR: bin
run: make -j$(($(nproc) + 1)) ${{ matrix.job.target }} run: make -j$(($(nproc) + 1)) ${{ matrix.job.target }}
@@ -142,9 +161,8 @@ jobs:
if: ${{ matrix.job.type=='WithCGO' && matrix.job.target=='android' }} if: ${{ matrix.job.type=='WithCGO' && matrix.job.target=='android' }}
id: setup-ndk id: setup-ndk
with: with:
ndk-version: r26 ndk-version: r26b
add-to-path: false add-to-path: true
local-cache: true
- name: Build Android - name: Build Android
if: ${{ matrix.job.type=='WithCGO' && matrix.job.target=='android' }} if: ${{ matrix.job.type=='WithCGO' && matrix.job.target=='android' }}
@@ -180,6 +198,17 @@ jobs:
ls -la ls -la
cd .. cd ..
- name: Rename
if: ${{ matrix.job.type=='WithoutCGO-GO120' }}
run: |
cd bin
ls -la
cp ../.github/rename-go120.sh ./
bash ./rename-go120.sh
rm ./rename-go120.sh
ls -la
cd ..
- name: Zip - name: Zip
if: ${{ success() }} if: ${{ success() }}
run: | run: |
@@ -241,7 +270,7 @@ jobs:
Release created at ${{ env.BUILDTIME }} Release created at ${{ env.BUILDTIME }}
Synchronize ${{ github.ref_name }} branch code updates, keeping only the latest version Synchronize ${{ github.ref_name }} branch code updates, keeping only the latest version
<br> <br>
[我应该下载哪个文件? / Which file should I download?](https://github.com/MetaCubeX/Clash.Meta/wiki/FAQ) [我应该下载哪个文件? / Which file should I download?](https://github.com/MetaCubeX/mihomo/wiki/FAQ)
[查看文档 / Docs](https://metacubex.github.io/Meta-Docs/) [查看文档 / Docs](https://metacubex.github.io/Meta-Docs/)
EOF EOF
@@ -262,23 +291,6 @@ jobs:
needs: [Build] needs: [Build]
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Get tags
run: |
echo "CURRENTVERSION=${GITHUB_REF#refs/tags/}" >> $GITHUB_ENV
git fetch --tags
echo "PREVERSION=$(git describe --tags --abbrev=0 HEAD^)" >> $GITHUB_ENV
- name: Generate release notes
run: |
cp ./.github/genReleaseNote.sh ./
bash ./genReleaseNote.sh -v ${PREVERSION}...${CURRENTVERSION}
rm ./genReleaseNote.sh
- uses: actions/download-artifact@v3 - uses: actions/download-artifact@v3
with: with:
name: artifact name: artifact
@@ -295,7 +307,6 @@ jobs:
tag_name: ${{ github.ref_name }} tag_name: ${{ github.ref_name }}
files: bin/* files: bin/*
generate_release_notes: true generate_release_notes: true
body_path: release.md
Docker: Docker:
if: ${{ github.event_name != 'pull_request' }} if: ${{ github.event_name != 'pull_request' }}
@@ -331,24 +342,25 @@ jobs:
id: meta id: meta
uses: docker/metadata-action@v4 uses: docker/metadata-action@v4
with: with:
images: ${{ env.REGISTRY }}/${{ secrets.DOCKERHUB_ACCOUNT }}/${{secrets.DOCKERHUB_REPO}} images: ${{ env.REGISTRY }}/${{ github.repository }}
- name: Show files - name: Show files
run: | run: |
ls . ls .
ls bin/ ls bin/
- name: Log into registry
if: github.event_name != 'pull_request' - name: login to ghcr.io
uses: docker/login-action@v2 uses: docker/login-action@v3
with: with:
registry: ${{ env.REGISTRY }} registry: ghcr.io
username: ${{ secrets.DOCKER_HUB_USER }} username: ${{ github.actor }}
password: ${{ secrets.DOCKER_HUB_TOKEN }} password: ${{ secrets.GITHUB_TOKEN }}
# Build and push Docker image with Buildx (don't push on PR) # Build and push Docker image with Buildx (don't push on PR)
# https://github.com/docker/build-push-action # https://github.com/docker/build-push-action
- name: Build and push Docker image - name: Build and push Docker image
id: build-and-push id: build-and-push
uses: docker/build-push-action@v4 uses: docker/build-push-action@v5
with: with:
context: . context: .
file: ./Dockerfile file: ./Dockerfile
@@ -356,8 +368,7 @@ jobs:
platforms: | platforms: |
linux/386 linux/386
linux/amd64 linux/amd64
linux/arm64/v8 linux/arm64
linux/arm/v7 linux/arm/v7
# linux/riscv64
tags: ${{ steps.meta.outputs.tags }} tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }} labels: ${{ steps.meta.outputs.labels }}

View File

@@ -0,0 +1,33 @@
name: Trigger CMFA Update
on:
workflow_dispatch:
push:
paths-ignore:
- "docs/**"
- "README.md"
- ".github/ISSUE_TEMPLATE/**"
branches:
- Alpha
tags:
- "v*"
pull_request_target:
branches:
- Alpha
jobs:
# Send "core-updated" to MetaCubeX/MihomoForAndroid to trigger update-dependencies
trigger-CMFA-update:
runs-on: ubuntu-latest
steps:
- uses: tibdex/github-app-token@v1
id: generate-token
with:
app_id: ${{ secrets.MAINTAINER_APPID }}
private_key: ${{ secrets.MAINTAINER_APP_PRIVATE_KEY }}
- name: Trigger update-dependencies
run: |
curl -X POST https://api.github.com/repos/MetaCubeX/MihomoForAndroid/dispatches \
-H "Accept: application/vnd.github.everest-preview+json" \
-H "Authorization: token ${{ steps.generate-token.outputs.token }}" \
-d '{"event_type": "core-updated"}'

View File

@@ -11,7 +11,7 @@ linters-settings:
custom-order: true custom-order: true
sections: sections:
- standard - standard
- prefix(github.com/Dreamacro/clash) - prefix(github.com/metacubex/mihomo)
- default - default
staticcheck: staticcheck:
go: '1.19' go: '1.19'

View File

@@ -3,25 +3,25 @@ ARG TARGETPLATFORM
RUN echo "I'm building for $TARGETPLATFORM" RUN echo "I'm building for $TARGETPLATFORM"
RUN apk add --no-cache gzip && \ RUN apk add --no-cache gzip && \
mkdir /clash-config && \ mkdir /mihomo-config && \
wget -O /clash-config/geoip.metadb https://fastly.jsdelivr.net/gh/MetaCubeX/meta-rules-dat@release/geoip.metadb && \ wget -O /mihomo-config/geoip.metadb https://fastly.jsdelivr.net/gh/MetaCubeX/meta-rules-dat@release/geoip.metadb && \
wget -O /clash-config/geosite.dat https://fastly.jsdelivr.net/gh/MetaCubeX/meta-rules-dat@release/geosite.dat && \ wget -O /mihomo-config/geosite.dat https://fastly.jsdelivr.net/gh/MetaCubeX/meta-rules-dat@release/geosite.dat && \
wget -O /clash-config/geoip.dat https://fastly.jsdelivr.net/gh/MetaCubeX/meta-rules-dat@release/geoip.dat wget -O /mihomo-config/geoip.dat https://fastly.jsdelivr.net/gh/MetaCubeX/meta-rules-dat@release/geoip.dat
COPY docker/file-name.sh /clash/file-name.sh COPY docker/file-name.sh /mihomo/file-name.sh
WORKDIR /clash WORKDIR /mihomo
COPY bin/ bin/ COPY bin/ bin/
RUN FILE_NAME=`sh file-name.sh` && echo $FILE_NAME && \ RUN FILE_NAME=`sh file-name.sh` && echo $FILE_NAME && \
FILE_NAME=`ls bin/ | egrep "$FILE_NAME.*"|awk NR==1` && echo $FILE_NAME && \ FILE_NAME=`ls bin/ | egrep "$FILE_NAME.*"|awk NR==1` && echo $FILE_NAME && \
mv bin/$FILE_NAME clash.gz && gzip -d clash.gz && echo "$FILE_NAME" > /clash-config/test mv bin/$FILE_NAME mihomo.gz && gzip -d mihomo.gz && echo "$FILE_NAME" > /mihomo-config/test
FROM alpine:latest FROM alpine:latest
LABEL org.opencontainers.image.source="https://github.com/MetaCubeX/Clash.Meta" LABEL org.opencontainers.image.source="https://github.com/MetaCubeX/mihomo"
RUN apk add --no-cache ca-certificates tzdata iptables RUN apk add --no-cache ca-certificates tzdata iptables
VOLUME ["/root/.config/clash/"] VOLUME ["/root/.config/mihomo/"]
COPY --from=builder /clash-config/ /root/.config/clash/ COPY --from=builder /mihomo-config/ /root/.config/mihomo/
COPY --from=builder /clash/clash /clash COPY --from=builder /mihomo/mihomo /mihomo
RUN chmod +x /clash RUN chmod +x /mihomo
ENTRYPOINT [ "/clash" ] ENTRYPOINT [ "/mihomo" ]

View File

@@ -1,4 +1,4 @@
NAME=clash.meta NAME=mihomo
BINDIR=bin BINDIR=bin
BRANCH=$(shell git branch --show-current) BRANCH=$(shell git branch --show-current)
ifeq ($(BRANCH),Alpha) ifeq ($(BRANCH),Alpha)
@@ -12,8 +12,8 @@ VERSION=$(shell git rev-parse --short HEAD)
endif endif
BUILDTIME=$(shell date -u) BUILDTIME=$(shell date -u)
GOBUILD=CGO_ENABLED=0 go build -tags with_gvisor -trimpath -ldflags '-X "github.com/Dreamacro/clash/constant.Version=$(VERSION)" \ GOBUILD=CGO_ENABLED=0 go build -tags with_gvisor -trimpath -ldflags '-X "github.com/metacubex/mihomo/constant.Version=$(VERSION)" \
-X "github.com/Dreamacro/clash/constant.BuildTime=$(BUILDTIME)" \ -X "github.com/metacubex/mihomo/constant.BuildTime=$(BUILDTIME)" \
-w -s -buildid=' -w -s -buildid='
PLATFORM_LIST = \ PLATFORM_LIST = \

324
README.md
View File

@@ -3,17 +3,17 @@
<br>Meta Kernel<br> <br>Meta Kernel<br>
</h1> </h1>
<h3 align="center">Another Clash Kernel.</h3> <h3 align="center">Another Mihomo Kernel.</h3>
<p align="center"> <p align="center">
<a href="https://goreportcard.com/report/github.com/Clash-Mini/Clash.Meta"> <a href="https://goreportcard.com/report/github.com/MetaCubeX/mihomo">
<img src="https://goreportcard.com/badge/github.com/Clash-Mini/Clash.Meta?style=flat-square"> <img src="https://goreportcard.com/badge/github.com/MetaCubeX/mihomo?style=flat-square">
</a> </a>
<img src="https://img.shields.io/github/go-mod/go-version/Dreamacro/clash?style=flat-square"> <img src="https://img.shields.io/github/go-mod/go-version/MetaCubeX/mihomo?style=flat-square">
<a href="https://github.com/Clash-Mini/Clash.Meta/releases"> <a href="https://github.com/MetaCubeX/mihomo/releases">
<img src="https://img.shields.io/github/release/Clash-Mini/Clash.Meta/all.svg?style=flat-square"> <img src="https://img.shields.io/github/release/MetaCubeX/mihomo/all.svg?style=flat-square">
</a> </a>
<a href="https://github.com/Clash-Mini/Clash.Meta"> <a href="https://github.com/MetaCubeX/mihomo">
<img src="https://img.shields.io/badge/release-Meta-00b4f0?style=flat-square"> <img src="https://img.shields.io/badge/release-Meta-00b4f0?style=flat-square">
</a> </a>
</p> </p>
@@ -21,270 +21,52 @@
## Features ## Features
- Local HTTP/HTTPS/SOCKS server with authentication support - Local HTTP/HTTPS/SOCKS server with authentication support
- VMess, Shadowsocks, Trojan, Snell protocol support for remote connections - VMess, VLESS, Shadowsocks, Trojan, Snell, TUIC, Hysteria protocol support
- Built-in DNS server that aims to minimize DNS pollution attack impact, supports DoH/DoT upstream and fake IP. - Built-in DNS server that aims to minimize DNS pollution attack impact, supports DoH/DoT upstream and fake IP.
- Rules based off domains, GEOIP, IPCIDR or Process to forward packets to different nodes - Rules based off domains, GEOIP, IPCIDR or Process to forward packets to different nodes
- Remote groups allow users to implement powerful rules. Supports automatic fallback, load balancing or auto select node - Remote groups allow users to implement powerful rules. Supports automatic fallback, load balancing or auto select node
based off latency based off latency
- Remote providers, allowing users to get node lists remotely instead of hard-coding in config - Remote providers, allowing users to get node lists remotely instead of hard-coding in config
- Netfilter TCP redirecting. Deploy Clash on your Internet gateway with `iptables`. - Netfilter TCP redirecting. Deploy Mihomo on your Internet gateway with `iptables`.
- Comprehensive HTTP RESTful API controller - Comprehensive HTTP RESTful API controller
## Dashboard ## Dashboard
We made an official web dashboard providing first class support for this project, check it out A web dashboard with first-class support for this project has been created; it can be checked out at [metacubexd](https://github.com/MetaCubeX/metacubexd).
at [metacubexd](https://github.com/MetaCubeX/metacubexd)
## Wiki ## Configration example
Configuration examples can be found Configuration example is located at [/docs/config.yaml](https://github.com/MetaCubeX/mihomo/blob/Alpha/docs/config.yaml).
at [/docs/config.yaml](https://github.com/MetaCubeX/Clash.Meta/blob/Alpha/docs/config.yaml), while documentation can be
found [Clash.Meta Wiki](https://clash-meta.wiki).
## Build ## Docs
You should install [golang](https://go.dev) first. Documentation can be found in [mihomo Docs](https://wiki.metacubex.one/).
Then get the source code of Clash.Meta: ## For development
Requirements:
[Go 1.20 or newer](https://go.dev/dl/)
Build mihomo:
```shell ```shell
git clone https://github.com/MetaCubeX/Clash.Meta.git git clone https://github.com/MetaCubeX/mihomo.git
cd Clash.Meta && go mod download cd mihomo && go mod download
go build
``` ```
If you can't visit GitHub, you should set proxy first: Set go proxy if a connection to GitHub is not possible:
```shell ```shell
go env -w GOPROXY=https://goproxy.io,direct go env -w GOPROXY=https://goproxy.io,direct
``` ```
Now you can build it: Build with gvisor tun stack:
```shell
go build
```
If you need gvisor for tun stack, build with:
```shell ```shell
go build -tags with_gvisor go build -tags with_gvisor
``` ```
<!-- ## Advanced usage of this fork -->
<!-- ### DNS configuration
Support `geosite` with `fallback-filter`.
Restore `Redir remote resolution`.
Support resolve ip with a `Proxy Tunnel`.
```yaml
proxy-groups:
- name: DNS
type: url-test
use:
- HK
url: http://cp.cloudflare.com
interval: 180
lazy: true
```
```yaml
dns:
enable: true
use-hosts: true
ipv6: false
enhanced-mode: redir-host
fake-ip-range: 198.18.0.1/16
listen: 127.0.0.1:6868
default-nameserver:
- 119.29.29.29
- 114.114.114.114
nameserver:
- https://doh.pub/dns-query
- tls://223.5.5.5:853
fallback:
- "https://1.0.0.1/dns-query#DNS" # append the proxy adapter name or group name to the end of DNS URL with '#' prefix.
- "tls://8.8.4.4:853#DNS"
fallback-filter:
geoip: false
geosite:
- gfw # `geosite` filter only use fallback server to resolve ip, prevent DNS leaks to unsafe DNS providers.
domain:
- +.example.com
ipcidr:
- 0.0.0.0/32
```
### TUN configuration
Supports macOS, Linux and Windows.
Built-in [Wintun](https://www.wintun.net) driver.
```yaml
# Enable the TUN listener
tun:
enable: true
stack: system # system/gvisor
dns-hijack:
- 0.0.0.0:53 # additional dns server listen on TUN
auto-route: true # auto set global route
```
### Rules configuration
- Support rule `GEOSITE`.
- Support rule-providers `RULE-SET`.
- Support `multiport` condition for rule `SRC-PORT` and `DST-PORT`.
- Support `network` condition for all rules.
- Support source IPCIDR condition for all rules, just append to the end.
- The `GEOSITE` databases via https://github.com/Loyalsoldier/v2ray-rules-dat.
```yaml
rules:
# network(tcp/udp) condition for all rules
- DOMAIN-SUFFIX,bilibili.com,DIRECT,tcp
- DOMAIN-SUFFIX,bilibili.com,REJECT,udp
# multiport condition for rules SRC-PORT and DST-PORT
- DST-PORT,123/136/137-139,DIRECT,udp
# rule GEOSITE
- GEOSITE,category-ads-all,REJECT
- GEOSITE,icloud@cn,DIRECT
- GEOSITE,apple@cn,DIRECT
- GEOSITE,apple-cn,DIRECT
- GEOSITE,microsoft@cn,DIRECT
- GEOSITE,facebook,PROXY
- GEOSITE,youtube,PROXY
- GEOSITE,geolocation-cn,DIRECT
- GEOSITE,geolocation-!cn,PROXY
# source IPCIDR condition for all rules in gateway proxy
#- GEOSITE,geolocation-!cn,REJECT,192.168.1.88/32,192.168.1.99/32
- GEOIP,telegram,PROXY,no-resolve
- GEOIP,private,DIRECT,no-resolve
- GEOIP,cn,DIRECT
- MATCH,PROXY
```
### Proxies configuration
Active health detection `urltest / fallback` (based on tcp handshake, multiple failures within a limited time will actively trigger health detection to use the node)
Support `Policy Group Filter`
```yaml
proxy-groups:
- name: 🚀 HK Group
type: select
use:
- ALL
filter: "HK"
- name: 🚀 US Group
type: select
use:
- ALL
filter: "US"
proxy-providers:
ALL:
type: http
url: "xxxxx"
interval: 3600
path: "xxxxx"
health-check:
enable: true
interval: 600
url: http://www.gstatic.com/generate_204
```
Support outbound transport protocol `VLESS`.
The XTLS support (TCP/UDP) transport by the XRAY-CORE.
```yaml
proxies:
- name: "vless"
type: vless
server: server
port: 443
uuid: uuid
servername: example.com # AKA SNI
# flow: xtls-rprx-direct # xtls-rprx-origin # enable XTLS
# skip-cert-verify: true
- name: "vless-ws"
type: vless
server: server
port: 443
uuid: uuid
tls: true
udp: true
network: ws
servername: example.com # priority over wss host
# skip-cert-verify: true
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
```
Support outbound transport protocol `Wireguard`
```yaml
proxies:
- name: "wg"
type: wireguard
server: 162.159.192.1
port: 2480
ip: 172.16.0.2
ipv6: fd01:5ca1:ab1e:80fa:ab85:6eea:213f:f4a5
private-key: eCtXsJZ27+4PbhDkHnB923tkUn2Gj59wZw5wFA75MnU=
public-key: Cr8hWlKvtDt7nrvf+f0brNQQzabAqrjfBvas9pmowjo=
udp: true
```
Support outbound transport protocol `Tuic`
```yaml
proxies:
- name: "tuic"
server: www.example.com
port: 10443
type: tuic
token: TOKEN
# ip: 127.0.0.1 # for overwriting the DNS lookup result of the server address set in option 'server'
# heartbeat-interval: 10000
# alpn: [h3]
# disable-sni: true
reduce-rtt: true
# request-timeout: 8000
udp-relay-mode: native # Available: "native", "quic". Default: "native"
# congestion-controller: bbr # Available: "cubic", "new_reno", "bbr". Default: "cubic"
# max-udp-relay-packet-size: 1500
# fast-open: true
# skip-cert-verify: true
``` -->
### IPTABLES configuration ### IPTABLES configuration
Work on Linux OS which supported `iptables` Work on Linux OS which supported `iptables`
@@ -298,61 +80,9 @@ iptables:
inbound-interface: eth0 # detect the inbound interface, default is 'lo' inbound-interface: eth0 # detect the inbound interface, default is 'lo'
``` ```
### General installation guide for Linux
- Create user given name `clash-meta`
- Download and decompress pre-built binaries from [releases](https://github.com/MetaCubeX/Clash.Meta/releases)
- Rename executable file to `Clash-Meta` and move to `/usr/local/bin/`
- Create folder `/etc/Clash-Meta/` as working directory
Run Meta Kernel by user `clash-meta` as a daemon.
Create the systemd configuration file at `/etc/systemd/system/Clash-Meta.service`:
```
[Unit]
Description=Clash-Meta Daemon, Another Clash Kernel.
After=network.target NetworkManager.service systemd-networkd.service iwd.service
[Service]
Type=simple
User=clash-meta
Group=clash-meta
LimitNPROC=500
LimitNOFILE=1000000
CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_RAW CAP_NET_BIND_SERVICE
AmbientCapabilities=CAP_NET_ADMIN CAP_NET_RAW CAP_NET_BIND_SERVICE
Restart=always
ExecStartPre=/usr/bin/sleep 1s
ExecStart=/usr/local/bin/Clash-Meta -d /etc/Clash-Meta
[Install]
WantedBy=multi-user.target
```
Launch clash-meta daemon on system startup with:
```shell
$ systemctl enable Clash-Meta
```
Launch clash-meta daemon immediately with:
```shell
$ systemctl start Clash-Meta
```
## Development
If you want to build an application that uses clash as a library, check out
the [GitHub Wiki](https://github.com/Dreamacro/clash/wiki/use-clash-as-a-library)
## Debugging ## Debugging
Check [wiki](https://github.com/MetaCubeX/Clash.Meta/wiki/How-to-use-debug-api) to get an instruction on using debug Check [wiki](https://wiki.metacubex.one/api/#debug) to get an instruction on using debug
API. API.
## Credits ## Credits
@@ -368,4 +98,4 @@ API.
This software is released under the GPL-3.0 license. This software is released under the GPL-3.0 license.
[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2FDreamacro%2Fclash.svg?type=large)](https://app.fossa.io/projects/git%2Bgithub.com%2FDreamacro%2Fclash?ref=badge_large) [![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2FMetaCubeX%2Fmihomo.svg?type=large)](https://app.fossa.io/projects/git%2Bgithub.com%2FMetaCubeX%2Fmihomo?ref=badge_large)

View File

@@ -12,14 +12,12 @@ import (
"strconv" "strconv"
"time" "time"
"github.com/Dreamacro/clash/common/atomic" "github.com/metacubex/mihomo/common/atomic"
"github.com/Dreamacro/clash/common/queue" "github.com/metacubex/mihomo/common/queue"
"github.com/Dreamacro/clash/common/utils" "github.com/metacubex/mihomo/common/utils"
"github.com/Dreamacro/clash/component/dialer" "github.com/metacubex/mihomo/component/dialer"
C "github.com/Dreamacro/clash/constant" C "github.com/metacubex/mihomo/constant"
"github.com/Dreamacro/clash/log" "github.com/puzpuzpuz/xsync/v3"
"github.com/puzpuzpuz/xsync/v2"
) )
var UnifiedDelay = atomic.NewBool(false) var UnifiedDelay = atomic.NewBool(false)
@@ -30,22 +28,17 @@ const (
type extraProxyState struct { type extraProxyState struct {
history *queue.Queue[C.DelayHistory] history *queue.Queue[C.DelayHistory]
alive *atomic.Bool alive atomic.Bool
} }
type Proxy struct { type Proxy struct {
C.ProxyAdapter C.ProxyAdapter
history *queue.Queue[C.DelayHistory] history *queue.Queue[C.DelayHistory]
alive *atomic.Bool alive atomic.Bool
url string url string
extra *xsync.MapOf[string, *extraProxyState] extra *xsync.MapOf[string, *extraProxyState]
} }
// Alive implements C.Proxy
func (p *Proxy) Alive() bool {
return p.alive.Load()
}
// AliveForTestUrl implements C.Proxy // AliveForTestUrl implements C.Proxy
func (p *Proxy) AliveForTestUrl(url string) bool { func (p *Proxy) AliveForTestUrl(url string) bool {
if state, ok := p.extra.Load(url); ok { if state, ok := p.extra.Load(url); ok {
@@ -181,7 +174,7 @@ func (p *Proxy) MarshalJSON() ([]byte, error) {
_ = json.Unmarshal(inner, &mapping) _ = json.Unmarshal(inner, &mapping)
mapping["history"] = p.DelayHistory() mapping["history"] = p.DelayHistory()
mapping["extra"] = p.ExtraDelayHistory() mapping["extra"] = p.ExtraDelayHistory()
mapping["alive"] = p.Alive() mapping["alive"] = p.AliveForTestUrl(p.url)
mapping["name"] = p.Name() mapping["name"] = p.Name()
mapping["udp"] = p.SupportUDP() mapping["udp"] = p.SupportUDP()
mapping["xudp"] = p.SupportXUDP() mapping["xudp"] = p.SupportXUDP()
@@ -191,13 +184,11 @@ func (p *Proxy) MarshalJSON() ([]byte, error) {
// URLTest get the delay for the specified URL // URLTest get the delay for the specified URL
// implements C.Proxy // implements C.Proxy
func (p *Proxy) URLTest(ctx context.Context, url string, expectedStatus utils.IntRanges[uint16], store C.DelayHistoryStoreType) (t uint16, err error) { func (p *Proxy) URLTest(ctx context.Context, url string, expectedStatus utils.IntRanges[uint16]) (t uint16, err error) {
defer func() { defer func() {
alive := err == nil alive := err == nil
store = p.determineFinalStoreType(store, url)
switch store { if len(p.url) == 0 || url == p.url {
case C.OriginalHistory:
p.alive.Store(alive) p.alive.Store(alive)
record := C.DelayHistory{Time: time.Now()} record := C.DelayHistory{Time: time.Now()}
if alive { if alive {
@@ -212,7 +203,7 @@ func (p *Proxy) URLTest(ctx context.Context, url string, expectedStatus utils.In
if len(p.url) == 0 { if len(p.url) == 0 {
p.url = url p.url = url
} }
case C.ExtraHistory: } else {
record := C.DelayHistory{Time: time.Now()} record := C.DelayHistory{Time: time.Now()}
if alive { if alive {
record.Delay = t record.Delay = t
@@ -236,8 +227,6 @@ func (p *Proxy) URLTest(ctx context.Context, url string, expectedStatus utils.In
if state.history.Len() > defaultHistoriesNum { if state.history.Len() > defaultHistoriesNum {
state.history.Pop() state.history.Pop()
} }
default:
log.Debugln("health check result will be discarded, url: %s alive: %t, delay: %d", url, alive, t)
} }
}() }()
@@ -316,7 +305,7 @@ func NewProxy(adapter C.ProxyAdapter) *Proxy {
history: queue.New[C.DelayHistory](defaultHistoriesNum), history: queue.New[C.DelayHistory](defaultHistoriesNum),
alive: atomic.NewBool(true), alive: atomic.NewBool(true),
url: "", url: "",
extra: xsync.NewMapOf[*extraProxyState]()} extra: xsync.NewMapOf[string, *extraProxyState]()}
} }
func urlToMetadata(rawURL string) (addr C.Metadata, err error) { func urlToMetadata(rawURL string) (addr C.Metadata, err error) {
@@ -349,24 +338,3 @@ func urlToMetadata(rawURL string) (addr C.Metadata, err error) {
} }
return return
} }
func (p *Proxy) determineFinalStoreType(store C.DelayHistoryStoreType, url string) C.DelayHistoryStoreType {
if store != C.DropHistory {
return store
}
if len(p.url) == 0 || url == p.url {
return C.OriginalHistory
}
if p.extra.Size() < 2*C.DefaultMaxHealthCheckUrlNum {
return C.ExtraHistory
}
_, ok := p.extra.Load(url)
if ok {
return C.ExtraHistory
}
return store
}

View File

@@ -1,13 +1,17 @@
package inbound package inbound
import ( import (
C "github.com/Dreamacro/clash/constant" "net"
C "github.com/metacubex/mihomo/constant"
) )
type Addition func(metadata *C.Metadata) type Addition func(metadata *C.Metadata)
func (a Addition) Apply(metadata *C.Metadata) { func ApplyAdditions(metadata *C.Metadata, additions ...Addition) {
a(metadata) for _, addition := range additions {
addition(metadata)
}
} }
func WithInName(name string) Addition { func WithInName(name string) Addition {
@@ -33,3 +37,29 @@ func WithSpecialProxy(specialProxy string) Addition {
metadata.SpecialProxy = specialProxy metadata.SpecialProxy = specialProxy
} }
} }
func WithDstAddr(addr net.Addr) Addition {
return func(metadata *C.Metadata) {
_ = metadata.SetRemoteAddr(addr)
}
}
func WithSrcAddr(addr net.Addr) Addition {
return func(metadata *C.Metadata) {
m := C.Metadata{}
if err := m.SetRemoteAddr(addr);err ==nil{
metadata.SrcIP = m.DstIP
metadata.SrcPort = m.DstPort
}
}
}
func WithInAddr(addr net.Addr) Addition {
return func(metadata *C.Metadata) {
m := C.Metadata{}
if err := m.SetRemoteAddr(addr);err ==nil{
metadata.InIP = m.DstIP
metadata.InPort = m.DstPort
}
}
}

45
adapter/inbound/auth.go Normal file
View File

@@ -0,0 +1,45 @@
package inbound
import (
"net"
"net/netip"
C "github.com/metacubex/mihomo/constant"
)
var skipAuthPrefixes []netip.Prefix
func SetSkipAuthPrefixes(prefixes []netip.Prefix) {
skipAuthPrefixes = prefixes
}
func SkipAuthPrefixes() []netip.Prefix {
return skipAuthPrefixes
}
func SkipAuthRemoteAddr(addr net.Addr) bool {
m := C.Metadata{}
if err := m.SetRemoteAddr(addr); err != nil {
return false
}
return skipAuth(m.AddrPort().Addr())
}
func SkipAuthRemoteAddress(addr string) bool {
m := C.Metadata{}
if err := m.SetRemoteAddress(addr); err != nil {
return false
}
return skipAuth(m.AddrPort().Addr())
}
func skipAuth(addr netip.Addr) bool {
if addr.IsValid() {
for _, prefix := range skipAuthPrefixes {
if prefix.Contains(addr.Unmap()) {
return true
}
}
}
return false
}

View File

@@ -3,26 +3,18 @@ package inbound
import ( import (
"net" "net"
C "github.com/Dreamacro/clash/constant" C "github.com/metacubex/mihomo/constant"
"github.com/Dreamacro/clash/context" "github.com/metacubex/mihomo/transport/socks5"
"github.com/Dreamacro/clash/transport/socks5"
) )
// NewHTTP receive normal http request and return HTTPContext // NewHTTP receive normal http request and return HTTPContext
func NewHTTP(target socks5.Addr, source net.Addr, conn net.Conn, additions ...Addition) *context.ConnContext { func NewHTTP(target socks5.Addr, srcConn net.Conn, conn net.Conn, additions ...Addition) (net.Conn, *C.Metadata) {
metadata := parseSocksAddr(target) metadata := parseSocksAddr(target)
metadata.NetWork = C.TCP metadata.NetWork = C.TCP
metadata.Type = C.HTTP metadata.Type = C.HTTP
for _, addition := range additions { metadata.RawSrcAddr = srcConn.RemoteAddr()
addition.Apply(metadata) metadata.RawDstAddr = srcConn.LocalAddr()
} ApplyAdditions(metadata, WithSrcAddr(srcConn.RemoteAddr()), WithInAddr(conn.LocalAddr()))
if ip, port, err := parseAddr(source); err == nil { ApplyAdditions(metadata, additions...)
metadata.SrcIP = ip return conn, metadata
metadata.SrcPort = port
}
if ip, port, err := parseAddr(conn.LocalAddr()); err == nil {
metadata.InIP = ip
metadata.InPort = port
}
return context.NewConnContext(conn, metadata)
} }

View File

@@ -4,24 +4,14 @@ import (
"net" "net"
"net/http" "net/http"
C "github.com/Dreamacro/clash/constant" C "github.com/metacubex/mihomo/constant"
"github.com/Dreamacro/clash/context"
) )
// NewHTTPS receive CONNECT request and return ConnContext // NewHTTPS receive CONNECT request and return ConnContext
func NewHTTPS(request *http.Request, conn net.Conn, additions ...Addition) *context.ConnContext { func NewHTTPS(request *http.Request, conn net.Conn, additions ...Addition) (net.Conn, *C.Metadata) {
metadata := parseHTTPAddr(request) metadata := parseHTTPAddr(request)
metadata.Type = C.HTTPS metadata.Type = C.HTTPS
for _, addition := range additions { ApplyAdditions(metadata, WithSrcAddr(conn.RemoteAddr()), WithInAddr(conn.LocalAddr()))
addition.Apply(metadata) ApplyAdditions(metadata, additions...)
} return conn, metadata
if ip, port, err := parseAddr(conn.RemoteAddr()); err == nil {
metadata.SrcIP = ip
metadata.SrcPort = port
}
if ip, port, err := parseAddr(conn.LocalAddr()); err == nil {
metadata.InIP = ip
metadata.InPort = port
}
return context.NewConnContext(conn, metadata)
} }

View File

@@ -1,42 +1,22 @@
package inbound package inbound
import ( import (
C "github.com/Dreamacro/clash/constant" C "github.com/metacubex/mihomo/constant"
"github.com/Dreamacro/clash/transport/socks5" "github.com/metacubex/mihomo/transport/socks5"
) )
// PacketAdapter is a UDP Packet adapter for socks/redir/tun
type PacketAdapter struct {
C.UDPPacket
metadata *C.Metadata
}
// Metadata returns destination metadata
func (s *PacketAdapter) Metadata() *C.Metadata {
return s.metadata
}
// NewPacket is PacketAdapter generator // NewPacket is PacketAdapter generator
func NewPacket(target socks5.Addr, packet C.UDPPacket, source C.Type, additions ...Addition) C.PacketAdapter { func NewPacket(target socks5.Addr, packet C.UDPPacket, source C.Type, additions ...Addition) (C.UDPPacket, *C.Metadata) {
metadata := parseSocksAddr(target) metadata := parseSocksAddr(target)
metadata.NetWork = C.UDP metadata.NetWork = C.UDP
metadata.Type = source metadata.Type = source
for _, addition := range additions { metadata.RawSrcAddr = packet.LocalAddr()
addition.Apply(metadata) metadata.RawDstAddr = metadata.UDPAddr()
} ApplyAdditions(metadata, WithSrcAddr(packet.LocalAddr()))
if ip, port, err := parseAddr(packet.LocalAddr()); err == nil {
metadata.SrcIP = ip
metadata.SrcPort = port
}
if p, ok := packet.(C.UDPPacketInAddr); ok { if p, ok := packet.(C.UDPPacketInAddr); ok {
if ip, port, err := parseAddr(p.InAddr()); err == nil { ApplyAdditions(metadata, WithInAddr(p.InAddr()))
metadata.InIP = ip
metadata.InPort = port
}
} }
ApplyAdditions(metadata, additions...)
return &PacketAdapter{ return packet, metadata
packet,
metadata,
}
} }

View File

@@ -2,51 +2,17 @@ package inbound
import ( import (
"net" "net"
"net/netip"
"strconv"
C "github.com/Dreamacro/clash/constant" C "github.com/metacubex/mihomo/constant"
"github.com/Dreamacro/clash/context" "github.com/metacubex/mihomo/transport/socks5"
"github.com/Dreamacro/clash/transport/socks5"
) )
// NewSocket receive TCP inbound and return ConnContext // NewSocket receive TCP inbound and return ConnContext
func NewSocket(target socks5.Addr, conn net.Conn, source C.Type, additions ...Addition) *context.ConnContext { func NewSocket(target socks5.Addr, conn net.Conn, source C.Type, additions ...Addition) (net.Conn, *C.Metadata) {
metadata := parseSocksAddr(target) metadata := parseSocksAddr(target)
metadata.NetWork = C.TCP metadata.NetWork = C.TCP
metadata.Type = source metadata.Type = source
for _, addition := range additions { ApplyAdditions(metadata, WithSrcAddr(conn.RemoteAddr()), WithInAddr(conn.LocalAddr()))
addition.Apply(metadata) ApplyAdditions(metadata, additions...)
} return conn, metadata
if ip, port, err := parseAddr(conn.RemoteAddr()); err == nil {
metadata.SrcIP = ip
metadata.SrcPort = port
}
if ip, port, err := parseAddr(conn.LocalAddr()); err == nil {
metadata.InIP = ip
metadata.InPort = port
}
return context.NewConnContext(conn, metadata)
}
func NewInner(conn net.Conn, address string) *context.ConnContext {
metadata := &C.Metadata{}
metadata.NetWork = C.TCP
metadata.Type = C.INNER
metadata.DNSMode = C.DNSNormal
metadata.Process = C.ClashName
if h, port, err := net.SplitHostPort(address); err == nil {
if port, err := strconv.ParseUint(port, 10, 16); err == nil {
metadata.DstPort = uint16(port)
}
if ip, err := netip.ParseAddr(h); err == nil {
metadata.DstIP = ip
} else {
metadata.Host = h
}
}
return context.NewConnContext(conn, metadata)
} }

View File

@@ -1,16 +1,15 @@
package inbound package inbound
import ( import (
"errors"
"net" "net"
"net/http" "net/http"
"net/netip" "net/netip"
"strconv" "strconv"
"strings" "strings"
"github.com/Dreamacro/clash/common/nnip" "github.com/metacubex/mihomo/common/nnip"
C "github.com/Dreamacro/clash/constant" C "github.com/metacubex/mihomo/constant"
"github.com/Dreamacro/clash/transport/socks5" "github.com/metacubex/mihomo/transport/socks5"
) )
func parseSocksAddr(target socks5.Addr) *C.Metadata { func parseSocksAddr(target socks5.Addr) *C.Metadata {
@@ -62,29 +61,3 @@ func parseHTTPAddr(request *http.Request) *C.Metadata {
return metadata return metadata
} }
func parseAddr(addr net.Addr) (netip.Addr, uint16, error) {
// Filter when net.Addr interface is nil
if addr == nil {
return netip.Addr{}, 0, errors.New("nil addr")
}
if rawAddr, ok := addr.(interface{ RawAddr() net.Addr }); ok {
ip, port, err := parseAddr(rawAddr.RawAddr())
if err == nil {
return ip, port, err
}
}
addrStr := addr.String()
host, port, err := net.SplitHostPort(addrStr)
if err != nil {
return netip.Addr{}, 0, err
}
var uint16Port uint16
if port, err := strconv.ParseUint(port, 10, 16); err == nil {
uint16Port = uint16(port)
}
ip, err := netip.ParseAddr(host)
return ip, uint16Port, err
}

View File

@@ -7,10 +7,10 @@ import (
"strings" "strings"
"syscall" "syscall"
N "github.com/Dreamacro/clash/common/net" N "github.com/metacubex/mihomo/common/net"
"github.com/Dreamacro/clash/common/utils" "github.com/metacubex/mihomo/common/utils"
"github.com/Dreamacro/clash/component/dialer" "github.com/metacubex/mihomo/component/dialer"
C "github.com/Dreamacro/clash/constant" C "github.com/metacubex/mihomo/constant"
) )
type Base struct { type Base struct {

View File

@@ -5,10 +5,10 @@ import (
"errors" "errors"
"net/netip" "net/netip"
N "github.com/Dreamacro/clash/common/net" N "github.com/metacubex/mihomo/common/net"
"github.com/Dreamacro/clash/component/dialer" "github.com/metacubex/mihomo/component/dialer"
"github.com/Dreamacro/clash/component/resolver" "github.com/metacubex/mihomo/component/resolver"
C "github.com/Dreamacro/clash/constant" C "github.com/metacubex/mihomo/constant"
) )
type Direct struct { type Direct struct {

View File

@@ -13,11 +13,11 @@ import (
"net/http" "net/http"
"strconv" "strconv"
N "github.com/Dreamacro/clash/common/net" N "github.com/metacubex/mihomo/common/net"
"github.com/Dreamacro/clash/component/ca" "github.com/metacubex/mihomo/component/ca"
"github.com/Dreamacro/clash/component/dialer" "github.com/metacubex/mihomo/component/dialer"
"github.com/Dreamacro/clash/component/proxydialer" "github.com/metacubex/mihomo/component/proxydialer"
C "github.com/Dreamacro/clash/constant" C "github.com/metacubex/mihomo/constant"
) )
type Http struct { type Http struct {

View File

@@ -14,17 +14,17 @@ import (
"github.com/metacubex/quic-go/congestion" "github.com/metacubex/quic-go/congestion"
M "github.com/sagernet/sing/common/metadata" M "github.com/sagernet/sing/common/metadata"
"github.com/Dreamacro/clash/component/ca" "github.com/metacubex/mihomo/component/ca"
"github.com/Dreamacro/clash/component/dialer" "github.com/metacubex/mihomo/component/dialer"
"github.com/Dreamacro/clash/component/proxydialer" "github.com/metacubex/mihomo/component/proxydialer"
C "github.com/Dreamacro/clash/constant" C "github.com/metacubex/mihomo/constant"
"github.com/Dreamacro/clash/log" "github.com/metacubex/mihomo/log"
hyCongestion "github.com/Dreamacro/clash/transport/hysteria/congestion" hyCongestion "github.com/metacubex/mihomo/transport/hysteria/congestion"
"github.com/Dreamacro/clash/transport/hysteria/core" "github.com/metacubex/mihomo/transport/hysteria/core"
"github.com/Dreamacro/clash/transport/hysteria/obfs" "github.com/metacubex/mihomo/transport/hysteria/obfs"
"github.com/Dreamacro/clash/transport/hysteria/pmtud_fix" "github.com/metacubex/mihomo/transport/hysteria/pmtud_fix"
"github.com/Dreamacro/clash/transport/hysteria/transport" "github.com/metacubex/mihomo/transport/hysteria/transport"
"github.com/Dreamacro/clash/transport/hysteria/utils" "github.com/metacubex/mihomo/transport/hysteria/utils"
) )
const ( const (
@@ -46,7 +46,7 @@ type Hysteria struct {
} }
func (h *Hysteria) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.Conn, error) { func (h *Hysteria) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.Conn, error) {
tcpConn, err := h.client.DialTCP(metadata.RemoteAddress(), h.genHdc(ctx, opts...)) tcpConn, err := h.client.DialTCP(metadata.String(), metadata.DstPort, h.genHdc(ctx, opts...))
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@@ -9,12 +9,12 @@ import (
"runtime" "runtime"
"strconv" "strconv"
CN "github.com/Dreamacro/clash/common/net" CN "github.com/metacubex/mihomo/common/net"
"github.com/Dreamacro/clash/component/ca" "github.com/metacubex/mihomo/component/ca"
"github.com/Dreamacro/clash/component/dialer" "github.com/metacubex/mihomo/component/dialer"
"github.com/Dreamacro/clash/component/proxydialer" "github.com/metacubex/mihomo/component/proxydialer"
C "github.com/Dreamacro/clash/constant" C "github.com/metacubex/mihomo/constant"
tuicCommon "github.com/Dreamacro/clash/transport/tuic/common" tuicCommon "github.com/metacubex/mihomo/transport/tuic/common"
"github.com/metacubex/sing-quic/hysteria2" "github.com/metacubex/sing-quic/hysteria2"
@@ -55,7 +55,7 @@ type Hysteria2Option struct {
func (h *Hysteria2) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) { func (h *Hysteria2) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) {
options := h.Base.DialOptions(opts...) options := h.Base.DialOptions(opts...)
h.dialer.SetDialer(dialer.NewDialer(options...)) h.dialer.SetDialer(dialer.NewDialer(options...))
c, err := h.client.DialConn(ctx, M.ParseSocksaddr(metadata.RemoteAddress())) c, err := h.client.DialConn(ctx, M.ParseSocksaddrHostPort(metadata.String(), metadata.DstPort))
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@@ -5,7 +5,7 @@ import (
"encoding/hex" "encoding/hex"
"errors" "errors"
tlsC "github.com/Dreamacro/clash/component/tls" tlsC "github.com/metacubex/mihomo/component/tls"
"golang.org/x/crypto/curve25519" "golang.org/x/crypto/curve25519"
) )

View File

@@ -6,23 +6,44 @@ import (
"net" "net"
"time" "time"
"github.com/Dreamacro/clash/common/buf" "github.com/metacubex/mihomo/common/buf"
"github.com/Dreamacro/clash/component/dialer" "github.com/metacubex/mihomo/component/dialer"
C "github.com/Dreamacro/clash/constant" C "github.com/metacubex/mihomo/constant"
) )
type Reject struct { type Reject struct {
*Base *Base
drop bool
}
type RejectOption struct {
Name string `proxy:"name"`
} }
// DialContext implements C.ProxyAdapter // DialContext implements C.ProxyAdapter
func (r *Reject) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.Conn, error) { func (r *Reject) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.Conn, error) {
if r.drop {
return NewConn(dropConn{}, r), nil
}
return NewConn(nopConn{}, r), nil return NewConn(nopConn{}, r), nil
} }
// ListenPacketContext implements C.ProxyAdapter // ListenPacketContext implements C.ProxyAdapter
func (r *Reject) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) { func (r *Reject) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) {
return newPacketConn(nopPacketConn{}, r), nil if r.drop {
return newPacketConn(&dropPacketConn{}, r), nil
}
return newPacketConn(&nopPacketConn{}, r), nil
}
func NewRejectWithOption(option RejectOption) *Reject {
return &Reject{
Base: &Base{
name: option.Name,
tp: C.Direct,
udp: true,
},
}
} }
func NewReject() *Reject { func NewReject() *Reject {
@@ -36,6 +57,18 @@ func NewReject() *Reject {
} }
} }
func NewRejectDrop() *Reject {
return &Reject{
Base: &Base{
name: "REJECT-DROP",
tp: C.RejectDrop,
udp: true,
prefer: C.DualStack,
},
drop: true,
}
}
func NewPass() *Reject { func NewPass() *Reject {
return &Reject{ return &Reject{
Base: &Base{ Base: &Base{
@@ -49,35 +82,29 @@ func NewPass() *Reject {
type nopConn struct{} type nopConn struct{}
func (rw nopConn) Read(b []byte) (int, error) { func (rw nopConn) Read(b []byte) (int, error) { return 0, io.EOF }
return 0, io.EOF
}
func (rw nopConn) ReadBuffer(buffer *buf.Buffer) error { func (rw nopConn) ReadBuffer(buffer *buf.Buffer) error { return io.EOF }
return io.EOF
}
func (rw nopConn) Write(b []byte) (int, error) { func (rw nopConn) Write(b []byte) (int, error) { return 0, io.EOF }
return 0, io.EOF func (rw nopConn) WriteBuffer(buffer *buf.Buffer) error { return io.EOF }
} func (rw nopConn) Close() error { return nil }
func (rw nopConn) LocalAddr() net.Addr { return nil }
func (rw nopConn) WriteBuffer(buffer *buf.Buffer) error { func (rw nopConn) RemoteAddr() net.Addr { return nil }
return io.EOF func (rw nopConn) SetDeadline(time.Time) error { return nil }
} func (rw nopConn) SetReadDeadline(time.Time) error { return nil }
func (rw nopConn) SetWriteDeadline(time.Time) error { return nil }
func (rw nopConn) Close() error { return nil }
func (rw nopConn) LocalAddr() net.Addr { return nil }
func (rw nopConn) RemoteAddr() net.Addr { return nil }
func (rw nopConn) SetDeadline(time.Time) error { return nil }
func (rw nopConn) SetReadDeadline(time.Time) error { return nil }
func (rw nopConn) SetWriteDeadline(time.Time) error { return nil }
var udpAddrIPv4Unspecified = &net.UDPAddr{IP: net.IPv4zero, Port: 0} var udpAddrIPv4Unspecified = &net.UDPAddr{IP: net.IPv4zero, Port: 0}
type nopPacketConn struct{} type nopPacketConn struct{}
func (npc nopPacketConn) WriteTo(b []byte, addr net.Addr) (n int, err error) { return len(b), nil } func (npc nopPacketConn) WriteTo(b []byte, addr net.Addr) (n int, err error) {
func (npc nopPacketConn) ReadFrom(b []byte) (int, net.Addr, error) { return 0, nil, io.EOF } return len(b), nil
}
func (npc nopPacketConn) ReadFrom(b []byte) (int, net.Addr, error) {
return 0, nil, io.EOF
}
func (npc nopPacketConn) WaitReadFrom() ([]byte, func(), net.Addr, error) { func (npc nopPacketConn) WaitReadFrom() ([]byte, func(), net.Addr, error) {
return nil, nil, nil, io.EOF return nil, nil, nil, io.EOF
} }
@@ -86,3 +113,38 @@ func (npc nopPacketConn) LocalAddr() net.Addr { return udpAddrIPv4U
func (npc nopPacketConn) SetDeadline(time.Time) error { return nil } func (npc nopPacketConn) SetDeadline(time.Time) error { return nil }
func (npc nopPacketConn) SetReadDeadline(time.Time) error { return nil } func (npc nopPacketConn) SetReadDeadline(time.Time) error { return nil }
func (npc nopPacketConn) SetWriteDeadline(time.Time) error { return nil } func (npc nopPacketConn) SetWriteDeadline(time.Time) error { return nil }
type dropConn struct{}
func (rw dropConn) Read(b []byte) (int, error) { return 0, io.EOF }
func (rw dropConn) ReadBuffer(buffer *buf.Buffer) error {
time.Sleep(C.DefaultDropTime)
return io.EOF
}
func (rw dropConn) Write(b []byte) (int, error) { return 0, io.EOF }
func (rw dropConn) WriteBuffer(buffer *buf.Buffer) error { return io.EOF }
func (rw dropConn) Close() error { return nil }
func (rw dropConn) LocalAddr() net.Addr { return nil }
func (rw dropConn) RemoteAddr() net.Addr { return nil }
func (rw dropConn) SetDeadline(time.Time) error { return nil }
func (rw dropConn) SetReadDeadline(time.Time) error { return nil }
func (rw dropConn) SetWriteDeadline(time.Time) error { return nil }
type dropPacketConn struct{}
func (npc dropPacketConn) WriteTo(b []byte, addr net.Addr) (n int, err error) {
time.Sleep(C.DefaultDropTime)
return len(b), nil
}
func (npc dropPacketConn) ReadFrom(b []byte) (int, net.Addr, error) {
time.Sleep(C.DefaultDropTime)
return 0, nil, io.EOF
}
func (npc dropPacketConn) WaitReadFrom() ([]byte, func(), net.Addr, error) {
return nil, nil, nil, io.EOF
}
func (npc dropPacketConn) Close() error { return nil }
func (npc dropPacketConn) LocalAddr() net.Addr { return udpAddrIPv4Unspecified }
func (npc dropPacketConn) SetDeadline(time.Time) error { return nil }
func (npc dropPacketConn) SetReadDeadline(time.Time) error { return nil }
func (npc dropPacketConn) SetWriteDeadline(time.Time) error { return nil }

View File

@@ -7,16 +7,16 @@ import (
"net" "net"
"strconv" "strconv"
N "github.com/Dreamacro/clash/common/net" N "github.com/metacubex/mihomo/common/net"
"github.com/Dreamacro/clash/common/structure" "github.com/metacubex/mihomo/common/structure"
"github.com/Dreamacro/clash/component/dialer" "github.com/metacubex/mihomo/component/dialer"
"github.com/Dreamacro/clash/component/proxydialer" "github.com/metacubex/mihomo/component/proxydialer"
"github.com/Dreamacro/clash/component/resolver" "github.com/metacubex/mihomo/component/resolver"
C "github.com/Dreamacro/clash/constant" C "github.com/metacubex/mihomo/constant"
"github.com/Dreamacro/clash/transport/restls" "github.com/metacubex/mihomo/transport/restls"
obfs "github.com/Dreamacro/clash/transport/simple-obfs" obfs "github.com/metacubex/mihomo/transport/simple-obfs"
shadowtls "github.com/Dreamacro/clash/transport/sing-shadowtls" shadowtls "github.com/metacubex/mihomo/transport/sing-shadowtls"
v2rayObfs "github.com/Dreamacro/clash/transport/v2ray-plugin" v2rayObfs "github.com/metacubex/mihomo/transport/v2ray-plugin"
restlsC "github.com/3andne/restls-client-go" restlsC "github.com/3andne/restls-client-go"
shadowsocks "github.com/metacubex/sing-shadowsocks2" shadowsocks "github.com/metacubex/sing-shadowsocks2"
@@ -58,14 +58,16 @@ type simpleObfsOption struct {
} }
type v2rayObfsOption struct { type v2rayObfsOption struct {
Mode string `obfs:"mode"` Mode string `obfs:"mode"`
Host string `obfs:"host,omitempty"` Host string `obfs:"host,omitempty"`
Path string `obfs:"path,omitempty"` Path string `obfs:"path,omitempty"`
TLS bool `obfs:"tls,omitempty"` TLS bool `obfs:"tls,omitempty"`
Fingerprint string `obfs:"fingerprint,omitempty"` Fingerprint string `obfs:"fingerprint,omitempty"`
Headers map[string]string `obfs:"headers,omitempty"` Headers map[string]string `obfs:"headers,omitempty"`
SkipCertVerify bool `obfs:"skip-cert-verify,omitempty"` SkipCertVerify bool `obfs:"skip-cert-verify,omitempty"`
Mux bool `obfs:"mux,omitempty"` Mux bool `obfs:"mux,omitempty"`
V2rayHttpUpgrade bool `obfs:"v2ray-http-upgrade,omitempty"`
V2rayHttpUpgradeFastOpen bool `obfs:"v2ray-http-upgrade-fast-open,omitempty"`
} }
type shadowTLSOption struct { type shadowTLSOption struct {
@@ -123,9 +125,9 @@ func (ss *ShadowSocks) StreamConnContext(ctx context.Context, c net.Conn, metada
} }
} }
if useEarly { if useEarly {
return ss.method.DialEarlyConn(c, M.ParseSocksaddr(metadata.RemoteAddress())), nil return ss.method.DialEarlyConn(c, M.ParseSocksaddrHostPort(metadata.String(), metadata.DstPort)), nil
} else { } else {
return ss.method.DialConn(c, M.ParseSocksaddr(metadata.RemoteAddress())) return ss.method.DialConn(c, M.ParseSocksaddrHostPort(metadata.String(), metadata.DstPort))
} }
} }
@@ -259,10 +261,12 @@ func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) {
} }
obfsMode = opts.Mode obfsMode = opts.Mode
v2rayOption = &v2rayObfs.Option{ v2rayOption = &v2rayObfs.Option{
Host: opts.Host, Host: opts.Host,
Path: opts.Path, Path: opts.Path,
Headers: opts.Headers, Headers: opts.Headers,
Mux: opts.Mux, Mux: opts.Mux,
V2rayHttpUpgrade: opts.V2rayHttpUpgrade,
V2rayHttpUpgradeFastOpen: opts.V2rayHttpUpgradeFastOpen,
} }
if opts.TLS { if opts.TLS {

View File

@@ -7,16 +7,16 @@ import (
"net" "net"
"strconv" "strconv"
N "github.com/Dreamacro/clash/common/net" N "github.com/metacubex/mihomo/common/net"
"github.com/Dreamacro/clash/component/dialer" "github.com/metacubex/mihomo/component/dialer"
"github.com/Dreamacro/clash/component/proxydialer" "github.com/metacubex/mihomo/component/proxydialer"
C "github.com/Dreamacro/clash/constant" C "github.com/metacubex/mihomo/constant"
"github.com/Dreamacro/clash/transport/shadowsocks/core" "github.com/metacubex/mihomo/transport/shadowsocks/core"
"github.com/Dreamacro/clash/transport/shadowsocks/shadowaead" "github.com/metacubex/mihomo/transport/shadowsocks/shadowaead"
"github.com/Dreamacro/clash/transport/shadowsocks/shadowstream" "github.com/metacubex/mihomo/transport/shadowsocks/shadowstream"
"github.com/Dreamacro/clash/transport/socks5" "github.com/metacubex/mihomo/transport/socks5"
"github.com/Dreamacro/clash/transport/ssr/obfs" "github.com/metacubex/mihomo/transport/ssr/obfs"
"github.com/Dreamacro/clash/transport/ssr/protocol" "github.com/metacubex/mihomo/transport/ssr/protocol"
) )
type ShadowSocksR struct { type ShadowSocksR struct {
@@ -125,7 +125,7 @@ func (ssr *ShadowSocksR) SupportWithDialer() C.NetWork {
func NewShadowSocksR(option ShadowSocksROption) (*ShadowSocksR, error) { func NewShadowSocksR(option ShadowSocksROption) (*ShadowSocksR, error) {
// SSR protocol compatibility // SSR protocol compatibility
// https://github.com/Dreamacro/clash/pull/2056 // https://github.com/metacubex/mihomo/pull/2056
if option.Cipher == "none" { if option.Cipher == "none" {
option.Cipher = "dummy" option.Cipher = "dummy"
} }

View File

@@ -5,11 +5,12 @@ import (
"errors" "errors"
"runtime" "runtime"
CN "github.com/Dreamacro/clash/common/net" CN "github.com/metacubex/mihomo/common/net"
"github.com/Dreamacro/clash/component/dialer" "github.com/metacubex/mihomo/component/dialer"
"github.com/Dreamacro/clash/component/proxydialer" "github.com/metacubex/mihomo/component/proxydialer"
"github.com/Dreamacro/clash/component/resolver" "github.com/metacubex/mihomo/component/resolver"
C "github.com/Dreamacro/clash/constant" C "github.com/metacubex/mihomo/constant"
"github.com/metacubex/mihomo/log"
mux "github.com/sagernet/sing-mux" mux "github.com/sagernet/sing-mux"
E "github.com/sagernet/sing/common/exceptions" E "github.com/sagernet/sing/common/exceptions"
@@ -25,14 +26,21 @@ type SingMux struct {
} }
type SingMuxOption struct { type SingMuxOption struct {
Enabled bool `proxy:"enabled,omitempty"` Enabled bool `proxy:"enabled,omitempty"`
Protocol string `proxy:"protocol,omitempty"` Protocol string `proxy:"protocol,omitempty"`
MaxConnections int `proxy:"max-connections,omitempty"` MaxConnections int `proxy:"max-connections,omitempty"`
MinStreams int `proxy:"min-streams,omitempty"` MinStreams int `proxy:"min-streams,omitempty"`
MaxStreams int `proxy:"max-streams,omitempty"` MaxStreams int `proxy:"max-streams,omitempty"`
Padding bool `proxy:"padding,omitempty"` Padding bool `proxy:"padding,omitempty"`
Statistic bool `proxy:"statistic,omitempty"` Statistic bool `proxy:"statistic,omitempty"`
OnlyTcp bool `proxy:"only-tcp,omitempty"` OnlyTcp bool `proxy:"only-tcp,omitempty"`
BrutalOpts BrutalOption `proxy:"brutal-opts,omitempty"`
}
type BrutalOption struct {
Enabled bool `proxy:"enabled,omitempty"`
Up string `proxy:"up,omitempty"`
Down string `proxy:"down,omitempty"`
} }
type ProxyBase interface { type ProxyBase interface {
@@ -42,7 +50,7 @@ type ProxyBase interface {
func (s *SingMux) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) { func (s *SingMux) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) {
options := s.base.DialOptions(opts...) options := s.base.DialOptions(opts...)
s.dialer.SetDialer(dialer.NewDialer(options...)) s.dialer.SetDialer(dialer.NewDialer(options...))
c, err := s.client.DialContext(ctx, "tcp", M.ParseSocksaddr(metadata.RemoteAddress())) c, err := s.client.DialContext(ctx, "tcp", M.ParseSocksaddrHostPort(metadata.String(), metadata.DstPort))
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -94,14 +102,23 @@ func closeSingMux(s *SingMux) {
} }
func NewSingMux(option SingMuxOption, proxy C.ProxyAdapter, base ProxyBase) (C.ProxyAdapter, error) { func NewSingMux(option SingMuxOption, proxy C.ProxyAdapter, base ProxyBase) (C.ProxyAdapter, error) {
// TODO
// "TCP Brutal is only supported on Linux-based systems"
singDialer := proxydialer.NewSingDialer(proxy, dialer.NewDialer(), option.Statistic) singDialer := proxydialer.NewSingDialer(proxy, dialer.NewDialer(), option.Statistic)
client, err := mux.NewClient(mux.Options{ client, err := mux.NewClient(mux.Options{
Dialer: singDialer, Dialer: singDialer,
Logger: log.SingLogger,
Protocol: option.Protocol, Protocol: option.Protocol,
MaxConnections: option.MaxConnections, MaxConnections: option.MaxConnections,
MinStreams: option.MinStreams, MinStreams: option.MinStreams,
MaxStreams: option.MaxStreams, MaxStreams: option.MaxStreams,
Padding: option.Padding, Padding: option.Padding,
Brutal: mux.BrutalOptions{
Enabled: option.BrutalOpts.Enabled,
SendBPS: StringToBps(option.BrutalOpts.Up),
ReceiveBPS: StringToBps(option.BrutalOpts.Down),
},
}) })
if err != nil { if err != nil {
return nil, err return nil, err

View File

@@ -6,13 +6,13 @@ import (
"net" "net"
"strconv" "strconv"
N "github.com/Dreamacro/clash/common/net" N "github.com/metacubex/mihomo/common/net"
"github.com/Dreamacro/clash/common/structure" "github.com/metacubex/mihomo/common/structure"
"github.com/Dreamacro/clash/component/dialer" "github.com/metacubex/mihomo/component/dialer"
"github.com/Dreamacro/clash/component/proxydialer" "github.com/metacubex/mihomo/component/proxydialer"
C "github.com/Dreamacro/clash/constant" C "github.com/metacubex/mihomo/constant"
obfs "github.com/Dreamacro/clash/transport/simple-obfs" obfs "github.com/metacubex/mihomo/transport/simple-obfs"
"github.com/Dreamacro/clash/transport/snell" "github.com/metacubex/mihomo/transport/snell"
) )
type Snell struct { type Snell struct {

View File

@@ -7,14 +7,15 @@ import (
"fmt" "fmt"
"io" "io"
"net" "net"
"net/netip"
"strconv" "strconv"
N "github.com/Dreamacro/clash/common/net" N "github.com/metacubex/mihomo/common/net"
"github.com/Dreamacro/clash/component/ca" "github.com/metacubex/mihomo/component/ca"
"github.com/Dreamacro/clash/component/dialer" "github.com/metacubex/mihomo/component/dialer"
"github.com/Dreamacro/clash/component/proxydialer" "github.com/metacubex/mihomo/component/proxydialer"
C "github.com/Dreamacro/clash/constant" C "github.com/metacubex/mihomo/constant"
"github.com/Dreamacro/clash/transport/socks5" "github.com/metacubex/mihomo/transport/socks5"
) )
type Socks5 struct { type Socks5 struct {
@@ -136,7 +137,8 @@ func (ss *Socks5) ListenPacketContext(ctx context.Context, metadata *C.Metadata,
} }
} }
bindAddr, err := socks5.ClientHandshake(c, serializesSocksAddr(metadata), socks5.CmdUDPAssociate, user) udpAssocateAddr := socks5.AddrFromStdAddrPort(netip.AddrPortFrom(netip.IPv4Unspecified(), 0))
bindAddr, err := socks5.ClientHandshake(c, udpAssocateAddr, socks5.CmdUDPAssociate, user)
if err != nil { if err != nil {
err = fmt.Errorf("client hanshake error: %w", err) err = fmt.Errorf("client hanshake error: %w", err)
return return

View File

@@ -8,14 +8,14 @@ import (
"net/http" "net/http"
"strconv" "strconv"
N "github.com/Dreamacro/clash/common/net" N "github.com/metacubex/mihomo/common/net"
"github.com/Dreamacro/clash/component/ca" "github.com/metacubex/mihomo/component/ca"
"github.com/Dreamacro/clash/component/dialer" "github.com/metacubex/mihomo/component/dialer"
"github.com/Dreamacro/clash/component/proxydialer" "github.com/metacubex/mihomo/component/proxydialer"
tlsC "github.com/Dreamacro/clash/component/tls" tlsC "github.com/metacubex/mihomo/component/tls"
C "github.com/Dreamacro/clash/constant" C "github.com/metacubex/mihomo/constant"
"github.com/Dreamacro/clash/transport/gun" "github.com/metacubex/mihomo/transport/gun"
"github.com/Dreamacro/clash/transport/trojan" "github.com/metacubex/mihomo/transport/trojan"
) )
type Trojan struct { type Trojan struct {
@@ -53,9 +53,12 @@ func (t *Trojan) plainStream(ctx context.Context, c net.Conn) (net.Conn, error)
if t.option.Network == "ws" { if t.option.Network == "ws" {
host, port, _ := net.SplitHostPort(t.addr) host, port, _ := net.SplitHostPort(t.addr)
wsOpts := &trojan.WebsocketOption{ wsOpts := &trojan.WebsocketOption{
Host: host, Host: host,
Port: port, Port: port,
Path: t.option.WSOpts.Path, Path: t.option.WSOpts.Path,
V2rayHttpUpgrade: t.option.WSOpts.V2rayHttpUpgrade,
V2rayHttpUpgradeFastOpen: t.option.WSOpts.V2rayHttpUpgradeFastOpen,
Headers: http.Header{},
} }
if t.option.SNI != "" { if t.option.SNI != "" {
@@ -63,11 +66,9 @@ func (t *Trojan) plainStream(ctx context.Context, c net.Conn) (net.Conn, error)
} }
if len(t.option.WSOpts.Headers) != 0 { if len(t.option.WSOpts.Headers) != 0 {
header := http.Header{}
for key, value := range t.option.WSOpts.Headers { for key, value := range t.option.WSOpts.Headers {
header.Add(key, value) wsOpts.Headers.Add(key, value)
} }
wsOpts.Headers = header
} }
return t.instance.StreamWebsocketConn(ctx, c, wsOpts) return t.instance.StreamWebsocketConn(ctx, c, wsOpts)

View File

@@ -10,12 +10,12 @@ import (
"strconv" "strconv"
"time" "time"
"github.com/Dreamacro/clash/component/ca" "github.com/metacubex/mihomo/component/ca"
"github.com/Dreamacro/clash/component/dialer" "github.com/metacubex/mihomo/component/dialer"
"github.com/Dreamacro/clash/component/proxydialer" "github.com/metacubex/mihomo/component/proxydialer"
"github.com/Dreamacro/clash/component/resolver" "github.com/metacubex/mihomo/component/resolver"
C "github.com/Dreamacro/clash/constant" C "github.com/metacubex/mihomo/constant"
"github.com/Dreamacro/clash/transport/tuic" "github.com/metacubex/mihomo/transport/tuic"
"github.com/gofrs/uuid/v5" "github.com/gofrs/uuid/v5"
"github.com/metacubex/quic-go" "github.com/metacubex/quic-go"

View File

@@ -11,9 +11,9 @@ import (
"strconv" "strconv"
"sync" "sync"
"github.com/Dreamacro/clash/component/resolver" "github.com/metacubex/mihomo/component/resolver"
C "github.com/Dreamacro/clash/constant" C "github.com/metacubex/mihomo/constant"
"github.com/Dreamacro/clash/transport/socks5" "github.com/metacubex/mihomo/transport/socks5"
) )
var ( var (

View File

@@ -12,20 +12,20 @@ import (
"strconv" "strconv"
"sync" "sync"
"github.com/Dreamacro/clash/common/convert" "github.com/metacubex/mihomo/common/convert"
N "github.com/Dreamacro/clash/common/net" N "github.com/metacubex/mihomo/common/net"
"github.com/Dreamacro/clash/common/utils" "github.com/metacubex/mihomo/common/utils"
"github.com/Dreamacro/clash/component/ca" "github.com/metacubex/mihomo/component/ca"
"github.com/Dreamacro/clash/component/dialer" "github.com/metacubex/mihomo/component/dialer"
"github.com/Dreamacro/clash/component/proxydialer" "github.com/metacubex/mihomo/component/proxydialer"
"github.com/Dreamacro/clash/component/resolver" "github.com/metacubex/mihomo/component/resolver"
tlsC "github.com/Dreamacro/clash/component/tls" tlsC "github.com/metacubex/mihomo/component/tls"
C "github.com/Dreamacro/clash/constant" C "github.com/metacubex/mihomo/constant"
"github.com/Dreamacro/clash/log" "github.com/metacubex/mihomo/log"
"github.com/Dreamacro/clash/transport/gun" "github.com/metacubex/mihomo/transport/gun"
"github.com/Dreamacro/clash/transport/socks5" "github.com/metacubex/mihomo/transport/socks5"
"github.com/Dreamacro/clash/transport/vless" "github.com/metacubex/mihomo/transport/vless"
"github.com/Dreamacro/clash/transport/vmess" "github.com/metacubex/mihomo/transport/vmess"
vmessSing "github.com/metacubex/sing-vmess" vmessSing "github.com/metacubex/sing-vmess"
"github.com/metacubex/sing-vmess/packetaddr" "github.com/metacubex/sing-vmess/packetaddr"
@@ -88,13 +88,15 @@ func (v *Vless) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.M
case "ws": case "ws":
host, port, _ := net.SplitHostPort(v.addr) host, port, _ := net.SplitHostPort(v.addr)
wsOpts := &vmess.WebsocketConfig{ wsOpts := &vmess.WebsocketConfig{
Host: host, Host: host,
Port: port, Port: port,
Path: v.option.WSOpts.Path, Path: v.option.WSOpts.Path,
MaxEarlyData: v.option.WSOpts.MaxEarlyData, MaxEarlyData: v.option.WSOpts.MaxEarlyData,
EarlyDataHeaderName: v.option.WSOpts.EarlyDataHeaderName, EarlyDataHeaderName: v.option.WSOpts.EarlyDataHeaderName,
ClientFingerprint: v.option.ClientFingerprint, V2rayHttpUpgrade: v.option.WSOpts.V2rayHttpUpgrade,
Headers: http.Header{}, V2rayHttpUpgradeFastOpen: v.option.WSOpts.V2rayHttpUpgradeFastOpen,
ClientFingerprint: v.option.ClientFingerprint,
Headers: http.Header{},
} }
if len(v.option.WSOpts.Headers) != 0 { if len(v.option.WSOpts.Headers) != 0 {

View File

@@ -11,17 +11,17 @@ import (
"strings" "strings"
"sync" "sync"
N "github.com/Dreamacro/clash/common/net" N "github.com/metacubex/mihomo/common/net"
"github.com/Dreamacro/clash/common/utils" "github.com/metacubex/mihomo/common/utils"
"github.com/Dreamacro/clash/component/ca" "github.com/metacubex/mihomo/component/ca"
"github.com/Dreamacro/clash/component/dialer" "github.com/metacubex/mihomo/component/dialer"
"github.com/Dreamacro/clash/component/proxydialer" "github.com/metacubex/mihomo/component/proxydialer"
"github.com/Dreamacro/clash/component/resolver" "github.com/metacubex/mihomo/component/resolver"
tlsC "github.com/Dreamacro/clash/component/tls" tlsC "github.com/metacubex/mihomo/component/tls"
C "github.com/Dreamacro/clash/constant" C "github.com/metacubex/mihomo/constant"
"github.com/Dreamacro/clash/ntp" "github.com/metacubex/mihomo/ntp"
"github.com/Dreamacro/clash/transport/gun" "github.com/metacubex/mihomo/transport/gun"
clashVMess "github.com/Dreamacro/clash/transport/vmess" mihomoVMess "github.com/metacubex/mihomo/transport/vmess"
vmess "github.com/metacubex/sing-vmess" vmess "github.com/metacubex/sing-vmess"
"github.com/metacubex/sing-vmess/packetaddr" "github.com/metacubex/sing-vmess/packetaddr"
@@ -87,10 +87,12 @@ type GrpcOptions struct {
} }
type WSOptions struct { type WSOptions struct {
Path string `proxy:"path,omitempty"` Path string `proxy:"path,omitempty"`
Headers map[string]string `proxy:"headers,omitempty"` Headers map[string]string `proxy:"headers,omitempty"`
MaxEarlyData int `proxy:"max-early-data,omitempty"` MaxEarlyData int `proxy:"max-early-data,omitempty"`
EarlyDataHeaderName string `proxy:"early-data-header-name,omitempty"` EarlyDataHeaderName string `proxy:"early-data-header-name,omitempty"`
V2rayHttpUpgrade bool `proxy:"v2ray-http-upgrade,omitempty"`
V2rayHttpUpgradeFastOpen bool `proxy:"v2ray-http-upgrade-fast-open,omitempty"`
} }
// StreamConnContext implements C.ProxyAdapter // StreamConnContext implements C.ProxyAdapter
@@ -104,14 +106,16 @@ func (v *Vmess) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.M
switch v.option.Network { switch v.option.Network {
case "ws": case "ws":
host, port, _ := net.SplitHostPort(v.addr) host, port, _ := net.SplitHostPort(v.addr)
wsOpts := &clashVMess.WebsocketConfig{ wsOpts := &mihomoVMess.WebsocketConfig{
Host: host, Host: host,
Port: port, Port: port,
Path: v.option.WSOpts.Path, Path: v.option.WSOpts.Path,
MaxEarlyData: v.option.WSOpts.MaxEarlyData, MaxEarlyData: v.option.WSOpts.MaxEarlyData,
EarlyDataHeaderName: v.option.WSOpts.EarlyDataHeaderName, EarlyDataHeaderName: v.option.WSOpts.EarlyDataHeaderName,
ClientFingerprint: v.option.ClientFingerprint, V2rayHttpUpgrade: v.option.WSOpts.V2rayHttpUpgrade,
Headers: http.Header{}, V2rayHttpUpgradeFastOpen: v.option.WSOpts.V2rayHttpUpgradeFastOpen,
ClientFingerprint: v.option.ClientFingerprint,
Headers: http.Header{},
} }
if len(v.option.WSOpts.Headers) != 0 { if len(v.option.WSOpts.Headers) != 0 {
@@ -139,12 +143,12 @@ func (v *Vmess) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.M
wsOpts.TLSConfig.ServerName = host wsOpts.TLSConfig.ServerName = host
} }
} }
c, err = clashVMess.StreamWebsocketConn(ctx, c, wsOpts) c, err = mihomoVMess.StreamWebsocketConn(ctx, c, wsOpts)
case "http": case "http":
// readability first, so just copy default TLS logic // readability first, so just copy default TLS logic
if v.option.TLS { if v.option.TLS {
host, _, _ := net.SplitHostPort(v.addr) host, _, _ := net.SplitHostPort(v.addr)
tlsOpts := &clashVMess.TLSConfig{ tlsOpts := &mihomoVMess.TLSConfig{
Host: host, Host: host,
SkipCertVerify: v.option.SkipCertVerify, SkipCertVerify: v.option.SkipCertVerify,
ClientFingerprint: v.option.ClientFingerprint, ClientFingerprint: v.option.ClientFingerprint,
@@ -155,24 +159,24 @@ func (v *Vmess) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.M
if v.option.ServerName != "" { if v.option.ServerName != "" {
tlsOpts.Host = v.option.ServerName tlsOpts.Host = v.option.ServerName
} }
c, err = clashVMess.StreamTLSConn(ctx, c, tlsOpts) c, err = mihomoVMess.StreamTLSConn(ctx, c, tlsOpts)
if err != nil { if err != nil {
return nil, err return nil, err
} }
} }
host, _, _ := net.SplitHostPort(v.addr) host, _, _ := net.SplitHostPort(v.addr)
httpOpts := &clashVMess.HTTPConfig{ httpOpts := &mihomoVMess.HTTPConfig{
Host: host, Host: host,
Method: v.option.HTTPOpts.Method, Method: v.option.HTTPOpts.Method,
Path: v.option.HTTPOpts.Path, Path: v.option.HTTPOpts.Path,
Headers: v.option.HTTPOpts.Headers, Headers: v.option.HTTPOpts.Headers,
} }
c = clashVMess.StreamHTTPConn(c, httpOpts) c = mihomoVMess.StreamHTTPConn(c, httpOpts)
case "h2": case "h2":
host, _, _ := net.SplitHostPort(v.addr) host, _, _ := net.SplitHostPort(v.addr)
tlsOpts := clashVMess.TLSConfig{ tlsOpts := mihomoVMess.TLSConfig{
Host: host, Host: host,
SkipCertVerify: v.option.SkipCertVerify, SkipCertVerify: v.option.SkipCertVerify,
NextProtos: []string{"h2"}, NextProtos: []string{"h2"},
@@ -184,24 +188,24 @@ func (v *Vmess) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.M
tlsOpts.Host = v.option.ServerName tlsOpts.Host = v.option.ServerName
} }
c, err = clashVMess.StreamTLSConn(ctx, c, &tlsOpts) c, err = mihomoVMess.StreamTLSConn(ctx, c, &tlsOpts)
if err != nil { if err != nil {
return nil, err return nil, err
} }
h2Opts := &clashVMess.H2Config{ h2Opts := &mihomoVMess.H2Config{
Hosts: v.option.HTTP2Opts.Host, Hosts: v.option.HTTP2Opts.Host,
Path: v.option.HTTP2Opts.Path, Path: v.option.HTTP2Opts.Path,
} }
c, err = clashVMess.StreamH2Conn(c, h2Opts) c, err = mihomoVMess.StreamH2Conn(c, h2Opts)
case "grpc": case "grpc":
c, err = gun.StreamGunWithConn(c, v.gunTLSConfig, v.gunConfig, v.realityConfig) c, err = gun.StreamGunWithConn(c, v.gunTLSConfig, v.gunConfig, v.realityConfig)
default: default:
// handle TLS // handle TLS
if v.option.TLS { if v.option.TLS {
host, _, _ := net.SplitHostPort(v.addr) host, _, _ := net.SplitHostPort(v.addr)
tlsOpts := &clashVMess.TLSConfig{ tlsOpts := &mihomoVMess.TLSConfig{
Host: host, Host: host,
SkipCertVerify: v.option.SkipCertVerify, SkipCertVerify: v.option.SkipCertVerify,
ClientFingerprint: v.option.ClientFingerprint, ClientFingerprint: v.option.ClientFingerprint,
@@ -213,7 +217,7 @@ func (v *Vmess) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.M
tlsOpts.Host = v.option.ServerName tlsOpts.Host = v.option.ServerName
} }
c, err = clashVMess.StreamTLSConn(ctx, c, tlsOpts) c, err = mihomoVMess.StreamTLSConn(ctx, c, tlsOpts)
} }
} }
@@ -260,10 +264,10 @@ func (v *Vmess) streamConn(c net.Conn, metadata *C.Metadata) (conn net.Conn, err
} else { } else {
if N.NeedHandshake(c) { if N.NeedHandshake(c) {
conn = v.client.DialEarlyConn(c, conn = v.client.DialEarlyConn(c,
M.ParseSocksaddr(metadata.RemoteAddress())) M.ParseSocksaddrHostPort(metadata.String(), metadata.DstPort))
} else { } else {
conn, err = v.client.DialConn(c, conn, err = v.client.DialConn(c,
M.ParseSocksaddr(metadata.RemoteAddress())) M.ParseSocksaddrHostPort(metadata.String(), metadata.DstPort))
} }
} }
if err != nil { if err != nil {
@@ -284,7 +288,7 @@ func (v *Vmess) DialContext(ctx context.Context, metadata *C.Metadata, opts ...d
safeConnClose(c, err) safeConnClose(c, err)
}(c) }(c)
c, err = v.client.DialConn(c, M.ParseSocksaddr(metadata.RemoteAddress())) c, err = v.client.DialConn(c, M.ParseSocksaddrHostPort(metadata.String(), metadata.DstPort))
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@@ -13,13 +13,13 @@ import (
"strings" "strings"
"sync" "sync"
CN "github.com/Dreamacro/clash/common/net" CN "github.com/metacubex/mihomo/common/net"
"github.com/Dreamacro/clash/component/dialer" "github.com/metacubex/mihomo/component/dialer"
"github.com/Dreamacro/clash/component/proxydialer" "github.com/metacubex/mihomo/component/proxydialer"
"github.com/Dreamacro/clash/component/resolver" "github.com/metacubex/mihomo/component/resolver"
C "github.com/Dreamacro/clash/constant" C "github.com/metacubex/mihomo/constant"
"github.com/Dreamacro/clash/dns" "github.com/metacubex/mihomo/dns"
"github.com/Dreamacro/clash/log" "github.com/metacubex/mihomo/log"
wireguard "github.com/metacubex/sing-wireguard" wireguard "github.com/metacubex/sing-wireguard"

View File

@@ -6,13 +6,13 @@ import (
"errors" "errors"
"time" "time"
"github.com/Dreamacro/clash/adapter/outbound" "github.com/metacubex/mihomo/adapter/outbound"
"github.com/Dreamacro/clash/common/callback" "github.com/metacubex/mihomo/common/callback"
N "github.com/Dreamacro/clash/common/net" N "github.com/metacubex/mihomo/common/net"
"github.com/Dreamacro/clash/common/utils" "github.com/metacubex/mihomo/common/utils"
"github.com/Dreamacro/clash/component/dialer" "github.com/metacubex/mihomo/component/dialer"
C "github.com/Dreamacro/clash/constant" C "github.com/metacubex/mihomo/constant"
"github.com/Dreamacro/clash/constant/provider" "github.com/metacubex/mihomo/constant/provider"
) )
type Fallback struct { type Fallback struct {
@@ -84,11 +84,11 @@ func (f *Fallback) MarshalJSON() ([]byte, error) {
all = append(all, proxy.Name()) all = append(all, proxy.Name())
} }
return json.Marshal(map[string]any{ return json.Marshal(map[string]any{
"type": f.Type().String(), "type": f.Type().String(),
"now": f.Now(), "now": f.Now(),
"all": all, "all": all,
"testUrl": f.testUrl, "testUrl": f.testUrl,
"expected": f.expectedStatus, "expectedStatus": f.expectedStatus,
}) })
} }
@@ -102,13 +102,11 @@ func (f *Fallback) findAliveProxy(touch bool) C.Proxy {
proxies := f.GetProxies(touch) proxies := f.GetProxies(touch)
for _, proxy := range proxies { for _, proxy := range proxies {
if len(f.selected) == 0 { if len(f.selected) == 0 {
// if proxy.Alive() {
if proxy.AliveForTestUrl(f.testUrl) { if proxy.AliveForTestUrl(f.testUrl) {
return proxy return proxy
} }
} else { } else {
if proxy.Name() == f.selected { if proxy.Name() == f.selected {
// if proxy.Alive() {
if proxy.AliveForTestUrl(f.testUrl) { if proxy.AliveForTestUrl(f.testUrl) {
return proxy return proxy
} else { } else {
@@ -135,12 +133,11 @@ func (f *Fallback) Set(name string) error {
} }
f.selected = name f.selected = name
// if !p.Alive() {
if !p.AliveForTestUrl(f.testUrl) { if !p.AliveForTestUrl(f.testUrl) {
ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond*time.Duration(5000)) ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond*time.Duration(5000))
defer cancel() defer cancel()
expectedStatus, _ := utils.NewIntRanges[uint16](f.expectedStatus) expectedStatus, _ := utils.NewIntRanges[uint16](f.expectedStatus)
_, _ = p.URLTest(ctx, f.testUrl, expectedStatus, C.ExtraHistory) _, _ = p.URLTest(ctx, f.testUrl, expectedStatus)
} }
return nil return nil

View File

@@ -7,14 +7,14 @@ import (
"sync" "sync"
"time" "time"
"github.com/Dreamacro/clash/adapter/outbound" "github.com/metacubex/mihomo/adapter/outbound"
"github.com/Dreamacro/clash/common/atomic" "github.com/metacubex/mihomo/common/atomic"
"github.com/Dreamacro/clash/common/utils" "github.com/metacubex/mihomo/common/utils"
C "github.com/Dreamacro/clash/constant" C "github.com/metacubex/mihomo/constant"
"github.com/Dreamacro/clash/constant/provider" "github.com/metacubex/mihomo/constant/provider"
types "github.com/Dreamacro/clash/constant/provider" types "github.com/metacubex/mihomo/constant/provider"
"github.com/Dreamacro/clash/log" "github.com/metacubex/mihomo/log"
"github.com/Dreamacro/clash/tunnel" "github.com/metacubex/mihomo/tunnel"
"github.com/dlclark/regexp2" "github.com/dlclark/regexp2"
) )
@@ -28,7 +28,7 @@ type GroupBase struct {
failedTestMux sync.Mutex failedTestMux sync.Mutex
failedTimes int failedTimes int
failedTime time.Time failedTime time.Time
failedTesting *atomic.Bool failedTesting atomic.Bool
proxies [][]C.Proxy proxies [][]C.Proxy
versions []atomic.Uint32 versions []atomic.Uint32
} }
@@ -202,7 +202,7 @@ func (gb *GroupBase) URLTest(ctx context.Context, url string, expectedStatus uti
proxy := proxy proxy := proxy
wg.Add(1) wg.Add(1)
go func() { go func() {
delay, err := proxy.URLTest(ctx, url, expectedStatus, C.DropHistory) delay, err := proxy.URLTest(ctx, url, expectedStatus)
if err == nil { if err == nil {
lock.Lock() lock.Lock()
mp[proxy.Name()] = delay mp[proxy.Name()] = delay
@@ -222,7 +222,7 @@ func (gb *GroupBase) URLTest(ctx context.Context, url string, expectedStatus uti
} }
func (gb *GroupBase) onDialFailed(adapterType C.AdapterType, err error) { func (gb *GroupBase) onDialFailed(adapterType C.AdapterType, err error) {
if adapterType == C.Direct || adapterType == C.Compatible || adapterType == C.Reject || adapterType == C.Pass { if adapterType == C.Direct || adapterType == C.Compatible || adapterType == C.Reject || adapterType == C.Pass || adapterType == C.RejectDrop {
return return
} }

View File

@@ -9,14 +9,14 @@ import (
"sync" "sync"
"time" "time"
"github.com/Dreamacro/clash/adapter/outbound" "github.com/metacubex/mihomo/adapter/outbound"
"github.com/Dreamacro/clash/common/cache" "github.com/metacubex/mihomo/common/callback"
"github.com/Dreamacro/clash/common/callback" "github.com/metacubex/mihomo/common/lru"
N "github.com/Dreamacro/clash/common/net" N "github.com/metacubex/mihomo/common/net"
"github.com/Dreamacro/clash/common/utils" "github.com/metacubex/mihomo/common/utils"
"github.com/Dreamacro/clash/component/dialer" "github.com/metacubex/mihomo/component/dialer"
C "github.com/Dreamacro/clash/constant" C "github.com/metacubex/mihomo/constant"
"github.com/Dreamacro/clash/constant/provider" "github.com/metacubex/mihomo/constant/provider"
"golang.org/x/net/publicsuffix" "golang.org/x/net/publicsuffix"
) )
@@ -190,9 +190,9 @@ func strategyConsistentHashing(url string) strategyFn {
func strategyStickySessions(url string) strategyFn { func strategyStickySessions(url string) strategyFn {
ttl := time.Minute * 10 ttl := time.Minute * 10
maxRetry := 5 maxRetry := 5
lruCache := cache.New[uint64, int]( lruCache := lru.New[uint64, int](
cache.WithAge[uint64, int](int64(ttl.Seconds())), lru.WithAge[uint64, int](int64(ttl.Seconds())),
cache.WithSize[uint64, int](1000)) lru.WithSize[uint64, int](1000))
return func(proxies []C.Proxy, metadata *C.Metadata, touch bool) C.Proxy { return func(proxies []C.Proxy, metadata *C.Metadata, touch bool) C.Proxy {
key := utils.MapHash(getKeyWithSrcAndDst(metadata)) key := utils.MapHash(getKeyWithSrcAndDst(metadata))
length := len(proxies) length := len(proxies)

View File

@@ -5,12 +5,12 @@ import (
"fmt" "fmt"
"strings" "strings"
"github.com/Dreamacro/clash/adapter/outbound" "github.com/metacubex/mihomo/adapter/outbound"
"github.com/Dreamacro/clash/adapter/provider" "github.com/metacubex/mihomo/adapter/provider"
"github.com/Dreamacro/clash/common/structure" "github.com/metacubex/mihomo/common/structure"
"github.com/Dreamacro/clash/common/utils" "github.com/metacubex/mihomo/common/utils"
C "github.com/Dreamacro/clash/constant" C "github.com/metacubex/mihomo/constant"
types "github.com/Dreamacro/clash/constant/provider" types "github.com/metacubex/mihomo/constant/provider"
) )
var ( var (
@@ -22,18 +22,19 @@ var (
type GroupCommonOption struct { type GroupCommonOption struct {
outbound.BasicOption outbound.BasicOption
Name string `group:"name"` Name string `group:"name"`
Type string `group:"type"` Type string `group:"type"`
Proxies []string `group:"proxies,omitempty"` Proxies []string `group:"proxies,omitempty"`
Use []string `group:"use,omitempty"` Use []string `group:"use,omitempty"`
URL string `group:"url,omitempty"` URL string `group:"url,omitempty"`
Interval int `group:"interval,omitempty"` Interval int `group:"interval,omitempty"`
Lazy bool `group:"lazy,omitempty"` Lazy bool `group:"lazy,omitempty"`
DisableUDP bool `group:"disable-udp,omitempty"` DisableUDP bool `group:"disable-udp,omitempty"`
Filter string `group:"filter,omitempty"` Filter string `group:"filter,omitempty"`
ExcludeFilter string `group:"exclude-filter,omitempty"` ExcludeFilter string `group:"exclude-filter,omitempty"`
ExcludeType string `group:"exclude-type,omitempty"` ExcludeType string `group:"exclude-type,omitempty"`
ExpectedStatus string `group:"expected-status,omitempty"` ExpectedStatus string `group:"expected-status,omitempty"`
IncludeAllProviders bool `group:"include-all-providers,omitempty"`
} }
func ParseProxyGroup(config map[string]any, 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) {
@@ -54,7 +55,18 @@ func ParseProxyGroup(config map[string]any, proxyMap map[string]C.Proxy, provide
providers := []types.ProxyProvider{} providers := []types.ProxyProvider{}
if len(groupOption.Proxies) == 0 && len(groupOption.Use) == 0 { var GroupUse []string
visited := make(map[string]bool)
if groupOption.IncludeAllProviders {
for name := range provider.ProxyProviderName {
GroupUse = append(GroupUse, name)
visited[name] = true
}
} else {
GroupUse = groupOption.Use
}
if len(groupOption.Proxies) == 0 && len(GroupUse) == 0 {
return nil, fmt.Errorf("%s: %w", groupName, errMissProxy) return nil, fmt.Errorf("%s: %w", groupName, errMissProxy)
} }
@@ -80,24 +92,20 @@ func ParseProxyGroup(config map[string]any, proxyMap map[string]C.Proxy, provide
return nil, fmt.Errorf("%s: %w", groupName, errDuplicateProvider) return nil, fmt.Errorf("%s: %w", groupName, errDuplicateProvider)
} }
var url string
var interval uint
// select don't need health check // select don't need health check
if groupOption.Type != "select" && groupOption.Type != "relay" { if groupOption.Type != "select" && groupOption.Type != "relay" {
if groupOption.URL == "" { if groupOption.URL == "" {
groupOption.URL = "https://cp.cloudflare.com/generate_204" groupOption.URL = C.DefaultTestURL
testUrl = groupOption.URL
} }
if groupOption.Interval == 0 { if groupOption.Interval == 0 {
groupOption.Interval = 300 groupOption.Interval = 300
} }
url = groupOption.URL
interval = uint(groupOption.Interval)
} }
hc := provider.NewHealthCheck(ps, url, interval, true, expectedStatus) hc := provider.NewHealthCheck(ps, groupOption.URL, uint(groupOption.Interval), true, expectedStatus)
pd, err := provider.NewCompatibleProvider(groupName, ps, hc) pd, err := provider.NewCompatibleProvider(groupName, ps, hc)
if err != nil { if err != nil {
return nil, fmt.Errorf("%s: %w", groupName, err) return nil, fmt.Errorf("%s: %w", groupName, err)
@@ -107,8 +115,8 @@ func ParseProxyGroup(config map[string]any, proxyMap map[string]C.Proxy, provide
providersMap[groupName] = pd providersMap[groupName] = pd
} }
if len(groupOption.Use) != 0 { if len(GroupUse) != 0 {
list, err := getProviders(providersMap, groupOption.Use) list, err := getProviders(providersMap, GroupUse)
if err != nil { if err != nil {
return nil, fmt.Errorf("%s: %w", groupName, err) return nil, fmt.Errorf("%s: %w", groupName, err)
} }

View File

@@ -0,0 +1,64 @@
//go:build android && cmfa
package outboundgroup
import (
C "github.com/metacubex/mihomo/constant"
"github.com/metacubex/mihomo/constant/provider"
)
type ProxyGroup interface {
C.ProxyAdapter
Providers() []provider.ProxyProvider
Proxies() []C.Proxy
Now() string
}
func (f *Fallback) Providers() []provider.ProxyProvider {
return f.providers
}
func (lb *LoadBalance) Providers() []provider.ProxyProvider {
return lb.providers
}
func (f *Fallback) Proxies() []C.Proxy {
return f.GetProxies(false)
}
func (lb *LoadBalance) Proxies() []C.Proxy {
return lb.GetProxies(false)
}
func (lb *LoadBalance) Now() string {
return ""
}
func (r *Relay) Providers() []provider.ProxyProvider {
return r.providers
}
func (r *Relay) Proxies() []C.Proxy {
return r.GetProxies(false)
}
func (r *Relay) Now() string {
return ""
}
func (s *Selector) Providers() []provider.ProxyProvider {
return s.providers
}
func (s *Selector) Proxies() []C.Proxy {
return s.GetProxies(false)
}
func (u *URLTest) Providers() []provider.ProxyProvider {
return u.providers
}
func (u *URLTest) Proxies() []C.Proxy {
return u.GetProxies(false)
}

View File

@@ -3,11 +3,11 @@ package outboundgroup
import ( import (
"context" "context"
"encoding/json" "encoding/json"
"github.com/Dreamacro/clash/adapter/outbound" "github.com/metacubex/mihomo/adapter/outbound"
"github.com/Dreamacro/clash/component/dialer" "github.com/metacubex/mihomo/component/dialer"
"github.com/Dreamacro/clash/component/proxydialer" "github.com/metacubex/mihomo/component/proxydialer"
C "github.com/Dreamacro/clash/constant" C "github.com/metacubex/mihomo/constant"
"github.com/Dreamacro/clash/constant/provider" "github.com/metacubex/mihomo/constant/provider"
) )
type Relay struct { type Relay struct {

View File

@@ -5,10 +5,10 @@ import (
"encoding/json" "encoding/json"
"errors" "errors"
"github.com/Dreamacro/clash/adapter/outbound" "github.com/metacubex/mihomo/adapter/outbound"
"github.com/Dreamacro/clash/component/dialer" "github.com/metacubex/mihomo/component/dialer"
C "github.com/Dreamacro/clash/constant" C "github.com/metacubex/mihomo/constant"
"github.com/Dreamacro/clash/constant/provider" "github.com/metacubex/mihomo/constant/provider"
) )
type Selector struct { type Selector struct {

View File

@@ -6,13 +6,13 @@ import (
"errors" "errors"
"time" "time"
"github.com/Dreamacro/clash/adapter/outbound" "github.com/metacubex/mihomo/adapter/outbound"
"github.com/Dreamacro/clash/common/callback" "github.com/metacubex/mihomo/common/callback"
N "github.com/Dreamacro/clash/common/net" N "github.com/metacubex/mihomo/common/net"
"github.com/Dreamacro/clash/common/singledo" "github.com/metacubex/mihomo/common/singledo"
"github.com/Dreamacro/clash/component/dialer" "github.com/metacubex/mihomo/component/dialer"
C "github.com/Dreamacro/clash/constant" C "github.com/metacubex/mihomo/constant"
"github.com/Dreamacro/clash/constant/provider" "github.com/metacubex/mihomo/constant/provider"
) )
type urlTestOption func(*URLTest) type urlTestOption func(*URLTest)
@@ -101,7 +101,7 @@ func (u *URLTest) fast(touch bool) C.Proxy {
proxies := u.GetProxies(touch) proxies := u.GetProxies(touch)
if u.selected != "" { if u.selected != "" {
for _, proxy := range proxies { for _, proxy := range proxies {
if !proxy.Alive() { if !proxy.AliveForTestUrl(u.testUrl) {
continue continue
} }
if proxy.Name() == u.selected { if proxy.Name() == u.selected {
@@ -113,7 +113,6 @@ func (u *URLTest) fast(touch bool) C.Proxy {
elm, _, shared := u.fastSingle.Do(func() (C.Proxy, error) { elm, _, shared := u.fastSingle.Do(func() (C.Proxy, error) {
fast := proxies[0] fast := proxies[0]
// min := fast.LastDelay()
min := fast.LastDelayForTestUrl(u.testUrl) min := fast.LastDelayForTestUrl(u.testUrl)
fastNotExist := true fastNotExist := true
@@ -122,12 +121,10 @@ func (u *URLTest) fast(touch bool) C.Proxy {
fastNotExist = false fastNotExist = false
} }
// if !proxy.Alive() {
if !proxy.AliveForTestUrl(u.testUrl) { if !proxy.AliveForTestUrl(u.testUrl) {
continue continue
} }
// delay := proxy.LastDelay()
delay := proxy.LastDelayForTestUrl(u.testUrl) delay := proxy.LastDelayForTestUrl(u.testUrl)
if delay < min { if delay < min {
fast = proxy fast = proxy
@@ -136,7 +133,6 @@ func (u *URLTest) fast(touch bool) C.Proxy {
} }
// tolerance // tolerance
// if u.fastNode == nil || fastNotExist || !u.fastNode.Alive() || u.fastNode.LastDelay() > fast.LastDelay()+u.tolerance {
if u.fastNode == nil || fastNotExist || !u.fastNode.AliveForTestUrl(u.testUrl) || u.fastNode.LastDelayForTestUrl(u.testUrl) > fast.LastDelayForTestUrl(u.testUrl)+u.tolerance { if u.fastNode == nil || fastNotExist || !u.fastNode.AliveForTestUrl(u.testUrl) || u.fastNode.LastDelayForTestUrl(u.testUrl) > fast.LastDelayForTestUrl(u.testUrl)+u.tolerance {
u.fastNode = fast u.fastNode = fast
} }
@@ -169,11 +165,11 @@ func (u *URLTest) MarshalJSON() ([]byte, error) {
all = append(all, proxy.Name()) all = append(all, proxy.Name())
} }
return json.Marshal(map[string]any{ return json.Marshal(map[string]any{
"type": u.Type().String(), "type": u.Type().String(),
"now": u.Now(), "now": u.Now(),
"all": all, "all": all,
"testUrl": u.testUrl, "testUrl": u.testUrl,
"expected": u.expectedStatus, "expectedStatus": u.expectedStatus,
}) })
} }

View File

@@ -3,11 +3,11 @@ package adapter
import ( import (
"fmt" "fmt"
tlsC "github.com/Dreamacro/clash/component/tls" tlsC "github.com/metacubex/mihomo/component/tls"
"github.com/Dreamacro/clash/adapter/outbound" "github.com/metacubex/mihomo/adapter/outbound"
"github.com/Dreamacro/clash/common/structure" "github.com/metacubex/mihomo/common/structure"
C "github.com/Dreamacro/clash/constant" C "github.com/metacubex/mihomo/constant"
) )
func ParseProxy(mapping map[string]any) (C.Proxy, error) { func ParseProxy(mapping map[string]any) (C.Proxy, error) {
@@ -120,6 +120,13 @@ func ParseProxy(mapping map[string]any) (C.Proxy, error) {
break break
} }
proxy = outbound.NewDirectWithOption(*directOption) proxy = outbound.NewDirectWithOption(*directOption)
case "reject":
rejectOption := &outbound.RejectOption{}
err = decoder.Decode(mapping, rejectOption)
if err != nil {
break
}
proxy = outbound.NewRejectWithOption(*rejectOption)
default: default:
return nil, fmt.Errorf("unsupport proxy type: %s", proxyType) return nil, fmt.Errorf("unsupport proxy type: %s", proxyType)
} }

View File

@@ -6,12 +6,12 @@ import (
"sync" "sync"
"time" "time"
"github.com/Dreamacro/clash/common/atomic" "github.com/metacubex/mihomo/common/atomic"
"github.com/Dreamacro/clash/common/batch" "github.com/metacubex/mihomo/common/batch"
"github.com/Dreamacro/clash/common/singledo" "github.com/metacubex/mihomo/common/singledo"
"github.com/Dreamacro/clash/common/utils" "github.com/metacubex/mihomo/common/utils"
C "github.com/Dreamacro/clash/constant" C "github.com/metacubex/mihomo/constant"
"github.com/Dreamacro/clash/log" "github.com/metacubex/mihomo/log"
"github.com/dlclark/regexp2" "github.com/dlclark/regexp2"
) )
@@ -34,12 +34,12 @@ type HealthCheck struct {
url string url string
extra map[string]*extraOption extra map[string]*extraOption
mu sync.Mutex mu sync.Mutex
started *atomic.Bool started atomic.Bool
proxies []C.Proxy proxies []C.Proxy
interval uint interval time.Duration
lazy bool lazy bool
expectedStatus utils.IntRanges[uint16] expectedStatus utils.IntRanges[uint16]
lastTouch *atomic.Int64 lastTouch atomic.TypedValue[time.Time]
done chan struct{} done chan struct{}
singleDo *singledo.Single[struct{}] singleDo *singledo.Single[struct{}]
} }
@@ -50,13 +50,14 @@ func (hc *HealthCheck) process() {
return return
} }
ticker := time.NewTicker(time.Duration(hc.interval) * time.Second) ticker := time.NewTicker(hc.interval)
hc.start() hc.start()
for { for {
select { select {
case <-ticker.C: case <-ticker.C:
now := time.Now().Unix() lastTouch := hc.lastTouch.Load()
if !hc.lazy || now-hc.lastTouch.Load() < int64(hc.interval) { since := time.Since(lastTouch)
if !hc.lazy || since < hc.interval {
hc.check() hc.check()
} else { } else {
log.Debugln("Skip once health check because we are lazy") log.Debugln("Skip once health check because we are lazy")
@@ -85,7 +86,7 @@ func (hc *HealthCheck) registerHealthCheckTask(url string, expectedStatus utils.
// if the provider has not set up health checks, then modify it to be the same as the group's interval // if the provider has not set up health checks, then modify it to be the same as the group's interval
if hc.interval == 0 { if hc.interval == 0 {
hc.interval = interval hc.interval = time.Duration(interval) * time.Second
} }
if hc.extra == nil { if hc.extra == nil {
@@ -103,12 +104,6 @@ func (hc *HealthCheck) registerHealthCheckTask(url string, expectedStatus utils.
return return
} }
// due to the time-consuming nature of health checks, a maximum of defaultMaxTestURLNum URLs can be set for testing
if len(hc.extra) > C.DefaultMaxHealthCheckUrlNum {
log.Debugln("skip add url: %s to health check because it has reached the maximum limit: %d", url, C.DefaultMaxHealthCheckUrlNum)
return
}
option := &extraOption{filters: map[string]struct{}{}, expectedStatus: expectedStatus} option := &extraOption{filters: map[string]struct{}{}, expectedStatus: expectedStatus}
splitAndAddFiltersToExtra(filter, option) splitAndAddFiltersToExtra(filter, option)
hc.extra[url] = option hc.extra[url] = option
@@ -135,7 +130,7 @@ func (hc *HealthCheck) auto() bool {
} }
func (hc *HealthCheck) touch() { func (hc *HealthCheck) touch() {
hc.lastTouch.Store(time.Now().Unix()) hc.lastTouch.Store(time.Now())
} }
func (hc *HealthCheck) start() { func (hc *HealthCheck) start() {
@@ -147,6 +142,10 @@ func (hc *HealthCheck) stop() {
} }
func (hc *HealthCheck) check() { func (hc *HealthCheck) check() {
if len(hc.proxies) == 0 {
return
}
_, _, _ = hc.singleDo.Do(func() (struct{}, error) { _, _, _ = hc.singleDo.Do(func() (struct{}, error) {
id := utils.NewUUIDV4().String() id := utils.NewUUIDV4().String()
log.Debugln("Start New Health Checking {%s}", id) log.Debugln("Start New Health Checking {%s}", id)
@@ -176,13 +175,8 @@ func (hc *HealthCheck) execute(b *batch.Batch[bool], url, uid string, option *ex
} }
var filterReg *regexp2.Regexp var filterReg *regexp2.Regexp
var store = C.OriginalHistory
var expectedStatus utils.IntRanges[uint16] var expectedStatus utils.IntRanges[uint16]
if option != nil { if option != nil {
if url != hc.url {
store = C.ExtraHistory
}
expectedStatus = option.expectedStatus expectedStatus = option.expectedStatus
if len(option.filters) != 0 { if len(option.filters) != 0 {
filters := make([]string, 0, len(option.filters)) filters := make([]string, 0, len(option.filters))
@@ -207,7 +201,7 @@ func (hc *HealthCheck) execute(b *batch.Batch[bool], url, uid string, option *ex
ctx, cancel := context.WithTimeout(context.Background(), defaultURLTestTimeout) ctx, cancel := context.WithTimeout(context.Background(), defaultURLTestTimeout)
defer cancel() defer cancel()
log.Debugln("Health Checking, proxy: %s, url: %s, id: {%s}", p.Name(), url, uid) log.Debugln("Health Checking, proxy: %s, url: %s, id: {%s}", p.Name(), url, uid)
_, _ = p.URLTest(ctx, url, expectedStatus, store) _, _ = p.URLTest(ctx, url, expectedStatus)
log.Debugln("Health Checked, proxy: %s, url: %s, alive: %t, delay: %d ms uid: {%s}", p.Name(), url, p.AliveForTestUrl(url), p.LastDelayForTestUrl(url), uid) log.Debugln("Health Checked, proxy: %s, url: %s, alive: %t, delay: %d ms uid: {%s}", p.Name(), url, p.AliveForTestUrl(url), p.LastDelayForTestUrl(url), uid)
return false, nil return false, nil
}) })
@@ -222,17 +216,16 @@ func NewHealthCheck(proxies []C.Proxy, url string, interval uint, lazy bool, exp
if len(url) == 0 { if len(url) == 0 {
interval = 0 interval = 0
expectedStatus = nil expectedStatus = nil
url = C.DefaultTestURL
} }
return &HealthCheck{ return &HealthCheck{
proxies: proxies, proxies: proxies,
url: url, url: url,
extra: map[string]*extraOption{}, extra: map[string]*extraOption{},
started: atomic.NewBool(false), interval: time.Duration(interval) * time.Second,
interval: interval,
lazy: lazy, lazy: lazy,
expectedStatus: expectedStatus, expectedStatus: expectedStatus,
lastTouch: atomic.NewInt64(0),
done: make(chan struct{}, 1), done: make(chan struct{}, 1),
singleDo: singledo.NewSingle[struct{}](time.Second), singleDo: singledo.NewSingle[struct{}](time.Second),
} }

View File

@@ -5,11 +5,12 @@ import (
"fmt" "fmt"
"time" "time"
"github.com/Dreamacro/clash/common/structure" "github.com/metacubex/mihomo/common/structure"
"github.com/Dreamacro/clash/common/utils" "github.com/metacubex/mihomo/common/utils"
"github.com/Dreamacro/clash/component/resource" "github.com/metacubex/mihomo/component/resource"
C "github.com/Dreamacro/clash/constant" C "github.com/metacubex/mihomo/constant"
types "github.com/Dreamacro/clash/constant/provider" "github.com/metacubex/mihomo/constant/features"
types "github.com/metacubex/mihomo/constant/provider"
) )
var ( var (
@@ -25,16 +26,26 @@ type healthCheckSchema struct {
ExpectedStatus string `provider:"expected-status,omitempty"` ExpectedStatus string `provider:"expected-status,omitempty"`
} }
type OverrideSchema struct {
UDP *bool `provider:"udp,omitempty"`
Up *string `provider:"up,omitempty"`
Down *string `provider:"down,omitempty"`
DialerProxy *string `provider:"dialer-proxy,omitempty"`
SkipCertVerify *bool `provider:"skip-cert-verify,omitempty"`
}
type proxyProviderSchema struct { type proxyProviderSchema struct {
Type string `provider:"type"` Type string `provider:"type"`
Path string `provider:"path,omitempty"` Path string `provider:"path,omitempty"`
URL string `provider:"url,omitempty"` URL string `provider:"url,omitempty"`
Interval int `provider:"interval,omitempty"` Interval int `provider:"interval,omitempty"`
Filter string `provider:"filter,omitempty"` Filter string `provider:"filter,omitempty"`
ExcludeFilter string `provider:"exclude-filter,omitempty"` ExcludeFilter string `provider:"exclude-filter,omitempty"`
ExcludeType string `provider:"exclude-type,omitempty"` ExcludeType string `provider:"exclude-type,omitempty"`
DialerProxy string `provider:"dialer-proxy,omitempty"` DialerProxy string `provider:"dialer-proxy,omitempty"`
HealthCheck healthCheckSchema `provider:"health-check,omitempty"`
HealthCheck healthCheckSchema `provider:"health-check,omitempty"`
Override OverrideSchema `provider:"override,omitempty"`
} }
func ParseProxyProvider(name string, mapping map[string]any) (types.ProxyProvider, error) { func ParseProxyProvider(name string, mapping map[string]any) (types.ProxyProvider, error) {
@@ -68,7 +79,7 @@ func ParseProxyProvider(name string, mapping map[string]any) (types.ProxyProvide
case "http": case "http":
if schema.Path != "" { if schema.Path != "" {
path := C.Path.Resolve(schema.Path) path := C.Path.Resolve(schema.Path)
if !C.Path.IsSafePath(path) { if !features.CMFA && !C.Path.IsSafePath(path) {
return nil, fmt.Errorf("%w: %s", errSubPath, path) return nil, fmt.Errorf("%w: %s", errSubPath, path)
} }
vehicle = resource.NewHTTPVehicle(schema.URL, path) vehicle = resource.NewHTTPVehicle(schema.URL, path)
@@ -85,6 +96,7 @@ func ParseProxyProvider(name string, mapping map[string]any) (types.ProxyProvide
excludeFilter := schema.ExcludeFilter excludeFilter := schema.ExcludeFilter
excludeType := schema.ExcludeType excludeType := schema.ExcludeType
dialerProxy := schema.DialerProxy dialerProxy := schema.DialerProxy
override := schema.Override
return NewProxySetProvider(name, interval, filter, excludeFilter, excludeType, dialerProxy, vehicle, hc) return NewProxySetProvider(name, interval, filter, excludeFilter, excludeType, dialerProxy, override, vehicle, hc)
} }

View File

@@ -0,0 +1,36 @@
//go:build android && cmfa
package provider
import (
"time"
)
var (
suspended bool
)
type UpdatableProvider interface {
UpdatedAt() time.Time
}
func (pp *proxySetProvider) UpdatedAt() time.Time {
return pp.Fetcher.UpdatedAt
}
func (pp *proxySetProvider) Close() error {
pp.healthCheck.close()
pp.Fetcher.Destroy()
return nil
}
func (cp *compatibleProvider) Close() error {
cp.healthCheck.close()
return nil
}
func Suspend(s bool) {
suspended = s
}

View File

@@ -10,20 +10,22 @@ import (
"strings" "strings"
"time" "time"
"github.com/Dreamacro/clash/adapter" "github.com/metacubex/mihomo/adapter"
"github.com/Dreamacro/clash/common/convert" "github.com/metacubex/mihomo/common/convert"
"github.com/Dreamacro/clash/common/utils" "github.com/metacubex/mihomo/common/utils"
clashHttp "github.com/Dreamacro/clash/component/http" mihomoHttp "github.com/metacubex/mihomo/component/http"
"github.com/Dreamacro/clash/component/resource" "github.com/metacubex/mihomo/component/resource"
C "github.com/Dreamacro/clash/constant" C "github.com/metacubex/mihomo/constant"
types "github.com/Dreamacro/clash/constant/provider" types "github.com/metacubex/mihomo/constant/provider"
"github.com/Dreamacro/clash/log" "github.com/metacubex/mihomo/log"
"github.com/Dreamacro/clash/tunnel/statistic" "github.com/metacubex/mihomo/tunnel/statistic"
"github.com/dlclark/regexp2" "github.com/dlclark/regexp2"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
) )
var ProxyProviderName = make(map[string]struct{})
const ( const (
ReservedName = "default" ReservedName = "default"
) )
@@ -46,12 +48,18 @@ type proxySetProvider struct {
} }
func (pp *proxySetProvider) MarshalJSON() ([]byte, error) { func (pp *proxySetProvider) MarshalJSON() ([]byte, error) {
expectedStatus := "*"
if pp.healthCheck.expectedStatus != nil {
expectedStatus = pp.healthCheck.expectedStatus.ToString()
}
return json.Marshal(map[string]any{ return json.Marshal(map[string]any{
"name": pp.Name(), "name": pp.Name(),
"type": pp.Type().String(), "type": pp.Type().String(),
"vehicleType": pp.VehicleType().String(), "vehicleType": pp.VehicleType().String(),
"proxies": pp.Proxies(), "proxies": pp.Proxies(),
"testUrl": pp.healthCheck.url, "testUrl": pp.healthCheck.url,
"expectedStatus": expectedStatus,
"updatedAt": pp.UpdatedAt, "updatedAt": pp.UpdatedAt,
"subscriptionInfo": pp.subscriptionInfo, "subscriptionInfo": pp.subscriptionInfo,
}) })
@@ -119,8 +127,8 @@ func (pp *proxySetProvider) getSubscriptionInfo() {
go func() { go func() {
ctx, cancel := context.WithTimeout(context.Background(), time.Second*90) ctx, cancel := context.WithTimeout(context.Background(), time.Second*90)
defer cancel() defer cancel()
resp, err := clashHttp.HttpRequest(ctx, pp.Vehicle().(*resource.HTTPVehicle).Url(), resp, err := mihomoHttp.HttpRequest(ctx, pp.Vehicle().(*resource.HTTPVehicle).Url(),
http.MethodGet, http.Header{"User-Agent": {"clash"}}, nil) http.MethodGet, http.Header{"User-Agent": {C.UA}}, nil)
if err != nil { if err != nil {
return return
} }
@@ -128,7 +136,7 @@ func (pp *proxySetProvider) getSubscriptionInfo() {
userInfoStr := strings.TrimSpace(resp.Header.Get("subscription-userinfo")) userInfoStr := strings.TrimSpace(resp.Header.Get("subscription-userinfo"))
if userInfoStr == "" { if userInfoStr == "" {
resp2, err := clashHttp.HttpRequest(ctx, pp.Vehicle().(*resource.HTTPVehicle).Url(), resp2, err := mihomoHttp.HttpRequest(ctx, pp.Vehicle().(*resource.HTTPVehicle).Url(),
http.MethodGet, http.Header{"User-Agent": {"Quantumultx"}}, nil) http.MethodGet, http.Header{"User-Agent": {"Quantumultx"}}, nil)
if err != nil { if err != nil {
return return
@@ -163,7 +171,7 @@ func stopProxyProvider(pd *ProxySetProvider) {
_ = pd.Fetcher.Destroy() _ = pd.Fetcher.Destroy()
} }
func NewProxySetProvider(name string, interval time.Duration, filter string, excludeFilter string, excludeType string, dialerProxy string, vehicle types.Vehicle, hc *HealthCheck) (*ProxySetProvider, error) { func NewProxySetProvider(name string, interval time.Duration, filter string, excludeFilter string, excludeType string, dialerProxy string, override OverrideSchema, vehicle types.Vehicle, hc *HealthCheck) (*ProxySetProvider, error) {
excludeFilterReg, err := regexp2.Compile(excludeFilter, 0) excludeFilterReg, err := regexp2.Compile(excludeFilter, 0)
if err != nil { if err != nil {
return nil, fmt.Errorf("invalid excludeFilter regex: %w", err) return nil, fmt.Errorf("invalid excludeFilter regex: %w", err)
@@ -191,8 +199,9 @@ func NewProxySetProvider(name string, interval time.Duration, filter string, exc
healthCheck: hc, healthCheck: hc,
} }
fetcher := resource.NewFetcher[[]C.Proxy](name, interval, vehicle, proxiesParseAndFilter(filter, excludeFilter, excludeTypeArray, filterRegs, excludeFilterReg, dialerProxy), proxiesOnUpdate(pd)) fetcher := resource.NewFetcher[[]C.Proxy](name, interval, vehicle, proxiesParseAndFilter(filter, excludeFilter, excludeTypeArray, filterRegs, excludeFilterReg, dialerProxy, override), proxiesOnUpdate(pd))
pd.Fetcher = fetcher pd.Fetcher = fetcher
ProxyProviderName[name] = struct{}{}
wrapper := &ProxySetProvider{pd} wrapper := &ProxySetProvider{pd}
runtime.SetFinalizer(wrapper, stopProxyProvider) runtime.SetFinalizer(wrapper, stopProxyProvider)
return wrapper, nil return wrapper, nil
@@ -211,12 +220,18 @@ type compatibleProvider struct {
} }
func (cp *compatibleProvider) MarshalJSON() ([]byte, error) { func (cp *compatibleProvider) MarshalJSON() ([]byte, error) {
expectedStatus := "*"
if cp.healthCheck.expectedStatus != nil {
expectedStatus = cp.healthCheck.expectedStatus.ToString()
}
return json.Marshal(map[string]any{ return json.Marshal(map[string]any{
"name": cp.Name(), "name": cp.Name(),
"type": cp.Type().String(), "type": cp.Type().String(),
"vehicleType": cp.VehicleType().String(), "vehicleType": cp.VehicleType().String(),
"proxies": cp.Proxies(), "proxies": cp.Proxies(),
"testUrl": cp.healthCheck.url, "testUrl": cp.healthCheck.url,
"expectedStatus": expectedStatus,
}) })
} }
@@ -292,7 +307,7 @@ func proxiesOnUpdate(pd *proxySetProvider) func([]C.Proxy) {
} }
} }
func proxiesParseAndFilter(filter string, excludeFilter string, excludeTypeArray []string, filterRegs []*regexp2.Regexp, excludeFilterReg *regexp2.Regexp, dialerProxy string) resource.Parser[[]C.Proxy] { func proxiesParseAndFilter(filter string, excludeFilter string, excludeTypeArray []string, filterRegs []*regexp2.Regexp, excludeFilterReg *regexp2.Regexp, dialerProxy string, override OverrideSchema) resource.Parser[[]C.Proxy] {
return func(buf []byte) ([]C.Proxy, error) { return func(buf []byte) ([]C.Proxy, error) {
schema := &ProxySchema{} schema := &ProxySchema{}
@@ -355,13 +370,32 @@ func proxiesParseAndFilter(filter string, excludeFilter string, excludeTypeArray
if _, ok := proxiesSet[name]; ok { if _, ok := proxiesSet[name]; ok {
continue continue
} }
if len(dialerProxy) > 0 { if len(dialerProxy) > 0 {
mapping["dialer-proxy"] = dialerProxy mapping["dialer-proxy"] = dialerProxy
} }
if override.UDP != nil {
mapping["udp"] = *override.UDP
}
if override.Up != nil {
mapping["up"] = *override.Up
}
if override.Down != nil {
mapping["down"] = *override.Down
}
if override.DialerProxy != nil {
mapping["dialer-proxy"] = *override.DialerProxy
}
if override.SkipCertVerify != nil {
mapping["skip-cert-verify"] = *override.SkipCertVerify
}
proxy, err := adapter.ParseProxy(mapping) proxy, err := adapter.ParseProxy(mapping)
if err != nil { if err != nil {
return nil, fmt.Errorf("proxy %d error: %w", idx, err) return nil, fmt.Errorf("proxy %d error: %w", idx, err)
} }
proxiesSet[name] = struct{}{} proxiesSet[name] = struct{}{}
proxies = append(proxies, proxy) proxies = append(proxies, proxy)
} }

View File

@@ -1,7 +1,6 @@
package provider package provider
import ( import (
"github.com/dlclark/regexp2"
"strconv" "strconv"
"strings" "strings"
) )
@@ -13,45 +12,28 @@ type SubscriptionInfo struct {
Expire int64 Expire int64
} }
func NewSubscriptionInfo(str string) (si *SubscriptionInfo, err error) { func NewSubscriptionInfo(userinfo string) (si *SubscriptionInfo, err error) {
si = &SubscriptionInfo{} userinfo = strings.ToLower(userinfo)
str = strings.ToLower(str) userinfo = strings.ReplaceAll(userinfo, " ", "")
reTraffic := regexp2.MustCompile("upload=(\\d+); download=(\\d+); total=(\\d+)", 0) si = new(SubscriptionInfo)
reExpire := regexp2.MustCompile("expire=(\\d+)", 0) for _, field := range strings.Split(userinfo, ";") {
switch name, value, _ := strings.Cut(field, "="); name {
match, err := reTraffic.FindStringMatch(str) case "upload":
if err != nil || match == nil { si.Upload, err = strconv.ParseInt(value, 10, 64)
return nil, err case "download":
} si.Download, err = strconv.ParseInt(value, 10, 64)
group := match.Groups() case "total":
si.Upload, err = str2uint64(group[1].String()) si.Total, err = strconv.ParseInt(value, 10, 64)
if err != nil { case "expire":
return nil, err if value == "" {
} si.Expire = 0
} else {
si.Download, err = str2uint64(group[2].String()) si.Expire, err = strconv.ParseInt(value, 10, 64)
if err != nil { }
return nil, err }
}
si.Total, err = str2uint64(group[3].String())
if err != nil {
return nil, err
}
match, _ = reExpire.FindStringMatch(str)
if match != nil {
group = match.Groups()
si.Expire, err = str2uint64(group[1].String())
if err != nil { if err != nil {
return nil, err return
} }
} }
return return
} }
func str2uint64(str string) (int64, error) {
i, err := strconv.ParseInt(str, 10, 64)
return i, err
}

229
common/arc/arc.go Normal file
View File

@@ -0,0 +1,229 @@
package arc
import (
"sync"
"time"
list "github.com/bahlo/generic-list-go"
"github.com/samber/lo"
)
//modify from https://github.com/alexanderGugel/arc
// Option is part of Functional Options Pattern
type Option[K comparable, V any] func(*ARC[K, V])
func WithSize[K comparable, V any](maxSize int) Option[K, V] {
return func(a *ARC[K, V]) {
a.c = maxSize
}
}
type ARC[K comparable, V any] struct {
p int
c int
t1 *list.List[*entry[K, V]]
b1 *list.List[*entry[K, V]]
t2 *list.List[*entry[K, V]]
b2 *list.List[*entry[K, V]]
mutex sync.Mutex
len int
cache map[K]*entry[K, V]
staleReturn bool
}
// New returns a new Adaptive Replacement Cache (ARC).
func New[K comparable, V any](options ...Option[K, V]) *ARC[K, V] {
arc := &ARC[K, V]{
p: 0,
t1: list.New[*entry[K, V]](),
b1: list.New[*entry[K, V]](),
t2: list.New[*entry[K, V]](),
b2: list.New[*entry[K, V]](),
len: 0,
cache: make(map[K]*entry[K, V]),
}
for _, option := range options {
option(arc)
}
return arc
}
// Set inserts a new key-value pair into the cache.
// This optimizes future access to this entry (side effect).
func (a *ARC[K, V]) Set(key K, value V) {
a.mutex.Lock()
defer a.mutex.Unlock()
a.set(key, value)
}
func (a *ARC[K, V]) set(key K, value V) {
a.setWithExpire(key, value, time.Unix(0, 0))
}
// SetWithExpire stores any representation of a response for a given key and given expires.
// The expires time will round to second.
func (a *ARC[K, V]) SetWithExpire(key K, value V, expires time.Time) {
a.mutex.Lock()
defer a.mutex.Unlock()
a.setWithExpire(key, value, expires)
}
func (a *ARC[K, V]) setWithExpire(key K, value V, expires time.Time) {
ent, ok := a.cache[key]
if ok != true {
a.len++
ent := &entry[K, V]{key: key, value: value, ghost: false, expires: expires.Unix()}
a.req(ent)
a.cache[key] = ent
} else {
if ent.ghost {
a.len++
}
ent.value = value
ent.ghost = false
ent.expires = expires.Unix()
a.req(ent)
}
}
// Get retrieves a previously via Set inserted entry.
// This optimizes future access to this entry (side effect).
func (a *ARC[K, V]) Get(key K) (value V, ok bool) {
a.mutex.Lock()
defer a.mutex.Unlock()
ent, ok := a.get(key)
if ok {
return ent.value, true
}
return lo.Empty[V](), false
}
func (a *ARC[K, V]) get(key K) (e *entry[K, V], ok bool) {
ent, ok := a.cache[key]
if ok {
a.req(ent)
return ent, !ent.ghost
}
return ent, false
}
// GetWithExpire returns 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 (a *ARC[K, V]) GetWithExpire(key K) (V, time.Time, bool) {
a.mutex.Lock()
defer a.mutex.Unlock()
ent, ok := a.get(key)
if !ok {
return lo.Empty[V](), time.Time{}, false
}
return ent.value, time.Unix(ent.expires, 0), true
}
// Len determines the number of currently cached entries.
// This method is side-effect free in the sense that it does not attempt to optimize random cache access.
func (a *ARC[K, V]) Len() int {
a.mutex.Lock()
defer a.mutex.Unlock()
return a.len
}
func (a *ARC[K, V]) req(ent *entry[K, V]) {
if ent.ll == a.t1 || ent.ll == a.t2 {
// Case I
ent.setMRU(a.t2)
} else if ent.ll == a.b1 {
// Case II
// Cache Miss in t1 and t2
// Adaptation
var d int
if a.b1.Len() >= a.b2.Len() {
d = 1
} else {
d = a.b2.Len() / a.b1.Len()
}
// a.p = min(a.p+d, a.c)
a.p = a.p + d
if a.c < a.p {
a.p = a.c
}
a.replace(ent)
ent.setMRU(a.t2)
} else if ent.ll == a.b2 {
// Case III
// Cache Miss in t1 and t2
// Adaptation
var d int
if a.b2.Len() >= a.b1.Len() {
d = 1
} else {
d = a.b1.Len() / a.b2.Len()
}
//a.p = max(a.p-d, 0)
a.p = a.p - d
if a.p < 0 {
a.p = 0
}
a.replace(ent)
ent.setMRU(a.t2)
} else if ent.ll == nil {
// Case IV
if a.t1.Len()+a.b1.Len() == a.c {
// Case A
if a.t1.Len() < a.c {
a.delLRU(a.b1)
a.replace(ent)
} else {
a.delLRU(a.t1)
}
} else if a.t1.Len()+a.b1.Len() < a.c {
// Case B
if a.t1.Len()+a.t2.Len()+a.b1.Len()+a.b2.Len() >= a.c {
if a.t1.Len()+a.t2.Len()+a.b1.Len()+a.b2.Len() == 2*a.c {
a.delLRU(a.b2)
}
a.replace(ent)
}
}
ent.setMRU(a.t1)
}
}
func (a *ARC[K, V]) delLRU(list *list.List[*entry[K, V]]) {
lru := list.Back()
list.Remove(lru)
a.len--
delete(a.cache, lru.Value.key)
}
func (a *ARC[K, V]) replace(ent *entry[K, V]) {
if a.t1.Len() > 0 && ((a.t1.Len() > a.p) || (ent.ll == a.b2 && a.t1.Len() == a.p)) {
lru := a.t1.Back().Value
lru.value = lo.Empty[V]()
lru.ghost = true
a.len--
lru.setMRU(a.b1)
} else {
lru := a.t2.Back().Value
lru.value = lo.Empty[V]()
lru.ghost = true
a.len--
lru.setMRU(a.b2)
}
}

59
common/arc/arc_test.go Normal file
View File

@@ -0,0 +1,59 @@
package arc
import "testing"
func TestBasic(t *testing.T) {
cache := New[string, string](WithSize[string, string](3))
if cache.Len() != 0 {
t.Error("Empty cache should have length 0")
}
cache.Set("Hello", "World")
if cache.Len() != 1 {
t.Error("Cache should have length 1")
}
var val interface{}
var ok bool
if val, ok = cache.Get("Hello"); val != "World" || ok != true {
t.Error("Didn't set \"Hello\" to \"World\"")
}
cache.Set("Hello", "World1")
if cache.Len() != 1 {
t.Error("Inserting the same entry multiple times shouldn't increase cache size")
}
if val, ok = cache.Get("Hello"); val != "World1" || ok != true {
t.Error("Didn't update \"Hello\" to \"World1\"")
}
cache.Set("Hallo", "Welt")
if cache.Len() != 2 {
t.Error("Inserting two different entries should result into lenght=2")
}
if val, ok = cache.Get("Hallo"); val != "Welt" || ok != true {
t.Error("Didn't set \"Hallo\" to \"Welt\"")
}
}
func TestBasicReplace(t *testing.T) {
cache := New[string, string](WithSize[string, string](3))
cache.Set("Hello", "Hallo")
cache.Set("World", "Welt")
cache.Get("World")
cache.Set("Cache", "Cache")
cache.Set("Replace", "Ersetzen")
value, ok := cache.Get("World")
if !ok || value != "Welt" {
t.Error("ARC should have replaced \"Hello\"")
}
if cache.Len() != 3 {
t.Error("ARC should have a maximum size of 3")
}
}

32
common/arc/entry.go Normal file
View File

@@ -0,0 +1,32 @@
package arc
import (
list "github.com/bahlo/generic-list-go"
)
type entry[K comparable, V any] struct {
key K
value V
ll *list.List[*entry[K, V]]
el *list.Element[*entry[K, V]]
ghost bool
expires int64
}
func (e *entry[K, V]) setLRU(list *list.List[*entry[K, V]]) {
e.detach()
e.ll = list
e.el = e.ll.PushBack(e)
}
func (e *entry[K, V]) setMRU(list *list.List[*entry[K, V]]) {
e.detach()
e.ll = list
e.el = e.ll.PushFront(e)
}
func (e *entry[K, V]) detach() {
if e.ll != nil {
e.ll.Remove(e.el)
}
}

View File

@@ -11,10 +11,9 @@ type Bool struct {
atomic.Bool atomic.Bool
} }
func NewBool(val bool) *Bool { func NewBool(val bool) (i Bool) {
i := &Bool{}
i.Store(val) i.Store(val)
return i return
} }
func (i *Bool) MarshalJSON() ([]byte, error) { func (i *Bool) MarshalJSON() ([]byte, error) {
@@ -39,12 +38,11 @@ type Pointer[T any] struct {
atomic.Pointer[T] atomic.Pointer[T]
} }
func NewPointer[T any](v *T) *Pointer[T] { func NewPointer[T any](v *T) (p Pointer[T]) {
var p Pointer[T]
if v != nil { if v != nil {
p.Store(v) p.Store(v)
} }
return &p return
} }
func (p *Pointer[T]) MarshalJSON() ([]byte, error) { func (p *Pointer[T]) MarshalJSON() ([]byte, error) {
@@ -68,10 +66,9 @@ type Int32 struct {
atomic.Int32 atomic.Int32
} }
func NewInt32(val int32) *Int32 { func NewInt32(val int32) (i Int32) {
i := &Int32{}
i.Store(val) i.Store(val)
return i return
} }
func (i *Int32) MarshalJSON() ([]byte, error) { func (i *Int32) MarshalJSON() ([]byte, error) {
@@ -96,10 +93,9 @@ type Int64 struct {
atomic.Int64 atomic.Int64
} }
func NewInt64(val int64) *Int64 { func NewInt64(val int64) (i Int64) {
i := &Int64{}
i.Store(val) i.Store(val)
return i return
} }
func (i *Int64) MarshalJSON() ([]byte, error) { func (i *Int64) MarshalJSON() ([]byte, error) {
@@ -124,10 +120,9 @@ type Uint32 struct {
atomic.Uint32 atomic.Uint32
} }
func NewUint32(val uint32) *Uint32 { func NewUint32(val uint32) (i Uint32) {
i := &Uint32{}
i.Store(val) i.Store(val)
return i return
} }
func (i *Uint32) MarshalJSON() ([]byte, error) { func (i *Uint32) MarshalJSON() ([]byte, error) {
@@ -152,10 +147,9 @@ type Uint64 struct {
atomic.Uint64 atomic.Uint64
} }
func NewUint64(val uint64) *Uint64 { func NewUint64(val uint64) (i Uint64) {
i := &Uint64{}
i.Store(val) i.Store(val)
return i return
} }
func (i *Uint64) MarshalJSON() ([]byte, error) { func (i *Uint64) MarshalJSON() ([]byte, error) {
@@ -180,10 +174,9 @@ type Uintptr struct {
atomic.Uintptr atomic.Uintptr
} }
func NewUintptr(val uintptr) *Uintptr { func NewUintptr(val uintptr) (i Uintptr) {
i := &Uintptr{}
i.Store(val) i.Store(val)
return i return
} }
func (i *Uintptr) MarshalJSON() ([]byte, error) { func (i *Uintptr) MarshalJSON() ([]byte, error) {

View File

@@ -11,6 +11,7 @@ func DefaultValue[T any]() T {
} }
type TypedValue[T any] struct { type TypedValue[T any] struct {
_ noCopy
value atomic.Value value atomic.Value
} }
@@ -51,8 +52,13 @@ func (t *TypedValue[T]) UnmarshalJSON(b []byte) error {
return nil return nil
} }
func NewTypedValue[T any](t T) *TypedValue[T] { func NewTypedValue[T any](t T) (v TypedValue[T]) {
v := &TypedValue[T]{}
v.Store(t) v.Store(t)
return v return
} }
type noCopy struct{}
// Lock is a no-op used by -copylocks checker from `go vet`.
func (*noCopy) Lock() {}
func (*noCopy) Unlock() {}

View File

@@ -10,6 +10,7 @@ const BufferSize = buf.BufferSize
type Buffer = buf.Buffer type Buffer = buf.Buffer
var New = buf.New var New = buf.New
var NewPacket = buf.NewPacket
var NewSize = buf.NewSize var NewSize = buf.NewSize
var With = buf.With var With = buf.With
var As = buf.As var As = buf.As

View File

@@ -1,9 +1,9 @@
package callback package callback
import ( import (
"github.com/Dreamacro/clash/common/buf" "github.com/metacubex/mihomo/common/buf"
N "github.com/Dreamacro/clash/common/net" N "github.com/metacubex/mihomo/common/net"
C "github.com/Dreamacro/clash/constant" C "github.com/metacubex/mihomo/constant"
) )
type firstWriteCallBackConn struct { type firstWriteCallBackConn struct {

View File

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

View File

@@ -9,10 +9,10 @@ import (
"strconv" "strconv"
"strings" "strings"
"github.com/Dreamacro/clash/log" "github.com/metacubex/mihomo/log"
) )
// ConvertsV2Ray convert V2Ray subscribe proxies data to clash proxies config // ConvertsV2Ray convert V2Ray subscribe proxies data to mihomo proxies config
func ConvertsV2Ray(buf []byte) ([]map[string]any, error) { func ConvertsV2Ray(buf []byte) ([]map[string]any, error) {
data := DecodeBase64(buf) data := DecodeBase64(buf)
@@ -142,6 +142,8 @@ func ConvertsV2Ray(buf []byte) ([]map[string]any, error) {
tuic["udp-relay-mode"] = udpRelayMode tuic["udp-relay-mode"] = udpRelayMode
} }
proxies = append(proxies, tuic)
case "trojan": case "trojan":
urlTrojan, err := url.Parse(line) urlTrojan, err := url.Parse(line)
if err != nil { if err != nil {

View File

@@ -6,7 +6,7 @@ import (
"strings" "strings"
"time" "time"
"github.com/Dreamacro/clash/common/utils" "github.com/metacubex/mihomo/common/utils"
"github.com/metacubex/sing-shadowsocks/shadowimpl" "github.com/metacubex/sing-shadowsocks/shadowimpl"
"github.com/zhangyunhao116/fastrand" "github.com/zhangyunhao116/fastrand"

View File

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

View File

@@ -1,4 +1,4 @@
package cache package lru
// Modified by https://github.com/die-net/lrucache // Modified by https://github.com/die-net/lrucache
@@ -6,8 +6,7 @@ import (
"sync" "sync"
"time" "time"
"github.com/Dreamacro/clash/common/generics/list" list "github.com/bahlo/generic-list-go"
"github.com/samber/lo" "github.com/samber/lo"
) )

View File

@@ -1,4 +1,4 @@
package cache package lru
import ( import (
"testing" "testing"

View File

@@ -4,7 +4,7 @@ import (
"bufio" "bufio"
"net" "net"
"github.com/Dreamacro/clash/common/buf" "github.com/metacubex/mihomo/common/buf"
) )
var _ ExtendedConn = (*BufferedConn)(nil) var _ ExtendedConn = (*BufferedConn)(nil)
@@ -22,6 +22,16 @@ func NewBufferedConn(c net.Conn) *BufferedConn {
return &BufferedConn{bufio.NewReader(c), NewExtendedConn(c), false} return &BufferedConn{bufio.NewReader(c), NewExtendedConn(c), false}
} }
func WarpConnWithBioReader(c net.Conn, br *bufio.Reader) net.Conn {
if br != nil && br.Buffered() > 0 {
if bc, ok := c.(*BufferedConn); ok && bc.r == br {
return bc
}
return &BufferedConn{br, NewExtendedConn(c), true}
}
return c
}
// Reader returns the internal bufio.Reader. // Reader returns the internal bufio.Reader.
func (c *BufferedConn) Reader() *bufio.Reader { func (c *BufferedConn) Reader() *bufio.Reader {
return c.r return c.r
@@ -74,9 +84,9 @@ func (c *BufferedConn) ReadCached() *buf.Buffer { // call in sing/common/bufio.C
length := c.r.Buffered() length := c.r.Buffered()
b, _ := c.r.Peek(length) b, _ := c.r.Peek(length)
_, _ = c.r.Discard(length) _, _ = c.r.Discard(length)
c.r = nil // drop bufio.Reader to let gc can clean up its internal buf
return buf.As(b) return buf.As(b)
} }
c.r = nil // drop bufio.Reader to let gc can clean up its internal buf
return nil return nil
} }

View File

@@ -0,0 +1,34 @@
package net
import (
"io"
"unsafe"
)
// bufioReader copy from stdlib bufio/bufio.go
// This structure has remained unchanged from go1.5 to go1.21.
type bufioReader struct {
buf []byte
rd io.Reader // reader provided by the client
r, w int // buf read and write positions
err error
lastByte int // last byte read for UnreadByte; -1 means invalid
lastRuneSize int // size of last rune read for UnreadRune; -1 means invalid
}
func (c *BufferedConn) AppendData(buf []byte) (ok bool) {
b := (*bufioReader)(unsafe.Pointer(c.r))
pos := len(b.buf) - b.w - len(buf)
if pos >= -b.r { // len(b.buf)-(b.w - b.r) >= len(buf)
if pos < 0 { // len(b.buf)-b.w < len(buf)
// Slide existing data to beginning.
copy(b.buf, b.buf[b.r:b.w])
b.w -= b.r
b.r = 0
}
b.w += copy(b.buf[b.w:], buf)
return true
}
return false
}

49
common/net/cached.go Normal file
View File

@@ -0,0 +1,49 @@
package net
import (
"net"
"github.com/metacubex/mihomo/common/buf"
)
var _ ExtendedConn = (*CachedConn)(nil)
type CachedConn struct {
ExtendedConn
data []byte
}
func NewCachedConn(c net.Conn, data []byte) *CachedConn {
return &CachedConn{NewExtendedConn(c), data}
}
func (c *CachedConn) Read(b []byte) (n int, err error) {
if len(c.data) > 0 {
n = copy(b, c.data)
c.data = c.data[n:]
return
}
return c.ExtendedConn.Read(b)
}
func (c *CachedConn) ReadCached() *buf.Buffer { // call in sing/common/bufio.Copy
if len(c.data) > 0 {
return buf.As(c.data)
}
return nil
}
func (c *CachedConn) Upstream() any {
return c.ExtendedConn
}
func (c *CachedConn) ReaderReplaceable() bool {
if len(c.data) > 0 {
return false
}
return true
}
func (c *CachedConn) WriterReplaceable() bool {
return true
}

31
common/net/context.go Normal file
View File

@@ -0,0 +1,31 @@
package net
import (
"context"
"net"
)
// SetupContextForConn is a helper function that starts connection I/O interrupter goroutine.
func SetupContextForConn(ctx context.Context, conn net.Conn) (done func(*error)) {
var (
quit = make(chan struct{})
interrupt = make(chan error, 1)
)
go func() {
select {
case <-quit:
interrupt <- nil
case <-ctx.Done():
// Close the connection, discarding the error
_ = conn.Close()
interrupt <- ctx.Err()
}
}()
return func(inputErr *error) {
close(quit)
if ctxErr := <-interrupt; ctxErr != nil && inputErr != nil {
// Return context error to user.
inputErr = &ctxErr
}
}
}

View File

@@ -6,8 +6,8 @@ import (
"runtime" "runtime"
"time" "time"
"github.com/Dreamacro/clash/common/atomic" "github.com/metacubex/mihomo/common/atomic"
"github.com/Dreamacro/clash/common/net/packet" "github.com/metacubex/mihomo/common/net/packet"
) )
type readResult struct { type readResult struct {

View File

@@ -5,7 +5,7 @@ import (
"os" "os"
"runtime" "runtime"
"github.com/Dreamacro/clash/common/net/packet" "github.com/metacubex/mihomo/common/net/packet"
) )
type EnhancePacketConn struct { type EnhancePacketConn struct {

View File

@@ -4,7 +4,7 @@ import (
"os" "os"
"runtime" "runtime"
"github.com/Dreamacro/clash/common/net/packet" "github.com/metacubex/mihomo/common/net/packet"
"github.com/sagernet/sing/common/buf" "github.com/sagernet/sing/common/buf"
"github.com/sagernet/sing/common/bufio" "github.com/sagernet/sing/common/bufio"
M "github.com/sagernet/sing/common/metadata" M "github.com/sagernet/sing/common/metadata"

67
common/net/earlyconn.go Normal file
View File

@@ -0,0 +1,67 @@
package net
import (
"net"
"sync"
"sync/atomic"
"unsafe"
"github.com/metacubex/mihomo/common/buf"
)
type earlyConn struct {
ExtendedConn // only expose standard N.ExtendedConn function to outside
resFunc func() error
resOnce sync.Once
resErr error
}
func (conn *earlyConn) Response() error {
conn.resOnce.Do(func() {
conn.resErr = conn.resFunc()
})
return conn.resErr
}
func (conn *earlyConn) Read(b []byte) (n int, err error) {
err = conn.Response()
if err != nil {
return 0, err
}
return conn.ExtendedConn.Read(b)
}
func (conn *earlyConn) ReadBuffer(buffer *buf.Buffer) (err error) {
err = conn.Response()
if err != nil {
return err
}
return conn.ExtendedConn.ReadBuffer(buffer)
}
func (conn *earlyConn) Upstream() any {
return conn.ExtendedConn
}
func (conn *earlyConn) Success() bool {
// atomic visit sync.Once.done
return atomic.LoadUint32((*uint32)(unsafe.Pointer(&conn.resOnce))) == 1 && conn.resErr == nil
}
func (conn *earlyConn) ReaderReplaceable() bool {
return conn.Success()
}
func (conn *earlyConn) ReaderPossiblyReplaceable() bool {
return !conn.Success()
}
func (conn *earlyConn) WriterReplaceable() bool {
return true
}
var _ ExtendedConn = (*earlyConn)(nil)
func NewEarlyConn(c net.Conn, f func() error) net.Conn {
return &earlyConn{ExtendedConn: NewExtendedConn(c), resFunc: f}
}

View File

@@ -1,8 +1,8 @@
package net package net
import ( import (
"github.com/Dreamacro/clash/common/net/deadline" "github.com/metacubex/mihomo/common/net/deadline"
"github.com/Dreamacro/clash/common/net/packet" "github.com/metacubex/mihomo/common/net/packet"
) )
type EnhancePacketConn = packet.EnhancePacketConn type EnhancePacketConn = packet.EnhancePacketConn

View File

@@ -3,7 +3,7 @@ package packet
import ( import (
"net" "net"
"github.com/Dreamacro/clash/common/pool" "github.com/metacubex/mihomo/common/pool"
) )
type WaitReadFrom interface { type WaitReadFrom interface {

View File

@@ -7,7 +7,7 @@ import (
"strconv" "strconv"
"syscall" "syscall"
"github.com/Dreamacro/clash/common/pool" "github.com/metacubex/mihomo/common/pool"
) )
type enhanceUDPConn struct { type enhanceUDPConn struct {

View File

@@ -5,7 +5,7 @@ import (
"runtime" "runtime"
"time" "time"
"github.com/Dreamacro/clash/common/buf" "github.com/metacubex/mihomo/common/buf"
) )
type refConn struct { type refConn struct {

View File

@@ -12,7 +12,7 @@ package net
// //
// go func() { // go func() {
// // Wrapping to avoid using *net.TCPConn.(ReadFrom) // // Wrapping to avoid using *net.TCPConn.(ReadFrom)
// // See also https://github.com/Dreamacro/clash/pull/1209 // // See also https://github.com/metacubex/mihomo/pull/1209
// _, err := io.Copy(WriteOnlyWriter{Writer: leftConn}, ReadOnlyReader{Reader: rightConn}) // _, err := io.Copy(WriteOnlyWriter{Writer: leftConn}, ReadOnlyReader{Reader: rightConn})
// leftConn.SetReadDeadline(time.Now()) // leftConn.SetReadDeadline(time.Now())
// ch <- err // ch <- err

View File

@@ -10,7 +10,11 @@ import (
"math/big" "math/big"
) )
func ParseCert(certificate, privateKey string) (tls.Certificate, error) { type Path interface {
Resolve(path string) string
}
func ParseCert(certificate, privateKey string, path Path) (tls.Certificate, error) {
if certificate == "" && privateKey == "" { if certificate == "" && privateKey == "" {
return newRandomTLSKeyPair() return newRandomTLSKeyPair()
} }
@@ -19,6 +23,8 @@ func ParseCert(certificate, privateKey string) (tls.Certificate, error) {
return cert, nil return cert, nil
} }
certificate = path.Resolve(certificate)
privateKey = path.Resolve(privateKey)
cert, loadErr := tls.LoadX509KeyPair(certificate, privateKey) cert, loadErr := tls.LoadX509KeyPair(certificate, privateKey)
if loadErr != nil { if loadErr != nil {
return tls.Certificate{}, fmt.Errorf("parse certificate failed, maybe format error:%s, or path error: %s", painTextErr.Error(), loadErr.Error()) return tls.Certificate{}, fmt.Errorf("parse certificate failed, maybe format error:%s, or path error: %s", painTextErr.Error(), loadErr.Error())

View File

@@ -5,7 +5,7 @@ import (
"testing" "testing"
"time" "time"
"github.com/Dreamacro/clash/common/atomic" "github.com/metacubex/mihomo/common/atomic"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )

View File

@@ -12,22 +12,28 @@ var defaultAllocator = NewAllocator()
// Allocator for incoming frames, optimized to prevent overwriting after zeroing // Allocator for incoming frames, optimized to prevent overwriting after zeroing
type Allocator struct { type Allocator struct {
buffers []sync.Pool buffers [11]sync.Pool
} }
// NewAllocator initiates a []byte allocator for frames less than 65536 bytes, // NewAllocator initiates a []byte allocator for frames less than 65536 bytes,
// the waste(memory fragmentation) of space allocation is guaranteed to be // the waste(memory fragmentation) of space allocation is guaranteed to be
// no more than 50%. // no more than 50%.
func NewAllocator() *Allocator { func NewAllocator() *Allocator {
alloc := new(Allocator) return &Allocator{
alloc.buffers = make([]sync.Pool, 17) // 1B -> 64K buffers: [...]sync.Pool{ // 64B -> 64K
for k := range alloc.buffers { {New: func() any { return new([1 << 6]byte) }},
i := k {New: func() any { return new([1 << 7]byte) }},
alloc.buffers[k].New = func() any { {New: func() any { return new([1 << 8]byte) }},
return make([]byte, 1<<uint32(i)) {New: func() any { return new([1 << 9]byte) }},
} {New: func() any { return new([1 << 10]byte) }},
{New: func() any { return new([1 << 11]byte) }},
{New: func() any { return new([1 << 12]byte) }},
{New: func() any { return new([1 << 13]byte) }},
{New: func() any { return new([1 << 14]byte) }},
{New: func() any { return new([1 << 15]byte) }},
{New: func() any { return new([1 << 16]byte) }},
},
} }
return alloc
} }
// Get a []byte from pool with most appropriate cap // Get a []byte from pool with most appropriate cap
@@ -40,12 +46,42 @@ func (alloc *Allocator) Get(size int) []byte {
case size > 65536: case size > 65536:
return make([]byte, size) return make([]byte, size)
default: default:
bits := msb(size) var index uint16
if size == 1<<bits { if size > 64 {
return alloc.buffers[bits].Get().([]byte)[:size] index = msb(size)
if size != 1<<index {
index += 1
}
index -= 6
} }
return alloc.buffers[bits+1].Get().([]byte)[:size] buffer := alloc.buffers[index].Get()
switch index {
case 0:
return buffer.(*[1 << 6]byte)[:size]
case 1:
return buffer.(*[1 << 7]byte)[:size]
case 2:
return buffer.(*[1 << 8]byte)[:size]
case 3:
return buffer.(*[1 << 9]byte)[:size]
case 4:
return buffer.(*[1 << 10]byte)[:size]
case 5:
return buffer.(*[1 << 11]byte)[:size]
case 6:
return buffer.(*[1 << 12]byte)[:size]
case 7:
return buffer.(*[1 << 13]byte)[:size]
case 8:
return buffer.(*[1 << 14]byte)[:size]
case 9:
return buffer.(*[1 << 15]byte)[:size]
case 10:
return buffer.(*[1 << 16]byte)[:size]
default:
panic("invalid pool index")
}
} }
} }
@@ -55,15 +91,45 @@ func (alloc *Allocator) Put(buf []byte) error {
if cap(buf) == 0 || cap(buf) > 65536 { if cap(buf) == 0 || cap(buf) > 65536 {
return nil return nil
} }
bits := msb(cap(buf)) bits := msb(cap(buf))
if cap(buf) != 1<<bits { if cap(buf) != 1<<bits {
return errors.New("allocator Put() incorrect buffer size") return errors.New("allocator Put() incorrect buffer size")
} }
if cap(buf) < 1<<6 {
return nil
}
bits -= 6
buf = buf[:cap(buf)]
//nolint //nolint
//lint:ignore SA6002 ignore temporarily //lint:ignore SA6002 ignore temporarily
alloc.buffers[bits].Put(buf) switch bits {
case 0:
alloc.buffers[bits].Put((*[1 << 6]byte)(buf))
case 1:
alloc.buffers[bits].Put((*[1 << 7]byte)(buf))
case 2:
alloc.buffers[bits].Put((*[1 << 8]byte)(buf))
case 3:
alloc.buffers[bits].Put((*[1 << 9]byte)(buf))
case 4:
alloc.buffers[bits].Put((*[1 << 10]byte)(buf))
case 5:
alloc.buffers[bits].Put((*[1 << 11]byte)(buf))
case 6:
alloc.buffers[bits].Put((*[1 << 12]byte)(buf))
case 7:
alloc.buffers[bits].Put((*[1 << 13]byte)(buf))
case 8:
alloc.buffers[bits].Put((*[1 << 14]byte)(buf))
case 9:
alloc.buffers[bits].Put((*[1 << 15]byte)(buf))
case 10:
alloc.buffers[bits].Put((*[1 << 16]byte)(buf))
default:
panic("invalid pool index")
}
return nil return nil
} }

View File

@@ -13,8 +13,8 @@ func TestAllocGet(t *testing.T) {
assert.Equal(t, 1, len(alloc.Get(1))) assert.Equal(t, 1, len(alloc.Get(1)))
assert.Equal(t, 2, len(alloc.Get(2))) assert.Equal(t, 2, len(alloc.Get(2)))
assert.Equal(t, 3, len(alloc.Get(3))) assert.Equal(t, 3, len(alloc.Get(3)))
assert.Equal(t, 4, cap(alloc.Get(3))) assert.Equal(t, 64, cap(alloc.Get(3)))
assert.Equal(t, 4, cap(alloc.Get(4))) assert.Equal(t, 64, cap(alloc.Get(4)))
assert.Equal(t, 1023, len(alloc.Get(1023))) assert.Equal(t, 1023, len(alloc.Get(1023)))
assert.Equal(t, 1024, cap(alloc.Get(1023))) assert.Equal(t, 1024, cap(alloc.Get(1023)))
assert.Equal(t, 1024, len(alloc.Get(1024))) assert.Equal(t, 1024, len(alloc.Get(1024)))

View File

@@ -5,7 +5,7 @@ import (
"testing" "testing"
"time" "time"
"github.com/Dreamacro/clash/common/atomic" "github.com/metacubex/mihomo/common/atomic"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )

View File

@@ -0,0 +1,8 @@
package utils
import "github.com/samber/lo"
func EmptyOr[T comparable](v T, def T) T {
ret, _ := lo.Coalesce(v, def)
return ret
}

View File

@@ -75,3 +75,26 @@ func (ranges IntRanges[T]) Check(status T) bool {
return false return false
} }
func (ranges IntRanges[T]) ToString() string {
if len(ranges) == 0 {
return "*"
}
terms := make([]string, len(ranges))
for i, r := range ranges {
start := r.Start()
end := r.End()
var term string
if start == end {
term = strconv.Itoa(int(start))
} else {
term = strconv.Itoa(int(start)) + "-" + strconv.Itoa(int(end))
}
terms[i] = term
}
return strings.Join(terms, "/")
}

View File

@@ -1,9 +1,5 @@
package auth package auth
import (
"github.com/puzpuzpuz/xsync/v2"
)
type Authenticator interface { type Authenticator interface {
Verify(user string, pass string) bool Verify(user string, pass string) bool
Users() []string Users() []string
@@ -15,12 +11,12 @@ type AuthUser struct {
} }
type inMemoryAuthenticator struct { type inMemoryAuthenticator struct {
storage *xsync.MapOf[string, string] storage map[string]string
usernames []string usernames []string
} }
func (au *inMemoryAuthenticator) Verify(user string, pass string) bool { func (au *inMemoryAuthenticator) Verify(user string, pass string) bool {
realPass, ok := au.storage.Load(user) realPass, ok := au.storage[user]
return ok && realPass == pass return ok && realPass == pass
} }
@@ -30,17 +26,13 @@ func NewAuthenticator(users []AuthUser) Authenticator {
if len(users) == 0 { if len(users) == 0 {
return nil return nil
} }
au := &inMemoryAuthenticator{
au := &inMemoryAuthenticator{storage: xsync.NewMapOf[string]()} storage: make(map[string]string),
for _, user := range users { usernames: make([]string, 0, len(users)),
au.storage.Store(user.User, user.Pass) }
for _, user := range users {
au.storage[user.User] = user.Pass
au.usernames = append(au.usernames, user.User)
} }
usernames := make([]string, 0, len(users))
au.storage.Range(func(key string, value string) bool {
usernames = append(usernames, key)
return true
})
au.usernames = usernames
return au return au
} }

View File

@@ -5,7 +5,7 @@ import (
"net" "net"
"runtime" "runtime"
"github.com/Dreamacro/clash/component/dialer" "github.com/metacubex/mihomo/component/dialer"
) )
func ListenDHCPClient(ctx context.Context, ifaceName string) (net.PacketConn, error) { func ListenDHCPClient(ctx context.Context, ifaceName string) (net.PacketConn, error) {
@@ -14,5 +14,15 @@ func ListenDHCPClient(ctx context.Context, ifaceName string) (net.PacketConn, er
listenAddr = "255.255.255.255:68" listenAddr = "255.255.255.255:68"
} }
return dialer.ListenPacket(ctx, "udp4", listenAddr, dialer.WithInterface(ifaceName), dialer.WithAddrReuse(true)) options := []dialer.Option{
dialer.WithInterface(ifaceName),
dialer.WithAddrReuse(true),
}
// fallback bind on windows, because syscall bind can not receive broadcast
if runtime.GOOS == "windows" {
options = append(options, dialer.WithFallbackBind(true))
}
return dialer.ListenPacket(ctx, "udp4", listenAddr, options...)
} }

View File

@@ -6,8 +6,8 @@ import (
"net" "net"
"net/netip" "net/netip"
"github.com/Dreamacro/clash/common/nnip" "github.com/metacubex/mihomo/common/nnip"
"github.com/Dreamacro/clash/component/iface" "github.com/metacubex/mihomo/component/iface"
"github.com/insomniacslk/dhcp/dhcpv4" "github.com/insomniacslk/dhcp/dhcpv4"
) )

View File

@@ -3,9 +3,10 @@ package dialer
import ( import (
"net" "net"
"net/netip" "net/netip"
"strconv"
"strings" "strings"
"github.com/Dreamacro/clash/component/iface" "github.com/metacubex/mihomo/component/iface"
) )
func LookupLocalAddrFromIfaceName(ifaceName string, network string, destination netip.Addr, port int) (net.Addr, error) { func LookupLocalAddrFromIfaceName(ifaceName string, network string, destination netip.Addr, port int) (net.Addr, error) {
@@ -14,7 +15,7 @@ func LookupLocalAddrFromIfaceName(ifaceName string, network string, destination
return nil, err return nil, err
} }
var addr *netip.Prefix var addr netip.Prefix
switch network { switch network {
case "udp4", "tcp4": case "udp4", "tcp4":
addr, err = ifaceObj.PickIPv4Addr(destination) addr, err = ifaceObj.PickIPv4Addr(destination)
@@ -49,3 +50,52 @@ func LookupLocalAddrFromIfaceName(ifaceName string, network string, destination
return nil, iface.ErrAddrNotFound return nil, iface.ErrAddrNotFound
} }
func fallbackBindIfaceToDialer(ifaceName string, dialer *net.Dialer, network string, destination netip.Addr) error {
if !destination.IsGlobalUnicast() {
return nil
}
local := uint64(0)
if dialer.LocalAddr != nil {
_, port, err := net.SplitHostPort(dialer.LocalAddr.String())
if err == nil {
local, _ = strconv.ParseUint(port, 10, 16)
}
}
addr, err := LookupLocalAddrFromIfaceName(ifaceName, network, destination, int(local))
if err != nil {
return err
}
dialer.LocalAddr = addr
return nil
}
func fallbackBindIfaceToListenConfig(ifaceName string, _ *net.ListenConfig, network, address string) (string, error) {
_, port, err := net.SplitHostPort(address)
if err != nil {
port = "0"
}
local, _ := strconv.ParseUint(port, 10, 16)
addr, err := LookupLocalAddrFromIfaceName(ifaceName, network, netip.Addr{}, int(local))
if err != nil {
return "", err
}
return addr.String(), nil
}
func fallbackParseNetwork(network string, addr netip.Addr) string {
// fix fallbackBindIfaceToListenConfig() force bind to an ipv4 address
if !strings.HasSuffix(network, "4") &&
!strings.HasSuffix(network, "6") &&
addr.Unmap().Is6() {
network += "6"
}
return network
}

View File

@@ -6,7 +6,7 @@ import (
"net/netip" "net/netip"
"syscall" "syscall"
"github.com/Dreamacro/clash/component/iface" "github.com/metacubex/mihomo/component/iface"
"golang.org/x/sys/unix" "golang.org/x/sys/unix"
) )

View File

@@ -5,55 +5,16 @@ package dialer
import ( import (
"net" "net"
"net/netip" "net/netip"
"strconv"
"strings"
) )
func bindIfaceToDialer(ifaceName string, dialer *net.Dialer, network string, destination netip.Addr) error { func bindIfaceToDialer(ifaceName string, dialer *net.Dialer, network string, destination netip.Addr) error {
if !destination.IsGlobalUnicast() { return fallbackBindIfaceToDialer(ifaceName, dialer, network, destination)
return nil
}
local := uint64(0)
if dialer.LocalAddr != nil {
_, port, err := net.SplitHostPort(dialer.LocalAddr.String())
if err == nil {
local, _ = strconv.ParseUint(port, 10, 16)
}
}
addr, err := LookupLocalAddrFromIfaceName(ifaceName, network, destination, int(local))
if err != nil {
return err
}
dialer.LocalAddr = addr
return nil
} }
func bindIfaceToListenConfig(ifaceName string, _ *net.ListenConfig, network, address string) (string, error) { func bindIfaceToListenConfig(ifaceName string, lc *net.ListenConfig, network, address string) (string, error) {
_, port, err := net.SplitHostPort(address) return fallbackBindIfaceToListenConfig(ifaceName, lc, network, address)
if err != nil {
port = "0"
}
local, _ := strconv.ParseUint(port, 10, 16)
addr, err := LookupLocalAddrFromIfaceName(ifaceName, network, netip.Addr{}, int(local))
if err != nil {
return "", err
}
return addr.String(), nil
} }
func ParseNetwork(network string, addr netip.Addr) string { func ParseNetwork(network string, addr netip.Addr) string {
// fix bindIfaceToListenConfig() force bind to an ipv4 address return fallbackParseNetwork(network, addr)
if !strings.HasSuffix(network, "4") &&
!strings.HasSuffix(network, "6") &&
addr.Unmap().Is6() {
network += "6"
}
return network
} }

View File

@@ -9,7 +9,7 @@ import (
"syscall" "syscall"
"unsafe" "unsafe"
"github.com/Dreamacro/clash/component/iface" "github.com/metacubex/mihomo/component/iface"
) )
const ( const (

View File

@@ -11,7 +11,8 @@ import (
"sync" "sync"
"time" "time"
"github.com/Dreamacro/clash/component/resolver" "github.com/metacubex/mihomo/component/resolver"
"github.com/metacubex/mihomo/constant/features"
) )
type dialFunc func(ctx context.Context, network string, ips []netip.Addr, port string, opt *option) (net.Conn, error) type dialFunc func(ctx context.Context, network string, ips []netip.Addr, port string, opt *option) (net.Conn, error)
@@ -70,11 +71,19 @@ func DialContext(ctx context.Context, network, address string, options ...Option
} }
func ListenPacket(ctx context.Context, network, address string, options ...Option) (net.PacketConn, error) { func ListenPacket(ctx context.Context, network, address string, options ...Option) (net.PacketConn, error) {
if features.CMFA && DefaultSocketHook != nil {
return listenPacketHooked(ctx, network, address)
}
cfg := applyOptions(options...) cfg := applyOptions(options...)
lc := &net.ListenConfig{} lc := &net.ListenConfig{}
if cfg.interfaceName != "" { if cfg.interfaceName != "" {
addr, err := bindIfaceToListenConfig(cfg.interfaceName, lc, network, address) bind := bindIfaceToListenConfig
if cfg.fallbackBind {
bind = fallbackBindIfaceToListenConfig
}
addr, err := bind(cfg.interfaceName, lc, network, address)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -110,6 +119,10 @@ func GetTcpConcurrent() bool {
} }
func dialContext(ctx context.Context, network string, destination netip.Addr, port string, opt *option) (net.Conn, error) { func dialContext(ctx context.Context, network string, destination netip.Addr, port string, opt *option) (net.Conn, error) {
if features.CMFA && DefaultSocketHook != nil {
return dialContextHooked(ctx, network, destination, port)
}
address := net.JoinHostPort(destination.String(), port) address := net.JoinHostPort(destination.String(), port)
netDialer := opt.netDialer netDialer := opt.netDialer
@@ -125,7 +138,11 @@ func dialContext(ctx context.Context, network string, destination netip.Addr, po
dialer := netDialer.(*net.Dialer) dialer := netDialer.(*net.Dialer)
if opt.interfaceName != "" { if opt.interfaceName != "" {
if err := bindIfaceToDialer(opt.interfaceName, dialer, network, destination); err != nil { bind := bindIfaceToDialer
if opt.fallbackBind {
bind = fallbackBindIfaceToDialer
}
if err := bind(opt.interfaceName, dialer, network, destination); err != nil {
return nil, err return nil, err
} }
} }

View File

@@ -7,7 +7,7 @@ import (
"net/netip" "net/netip"
"sync" "sync"
"github.com/Dreamacro/clash/log" "github.com/metacubex/mihomo/log"
) )
var printMarkWarnOnce sync.Once var printMarkWarnOnce sync.Once

View File

@@ -4,8 +4,8 @@ import (
"context" "context"
"net" "net"
"github.com/Dreamacro/clash/common/atomic" "github.com/metacubex/mihomo/common/atomic"
"github.com/Dreamacro/clash/component/resolver" "github.com/metacubex/mihomo/component/resolver"
) )
var ( var (
@@ -20,6 +20,7 @@ type NetDialer interface {
type option struct { type option struct {
interfaceName string interfaceName string
fallbackBind bool
addrReuse bool addrReuse bool
routingMark int routingMark int
network int network int
@@ -38,6 +39,12 @@ func WithInterface(name string) Option {
} }
} }
func WithFallbackBind(fallback bool) Option {
return func(opt *option) {
opt.fallbackBind = fallback
}
}
func WithAddrReuse(reuse bool) Option { func WithAddrReuse(reuse bool) Option {
return func(opt *option) { return func(opt *option) {
opt.addrReuse = reuse opt.addrReuse = reuse

View File

@@ -0,0 +1,39 @@
//go:build android && cmfa
package dialer
import (
"context"
"net"
"net/netip"
"syscall"
)
type SocketControl func(network, address string, conn syscall.RawConn) error
var DefaultSocketHook SocketControl
func dialContextHooked(ctx context.Context, network string, destination netip.Addr, port string) (net.Conn, error) {
dialer := &net.Dialer{
Control: DefaultSocketHook,
}
conn, err := dialer.DialContext(ctx, network, net.JoinHostPort(destination.String(), port))
if err != nil {
return nil, err
}
if t, ok := conn.(*net.TCPConn); ok {
t.SetKeepAlive(false)
}
return conn, nil
}
func listenPacketHooked(ctx context.Context, network, address string) (net.PacketConn, error) {
lc := &net.ListenConfig{
Control: DefaultSocketHook,
}
return lc.ListenPacket(ctx, network, address)
}

View File

@@ -0,0 +1,22 @@
//go:build !(android && cmfa)
package dialer
import (
"context"
"net"
"net/netip"
"syscall"
)
type SocketControl func(network, address string, conn syscall.RawConn) error
var DefaultSocketHook SocketControl
func dialContextHooked(ctx context.Context, network string, destination netip.Addr, port string) (net.Conn, error) {
return nil, nil
}
func listenPacketHooked(ctx context.Context, network, address string) (net.PacketConn, error) {
return nil, nil
}

View File

@@ -173,7 +173,7 @@ static __always_inline bool is_lan_ip(__be32 addr) {
return false; return false;
} }
SEC("tc_clash_auto_redir_ingress") SEC("tc_mihomo_auto_redir_ingress")
int tc_redir_ingress_func(struct __sk_buff *skb) { int tc_redir_ingress_func(struct __sk_buff *skb) {
void *data = (void *)(long)skb->data; void *data = (void *)(long)skb->data;
void *data_end = (void *)(long)skb->data_end; void *data_end = (void *)(long)skb->data_end;
@@ -264,7 +264,7 @@ int tc_redir_ingress_func(struct __sk_buff *skb) {
return TC_ACT_OK; return TC_ACT_OK;
} }
SEC("tc_clash_auto_redir_egress") SEC("tc_mihomo_auto_redir_egress")
int tc_redir_egress_func(struct __sk_buff *skb) { int tc_redir_egress_func(struct __sk_buff *skb) {
void *data = (void *)(long)skb->data; void *data = (void *)(long)skb->data;
void *data_end = (void *)(long)skb->data_end; void *data_end = (void *)(long)skb->data_end;
@@ -276,10 +276,10 @@ int tc_redir_egress_func(struct __sk_buff *skb) {
if (eth->h_proto != bpf_htons(ETH_P_IP)) if (eth->h_proto != bpf_htons(ETH_P_IP))
return TC_ACT_OK; return TC_ACT_OK;
__u32 key = 0, *redir_ip, *redir_port; // *clash_mark __u32 key = 0, *redir_ip, *redir_port; // *mihomo_mark
// clash_mark = bpf_map_lookup_elem(&redir_params_map, &key); // mihomo_mark = bpf_map_lookup_elem(&redir_params_map, &key);
// if (clash_mark && *clash_mark != 0 && *clash_mark == skb->mark) // if (mihomo_mark && *mihomo_mark != 0 && *mihomo_mark == skb->mark)
// return TC_ACT_OK; // return TC_ACT_OK;
struct iphdr *iph = (struct iphdr *)(eth + 1); struct iphdr *iph = (struct iphdr *)(eth + 1);

View File

@@ -38,7 +38,7 @@ static __always_inline bool is_lan_ip(__be32 addr) {
return false; return false;
} }
SEC("tc_clash_redirect_to_tun") SEC("tc_mihomo_redirect_to_tun")
int tc_tun_func(struct __sk_buff *skb) { int tc_tun_func(struct __sk_buff *skb) {
void *data = (void *)(long)skb->data; void *data = (void *)(long)skb->data;
void *data_end = (void *)(long)skb->data_end; void *data_end = (void *)(long)skb->data_end;
@@ -50,13 +50,13 @@ int tc_tun_func(struct __sk_buff *skb) {
if (eth->h_proto == bpf_htons(ETH_P_ARP)) if (eth->h_proto == bpf_htons(ETH_P_ARP))
return TC_ACT_OK; return TC_ACT_OK;
__u32 key = 0, *clash_mark, *tun_ifindex; __u32 key = 0, *mihomo_mark, *tun_ifindex;
clash_mark = bpf_map_lookup_elem(&tc_params_map, &key); mihomo_mark = bpf_map_lookup_elem(&tc_params_map, &key);
if (!clash_mark) if (!mihomo_mark)
return TC_ACT_OK; return TC_ACT_OK;
if (skb->mark == *clash_mark) if (skb->mark == *mihomo_mark)
return TC_ACT_OK; return TC_ACT_OK;
if (eth->h_proto == bpf_htons(ETH_P_IP)) { if (eth->h_proto == bpf_htons(ETH_P_IP)) {

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