Compare commits

...

1228 Commits

Author SHA1 Message Date
enfein
a001b1b110 chore: update mieru version (#2403) 2025-12-01 08:42:28 +08:00
wwqgtxx
d1f89fa05e chore: update tfo-go ready for go1.26 2025-12-01 01:12:31 +08:00
wwqgtxx
e2796e2d5c chore: apply ping destination filter for windows 2025-11-30 17:00:04 +08:00
futai
93de49d20c chore: sync sudoku with mihomo log (#2402) 2025-11-29 15:21:29 +08:00
wwqgtxx
4d3167ff2f chore: completely remove relay group type using dialer-proxy instead
which was marked as deprecated in v1.18.6
2025-11-29 09:39:28 +08:00
enfein
5998956a72 fix: a nil pointer error when closing mieru underlay (#2401) 2025-11-29 08:39:45 +08:00
futai
6cf1743961 feat: add Sudoku protocol inbound & outbound support (#2397) 2025-11-28 23:40:00 +08:00
Sinspired
8b6ba22b90 fix: replace wrong SetString() with SetBool() for uint weak-typed input (#2394)
The uint branch in decodeBool() incorrectly used SetString(). Use SetBool(dataVal.Uint() != 0) to match expected behavior.
2025-11-26 10:35:26 +08:00
wwqgtxx
7571c87afb chore: add fake-ip-ttl to dns section 2025-11-23 21:34:30 +08:00
wwqgtxx
d4d2c062a3 test: skip inbound test on darwin 2025-11-23 21:34:30 +08:00
TargetLocked
438d4138d6 fix: compare authentication scheme case-insensitively (#2386) 2025-11-23 19:34:02 +08:00
wwqgtxx
140d892ccf chore: better log 2025-11-22 20:59:53 +08:00
enfein
5aa140c493 feat: support mieru UDP outbound (#2384) 2025-11-22 08:54:14 +08:00
enfein
c107c6a824 fix: crash due to nil net.Conn from mieru inbound (#2361) 2025-11-15 07:42:29 +08:00
wwqgtxx
f6e494e73f chore: upgrade the embedded xsync.Map to v4.2.0 2025-11-14 00:23:01 +08:00
hi
0b3159bf9b chore: remove redundant code (#2355) 2025-11-11 17:06:49 +08:00
wwqgtxx
45fd628788 fix: bugs in kcp-go and smux 2025-11-11 09:33:41 +08:00
wwqgtxx
2f545ef634 fix: hosts not working
https://github.com/MetaCubeX/mihomo/issues/2351
2025-11-10 10:55:02 +08:00
wwqgtxx
054e63cb3f chore: remove depend of purego 2025-11-10 00:46:09 +08:00
wwqgtxx
d48bcf1e1e fix: fakeip6 logic not work correctly 2025-11-09 19:19:20 +08:00
wwqgtxx
0df2f79ece fix: missing metadata in mieru inbound 2025-11-09 11:41:37 +08:00
wwqgtxx
644c04fdc9 chore: update sing-tun 2025-11-09 10:09:01 +08:00
wwqgtxx
926aaec717 chore: update purego 2025-11-09 10:08:51 +08:00
enfein
a4b76809ac feat: support mieru inbound (#2347) 2025-11-09 09:29:47 +08:00
wwqgtxx
ff76576cbe chore: cleanup import path for listener 2025-11-06 19:39:03 +08:00
wwqgtxx
1d5890abc1 chore: cleanup import path for constant/provider 2025-11-06 19:32:12 +08:00
wwqgtxx
a3c023ae3e chore: cleanup import path for component/process 2025-11-06 19:27:40 +08:00
wwqgtxx
8b32c4371e fix: race in kcp-go 2025-11-05 16:50:22 +08:00
wwqgtxx
5a285acd32 chore: reduce the global variables that should not be used in amneziawg-go 2025-11-05 01:44:18 +08:00
wwqgtxx
c2209d68f7 fix: vision panic with dialer-proxy
https://github.com/MetaCubeX/mihomo/issues/2334
2025-11-04 18:54:33 +08:00
wwqgtxx
fd39c2a7fc chore: better maphash test 2025-11-03 15:34:46 +08:00
wwqgtxx
421dc79aea chore: cleanup default value in parseProxy 2025-11-03 15:29:14 +08:00
wwqgtxx
27b47f976c chore: structure support ignore tag 2025-11-03 15:01:07 +08:00
wwqgtxx
c25a38898f chore: share append chains logic 2025-11-03 10:12:24 +08:00
wwqgtxx
f3edbc2b45 chore: remove unused code 2025-11-03 09:56:19 +08:00
Restia-Ashbell
6fb1f796a5 fix: handle nil pointer stored in non-nil interface (#2337) 2025-11-02 20:02:16 +08:00
wwqgtxx
99e68e9983 chore: update dependencies 2025-10-31 17:18:22 +08:00
wwqgtxx
6bffbdd9d3 chore: ignore ipv6 check when get interface addrs failed 2025-10-28 18:30:57 +08:00
wwqgtxx
cfdaebe952 chore: check fake-ip-range and fake-ip-range6 are indeed ipv4 and ipv6 prefixes 2025-10-28 16:23:01 +08:00
wwqgtxx
c8af92a01f feat: support fake-ip-range6 in dns module 2025-10-28 11:40:00 +08:00
wwqgtxx
ff62386f6b chore: reduce internal dependencies of the ntp package 2025-10-27 17:29:57 +08:00
wwqgtxx
85c56e7446 chore: revert ade42346 for convert speed 2025-10-27 11:37:49 +08:00
wwqgtxx
9ed9c3d1c3 fix: docker build 2025-10-26 11:10:40 +08:00
vernesong
dcfe664a7d fix: strategyFn index out of range if proxies changed (#2330) 2025-10-26 10:10:36 +08:00
wwqgtxx
fb1ae21fb7 chore: remove unused code 2025-10-26 09:42:36 +08:00
wwqgtxx
90f47a6d0c fix: openbsd build 2025-10-24 18:20:05 +08:00
wwqgtxx
f2bf4a077e fix: memory leak in h3 stream hijack 2025-10-24 14:03:53 +08:00
wwqgtxx
8701639347 chore: update bart 2025-10-20 12:19:36 +08:00
wwqgtxx
5bc0ac7281 fix: openbsd build 2025-10-18 19:56:39 +08:00
wwqgtxx
c5fe3670ef action: fix build 2025-10-15 01:35:29 +08:00
wwqgtxx
de2ff37f4f chore: update utls 2025-10-14 23:08:15 +08:00
wwqgtxx
da69b192f2 chore: remove unused global goroutine in kcp-go 2025-10-08 02:36:37 +08:00
wwqgtxx
c13549f564 action: update actions version 2025-10-06 09:23:22 +08:00
wwqgtxx
ce168c0e67 action: update macos-13 to macos-15-intel 2025-10-06 09:16:50 +08:00
wwqgtxx
d225625378 chore: update quic-go to 0.55.0 2025-10-04 13:41:35 +08:00
wwqgtxx
40e0813869 chore: adjust the internal code structure of the dns module 2025-09-29 11:24:41 +08:00
wwqgtxx
94b591ed44 chore: separate the DNS enhancer config passing 2025-09-29 10:36:55 +08:00
wwqgtxx
f7bd8b83e5 chore: revert "chore: consolidate mieru port configuration (#2277)"
The `port` field should not be allowed to be set to non-int values, as this would break some downstream assumptions that the option is an int.

This reverts commit 1b1f95aa9c.
2025-09-28 20:28:48 +08:00
wwqgtxx
f45c6f5e91 chore: update quic-go to 0.54.1 2025-09-26 08:13:31 +08:00
wwqgtxx
3b15cbd9eb doc: fix resend doc 2025-09-24 23:28:08 +08:00
wwqgtxx
6f4da5f1fb doc: fix crypt doc 2025-09-24 20:43:13 +08:00
wwqgtxx
fdc46f0471 chore: update utls to tag version 2025-09-24 16:05:38 +08:00
wwqgtxx
57b527d54a chore: simplify GetMemoryInfo in darwin 2025-09-24 02:53:35 +08:00
wwqgtxx
29eaa4d699 chore: add test for memory module 2025-09-24 02:35:04 +08:00
wwqgtxx
92ecdfcc00 fix: data race on darwin 2025-09-24 02:25:26 +08:00
wwqgtxx
0992ee8adf chore: remove depend of gopsutil 2025-09-24 02:21:47 +08:00
wwqgtxx
0c25831726 chore: replace HasAESGCMHardwareSupport in vless encryption 2025-09-23 23:14:45 +08:00
wwqgtxx
9cc208bd47 fix: reality shouldn't check chacha 2025-09-23 21:57:47 +08:00
wwqgtxx
b57f3052a8 chore: speedup convid generation 2025-09-23 08:33:11 +08:00
wwqgtxx
3a1caf1316 chore: better batchConn handle in kcp-go 2025-09-23 08:23:30 +08:00
enfein
1b1f95aa9c chore: consolidate mieru port configuration (#2277)
Allow multiple port numbers and port ranges in "port" configuration.
The "port-range" configuration is deprecated but still functional.
2025-09-23 08:19:12 +08:00
wwqgtxx
8a9300d44e chore: better WriteBuffers support in smux 2025-09-23 01:53:26 +08:00
wwqgtxx
abe6c3bb35 feat: support kcptun plugin for ss client/server 2025-09-23 00:28:38 +08:00
anytls
e28c8e6a51 chore: sync anytls v0.0.11 (#2276)
Co-authored-by: anytls <anytls>
2025-09-22 17:29:32 +08:00
wwqgtxx
74a86f147b chore: update dependencies 2025-09-21 18:48:36 +08:00
wwqgtxx
7917f24f42 chore: more check in listeners start 2025-09-20 01:13:55 +08:00
wwqgtxx
0dc5e3051d feat: add mTLS support for client & server
`certificate` and `private-key` for proxies
`client-auth-type` and `client-auth-cert` for listeners
2025-09-20 00:41:39 +08:00
wwqgtxx
40b2cde2b2 chore: cleanup dns client code 2025-09-19 21:18:16 +08:00
wwqgtxx
6786705212 feat: remove ca and ca-str in hy1/hy2/tuic outbound, using fingerprint instead 2025-09-19 18:19:36 +08:00
wwqgtxx
00638f30a7 chore: don't test sing-mux over grpc 2025-09-19 14:46:38 +08:00
wwqgtxx
2222d0e3fa chore: update gvisor 2025-09-19 08:51:52 +08:00
wwqgtxx
f3ebd5c489 chore: clarify function descriptions and variable names 2025-09-19 08:02:25 +08:00
wwqgtxx
74e64d31e3 fix: maybe "invalid cross-device link" in update ui
https://github.com/MetaCubeX/mihomo/issues/2270
2025-09-18 18:49:24 +08:00
wwqgtxx
0c556bcaf3 chore: replace hashicorp/yamux to our forked libp2p/go-yamux 2025-09-18 16:48:33 +08:00
wwqgtxx
7e71d21ab4 chore: improve fingerprint verifier handle non-leaf certificate 2025-09-17 11:18:14 +08:00
wwqgtxx
30bead4e2e fix: ntp not apply to reality client 2025-09-17 10:46:13 +08:00
wwqgtxx
16836ea465 action: fix test 2025-09-16 17:47:42 +08:00
wwqgtxx
d6f1af5372 chore: cleanup queue code 2025-09-16 14:19:42 +08:00
DNEGEL3125
26335f8ecc test: add unit tests for queue (#2266)
Add tests covering all queue operations
2025-09-16 14:04:26 +08:00
wwqgtxx
c4449a9f62 fix: ntp not apply to reality server 2025-09-16 10:23:36 +08:00
wwqgtxx
5e17d6fe01 chore: simplify N.Relay 2025-09-16 09:49:41 +08:00
wwqgtxx
8cdfd87d1e fix: ip4p port not apply in resolveUDPAddr 2025-09-15 08:34:30 +08:00
wwqgtxx
f02766a765 fix: reshaping buffer maybe too long in vision 2025-09-14 12:24:36 +08:00
wwqgtxx
cea29e2615 chore: sync code style 2025-09-13 14:31:30 +08:00
wwqgtxx
a0f1ac4ef5 chore: apply ntp time function more place 2025-09-13 14:21:33 +08:00
wwqgtxx
57e14e5b62 chore: cleanup internal ca using 2025-09-13 14:13:01 +08:00
wwqgtxx
08fc100c85 chore: cleanup ntp code 2025-09-13 11:19:06 +08:00
enfein
571be856ea feat: support mieru 0-RTT handshake (#2261) 2025-09-13 10:59:57 +08:00
wwqgtxx
ad69ee84a9 chore: cleanup ntp code 2025-09-13 10:30:14 +08:00
wwqgtxx
dd7b3c28ad fix: race codes 2025-09-13 10:29:38 +08:00
wwqgtxx
1b99759eaf fix: ntp time method not passing to ss2022 client 2025-09-12 23:09:46 +08:00
wwqgtxx
a8f7e25851 fix: backticks cannot be used to separate multiple regular expressions in the exclude-filter of proxy-providers
https://github.com/MetaCubeX/mihomo/issues/2259
2025-09-12 17:55:22 +08:00
wwqgtxx
909729ca8f fix: allow use vision on vless encryption over ws 2025-09-11 23:49:14 +08:00
wwqgtxx
6c527f8d20 fix: panic when wintun dll fails to load 2025-09-10 15:02:27 +08:00
wwqgtxx
7061c5a8ea fix: possible data location errors in vision read 2025-09-10 10:14:21 +08:00
wwqgtxx
23448ec119 fix: incomplete read filter in vision 2025-09-10 02:36:38 +08:00
wwqgtxx
318b3524c3 chore: better handwritten addons parsing 2025-09-09 15:16:57 +08:00
wwqgtxx
1d09ed82f1 chore: simplify resolveUDPAddr 2025-09-09 08:48:09 +08:00
wwqgtxx
b27325eed0 chore: update dependencies 2025-09-09 08:35:50 +08:00
wwqgtxx
0d3d31dc5f chore: ready for handwritten addons parsing 2025-09-08 21:51:30 +08:00
wwqgtxx
108bf645fa chore: merge the server-side and client-side vision implementations 2025-09-08 16:29:07 +08:00
wwqgtxx
50e1afd963 chore: cleanup vless code 2025-09-08 15:58:54 +08:00
wwqgtxx
0336d64e52 chore: cleanup vision code 2025-09-08 10:55:09 +08:00
wwqgtxx
02d954bfa8 fix: server mux conn not close 2025-09-08 10:55:09 +08:00
NuoFang
9a124a390f feat: add disable-icmp-forwarding option to tun (#2248)
When enabled, the TUN listener will use fake ping echo. This can be useful to prevent potential ICMP routing loops in certain network configurations.
2025-09-07 19:40:30 +08:00
nunu6689
fed4b369a3 fix: auto update local file provider (#2245)
Using `Direct: true` will ignore the changes when the file was modified using replace method. Which is a common method used by ftp software.
2025-09-05 13:07:51 +08:00
wwqgtxx
f8ee5c1e15 chore: sync vless encryption code 2025-09-04 22:09:11 +08:00
wwqgtxx
8eba1c8afd chore: sync vless encryption code 2025-09-04 21:57:52 +08:00
wwqgtxx
65d3920f02 chore: update dependencies 2025-09-04 15:48:57 +08:00
wwqgtxx
7e9e12cc4c fix: SyscallVectorisedPacketWriter not handle inet type in address processing 2025-09-03 10:31:44 +08:00
wwqgtxx
80a90f05f3 feat: support AmneziaWG v2.0 2025-09-02 22:15:56 +08:00
wwqgtxx
3b63fef2eb chore: better defensive programming 2025-09-02 01:15:40 +08:00
wwqgtxx
29872007b3 chore: sync vless encryption code 2025-09-02 01:09:48 +08:00
wwqgtxx
33cde6592e chore: sync vless encryption code 2025-08-31 19:43:14 +08:00
wwqgtxx
c98f5f44b7 chore: sync vless encryption code 2025-08-31 12:31:13 +08:00
wwqgtxx
d094075c88 doc: update doc 2025-08-31 10:42:09 +08:00
wwqgtxx
4188277b61 fix: tuic server goroutine leak 2025-08-31 10:42:09 +08:00
wwqgtxx
545d9b844d chore: sync vless encryption code 2025-08-31 10:42:09 +08:00
wwqgtxx
472cefb6d7 fix: snat key in packet listener 2025-08-29 11:28:50 +08:00
wwqgtxx
ccff0035cb fix: get localAddr error 2025-08-29 11:28:50 +08:00
wwqgtxx
455f2136f1 fix: xudp server source addr losing 2025-08-29 09:23:45 +08:00
wwqgtxx
63781b3a6a chore: decrease memory using 2025-08-28 23:44:03 +08:00
wwqgtxx
c6e596f33f chore: full reset buffer after directRead 2025-08-28 23:44:03 +08:00
wwqgtxx
45cb45accb chore: simplify randBetween 2025-08-28 23:44:03 +08:00
wwqgtxx
5c73025b53 chore: change vless encryption code to our style 2025-08-28 23:44:03 +08:00
wwqgtxx
cdd02a90c3 chore: sync vless encryption code 2025-08-28 08:49:21 +08:00
wwqgtxx
0ced98da4d feat: support sending ping requests via direct in tun mode 2025-08-27 18:00:25 +08:00
wwqgtxx
84086a6e6c chore: update dependencies 2025-08-27 17:14:48 +08:00
wwqgtxx
443200a51e chore: sync vless encryption code 2025-08-25 20:23:09 +08:00
wwqgtxx
aca0d97beb chore: sync vless encryption code 2025-08-25 11:31:21 +08:00
xishang0128
2605bf78f9 fix: add code signing for macOS executables during file copy 2025-08-25 11:31:21 +08:00
eWloYW8
d2395fb43a fix: allow disabling ALPN by setting an empty array (#2225) 2025-08-25 11:31:21 +08:00
wwqgtxx
e3d9a8e2fd fix: vision on vless encryption 2025-08-25 11:31:21 +08:00
wwqgtxx
1ae050ca3b chore: sync vless encryption code 2025-08-25 11:31:21 +08:00
wwqgtxx
7f38763e22 chore: update hkdf using 2025-08-23 20:13:11 +08:00
wwqgtxx
2a8831b0d0 chore: sync vless encryption code 2025-08-22 18:45:17 +08:00
wwqgtxx
cdf5e0c73e chore: rewrite vision client write 2025-08-22 11:28:17 +08:00
wwqgtxx
48f3ea8bc9 fix: buffer handle in vision server read 2025-08-22 11:28:05 +08:00
wwqgtxx
375e160368 fix: data loss in vision server read 2025-08-22 11:27:55 +08:00
wwqgtxx
b31664beeb chore: sync vless encryption code 2025-08-21 19:42:56 +08:00
wwqgtxx
7960bcae15 chore: code cleanup 2025-08-21 19:37:26 +08:00
wwqgtxx
664ddb8d55 chore: simplifying generator code 2025-08-21 16:02:17 +08:00
wwqgtxx
e4dfe09744 chore: output vless hash11 in generater 2025-08-21 11:25:41 +08:00
wwqgtxx
b56068ee1c chore: make vision server support splice 2025-08-21 11:17:36 +08:00
wwqgtxx
99e888c829 fix: missing WriterReplaceable for deadline.Conn 2025-08-21 10:46:55 +08:00
wwqgtxx
873d0deeaa chore: make XorConn replaceable for splice 2025-08-21 09:03:44 +08:00
wwqgtxx
7e0a77c99c chore: sync vless encryption code 2025-08-21 08:33:44 +08:00
wwqgtxx
5f09db2655 feat: support AmneziaWG v1.5 2025-08-20 15:42:04 +08:00
wwqgtxx
10174d281c chore: update wireguard-go 2025-08-20 14:50:19 +08:00
wwqgtxx
12c30acdda chore: cleanup vision code 2025-08-20 10:34:38 +08:00
wwqgtxx
2790481709 chore: update cast using in sing-vmess 2025-08-19 23:15:51 +08:00
wwqgtxx
182f60d424 chore: sync vless encryption code 2025-08-19 21:37:02 +08:00
wwqgtxx
930c70f065 doc: remind ordinary users that they should use tun in the top-level configuration 2025-08-19 16:56:35 +08:00
wwqgtxx
fc61715e4e chore: add handshake-mode for mieru 2025-08-19 10:16:59 +08:00
enfein
438be2d379 chore: update mieru version (#2215)
v3.19.0 optimized CPU consumption.

Tested: https://github.com/enfein/mieru/actions/runs/17014543289
2025-08-19 07:10:29 +08:00
wwqgtxx
4e20ed65f2 chore: sync vless encryption code 2025-08-18 23:08:30 +08:00
wwqgtxx
ce760fcf19 action: better patch file download 2025-08-18 10:23:43 +08:00
wwqgtxx
0f76fdf4c5 fix: vision on vless encryption 2025-08-18 09:34:20 +08:00
wwqgtxx
03f4513f61 chore: sync vless encryption code 2025-08-18 08:43:17 +08:00
wwqgtxx
26f603057f fix: 335d54e4 sync mistake 2025-08-18 08:40:52 +08:00
wwqgtxx
b481eca4a4 chore: allow vision with vless encryption 2025-08-17 16:14:20 +08:00
wwqgtxx
eb028b65fc chore: better reflect using in vision 2025-08-17 16:03:35 +08:00
wwqgtxx
48c1b1cdb2 chore: remove depend on lunixbochs/struc 2025-08-16 11:16:53 +08:00
wwqgtxx
76e40baebc chore: sync vless encryption code 2025-08-14 23:52:05 +08:00
wwqgtxx
946b4025df chore: code cleanup 2025-08-14 23:48:59 +08:00
wwqgtxx
089766b285 chore: update TypedValue in sing 2025-08-14 18:36:01 +08:00
wwqgtxx
b643388539 chore: sync vless encryption code 2025-08-14 18:31:56 +08:00
wwqgtxx
0836ec6ee3 chore: change time.Duration atomic using 2025-08-14 16:01:20 +08:00
wwqgtxx
eeb2ad8dae chore: add more test for TypedValue 2025-08-14 14:37:39 +08:00
wwqgtxx
71290b057f chore: reimplement TypedValue by atomic.Pointer 2025-08-14 10:57:56 +08:00
wwqgtxx
41b321dfe1 chore: sync vless encryption code 2025-08-14 09:57:20 +08:00
wwqgtxx
a18e99f966 chore: update dependencies 2025-08-14 09:55:26 +08:00
wwqgtxx
f90d0b954c chore: using atomic.Pointer in anytls 2025-08-14 00:51:55 +08:00
wwqgtxx
0408da2aee chore: sync vless encryption code 2025-08-13 20:50:46 +08:00
wwqgtxx
335d54e488 chore: sync vless encryption code 2025-08-13 19:50:53 +08:00
wwqgtxx
d11f9c895c chore: sync vless encryption code 2025-08-13 18:54:26 +08:00
wwqgtxx
e54ca7ceca chore: sync vless encryption code 2025-08-13 18:51:47 +08:00
wwqgtxx
ce82d49c25 chore: update golang to 1.25 2025-08-13 18:05:24 +08:00
wwqgtxx
8e6be1992b fix: h2mux client closed 2025-08-13 16:43:26 +08:00
wwqgtxx
0e9102daae chore: don't test h2mux for the inbound 2025-08-13 01:15:39 +08:00
wwqgtxx
46dccf26d1 chore: sync vless encryption code 2025-08-13 01:14:22 +08:00
wwqgtxx
854c6a13c3 chore: sync vless encryption code 2025-08-12 22:59:25 +08:00
wwqgtxx
b4c3bbf660 chore: sync vless encryption code 2025-08-12 20:06:08 +08:00
wwqgtxx
6c726d6436 chore: test different http data size for inbound 2025-08-12 15:54:39 +08:00
wwqgtxx
a0bdb861a9 chore: rebuild vless encryption string parsing 2025-08-12 08:46:44 +08:00
wwqgtxx
eca5a27774 fix: mlkem768 logging 2025-08-11 23:00:35 +08:00
wwqgtxx
16d95df100 chore: better wildcard test 2025-08-11 22:33:01 +08:00
wwqgtxx
9b90719ddd feat: support optional aes128xor layer for vless encryption 2025-08-11 20:57:23 +08:00
wwqgtxx
7392529677 chore: add a confused benchmark for wildcard 2025-08-11 18:13:34 +08:00
wwqgtxx
dc52c38179 fix: ? in DOMAIN-WILDCARD should match exactly one character
https://github.com/MetaCubeX/mihomo/issues/2204
2025-08-11 16:23:39 +08:00
wwqgtxx
d7999a32d3 chore: using named const value 2025-08-11 16:23:39 +08:00
wwqgtxx
b41ea05481 chore: add encryption to converter 2025-08-11 16:23:39 +08:00
wwqgtxx
e6fe895190 chore: sync code
3e19bf9233
2025-08-11 16:23:39 +08:00
wwqgtxx
adf553a958 fix: generate doc 2025-08-11 16:23:39 +08:00
wwqgtxx
2a915a5c94 fix: vless server close 2025-08-10 22:43:31 +08:00
wwqgtxx
1b0c72bfab feat: support vless encryption 2025-08-10 22:24:39 +08:00
wwqgtxx
e89af723cd fix: auto redirect panic 2025-08-01 21:02:59 +08:00
wwqgtxx
e8fddd85af fix: vless packetaddr not working 2025-07-31 11:39:06 +08:00
wwqgtxx
f04af734e3 chore: update quic-go to 0.54.0 2025-07-30 19:45:36 +08:00
wwqgtxx
00035302a1 chore: let /upgrade support channel and force as parameters in restful api
Leaving `channel` blank will automatically determine the channel. Other valid values are `alpha`/`release`.

Setting `force` to `true` will bypass the version check and force the update.
2025-07-30 17:50:11 +08:00
wwqgtxx
578e659bb9 chore: keep original file permissions when unpack in updater 2025-07-30 17:09:08 +08:00
wwqgtxx
0f1baeb935 fix: updater may not be able to overwrite files directly 2025-07-28 22:21:57 +08:00
wwqgtxx
16ff9e815b chore: code cleanup 2025-07-27 22:30:39 +08:00
wwqgtxx
5f1f296213 chore: add /cache/dns/flush to restful api 2025-07-27 12:30:33 +08:00
wwqgtxx
66fd5c9f0c chore: allow setting cache-max-size in dns section 2025-07-27 10:31:12 +08:00
wwqgtxx
c3a3009a8c chore: keep original file permissions when copyFile in updater 2025-07-26 22:10:47 +08:00
xishang0128
01cd7e2c0e chore: improve backup and replace logic in updater 2025-07-26 22:49:20 +09:00
wwqgtxx
deec7aafe5 chore: optimizing download in updater 2025-07-26 15:15:11 +08:00
wwqgtxx
a9b7e705f0 chore: optimizing copyFile in updater 2025-07-26 01:32:49 +08:00
xishang0128
fb043df1b6 chore: use canonical return value order 2025-07-25 23:38:35 +09:00
xishang0128
748b5df902 chore: keep original file permissions after update 2025-07-25 23:38:35 +09:00
wwqgtxx
8cbae59d55 chore: upgrade bbolt 2025-07-25 21:59:54 +08:00
wwqgtxx
a37440c81b fix: some downstream dependencies on the upgrader's output fields 2025-07-25 17:57:34 +08:00
wwqgtxx
dbb002a5ba action: add deb/rpm packages for GOAMD64 v1/2/3 2025-07-24 17:40:18 +08:00
wwqgtxx
1a84153213 chore: code cleanup 2025-07-24 15:40:32 +08:00
wwqgtxx
dfe6e0509b chore: rebuild core updater 2025-07-24 02:08:14 +08:00
xishang0128
b6dde7ded7 action: use a more standardized naming format while retaining some compatibility with the old format 2025-07-23 22:58:41 +09:00
wwqgtxx
9f1da11792 chore: use the compile-time GOAMD64 flag in the updater 2025-07-23 18:08:08 +08:00
白日梦主义
63ad95e10f fix: remove unconventional bits when unpacking for update_ui (#2178) 2025-07-22 22:45:20 +08:00
白日梦主义
b06ec5bef8 fix: add path safety check in file type providers (#2177) 2025-07-22 21:37:54 +08:00
wwqgtxx
d4fbffd8e8 chore: update utls to 1.8.0 2025-07-22 15:00:25 +08:00
wwqgtxx
305020175d fix: darwin system stack problem 2025-07-21 10:11:03 +08:00
wwqgtxx
79decdc253 fix: vision server crash 2025-07-20 15:18:01 +08:00
wwqgtxx
407c13b8a4 fix: hy2 server crash 2025-07-19 00:58:33 +08:00
wwqgtxx
d84b182be3 fix: darwin tun mixed stack not working 2025-07-18 11:29:39 +08:00
wwqgtxx
8f18d3f6db chore: add recvmsgx and sendmsgx config to tun
Only for advanced users, enabling `recvmsgx` under darwin can improve performance, but enabling `sendmsgx` may cause unknown problems, please use with caution.
2025-07-17 23:42:25 +08:00
wwqgtxx
b9260e06b8 chore: improve darwin tun performance 2025-07-17 22:11:57 +08:00
wwqgtxx
6337151207 chore: upgrade bbolt to 1.4.2 2025-07-15 22:09:51 +08:00
wwqgtxx
aa555ced5f chore: allow embedded xsync.Map to be lazily initialized 2025-07-15 17:33:36 +08:00
wwqgtxx
349b773b40 chore: upgrade and embed the xsync.Map to v4 2025-07-15 13:39:03 +08:00
wwqgtxx
300eb8b12a chore: rebuild rule parsing code 2025-07-14 10:35:33 +08:00
wwqgtxx
2b84dd3618 fix: regex in logic rules
https://github.com/MetaCubeX/mihomo/issues/2150
2025-07-07 16:16:16 +08:00
wwqgtxx
6a620ba287 chore: revert "chore: better dns batchExchange"
This reverts commit 55f626424f.

The previous changes resulted in a situation where no resolution results were found when multiple DNS servers were used concurrently, and the final resolution time was dragged down by the slowest server.
2025-07-05 23:05:49 +08:00
wwqgtxx
56c3462b76 chore: update quic-go to 0.53.0 2025-06-28 18:16:29 +08:00
wwqgtxx
6f4fe71e41 chore: update dependencies 2025-06-28 12:51:06 +08:00
enfein
ba3e7187a6 chore: update mieru to v3.16.1 (#2138)
Fix a bug that closed session can cause memory leak with bad timing.
2025-06-28 11:00:58 +08:00
JianGuo Wang
0d92b6724b fix: add base64 decoding for VLESS host in ConvertsV2Ray function (#2125) 2025-06-27 16:56:31 +08:00
ayanamist
241ae92bce feat: support DOMAIN-WILDCARD rule (#2124)
only support asterisk(*) and question mark(?)
2025-06-27 16:35:55 +08:00
phanium
91985c1ef8 chore: typo (#2127) 2025-06-26 07:45:46 +08:00
Leo
6a9d428991 chore: remove unused code (#2126) 2025-06-25 22:49:00 +08:00
wwqgtxx
765cbbcc01 fix: miss config in patch 2025-06-25 21:19:36 +08:00
wwqgtxx
5b975275f5 fix: incorrect checking of strings.Split return value
strings.Split will never return a slice of length 0 if sep is not empty, so any code that checks if the return value is of length 0 is incorrect and useless.
2025-06-25 16:20:37 +08:00
ayanamist
166392fe17 chore: sniffer replace domain only if domain is valid (#2122) 2025-06-24 21:44:26 +08:00
ayanamist
5c6aa433ca chore: unconditionally allow clients with passwords for password-free socks5 inbound (#2123) 2025-06-24 19:01:12 +08:00
xishang0128
2c55dc2557 action: fix run build on pull_request 2025-06-24 19:01:03 +08:00
wwqgtxx
56c0b088e8 doc: update path doc 2025-06-21 22:46:55 +08:00
Restia-Ashbell
5344e869a8 fix: ssr uri decode (#2116) 2025-06-21 12:19:13 +08:00
wwqgtxx
6cfaf15cbf fix: missing error return 2025-06-21 12:08:41 +08:00
wwqgtxx
31f0060b30 fix: chacha20 counter overflow
the implement it's a not safe chacha20 using but for compatible
2025-06-21 10:42:14 +08:00
wwqgtxx
c60750d549 chore: allow tun to skip the system ipv6 check when starting by environment variable SKIP_SYSTEM_IPV6_CHECK 2025-06-14 15:57:54 +08:00
wwqgtxx
ebf5918e94 fix: v2ray-plugin mux maybe not close underlay connection 2025-06-14 12:32:45 +08:00
riolurs
93ca18517c chore: converter support fingerprint for anytls 2025-06-13 23:05:06 +08:00
beck
32d447ce99 fix: convert https (#2102) 2025-06-12 17:10:09 +08:00
beck
617fef84ae feat: converter support anytls/socks/http (#2100) 2025-06-12 16:17:25 +08:00
wwqgtxx
d19199322d action: don't trigger cmfa update on pull request 2025-06-12 15:33:19 +08:00
wwqgtxx
87795e3a07 chore: add yaml marshal for common/atomic 2025-06-12 15:24:29 +08:00
wwqgtxx
85bb40aaf8 chore: add Int32Enum for common/atomic 2025-06-12 15:24:29 +08:00
wwqgtxx
082bcec281 chore: apply find process mode in direct/global mode 2025-06-12 00:27:51 +08:00
wwqgtxx
9283cb0f5f feat: add loopback-address support for tun 2025-06-11 17:45:28 +08:00
wwqgtxx
ae7967f662 chore: the resolve and findProcess behaviors of Logic and SubRules follow the order and needs of the internal rules 2025-06-10 20:11:50 +08:00
wwqgtxx
01f8f2db2f chore: cleanup allocator code 2025-06-10 10:54:08 +08:00
wwqgtxx
255ff5e977 chore: add rate limiting support for reality listener 2025-06-10 10:40:26 +08:00
wwqgtxx
939e4109d7 chore: write dns reply in single syscall 2025-06-07 00:38:39 +08:00
wwqgtxx
40587b62b8 feat: all dns client support skip-cert-verify params 2025-06-06 00:52:12 +08:00
wwqgtxx
85e6d25de5 feat: all dns client support ecs and ecs-override params 2025-06-06 00:45:58 +08:00
wwqgtxx
29a37f4f4b feat: all dns client support disable-ipv4 and disable-ipv6 params 2025-06-06 00:24:57 +08:00
wwqgtxx
2f9a3b3469 chore: cleanup code 2025-06-05 21:20:38 +08:00
wwqgtxx
40ea0ba098 fix: correct constructor for 2022-blake3-chacha8-poly1305 2025-06-05 13:47:26 +08:00
wwqgtxx
8d7f947a80 fix: TypedValue.CompareAndSwap
84aa7ff3bb
2025-06-05 13:43:30 +08:00
wwqgtxx
71a8705636 fix: remote dst parse 2025-05-31 22:57:05 +08:00
wwqgtxx
c0f452b540 chore: more unmap for 4in6 address 2025-05-29 10:14:06 +08:00
wwqgtxx
6c9abe16cc fix: vmess listener error 2025-05-28 21:33:44 +08:00
wwqgtxx
213d80c1e2 fix: quic sniffer should consider skipDomain 2025-05-28 10:06:53 +08:00
wwqgtxx
1db89da122 fix: quic sniffer should not replace domain when no valid host is read 2025-05-28 09:22:28 +08:00
wwqgtxx
689c58f661 chore: clear dstIP when overrideDest in sniffer 2025-05-27 22:47:21 +08:00
wwqgtxx
33590c4066 fix: destination should unmap before find interface 2025-05-27 18:26:35 +08:00
wwqgtxx
60ae9dce56 chore: recover log leval for preHandleMetadata 2025-05-27 18:10:44 +08:00
wwqgtxx
4741ac6702 fix: in-port not work with shadowsocks listener 2025-05-27 16:32:42 +08:00
wwqgtxx
ef3d7e4dd7 chore: remove unneeded dns resolve when proxydialer dial udp 2025-05-27 15:04:01 +08:00
wwqgtxx
a1c7881229 chore: rebuild udp dns resolve
The DNS resolution of the overall UDP part has been delayed to the connection initiation stage. During the rule matching process, it will only be triggered when the IP rule without no-resolve is matched.

For direct and wireguard outbound, the same logic as the TCP part will be followed, that is, when direct-nameserver (or DNS configured by wireguard) exists, the result of the matching process will be discarded and the domain name will be re-resolved. This re-resolution logic is only effective for fakeip.

For reject and DNS outbound, no resolution is required.

For other outbound, resolution will still be performed when the connection is initiated, and the domain name will not be sent directly to the remote server at present.
2025-05-27 10:45:26 +08:00
wwqgtxx
12e3952b74 chore: code cleanup 2025-05-26 12:33:24 +08:00
wwqgtxx
88419cbd12 chore: better parse remote dst 2025-05-26 01:12:35 +08:00
wwqgtxx
4ed830330e chore: remove confused code 2025-05-25 22:22:23 +08:00
wwqgtxx
3ed6ff9402 chore: export pipeDeadline 2025-05-25 22:07:29 +08:00
wwqgtxx
34de62d21d chore: better get localAddr 2025-05-24 23:19:38 +08:00
wwqgtxx
d2e255f257 fix: some error in tun 2025-05-24 22:23:10 +08:00
wwqgtxx
a0c46bb4b7 chore: remove the redundant layer of udpnat in sing-tun to reduce resource usage when processing udp 2025-05-24 15:57:49 +08:00
wwqgtxx
9e3bf14b1a chore: handle two interfaces have the same prefix but different address 2025-05-24 11:32:36 +08:00
wwqgtxx
28c387a9b6 chore: restore break change in sing-tun 2025-05-23 20:19:18 +08:00
wwqgtxx
15eda703b4 fix: hysteria2 panic 2025-05-23 20:12:38 +08:00
wwqgtxx
b1d12a15db chore: proxy's ech should fetch from proxy-nameserver 2025-05-22 17:42:40 +08:00
wwqgtxx
5a21bf3642 fix: listener close panic 2025-05-22 17:01:24 +08:00
wwqgtxx
199fb8fd5d chore: update quic-go to 0.52.0 2025-05-22 10:28:10 +08:00
wwqgtxx
fd959feff2 chore: update dependencies 2025-05-21 21:37:20 +08:00
wwqgtxx
d5a03901d2 fix: race in close grpc transport 2025-05-20 16:15:04 +08:00
wwqgtxx
257fead538 docs: update config.yaml follow 5cf0f18c 2025-05-20 11:08:42 +08:00
wwqgtxx
c489c5260b fix: hysteria2 hop ports init
https://github.com/MetaCubeX/mihomo/issues/2056
2025-05-20 10:56:14 +08:00
wwqgtxx
8f92b1de13 chore: simplify the single root decompression process 2025-05-20 09:48:05 +08:00
wwqgtxx
9f7a2a36c1 chore: unpack externalUI in a separate temporary directory to avoid malicious compressed packages from polluting workdir 2025-05-20 01:58:25 +08:00
wwqgtxx
a93479124c chore: stricter path checking when unpacking zip/tgz 2025-05-20 00:00:30 +08:00
wwqgtxx
ed42c4feb8 chore: disallow symlink in unzip 2025-05-19 23:42:39 +08:00
wwqgtxx
608ddb1b44 fix: external-ui-name must in local 2025-05-19 23:11:52 +08:00
wwqgtxx
d036d98128 fix: http server does not handle http2 logic correctly 2025-05-18 23:05:00 +08:00
wwqgtxx
d900c71214 fix: shadowtls v2 not work with X25519MLKEM768 2025-05-18 23:03:07 +08:00
wwqgtxx
1672750c47 chore: simplifying the old fingerprint processing method 2025-05-18 23:03:07 +08:00
wwqgtxx
41b57afb3f fix: grpc deadline implement 2025-05-18 23:03:07 +08:00
wwqgtxx
188372cb04 feat: add tls.ech-key for external-controller-tls 2025-05-17 21:21:02 +08:00
wwqgtxx
a1350d4985 feat: add ech-key for listeners 2025-05-17 20:50:21 +08:00
wwqgtxx
dc958e6a39 feat: add ech-opts for hysteria/hysteria2/tuic outbound 2025-05-17 18:41:39 +08:00
wwqgtxx
8a5f3b8909 chore: simplify port hop costs 2025-05-17 17:06:38 +08:00
wwqgtxx
c6d7ef8cb8 feat: add ech-opts for anytls/shadowsocks/trojan/vmess/vless outbound 2025-05-17 13:53:21 +08:00
wwqgtxx
bb8c47d83d fix: error typo 2025-05-15 18:07:55 +08:00
wwqgtxx
5cf0f18c29 feat: reality add support-x25519mlkem768, it only works with new version server 2025-05-15 14:54:43 +08:00
wwqgtxx
83213d493e chore: adjust min backoff from 1s to 10s 2025-05-14 21:51:18 +08:00
wwqgtxx
90ed01ed53 fix: backoff not reset when the file unchanged 2025-05-14 21:45:12 +08:00
wwqgtxx
f91a586da8 fix: inline proxy provider's healthcheck not work 2025-05-13 19:00:32 +08:00
wwqgtxx
266fb03838 chore: update dependencies 2025-05-13 12:09:38 +08:00
wwqgtxx
76e9607fd7 chore: move start healthcheck.process() from New to Initial in provider
avoid panic cause by build-in proxy have not set to tunnel
2025-05-13 01:12:06 +08:00
wwqgtxx
23e2d3a132 chore: rebuild provider load 2025-05-12 22:19:49 +08:00
wwqgtxx
6e35cf9399 fix: truncated UDP response in system dns
https://github.com/MetaCubeX/mihomo/issues/2031
2025-05-12 12:34:22 +08:00
wwqgtxx
2116640886 chore: the updateConfigs api also adds a check for SAFE_PATHS 2025-05-12 11:28:15 +08:00
wwqgtxx
a4fcd3af07 chore: rollback incompatible changes to updateConfigs api 2025-05-12 10:00:01 +08:00
wwqgtxx
d22a893060 fix: hysteria server port hopping compatibility issues 2025-05-11 11:44:42 +08:00
Anya Lin
00cceba890 docs: update config.yaml follow 7e7016b (#2022) 2025-05-10 13:12:45 +08:00
wwqgtxx
2b4726b9ad fix: build on go1.24.3
https://github.com/golang/go/issues/73617
2025-05-10 12:32:47 +08:00
xishang0128
26e6d83f8b chore: make select display the specified testUrl
for https://github.com/MetaCubeX/mihomo/issues/2013
2025-05-07 18:21:21 +08:00
wwqgtxx
50d7834e09 chore: change the separator of the SAFE_PATHS environment variable to the default separator of the operating system platform (i.e., ; in Windows and : in other systems) 2025-05-05 01:32:25 +08:00
wwqgtxx
86c127db8b fix: missing read waiter for cancelers 2025-05-04 11:18:42 +08:00
wwqgtxx
febb6021aa fix: hysteria2 inbound not set UDPTimeout 2025-05-04 11:18:42 +08:00
wwqgtxx
9e57b298bf chore: update dependencies 2025-05-03 15:06:13 +08:00
wwqgtxx
791ea5e568 chore: allow setting addition safePaths by environment variable SAFE_PATHS
package managers can allow for pre-defined safe paths without disabling the entire security check feature
for https://github.com/MetaCubeX/mihomo/issues/2004
2025-05-01 12:33:21 +08:00
wwqgtxx
7e7016b567 chore: removed routing-mark and interface-name of the group, please set it directly on the proxy instead 2025-05-01 02:13:35 +08:00
wwqgtxx
b4fe669848 chore: better path checks 2025-05-01 02:13:35 +08:00
wwqgtxx
cad26ac6a8 chore: fetcher will change duration to achieve fast retry when the update failed with a 2x factor step from 1s to interval 2025-04-30 17:28:06 +08:00
wwqgtxx
f328203bc1 feat: not inline proxy-provider can also set payload as fallback proxies when file/http parsing fails 2025-04-30 16:03:02 +08:00
wwqgtxx
5c40a6340c feat: not inline rule-provider can also set payload as fallback rules when file/http parsing fails 2025-04-30 14:09:15 +08:00
wwqgtxx
61d6a9abd6 fix: fetcher does not start the pull loop when local file parsing errors occur and the first remote update fails 2025-04-30 13:29:19 +08:00
wwqgtxx
a013ac32a3 chore: give better error messages for some stupid config files 2025-04-29 21:52:44 +08:00
wwqgtxx
ee5d77cfd1 chore: cleanup tls clientFingerprint code 2025-04-29 21:15:48 +08:00
wwqgtxx
936df90ace chore: update dependencies 2025-04-29 09:01:54 +08:00
Larvan2
f774276896 fix: ensure wait group completes 2025-04-28 03:07:21 +00:00
wwqgtxx
aa51b9faba chore: replace using internal batch package to x/sync/errgroup
In the original batch implementation, the Go() method will always start a new goroutine and then wait for the concurrency limit, which is unnecessary for the current code. x/sync/errgroup will block Go() until the concurrency limit is met, which can effectively reduce memory usage.
In addition, the original batch always saves the return value of Go(), but it is not used in the current code, which will also waste a lot of memory space in high concurrency scenarios.
2025-04-28 10:28:45 +08:00
wwqgtxx
d55b047125 chore: ignore interfaces not with FlagUp in local interface finding 2025-04-27 09:40:17 +08:00
xishang0128
efc7abc6e0 actions: fix pacman build 2025-04-25 12:36:28 +08:00
wwqgtxx
c2301f66a4 chore: rebuild fingerprint and keypair handle 2025-04-25 10:34:34 +08:00
WeidiDeng
468cfc3cc4 fix: set sni to servername if not specified for trojan outbound (#1991) 2025-04-24 19:50:16 +08:00
xishang0128
5dce957755 actions: improve build process 2025-04-24 19:17:32 +08:00
wwqgtxx
4ecb49b3b9 chore: dynamic fetch remoteAddr in hysteria2 service 2025-04-23 12:25:42 +08:00
wwqgtxx
7de4af28d2 fix: shadowtls test 2025-04-23 12:10:37 +08:00
wwqgtxx
48d8efb3e9 fix: do NOT reset the quic-go internal state when only port is different 2025-04-23 12:00:10 +08:00
wwqgtxx
e6e7aa5ae2 fix: alpn apply on shadowtls 2025-04-22 23:44:55 +08:00
wwqgtxx
99aa1b0de1 feat: inbound support shadow-tls 2025-04-22 21:16:56 +08:00
wwqgtxx
52ad793d11 fix: shadowtls v1 not work 2025-04-22 20:52:34 +08:00
wwqgtxx
2fb9331211 fix: some resources are not released in listener 2025-04-22 20:52:33 +08:00
wwqgtxx
793ce45db0 chore: update quic-go to 0.51.0 2025-04-21 22:58:08 +08:00
wwqgtxx
39d6a0d7ba chore: update utls to 1.7.0 2025-04-21 12:07:33 +08:00
wwqgtxx
d5243adf89 chore: better global-client-fingerprint handle 2025-04-19 02:04:09 +08:00
wwqgtxx
6236cb1cf0 chore: cleanup trojan code 2025-04-19 01:32:55 +08:00
wwqgtxx
619c9dc0c6 chore: apply the default interface/mark of the dialer in the final stage 2025-04-18 20:16:51 +08:00
wwqgtxx
9c5067e519 action: disable MinGW's path conversion in test 2025-04-18 19:48:22 +08:00
wwqgtxx
feee9b320c chore: remove unneeded tls timeout in anytls 2025-04-18 16:59:53 +08:00
wwqgtxx
63e66f49ca chore: cleanup trojan code 2025-04-18 16:59:28 +08:00
wwqgtxx
bad61f918f fix: avoid panic in inbound test 2025-04-18 11:40:37 +08:00
wwqgtxx
69ce4d0f8c chore: speed up inbound test 2025-04-17 23:40:46 +08:00
wwqgtxx
b59f11f7ac chore: add singMux inbound test for shadowsocks/trojan/vless/vmess 2025-04-17 21:07:35 +08:00
wwqgtxx
30d90d49f0 chore: update option checks to use IsZeroOptions 2025-04-17 21:06:55 +08:00
wwqgtxx
76052b5b26 fix: grpc in trojan not apply client-fingerprint 2025-04-17 12:54:36 +08:00
wwqgtxx
7d7f5c8980 chore: add inbound test for tuic 2025-04-17 10:02:48 +08:00
wwqgtxx
e79465d306 chore: add inbound test for hysteria2 2025-04-17 09:26:12 +08:00
wwqgtxx
345d3d7052 chore: add inbound test for anytls 2025-04-17 09:01:26 +08:00
wwqgtxx
3d806b5e4c chore: add inbound test for shadowsocks/trojan 2025-04-17 01:36:14 +08:00
wwqgtxx
b5fcd1d1d1 fix: chacha8-ietf-poly1305 not work 2025-04-17 00:11:24 +08:00
wwqgtxx
b21b8ee046 fix: panic in ssr packet 2025-04-16 22:22:56 +08:00
wwqgtxx
d0d0c392d7 chore: add inbound test for vmess/vless 2025-04-16 20:44:48 +08:00
wwqgtxx
a75e570cca fix: vision conn read short buffer error 2025-04-16 20:38:10 +08:00
wwqgtxx
9e0889c02c fix: observable test 2025-04-16 13:16:11 +08:00
wwqgtxx
55cbbf7f41 fix: singledo test 2025-04-16 13:13:01 +08:00
wwqgtxx
664b134015 fix: websocket data losing 2025-04-16 13:02:50 +08:00
wwqgtxx
ba3c44a169 chore: code cleanup 2025-04-16 09:54:02 +08:00
wwqgtxx
dcb20e2824 fix: websocket server upgrade in golang1.20 2025-04-16 08:47:44 +08:00
wwqgtxx
3d2cb992fa fix: grpc outbound not apply ca fingerprint 2025-04-16 01:00:06 +08:00
wwqgtxx
984535f006 action: run tests on more platforms 2025-04-15 22:02:40 +08:00
wwqgtxx
8fa4e8122c chore: remove internal crypto/tls fork in reality server 2025-04-13 03:03:28 +08:00
wwqgtxx
7551c8a545 chore: remove unneed code 2025-04-12 23:42:57 +08:00
wwqgtxx
237e2edea4 chore: tun will add firewall rule for Profile ALL on windows system stack 2025-04-12 22:46:26 +08:00
wwqgtxx
fe01033efe chore: quic sniffer should use the exact length of crypto stream when assembling 2025-04-12 22:27:07 +08:00
wwqgtxx
84cd0ef688 chore: remove internal crypto/tls fork in shadowtls 2025-04-12 20:28:26 +08:00
wwqgtxx
cedb36df5f chore: using SetupContextForConn to reduce the DialContext cannot be cancelled 2025-04-12 11:19:03 +08:00
HiMetre
7a260f7bcf fix: udp dial support ip4p (#1377) 2025-04-11 09:20:58 +08:00
wwqgtxx
8085c68b6d chore: decrease direct using *net.TCPConn 2025-04-11 00:33:07 +08:00
wwqgtxx
dbb5b7db1c fix: SetupContextForConn should return context error to user 2025-04-11 00:03:46 +08:00
wwqgtxx
bfd06ebad0 chore: rebuild SetupContextForConn with context.AfterFunc 2025-04-10 01:29:55 +08:00
wwqgtxx
e8af058694 fix: websocketWithEarlyDataConn can't close underlay conn when is dialing or not dialed 2025-04-10 00:13:14 +08:00
wwqgtxx
487d7fa81f fix: panic under some stupid input config 2025-04-09 18:02:13 +08:00
wwqgtxx
4b15568a29 chore: cleanup metadata code 2025-04-09 18:02:13 +08:00
wwqgtxx
cac2bf72e1 chore: cleanup netip code 2025-04-09 18:02:13 +08:00
wwqgtxx
b2d2890866 chore: cleanup resolveUDPAddr code 2025-04-09 18:02:12 +08:00
anytls
8752f80595 fix: anytls stream read error (#1970)
Co-authored-by: anytls <anytls>
2025-04-09 18:02:12 +08:00
wwqgtxx
a6c0c02e0d chore: ignore interfaces not in IfOperStatusUp when fetch system dns server on windows 2025-04-09 18:02:12 +08:00
wwqgtxx
2acb0b71ee fix: tun IncludeInterface/ExcludeInterface priority 2025-04-08 19:20:29 +08:00
wwqgtxx
2a40eba0ca feat: tun add exclude-src-port,exclude-src-port-range,exclude-dst-port and exclude-dst-port-range on linux 2025-04-08 19:07:39 +08:00
okhowang
a22efd5c91 feat: add exclude port and exclude port range options (#1951)
Fixes #1769
2025-04-08 12:10:30 +08:00
wwqgtxx
9e8f4ada47 chore: better addr parsing 2025-04-06 10:43:21 +08:00
wwqgtxx
09c7ee0d12 fix: grpc server panic 2025-04-06 10:12:57 +08:00
wwqgtxx
2a08c44f51 action: fix run build on pull_request 2025-04-05 10:48:07 +08:00
wwqgtxx
190047c8c0 fix: grpc transport not apply dial timeout 2025-04-04 21:05:54 +08:00
wwqgtxx
24a9ff6d03 fix: disallow dialFunc be called after grpc transport has be closed 2025-04-04 13:33:00 +08:00
wwqgtxx
efa224373f fix: shut it down more aggressively in grpc transport closing 2025-04-04 11:54:19 +08:00
wwqgtxx
b0bd4f4caf fix: resources not released when hysteria2 verification failed 2025-04-04 11:12:08 +08:00
wwqgtxx
eaaccffcef fix: race in Single.Do 2025-04-04 10:55:16 +08:00
wwqgtxx
e81f3a97af fix: correctly implement references to proxies 2025-04-04 09:08:52 +08:00
wwqgtxx
323973f22f fix: converter judgment conditions 2025-04-04 00:22:52 +08:00
5aaee9
ed7533ca1a fix: tproxy high cpu usage (#1957) 2025-04-03 23:52:19 +08:00
wwqgtxx
7de24e26b4 fix: StreamGunWithConn not synchronously close the incoming net.Conn 2025-04-03 23:41:24 +08:00
wwqgtxx
622d99d000 chore: rebuild outdated proxy auto close mechanism 2025-04-03 22:42:32 +08:00
wwqgtxx
7f1225b0c4 fix: grpc transport can't be closed 2025-04-03 22:41:05 +08:00
wwqgtxx
23ffe451f4 chore: using http/httptrace to get local/remoteAddr for grpc client 2025-04-03 19:47:49 +08:00
wwqgtxx
7b37fcfc8d fix: auto_redirect should only hijack DNS requests from local addresses 2025-04-02 23:47:34 +08:00
wwqgtxx
daa592c7f3 fix: converter panic 2025-04-02 21:13:46 +08:00
wwqgtxx
577f64a601 fix: X25519MLKEM768 does not work properly with reality 2025-04-02 14:39:07 +08:00
wwqgtxx
025ff19fab fix: wrong conditional judgment in removeExtraHTTPHostPort
https://github.com/MetaCubeX/mihomo/issues/1939
2025-03-28 11:08:01 +08:00
anytls
f61534602b chore: anytls protocol version 2 (#1936) 2025-03-27 20:25:31 +08:00
wwqgtxx
7b382611bb chore: update gvisor 2025-03-25 01:19:39 +08:00
enfein
0f32c054f4 feat: support UDP over TCP in mieru (#1926) 2025-03-20 13:58:04 +08:00
5aaee9
4f8b70c8c6 fix: buffer in tproxy not recycle (#1923) 2025-03-19 12:20:48 +08:00
wwqgtxx
dcef78782b chore: update utls 2025-03-18 10:06:53 +08:00
wwqgtxx
7c444a91d3 fix: correctly handle ipv6 zone 2025-03-17 23:51:21 +08:00
wwqgtxx
e3d4ec2476 fix: race at interfaceName setting 2025-03-17 14:00:51 +08:00
xishang0128
14217e7847 chore: update service capabilities to include CAP_SYS_TIME and CAP_DAC_OVERRIDE 2025-03-17 13:21:23 +08:00
wwqgtxx
68abb1348a chore: support longest-prefix matches in local interface finding 2025-03-17 11:10:27 +08:00
Cesaryuan
dee5898e36 fix: memory leak due to unclosed session (#1908) 2025-03-15 13:27:29 +08:00
wwqgtxx
1e22f4daa9 chore: reduce data copying in quic sniffer and better handle data fragmentation and overlap 2025-03-14 13:14:42 +08:00
wwqgtxx
a7a796bb30 chore: cleanup quic sniff's code 2025-03-13 16:29:07 +08:00
Cesaryuan
ff89bf0ea0 feat: add gost-plugin in which only ws and mws are currently supported. (#1896) 2025-03-13 13:28:40 +08:00
5aaee9
801f3c35ce feat: support sniff quic fragment data (#1899) 2025-03-13 13:19:36 +08:00
wwqgtxx
7ff046a455 chore: modify UDPSniff's function signature to prepare for its ability to handle multiple packets. 2025-03-13 08:52:27 +08:00
wwqgtxx
0ed159e41d chore: code cleanup 2025-03-12 13:33:52 +08:00
wwqgtxx
070eb3142b chore: speedup system stack in tun 2025-03-12 12:27:41 +08:00
wwqgtxx
f318b80557 chore: better cache implement for group's getProxies 2025-03-11 23:27:18 +08:00
wwqgtxx
c0de3c0e42 fix: some default value in dialer not restore in tun when config reload 2025-03-10 11:10:39 +08:00
wwqgtxx
4bd3ae52bd chore: dialer will consider the routing of the local interface when auto-detect-interface in tun is enabled
for #1881 #1819
2025-03-10 10:45:31 +08:00
Skyxim
00e6466153 chore: update checksum generation step 2025-03-10 09:13:38 +08:00
Skyxim
c94b4421e5 chore: add checksum generation for production artifacts 2025-03-10 09:02:08 +08:00
ForestL
8bc6f77e36 fix DEB packaging (#1868) 2025-03-03 11:37:36 +08:00
anytls
a7e56f1c43 fix: anytls client close (#1871)
Co-authored-by: anytls <anytls>
2025-03-02 10:47:10 +08:00
wwqgtxx
05e8f13a8d fix: integer overflow in ports iteration 2025-02-28 15:48:25 +08:00
wwqgtxx
136d114196 feat: socks5/http/mixed inbound support setting tls in listeners 2025-02-28 13:13:53 +08:00
wwqgtxx
938ab7f44d fix: syscall packet read waiter for windows 2025-02-28 12:18:59 +08:00
wwqgtxx
a00f4f1108 fix: vless inbound allow not use flow when request send empty flow 2025-02-28 08:30:36 +08:00
wwqgtxx
1213023f11 fix: reality not work with vmess+grpc outbound 2025-02-28 08:24:22 +08:00
wwqgtxx
3b40bf76b7 fix: grpc server's ALPN order 2025-02-27 22:12:49 +08:00
wwqgtxx
1dc4155195 feat: inbound's port can use ports format 2025-02-27 09:59:09 +08:00
wwqgtxx
d81c19a7c8 fix: grpc server panic 2025-02-26 13:17:26 +08:00
anytls
e2140e62ca chore: update anytls (#1863)
Co-authored-by: anytls <anytls>
2025-02-26 11:17:12 +08:00
wwqgtxx
8d783c65c1 feat: inbound support grpc(lite) 2025-02-26 11:00:11 +08:00
wwqgtxx
91324b76d2 feat: inbound support trojan 2025-02-25 10:30:27 +08:00
wwqgtxx
e23f40a56b chore: tradition shadowsocks server could handle smux 2025-02-24 16:27:20 +08:00
Larvan2
5830afcbde chore: add MinIdleSession option to AnyTLS configuration 2025-02-21 13:30:24 +08:00
anytls
e2b75b35bb chore: update anytls (#1851)
* Implement deadline for `Stream`

* chore: code cleanup

* fix: buffer release

* fix: do not use buffer for `cmdUpdatePaddingScheme`

---------

Co-authored-by: anytls <anytls>
2025-02-19 15:54:56 +08:00
wwqgtxx
06b9e6c367 chore: update dependencies 2025-02-19 08:55:51 +08:00
anytls
dc1145a484 fix: anytls padding send (#1848)
Co-authored-by: anytls <anytls>
2025-02-17 21:18:40 +08:00
wwqgtxx
b151e7d69c chore: support fingerprint for anytls 2025-02-17 20:14:54 +08:00
wwqgtxx
808fdcf624 chore: code cleanup 2025-02-17 19:43:58 +08:00
anytls
9962a0d091 feat: implement anytls client and server (#1844) 2025-02-17 18:51:11 +08:00
ForestL
ef29e4501e chore: complete classical rule parse error log (#1839) 2025-02-13 17:25:45 +08:00
wwqgtxx
d1d846f1ab fix: s390x golang1.24 build 2025-02-13 08:50:53 +08:00
wwqgtxx
eaaccbc6dd chore: update dependencies 2025-02-13 00:34:37 +08:00
wwqgtxx
447c416391 chore: update quic-go to 0.49.0 2025-02-13 00:22:56 +08:00
wwqgtxx
6d24ca9ae6 action: remove 32-bit windows/arm build
windows/arm32 has been broken since Go 1.17
2025-02-12 23:23:05 +08:00
wwqgtxx
b52b7537fc action: force s390x build using golang1.23 2025-02-12 23:22:16 +08:00
wwqgtxx
3cc67fd759 chore: update golang to 1.24 2025-02-12 23:07:05 +08:00
5aaee9
9074b78e36 chore(dns): increase MaxConnsPerHost (#1834) 2025-02-10 15:21:18 +08:00
clash-meta-maintainer[bot]
ccc3f84da2 license: any downstream projects not affiliated with MetaCubeX shall not contain the word mihomo in their names 2025-02-08 23:37:04 +08:00
ForestL
9bfb10d7ae chore: extracting compressed files to correct location (#1823) 2025-02-05 10:10:58 +08:00
wwqgtxx
0a5ea37c07 chore: update dependencies 2025-02-04 15:28:24 +08:00
wwqgtxx
a440f64080 chore: alignment capability for vmess inbound 2025-02-04 15:28:24 +08:00
wwqgtxx
0ac6c3b185 feat: inbound support vless 2025-02-04 00:44:18 +08:00
wwqgtxx
b69e52d4d7 chore: deprecated routing-mark and interface-name of the group, please set it directly on the proxy instead 2025-01-21 00:45:49 +08:00
wwqgtxx
9c73b5b750 fix: the trustcerts not add to globalCerts after ca.ResetCertificate (#1801)
support PEM format for custom-certificates too
2025-01-20 23:01:26 +08:00
wwqgtxx
fc233184fd feat: add receive window config for hy2
https://github.com/MetaCubeX/mihomo/issues/1796
2025-01-19 09:56:16 +08:00
tnextday
192d769f75 chore: ensure forced domains are always sniffed (#1793)
When a domain matches forceDomain:
- SkipList is not checked
- Failed attempts are not cached
- Sniffing is attempted every time

This ensures forced domains are always sniffed regardless of previous failures.
2025-01-16 10:17:32 +08:00
wwqgtxx
c99c71a969 chore: listening tcp together for dns server (#1792) 2025-01-16 10:16:37 +08:00
lucidhz
c7661d7765 fix: initialize error message with cipher (#1760) 2025-01-07 14:28:56 +08:00
Mossia
56c128880c fix: empty proxy provider subscription info not omitted (#1759) 2025-01-07 13:26:56 +08:00
enfein
f4806b49b4 chore: update mieru version (#1762) 2025-01-07 13:25:32 +08:00
J.K.SAGE
49d54cc293 fix: remote conn statistic error (#1776)
TCP handshake traffic should be counted as upload traffic for the remote connection
2025-01-07 13:23:05 +08:00
wwqgtxx
1c5f4a3ab1 chore: update dependencies 2024-12-31 16:42:33 +08:00
wwqgtxx
368b1e1296 chore: rollback tfo-go version 2024-12-30 22:33:13 +08:00
wwqgtxx
a9ce5da09d fix: key missing for tun inbound
https://github.com/MetaCubeX/mihomo/issues/1672
2024-12-28 11:39:45 +08:00
wwqgtxx
301c78ff9a chore: update sing-tun to v0.4.5 2024-12-26 10:50:08 +08:00
wwqgtxx
72a126e580 feat: support inline proxy provider 2024-12-25 10:34:16 +08:00
wwqgtxx
20739f5db7 chore: code cleanup 2024-12-25 10:34:16 +08:00
valord577
89dfabe9b3 chore: align time fields in logs (#1704)
ref: A comma or decimal point followed by one or more zeros represents a fractional second, printed to the given number of decimal places. A comma or decimal point followed by one or more nines represents a fractional second, printed to the given number of decimal places, with trailing zeros removed. For example "15:04:05,000" or "15:04:05.000" formats or parses with millisecond precision.
2024-12-19 15:55:47 +08:00
wwqgtxx
5a9ad0ed3c chore: code cleanup 2024-12-19 09:29:17 +08:00
qianlongzt
bb803249fa feat: support inline rule provider (#1731) 2024-12-19 09:16:45 +08:00
wwqgtxx
3f6823ba49 fix: handle invalid values in Decoder's decode method 2024-12-16 09:26:11 +08:00
wwqgtxx
c786b72030 chore: update dependencies 2024-12-14 13:27:28 +08:00
wwqgtxx
269c52575c chore: update gopsutil to v4 2024-12-14 11:09:51 +08:00
laburaps
c7fc93df37 fix: the TLS Sniffer fails when the length of the ClientHello packet exceeds the TCP MSS (#1711)
* chore: add uniformly formatted debug info to sniffDomain

* fix: when data is not enough, attempt to peek more data and retry

* chore: reduce debug info of sniffDomain
2024-12-12 19:02:34 +08:00
laburaps
5d9d8f4d3b fix: check whether the dst port is within the specified range (#1706) 2024-12-10 16:15:08 +08:00
wwqgtxx
f3a43fe3a6 feat: support read config file from stdin
via `-f -`
2024-12-10 09:57:20 +08:00
wwqgtxx
9a959202ed chore: support config multiplexing of mieru 2024-12-10 09:19:59 +08:00
enfein
cd23112dc5 chore: remove gRPC dependency from mieru (#1705) 2024-12-10 08:03:17 +08:00
enfein
613becd8ea feat: support mieru protocol (#1702) 2024-12-09 12:05:11 +08:00
hingbong
d6b496d3c0 chore: allow upgrade ui in embed mode (#1692) 2024-12-04 08:54:01 +08:00
ForestL
5a24efdabf fix: DisableKeepAlive default value of android (#1690) 2024-12-02 22:49:16 +08:00
wwqgtxx
9de9f1ef51 fix: don't panic when listen on localhost
https://github.com/MetaCubeX/mihomo/issues/1655
2024-11-27 11:03:38 +08:00
wwqgtxx
fbead56ec9 feat: add size-limit for provider
https://github.com/MetaCubeX/mihomo/issues/1645
2024-11-27 09:28:38 +08:00
wwqgtxx
1fff34d30e chore: update quic-go to 0.48.2 2024-11-26 13:39:54 +08:00
wwqgtxx
a35f712478 chore: update gvisor 2024-11-26 10:28:07 +08:00
wwqgtxx
f805a9f4c6 chore: cleaned up some weird code 2024-11-26 10:04:41 +08:00
xishang0128
eb985b002e chore: restful api displays more information 2024-11-21 22:50:54 +08:00
wwqgtxx
462343531e chore: update sing-tun to v0.4.1 2024-11-21 11:06:25 +08:00
wwqgtxx
671d901ee2 ci: align loongarch golang version when it is not abi1 2024-11-18 10:41:15 +08:00
wwqgtxx
80e4eaad14 fix: process IPv6 Link-Local address (#1657) 2024-11-18 10:34:43 +08:00
xishang0128
25b3c86d31 ci: update loongarch golang and android ndk 2024-11-17 23:31:46 +08:00
Chenx Dust
de19f927e8 chore: restful api display smux and mptcp 2024-11-14 10:08:02 +08:00
Larvan2
792f16265e fix: find process panic 2024-11-08 16:29:32 +08:00
wwqgtxx
215bf0995f chore: switch syscall.SyscallN back to syscall.Syscall6
Until the current version, SyscallN always escapes the variadic argument
2024-11-08 09:40:38 +08:00
wwqgtxx
91d54bdac1 fix: android tun start error 2024-11-06 20:04:14 +08:00
wwqgtxx
ce52c3438b chore: cleaned up some confusing code 2024-11-05 10:03:21 +08:00
wwqgtxx
d4478dbfa2 chore: reduce the performance overhead of not enabling LoopBackDetector 2024-11-05 09:29:56 +08:00
wwqgtxx
69454b030e chore: allow disabled overrideAndroidVPN by environment variable DISABLE_OVERRIDE_ANDROID_VPN 2024-11-05 09:15:30 +08:00
wwqgtxx
e6d1c8cedf chore: update sing-tun to v0.4.0-rc.5 2024-11-05 09:12:20 +08:00
wwqgtxx
fabd216c34 chore: update quic-go to 0.48.1 2024-11-05 08:58:41 +08:00
xishang0128
a86c562852 chore: Increase support for other format of ASN 2024-11-04 19:31:43 +08:00
wwqgtxx
3e966e82c7 chore: update quic-go to 0.48.0 2024-10-21 09:38:21 +08:00
wwqgtxx
b9171ade7f chore: update sing-tun to v0.4.0-rc.4 2024-10-21 09:17:37 +08:00
xishang0128
95af5f7325 chore: change subscription-userinfo retrieval 2024-10-20 06:01:02 +08:00
xishang0128
ca3f1ebae6 fix: sticky-sessions may not be effective 2024-10-12 08:26:37 +08:00
ForestL
4437c8861c chore: better getUpdateTime() for iterating all Geofiles (#1570) 2024-10-11 08:46:31 +08:00
xishang0128
57725078e0 chore: Adjust the error log for the search process 2024-10-11 07:35:51 +08:00
wwqgtxx
08dcef80bf fix: mistaken using net.Dialer
https://github.com/MetaCubeX/mihomo/issues/1572
2024-10-09 12:04:56 +08:00
wwqgtxx
9fd63fe938 chore: update dependencies 2024-10-06 10:34:54 +08:00
wwqgtxx
8e6eb70e71 chore: temporary update general in ParseRawConfig and rollback before its retur 2024-10-06 00:21:00 +08:00
wwqgtxx
9937ae1002 fix: defaultNS not working in system dns 2024-10-05 14:20:54 +08:00
wwqgtxx
8f5a86410c chore: cleanup unneeded setting in parseGeneral, move to executor 2024-10-05 13:58:49 +08:00
wwqgtxx
9286e21026 chore: rebuild external ui updater 2024-10-05 13:40:00 +08:00
wwqgtxx
c63a851bba feat: add direct-nameserver and direct-nameserver-follow-policy in dns section 2024-10-04 14:20:10 +08:00
wwqgtxx
4a16d22398 chore: no longer used net.DefaultResolver when dns section is disabled, now is equally only "system://" 2024-10-02 14:45:06 +08:00
wwqgtxx
990de84391 chore: better atomic using 2024-10-02 14:45:06 +08:00
Skyxim
ecd8facd81 chore: add warning for unified delay test when second failed 2024-10-01 03:14:37 +00:00
wwqgtxx
a330fa1506 chore: disallow some restful api for CMFA 2024-09-30 13:08:50 +08:00
wwqgtxx
fc9d5cfee9 feat: add external-controller-cors can config allow-origins and allow-private-network 2024-09-29 17:13:43 +08:00
wwqgtxx
264713571d chore: set 0o666 to unix socket file 2024-09-27 22:36:19 +08:00
wwqgtxx
a67c379884 chore: code cleanup 2024-09-27 21:42:06 +08:00
xishang0128
af5ad3254b chore: Use DELETE to clear the proxy group fixed 2024-09-27 21:14:04 +08:00
wwqgtxx
acfc9f8baa chore: reset resolver's connection after default interface changed 2024-09-27 20:36:00 +08:00
wwqgtxx
1633885794 chore: update dependencies 2024-09-27 20:36:00 +08:00
wwqgtxx
2afa2798b1 chore: allow set security descriptor of namedpipe by environment variable LISTEN_NAMEDPIPE_SDDL 2024-09-27 18:31:50 +08:00
wwqgtxx
cd2d1c6bb0 fix: skip-auth-prefixes not apply on listeners when users is unset 2024-09-27 18:10:05 +08:00
wwqgtxx
88bfe7cffe feat: add external-controller-pipe for windows
maybe useful for electron and tauri client, node.js and rust still not support AF_UNIX on windows
2024-09-27 16:09:03 +08:00
wwqgtxx
43cb48231a cache: add dns cache in udp packet sender
reduce the cost of re-resolving DNS for each packet received and prevent the target IP from jumping between multiple resolution results
2024-09-26 22:21:59 +08:00
wwqgtxx
4fa15c6334 chore: ensures packets can be sent without blocking the tunnel 2024-09-26 11:21:07 +08:00
wwqgtxx
5812a7bdeb chore: simplify the code 2024-09-25 21:37:15 +08:00
HamsterReserved
3922b17067 chore: deliver UDP packets from same connection in receiving order (#1540)
All UDP packets are queued into a single channel, and multiple
workers are launched to poll the channel in current design.

This introduces a problem where UDP packets from a single connection
are delivered to different workers, thus forwarded in a random order
if workers are on different CPU cores. Though UDP peers normally
have their own logic to handle out-of-order packets, this behavior will
inevitably cause significant variance in delay and harm connection quality.
Furthermore, this out-of-order behavior is noticeable even if the underlying
transport could provide guaranteed orderly delivery -  this is unacceptable.

This commit takes the idea of RSS in terms of NICs: it creates a distinct
queue for each worker, hashes incoming packets, and distribute the packet
to a worker by hash result. The tuple (SrcIP, SrcPort, DstIP, DstPort, Proto)
is used for hashing (Proto is always UDP so it's dropped from final
implementation), thus packets from the same connection can be sent to
the same worker, keeping the receiving order. Different connections can be
hashed to different workers to maintain performance.

Performance for single UDP connection is not affected, as there is already
a lock in natTable that prevents multiple packets being processed in different
workers, limiting single connection forwarding performance to 1 worker.
The only performance penalty is the hashing code, which should be neglectable
given the footprint of en/decryption work.

Co-authored-by: Hamster Tian <haotia@gmail.com>
2024-09-25 21:28:30 +08:00
wwqgtxx
a4e84f0479 chore: better apply tcp keepalive to listeners 2024-09-25 15:10:53 +08:00
wwqgtxx
6c0383026e fix: AmneziaWG not working 2024-09-24 13:25:13 +08:00
wwqgtxx
59a2b24593 chore: save etag in bbolt by msgpack 2024-09-23 19:25:35 +08:00
wwqgtxx
966eeae41b chore: rewrite bbolt cachefile implements
never use returned byte slices outside the transaction, ref:
https://pkg.go.dev/go.etcd.io/bbolt#hdr-Caveats
2024-09-23 09:35:48 +08:00
wwqgtxx
150c6ccd25 chore: skip duplicates nameserver when parse 2024-09-23 08:54:07 +08:00
wwqgtxx
33823f1728 chore: sync internal interface 2024-09-22 22:45:55 +08:00
wwqgtxx
781b783346 feat: add amnezia-wg-option to wireguard outbound 2024-09-22 22:07:14 +08:00
wwqgtxx
ddfa9e8671 feat: add etag-support to let user can disable this feature manually 2024-09-22 14:41:45 +08:00
wwqgtxx
b7cb6774bf chore: support ETag for update geo 2024-09-22 13:57:57 +08:00
wwqgtxx
5d242510c8 chore: support ETag for providers 2024-09-22 11:42:29 +08:00
wwqgtxx
223eae0e06 chore: force refresh provider in background 2024-09-22 00:24:49 +08:00
wwqgtxx
7dafe7889e chore: disallow space at begin or end in DomainTrie 2024-09-21 21:03:59 +08:00
wwqgtxx
d80e8bb0c2 chore: remove some confusing restrictions on comma separation in NameServerPolicy configuration 2024-09-21 20:03:17 +08:00
wwqgtxx
f52fe6aa74 fix: tun.device not shown in restful api 2024-09-21 19:46:39 +08:00
wwqgtxx
a08aa10630 chore: some internal types support encoding.TextUnmarshaler 2024-09-19 18:36:24 +08:00
wwqgtxx
794645b7f8 chore: direct using structure package decode proxy-name 2024-09-19 18:26:05 +08:00
wwqgtxx
f020b20ab9 chore: structure support encoding.TextUnmarshaler 2024-09-19 18:26:05 +08:00
Chun
3676d1b79f feat: add proxy name replacement functionality for override (#1481)
* feat: add proxy name replacement functionality for override

* style: modify `override schema` info and provider parse error message

---------

Co-authored-by: chun <pujichun@outlook.com>
2024-09-18 22:41:06 +08:00
落心
58c973ee2b fix: NewRejectWithOption has wrong type (#1518)
Co-authored-by: nico <nico@starpay.com>
2024-09-18 22:36:20 +08:00
Larvan2
fb4d3c41c8 chore: simplify VlessFlow Option 2024-09-17 12:03:24 +08:00
wwqgtxx
e33d4a4769 chore: cleanup the patch code 2024-09-12 11:19:54 +08:00
xishang0128
4c3fe98ebd chore: modify the default download address of ASN 2024-09-11 19:24:53 +08:00
wwqgtxx
0a2f606e1b chore: cleanup the patch code 2024-09-11 16:10:49 +08:00
Larvan2
8230bc8e7d chore: parse float in subscription info 2024-09-11 13:34:59 +08:00
wwqgtxx
ecbbf9d220 feat: doh client support ecs and ecs-override 2024-09-11 10:56:51 +08:00
wwqgtxx
f305e440ef fix: new tun with fd unneeded calculate interface name 2024-09-11 10:04:50 +08:00
wwqgtxx
910f236696 fix: UpdateMonitor should start when AutoDetectInterface enabled 2024-09-11 09:52:21 +08:00
wwqgtxx
417d709d60 fix: RawSrcAddr and RawSrcAddr in metadata 2024-09-10 21:46:56 +08:00
wwqgtxx
f8557f5fd8 chore: don't start UpdateMonitor when AutoRoute not enabled 2024-09-10 17:13:13 +08:00
wwqgtxx
89b9438fc0 fix: restful server restart 2024-09-10 16:43:00 +08:00
wwqgtxx
7c8f451892 chore: cleanup geo internal code 2024-09-09 16:08:48 +08:00
wwqgtxx
ef244b896a chore: update geo in a batch 2024-09-09 10:07:50 +08:00
wwqgtxx
595a575cde chore: add Count for ProxyProvider 2024-09-09 10:07:50 +08:00
wwqgtxx
b1301b1b41 chore: update quic-go to 0.47.0 2024-09-09 10:07:50 +08:00
wwqgtxx
dc29514fb6 chore: typo 2024-09-09 10:07:50 +08:00
wwqgtxx
8940bdd56f chore: better pool_test 2024-09-09 10:07:50 +08:00
H1JK
ade4234615 chore: mrs use best compression 2024-09-07 20:22:21 +08:00
xishang0128
faaa90f8a6 feat: Allows passing in base64-encoded configuration strings 2024-09-03 17:55:11 +08:00
xishang0128
43f21c0b41 fix: fallback cannot be unfixed 2024-09-02 16:18:28 +08:00
Larvan2
56fe7d5304 chore: clean up update_ui code 2024-09-02 11:17:35 +08:00
Larvan2
802267fb5b ci: better release 2024-08-31 23:38:31 +08:00
wwqgtxx
6306c6b580 chore: add route.ApplyConfig for CMFA 2024-08-31 22:42:24 +08:00
wwqgtxx
f6164ac195 feat: add fake-ip-filter-mode in dns
https://github.com/MetaCubeX/mihomo/issues/1479
2024-08-31 09:59:48 +08:00
wwqgtxx
08ac9a3fae fix: tfo ipv6 addr zone 2024-08-30 20:09:50 +08:00
wwqgtxx
38fd37108b feat: GEOIP,IP-ASN,IP-CIDR,IP-CIDR6 and IP-SUFFIX in rules support ,src option
keep the same writing style as `RULE-SET`
2024-08-30 00:04:59 +08:00
wwqgtxx
763a127287 feat: RULE-SET in rules support ,src option
should only be used with `ipcidr` behavior
2024-08-29 23:49:16 +08:00
wwqgtxx
a96f72ade4 fix: geoip wrong matching logic in fallback-filter
https://github.com/MetaCubeX/mihomo/issues/1478
2024-08-29 22:00:55 +08:00
wwqgtxx
4fecf68b8b chore: add sourceGeoIP and sourceIPASN to metadata 2024-08-28 12:25:45 +08:00
wwqgtxx
8483178524 feat: sniff add skip-src-address and skip-dst-address 2024-08-27 20:33:43 +08:00
wwqgtxx
3e2c9ce821 chore: cleanup patch code 2024-08-27 11:04:42 +08:00
wwqgtxx
6e04e1e9dc fix: hysteria2 close safety 2024-08-27 08:41:57 +08:00
wwqgtxx
d79423a4fa fix: tun should not care "force" when Put configs from restful api 2024-08-26 20:54:32 +08:00
wwqgtxx
9cf3eb39f5 fix: hysteria1 outbound should be closed when proxy removed 2024-08-26 18:47:54 +08:00
wwqgtxx
81756fc927 fix: wireguard outbound memory leaks when close 2024-08-26 14:28:31 +08:00
wwqgtxx
518e9bdb0b feat: socks5, http and mixed listeners support independence users 2024-08-25 19:33:31 +08:00
wwqgtxx
27bcb26ecd chore: better config internal structure 2024-08-24 20:49:12 +08:00
karin0
53425bb9f2 chore: add json struct tags for more fields in config.RawConfig (#1469)
Applications like CMFA marshall their config overrides in JSON, so
`json` tags are required for parsing configurations of features such as
external controllers.
2024-08-24 18:10:09 +08:00
wwqgtxx
f5834dd5e2 chore: code cleanup 2024-08-24 14:20:21 +08:00
wwqgtxx
16c95fca87 fix: tradition shadowsocks server not apply additions
https://github.com/MetaCubeX/mihomo/issues/1466
2024-08-23 21:14:36 +08:00
wwqgtxx
41efc5e5ab chore: update dependencies 2024-08-22 09:24:27 +08:00
wwqgtxx
c4660e1aad chore: reopen tfo support on windows for golang1.23
maybe broken again when golang1.24 release
2024-08-21 10:57:44 +08:00
Larvan2
512d188384 fix redundant WindowsDNS (#1456)
Co-authored-by: ForestL <45709305+forestl18@users.noreply.github.com>
2024-08-20 21:13:26 +08:00
wwqgtxx
ba605b693b action: show go env when build 2024-08-20 09:39:44 +08:00
xishang0128
c8380335cb chore: improve include-all-proxies compatibility 2024-08-19 16:02:43 +08:00
wwqgtxx
82984891e4 action: fix build for armv5/6/7 2024-08-19 10:01:30 +08:00
Larvan2
0793998de8 chore: drop support of eBPF 2024-08-16 14:15:36 +08:00
wwqgtxx
6bf419c5fe chore: better geo init logging 2024-08-16 12:04:37 +08:00
wwqgtxx
4fedfc47b0 chore: update geo unneeded reload whole config 2024-08-16 09:19:18 +08:00
wwqgtxx
92ec5f2236 chore: cleanup dns policy match code 2024-08-15 20:08:14 +08:00
wwqgtxx
4c10d42fbf fix: normal rule not working in fake-ip-filter 2024-08-15 07:42:59 +08:00
wwqgtxx
7fd0467aef feat: sniffer's force-domain and skip-domain support rule-set: and geosite: 2024-08-14 23:29:53 +08:00
wwqgtxx
696b75ee37 feat: fake-ip-filter support rule-set: and geosite: 2024-08-14 20:45:36 +08:00
wwqgtxx
f20f371a61 chore: better keepalive handle 2024-08-14 13:01:06 +08:00
Larvan2
24c6e7d819 chore: update tcp keepAlive setting for go1.23 2024-08-14 11:51:39 +08:00
wwqgtxx
acaacd8ab1 action: let golang1.23's build can work on windows7/8 2024-08-14 10:13:57 +08:00
wwqgtxx
12c5cf361d chore: update golang to 1.23 2024-08-14 09:03:42 +08:00
Larvan2
50d0cd363c chore: auto download external UI when 'external-ui' is set and not empty 2024-08-13 14:19:34 +08:00
wwqgtxx
5bf22422d9 fix: wireguard not working in CMFA 2024-08-13 13:36:25 +08:00
xishang0128
c17d7c0281 ci: update loongarch golang and android ndk 2024-08-13 00:57:08 +08:00
wwqgtxx
951cae2156 chore: corrected the incoming parameters of PowerUnregisterSuspendResumeNotification 2024-08-08 13:27:06 +08:00
wwqgtxx
4f339265d3 chore: update dependencies 2024-08-08 07:29:56 +08:00
wwqgtxx
ea4181308d chore: update quic-go to 0.46.0 2024-08-08 07:25:16 +08:00
Chris Gardner
030631607e fix: parameter order in ChaCha20 constructor 2024-08-07 15:54:16 +08:00
wwqgtxx
beefe37260 chore: logic rules dynamic obtain parameters 2024-08-06 17:17:48 +08:00
xishang0128
5a73d99c6f fix: logic rules display error 2024-08-06 16:30:44 +08:00
wwqgtxx
e4646fc3d2 chore: update dependencies 2024-08-03 09:51:13 +08:00
wwqgtxx
bb554e89d9 action: add GOTOOLCHAIN=local to env 2024-08-03 09:12:10 +08:00
wwqgtxx
fd205bfa8d chore: update quic-go to 0.45.2 2024-08-03 08:42:36 +08:00
OxO
e7e1400126 chore: reduce image size 2024-07-31 13:04:30 +08:00
wwqgtxx
117cdd8b54 chore: remove suppress_prefixlength in tun linux auto-route for inet4/6-route-address
https://github.com/MetaCubeX/mihomo/issues/1368
2024-07-29 21:14:59 +08:00
wwqgtxx
c830b8aaf7 feat: support convert mrs format back to text format 2024-07-28 11:00:27 +08:00
wwqgtxx
1db3e4583b chore: better converter 2024-07-27 23:54:28 +08:00
wwqgtxx
4f8a5a5f54 feat: add mrs format ipcidr ruleset 2024-07-27 10:36:11 +08:00
wwqgtxx
303f6e4567 feat: add mrs format domain ruleset 2024-07-26 22:30:42 +08:00
wwqgtxx
0d90a93645 chore: sort proxies and providers by name before include all 2024-07-26 10:59:39 +08:00
wwqgtxx
40c9829328 fix: auth with CFMA compile issue 2024-07-26 10:55:03 +08:00
wwqgtxx
4051ea522a chore: improve authentication parsing logic in http listener (#1336) 2024-07-25 19:49:56 +08:00
wwqgtxx
cc7823dad8 fix: remove unneeded http proxy compression 2024-07-24 14:56:46 +08:00
wwqgtxx
d6a1af23a7 feat: local file type provider will auto update after modify 2024-07-24 14:37:10 +08:00
wwqgtxx
313493cc94 chore: add fswatch 2024-07-24 14:37:10 +08:00
wwqgtxx
4b9fdacbad feat: doh client support plain http and skip-cert-verify 2024-07-23 10:48:54 +08:00
wwqgtxx
13b7ab8da3 fix: better doh server compatibility 2024-07-23 08:46:27 +08:00
wwqgtxx
de61e81ff7 feat: support external-doh-server 2024-07-23 00:01:41 +08:00
wwqgtxx
4eb13a73bf fix: wrong usage of RLock 2024-07-22 09:57:57 +08:00
wwqgtxx
fd5b537ab1 fix: doh concurrent race issue 2024-07-21 23:03:04 +08:00
ruokeqx
28794c62c4 chore: reduce func findProcessName mem allocs and copy (#1393) 2024-07-19 22:24:27 +08:00
wwqgtxx
e263518f01 fix: some auto-redirect issue 2024-07-19 22:20:07 +08:00
wwqgtxx
345061a7cc chore: support some chacha8 method 2024-07-19 22:08:05 +08:00
wwqgtxx
a05016a5da chore: better dns logging 2024-07-19 19:27:29 +08:00
xishang0128
9e3589d638 chore: include-all-providers logic correction 2024-07-15 13:12:40 +08:00
xishang0128
fc03bd2f0d chore: Modify the default value to avoid outputting Deprecated 2024-07-12 02:59:14 +08:00
xishang0128
0e228765fc fix: Make the ruleset take effect in a single line 2024-06-28 14:14:36 +08:00
wwqgtxx
f45ccc0761 chore: update dependencies 2024-06-27 09:52:47 +08:00
xishang0128
9f4cd646c2 fix: dhcp:// with special notation cannot be parsed 2024-06-23 15:33:38 +08:00
wwqgtxx
a9ecc627e6 fix: subrule can't recursion correctly (#1339) 2024-06-22 13:18:23 +08:00
wwqgtxx
50286678bf fix: auto-redirect rule error 2024-06-22 13:08:15 +08:00
wwqgtxx
917c5fdd80 fix: auto-redirect android rules 2024-06-19 12:11:00 +08:00
wwqgtxx
1457f83530 fix: dns server using direct outbound lookback resolve problem 2024-06-18 13:15:20 +08:00
wwqgtxx
5ab8154e7e fix: wireguard ip update 2024-06-18 10:30:43 +08:00
wwqgtxx
09be5cbc99 feat: tun support auto-redirect, route-address-set and route-exclude-address-set 2024-06-17 22:04:51 +08:00
xishang0128
0738e18100 chore: add override fields 2024-06-16 18:19:04 +08:00
wwqgtxx
40f40f6d24 fix: dns dial to wrong target 2024-06-15 13:32:57 +08:00
wwqgtxx
ad5bc51c77 chore: deprecated the relay group type, please using dialer-proxy instead 2024-06-15 13:32:57 +08:00
wwqgtxx
75c16f9b87 feat: add refresh-server-ip-interval for wireguard outbound 2024-06-14 14:01:52 +08:00
wwqgtxx
d96d7651ca chore: add inner dns proxied connection log 2024-06-13 09:07:05 +08:00
wwqgtxx
a5f25a2246 chore: code split 2024-06-12 20:54:43 +08:00
wwqgtxx
2b4741fbc7 chore: add inner dns proxied connection statistic to restful api 2024-06-12 17:10:47 +08:00
wwqgtxx
f317baa8de feat: add respect-rules for dns 2024-06-12 15:25:34 +08:00
wwqgtxx
5678131591 fix: wireguard server resolve when only a server in peers 2024-06-12 11:37:23 +08:00
xishang0128
10f8ba4434 chore: Disable the loop back detector for CMFA 2024-06-12 04:46:13 +08:00
wwqgtxx
cacfefad4b fix: quic-go cached dial error 2024-06-10 08:48:23 +08:00
wwqgtxx
0d4e57cb21 chore: update quic-go to 0.45.0 2024-06-07 21:41:49 +08:00
wwqgtxx
063836fe5d chore: sync hysteria2 bbr changes
e0e75c4630
2024-06-05 11:56:27 +08:00
xishang0128
7b3c9e94e6 chore: Better package name handling on Android 2024-06-02 02:36:15 +08:00
wwqgtxx
be3d121ec6 fix: darwin calculate correct tunIndex 2024-06-01 13:36:57 +08:00
xishang0128
59fd3cffe3 ci: fix arm package create 2024-05-31 17:02:31 +08:00
wwqgtxx
39eda257a7 chore: replace zhangyunhao116/fastrand to our metacubex/randv2 2024-05-31 13:07:48 +08:00
wwqgtxx
d3fea909e9 chore: remove tfo windows support
Golang officially decided not to open `internal/poll.execIO` to third-party libraries after 1.23 was released, so we can only choose to remove tfo support on the Windows platform.
2024-05-30 10:39:17 +08:00
wwqgtxx
7eb70aeb4d fix: windows build number 2024-05-29 00:08:32 +08:00
xishang0128
846bdfa812 chore: Allow customization of GLOBAL 2024-05-25 08:09:59 +08:00
wwqgtxx
0b6ae6ffb8 feat: add ss-opts for trojan outbound like trojan-go's shadowsocks config
https://github.com/MetaCubeX/mihomo/issues/1269
2024-05-22 09:00:59 +08:00
wwqgtxx
71922dd0b1 fix: bad usage for exec in sing-tun
https://github.com/MetaCubeX/mihomo/issues/1234
2024-05-21 23:53:13 +08:00
moexiami
5eb8958ff2 fix: correct type for vmess.ws-opts.path in ConvertsV2Ray (#1145)
It should be a string for the following reasons:
1. During conversion, it is conditionally assigned with
   `wsOpts["path"] = path.(string)`
2. After conversion, it is decoded into `WSOptions.Path` in
   `adapter/outbound/vmess.go` which requires a string.
2024-05-21 23:31:28 +08:00
5aaee9
ac2506154f fix: possibly using released buffer in tproxy (#1286) 2024-05-21 23:25:28 +08:00
wwqgtxx
43bdc76f87 fix: darwin calculate correct tunIndex
https://github.com/MetaCubeX/mihomo/pull/1285
2024-05-21 19:13:44 +08:00
wwqgtxx
3195c678c7 chore: update quic-go to 0.44.0 2024-05-21 08:45:36 +08:00
Larvan2
bd43eca09d ci:docker tags 2024-05-20 19:47:25 +08:00
xishang0128
c504985b99 chore: Adjust sniff logs 2024-05-19 19:35:12 +08:00
Larvan2
4243a74284 chore: auto update geo 2024-05-19 18:51:06 +08:00
Larvan2
e749c7e492 ci: docker 2024-05-19 16:41:05 +08:00
Larvan2
c3ee921d30 chore: apply config when geo update 2024-05-19 15:46:23 +08:00
wwqgtxx
df69a31e62 chore: stop using go:linkname for crypto/tls.aesgcmPreferred and update utls to 1.6.6 2024-05-19 11:32:22 +08:00
Larvan2
b7c02a5923 ci: fix docker 2024-05-18 23:09:21 +08:00
wwqgtxx
bfb6caeeaf chore: stop using go:linkname for x/sys/windows 2024-05-18 20:54:28 +08:00
wwqgtxx
30a913aad6 chore: stop using go:linkname for net.lookupStaticHost 2024-05-18 20:45:15 +08:00
hunshcn
00e361c5ac chore: stop using go:linkname for http.registerOnHitEOF, http.requestBodyRemains (#1275)
relate to https://github.com/MetaCubeX/mihomo/pull/952#issuecomment-2118639385
2024-05-18 20:16:53 +08:00
Larvan2
56edd8f671 ci: better release 2024-05-18 18:36:00 +08:00
wwqgtxx
2b52809d2c chore: update quic-go to 0.43.1 2024-05-18 11:47:45 +08:00
Larvan2
5c3a9b1dfc fix: geo auto update #1261 2024-05-17 11:51:32 +08:00
xishang0128
fe88f0e437 chore: Ensure that some expressions take effect 2024-05-15 15:38:55 +08:00
wwqgtxx
87877d1b80 fix: don't ignore http.NewRequest's error 2024-05-15 13:53:18 +08:00
wwqgtxx
1bc3c16b59 feat: add PROCESS-NAME-REGEX and PROCESS-PATH-REGEX 2024-05-15 10:44:56 +08:00
wwqgtxx
ed1e7e32c7 action: revert more golang1.22 commit for win7 2024-05-15 09:14:34 +08:00
xishang0128
5da9ccaa98 action: Upgrade loongarch golang version 2024-05-15 08:32:57 +08:00
xishang0128
fd7ecc004f chore: Add filter for include-all-proxies 2024-05-13 20:30:31 +08:00
wwqgtxx
3ae4014b39 chore: disable tfo when lower than Windows 10.0.14393 2024-05-12 20:44:12 +08:00
wwqgtxx
a50339bd5f chore: swtich RtlGetNtVersionNumbers to RtlGetVersion
https://go-review.googlesource.com/c/go/+/571015
2024-05-12 20:23:13 +08:00
wwqgtxx
7df1c26942 fix: fingerprint passing 2024-05-12 19:34:25 +08:00
wwqgtxx
fc82a32a48 fix: system tun stack not working in win7 2024-05-12 15:52:10 +08:00
wwqgtxx
adf0ff588f action: let golang1.22's build can work on windows7/8 2024-05-12 13:32:07 +08:00
wwqgtxx
b840eae4c6 fix: x509 error in windows7/8 2024-05-12 12:36:48 +08:00
wwqgtxx
619f34119e action: add golang1.21 with special revert commit to work on Windows7 2024-05-12 00:10:51 +08:00
wwqgtxx
6d1c62bbf0 fix: shadowsocks uot not work with dialer-proxy 2024-05-08 09:27:31 +08:00
xishang0128
5dd883e790 chore: Add use-system-hosts option 2024-05-06 14:03:29 +08:00
Larvan2
a2b43faa0b Update README.md 2024-05-04 18:41:08 +08:00
wwqgtxx
8861eaf903 chore: hysteria2 will only change remote port in hopLoop 2024-05-01 09:41:22 +08:00
Pylogmon
107e3e7630 feat: Allow upgrade to latest release (#1235) 2024-04-30 17:01:46 +08:00
wwqgtxx
314c0bb34b fix: hy2 udp incompatible with quic-go 0.43.0 2024-04-29 12:14:11 +08:00
wwqgtxx
89a097faa8 chore: update quic-go to 0.43.0 2024-04-28 13:24:33 +08:00
H1JK
df01582996 fix: HTTP proxy variable shadowing 2024-04-27 01:05:34 +08:00
xishang0128
8ff56b5bb8 chore: Add InUser for http/socks/mixed 2024-04-25 11:48:53 +08:00
wwqgtxx
2f8f139f7c fix: wireguard can't be auto closed 2024-04-24 11:07:22 +08:00
wwqgtxx
b2280c85b7 chore: update dependencies 2024-04-23 14:40:21 +08:00
xishang0128
002b8af94a Chore: Let CA read following homeDir 2024-04-20 22:22:17 +08:00
xishang0128
99b274acbc chore: Make unix socket follow homeDir 2024-04-19 17:33:00 +08:00
xishang0128
d4ececae20 chore: Update workflow 2024-04-18 18:02:12 +08:00
xishang0128
ff2071c1da chore: Update systemd service file 2024-04-17 17:55:13 +08:00
xishang0128
189b7b9c5f chore: Working with unix directory 2024-04-17 11:12:29 +08:00
wwqgtxx
a878254662 feat: support -ext-ctl-unix cmdline to override external controller unix address 2024-04-17 10:08:54 +08:00
wwqgtxx
3566542d0e doc: cleanup 2024-04-17 09:39:30 +08:00
wwqgtxx
ca84ab1a94 feat: support external-controller-unix 2024-04-17 09:39:00 +08:00
wwqgtxx
d84f88b50f fix: system:// should ignore dns server setting by tun listener 2024-04-13 08:02:43 +08:00
xishang0128
e3b69b8ae2 chore: Make SubScriptioninfo query also follow Proxy 2024-04-12 04:58:07 +08:00
wwqgtxx
91a7ffaad2 fix: udp loopback detector not working 2024-04-11 09:24:53 +08:00
wwqgtxx
16fadd2441 fix: don't ignore bind6 error when udp's target is ipv6 address 2024-04-10 15:34:28 +08:00
wwqgtxx
cff7df164f fix: hy2 packetId race 2024-04-09 14:52:22 +08:00
wwqgtxx
de38fa882c fix: dns outbound udp timeout 2024-04-09 14:26:14 +08:00
wwqgtxx
bd703b8ff2 fix: truncate dns message in udp response
https://github.com/MetaCubeX/mihomo/issues/1168
2024-04-09 14:16:09 +08:00
wwqgtxx
72df27be44 chore: update dependencies 2024-04-08 10:15:29 +08:00
xishang0128
f3e23b1128 feat: Allow providers to set individual proxy and headers 2024-04-08 01:27:17 +08:00
Larvan2
19f7220c0b chore: adjust testURL priority 2024-04-06 11:49:46 +08:00
wwqgtxx
3249572dc1 action: typo 2024-04-05 10:23:15 +08:00
wwqgtxx
ba09139bd7 fix: avoid netlink dos networkUpdateMonitor 2024-04-05 10:18:05 +08:00
riolurs
90bf158e9f fix(group.parser): set default TestURL if empty 2024-04-05 01:51:18 +08:00
wwqgtxx
d1539e6c07 fix: IN-PORT not work in tproxy tcp inbound
https://github.com/MetaCubeX/mihomo/issues/1162
2024-04-04 23:54:25 +08:00
wwqgtxx
c893e3c462 fix: IN-PORT not work in http inbound
https://github.com/MetaCubeX/mihomo/issues/1162
2024-04-04 21:25:27 +08:00
wwqgtxx
b56e73a02a fix: close mixed stack panic #1014 2024-04-03 23:41:04 +08:00
wwqgtxx
40f5c5b987 chore: don't retry when rejected loopback connection 2024-04-03 08:42:15 +08:00
wwqgtxx
d48517b29d fix: timer usage for monitor check update 2024-04-02 23:01:04 +08:00
xishang0128
3b472f786e chore: Add source matching for ip type rules 2024-04-01 18:16:34 +08:00
xishang0128
f3743fc7f9 chore: Introducing Punycode conversion for domain matching 2024-04-01 16:21:34 +08:00
wwqgtxx
56ed9019a6 ci: add armv5/6 back 2024-03-30 23:06:02 +08:00
H1JK
3e0bd65135 feat: Converter support Xray HTTPUpgrade fast open path 2024-03-30 18:19:40 +08:00
wwqgtxx
72d0948224 fix: atomic.TypedValue panic 2024-03-29 14:43:42 +08:00
wwqgtxx
4542fc0991 fix: tun lookback when don't have an activated network 2024-03-29 14:33:05 +08:00
xishang0128
914bc8a3e9 fix: Fix DOMAIN-REGEX parsing 2024-03-29 13:43:11 +08:00
wwqgtxx
11f0983e5c fix: resolve by ProxyServerHostResolver should not retry with DefaultResolver when error occurs 2024-03-28 23:33:56 +08:00
wwqgtxx
89d7b8138a chore: turned off ECN by default 2024-03-28 23:19:42 +08:00
wwqgtxx
eae1f05e88 fix: wireguard multi peers public key parse 2024-03-28 21:57:48 +08:00
wwqgtxx
367a287153 chore: don't lookup process when Type==INNER 2024-03-28 21:49:44 +08:00
wwqgtxx
06b5121d9e chore: embed ca-certificates.crt 2024-03-28 19:26:57 +08:00
Larvan2
82517e6ba8 chore: include short commit ID in release note 2024-03-27 19:34:11 +08:00
落心
0b4662e4b7 fixed: invalid argument to Intn (#1133) 2024-03-26 14:19:33 +08:00
bobo liu
288899a473 chore: stylish d2ae94f2 (#1132) 2024-03-24 21:41:05 +08:00
wwqgtxx
5af7f4e847 chore: allow config table-index for tun
https://github.com/MetaCubeX/mihomo/issues/1128
2024-03-24 21:31:52 +08:00
wwqgtxx
d2ae94f20b fix: iface panic
https://github.com/MetaCubeX/mihomo/issues/1130
2024-03-24 21:24:50 +08:00
wwqgtxx
d56a439a74 fix: dns truncate not work 2024-03-23 22:30:19 +08:00
wwqgtxx
9c08e936f9 fix: unmap 4in6 ip in wireguard 2024-03-22 00:33:38 +08:00
wwqgtxx
284b01ca38 fix: wireguard client bind 2024-03-21 12:23:45 +08:00
wwqgtxx
2e94531c72 Revert "fix hysteria faketcp lookback in TUN mode (#601)"
This reverts commit fdaa6a22a4.
2024-03-20 18:13:34 +08:00
wwqgtxx
e1a5b93cce chore: rebuild wireguard server address resolve 2024-03-20 12:34:55 +08:00
wwqgtxx
7fd5902e6b chore: wireguard outbound only can set ip and ipv6 outside peers
https://github.com/MetaCubeX/mihomo/issues/522
2024-03-20 12:34:54 +08:00
wwqgtxx
143fe84b8e chore: update gvisor 2024-03-20 09:30:00 +08:00
wwqgtxx
80408855ac chore: update quic-go to 0.42.0 2024-03-19 15:18:00 +08:00
wwqgtxx
c80dd5d738 chore: retry DNS over TCP when receive a truncated UDP response
https://github.com/MetaCubeX/mihomo/issues/1117
2024-03-19 14:44:36 +08:00
wwqgtxx
b3db113b1b chore: allow disabled system hosts by environment variable DISABLE_SYSTEM_HOSTS 2024-03-13 15:32:26 +08:00
wwqgtxx
dceb8ee535 fix: resolve atomic.Value usages with interface types 2024-03-13 14:49:46 +08:00
wwqgtxx
31d3614060 chore: upgrade dependencies 2024-03-13 08:54:50 +08:00
wwqgtxx
5fdfde6a07 chore: ssh outbound add private-key-passphrase,host-key,host-key-algorithms
rename `privateKey` to `private-key` and support direct write private key value in config file
2024-03-13 08:30:41 +08:00
wwqgtxx
81c832ef9e chore: code cleanup 2024-03-12 15:14:56 +08:00
wwqgtxx
012e448562 fix: when hysteria2 set ports, port can be empty 2024-03-12 15:06:41 +08:00
xishang0128
44d8a14629 feat: add IP-ASN rule 2024-03-12 03:14:25 +08:00
wwqgtxx
7ad37ca0e3 fix: hysteria2 server domain resolve 2024-03-10 23:49:54 +08:00
xishang0128
f0ff6546e4 chore: Correct android update name 2024-03-10 20:38:30 +08:00
xishang0128
77c10d90f3 chore: Replace android timezone implementation
kanged from https://github.com/SagerNet/sing-box/blob/dev-next/include/tz_android.go
2024-03-10 02:24:28 +08:00
xishang0128
e0248faebd feat: Experimental supports dialer IP4P address convert
form https://github.com/heiher/natmap/wiki/faq
2024-03-10 02:24:20 +08:00
keakon
feedc9ec66 feat: implement port hopping (#1064)
* implement port hopping using sing and sing-quic

* 更新quic-go

* 更新sing

* Update go.sum

---------

Co-authored-by: wwqgtxx <wwqgtxx@gmail.com>
2024-03-08 22:54:24 +08:00
H1JK
7754b46dc4 fix: MaxMind MMDB code character case 2024-03-08 22:52:36 +08:00
H1JK
90d0ef033b chore: Check regex rule expression when initializing 2024-03-08 22:52:36 +08:00
wwqgtxx
5702d28cda chore: rebuild ssh outbound 2024-03-08 22:43:41 +08:00
TreviD
0bb5568de9 feat: add ssh outbound (#1087)
* feat: add ssh outbound

* fix: Modify the way to get dstAddr

---------

Co-authored-by: trevid <trevidmy@gmail.com>
2024-03-08 22:43:41 +08:00
xishang0128
37b02b18f7 chore: Temporarily abandon pkg.tar.zst package building 2024-03-08 22:43:40 +08:00
xishang0128
cd9e9cd2c1 fix: fix timezone for Android 2024-03-08 01:39:43 +08:00
xishang0128
234a4bfc93 feat: add DOMAIN-REGEX rule 2024-03-07 23:32:07 +08:00
wwqgtxx
fad1a08378 chore: dns outbound support tcp 2024-03-07 13:12:40 +08:00
xishang0128
04886761a2 chore: Add max-failed-times 2024-03-07 03:35:11 +08:00
xishang0128
823f59b5c7 chore: Add dns-redirect options to iptables 2024-03-07 00:52:20 +08:00
wwqgtxx
974332c0cc chore: rebuild sync.Once visit code 2024-03-05 10:57:25 +08:00
wwqgtxx
8b9813079b chore: share RelayDnsPacket function code 2024-03-04 22:12:27 +08:00
xishang0128
fe4acebb8b chore: Supplement type 2024-03-04 20:02:09 +08:00
wwqgtxx
69bf434e2c chore: vlessPacketConn should wrap ThreadSafePacketConn 2024-03-04 19:14:40 +08:00
wwqgtxx
e867497315 chore: rebuild DNS outbound code 2024-03-04 19:00:19 +08:00
sduoduo233
3ec23c1fc5 feat: Add DNS outbound to hijack DNS packets (#1078) 2024-03-04 18:21:50 +08:00
H1JK
d27340867f chore: Add GeoIP result to metadata 2024-03-02 17:41:04 +08:00
wwqgtxx
7eb16a098a chore: upgrade dependencies 2024-02-28 11:14:10 +08:00
xishang0128
f0bc68585a chore: Update workflow 2024-02-27 23:11:56 +08:00
wwqgtxx
092e53586e ci: better build.yml 2024-02-27 15:21:30 +08:00
wwqgtxx
41e3571e1d ci: let go120 build work 2024-02-27 14:26:49 +08:00
xishang0128
e58294198c chore: Distinguish between abi1.0 and abi2.0 of loongarch64 2024-02-27 02:14:18 +08:00
Skyxim
0619c75276 fix: url format error when host is IPv6
[Bug] 使用IPV6地址+vmess+http伪装的配置引起内核panic MetaCubeX/mihomo#1063
2024-02-26 06:05:06 +00:00
xishang0128
78b4b11f26 chore: Update workflow 2024-02-25 20:42:01 +08:00
Larvan2
8d9eb1e534 chore: get HealCheckURL from pd if groupOption URL is empty 2024-02-25 14:28:27 +08:00
Larvan2
d2a5376cb8 revert: "modify default url"
This reverts commit 3d643cb95a.
2024-02-24 13:50:53 +08:00
Larvan2
f8295a02fd fix: update mmdb fail 2024-02-21 21:56:20 +08:00
Larvan2
3d833ef6a8 chore: don't panic when set deadline error 2024-02-21 21:00:33 +08:00
xishang0128
1c7e011f87 fix: api does not return configuration value 2024-02-21 17:14:08 +08:00
wwqgtxx
9e7eaf720f fix: ipv6 http host addr 2024-02-21 15:04:40 +08:00
xishang0128
6399347a63 chore: add some fields for override 2024-02-20 21:52:31 +08:00
wwqgtxx
23e3f12e88 chore: better timer using 2024-02-16 11:29:33 +08:00
wwqgtxx
985b884d85 chore: add power event code for windows 2024-02-16 11:25:10 +08:00
H1JK
93b48a94fc chore: Update workflow 2024-02-15 21:48:48 +08:00
wwqgtxx
080d316059 chore: update gvisor 2024-02-14 18:07:40 +08:00
wwqgtxx
0c384b1e42 fix: tproxy start error 2024-02-07 21:07:41 +08:00
wwqgtxx
324c0bde7d chore: update golang to 1.22 2024-02-07 18:23:18 +08:00
wwqgtxx
9e57e7d29b fix: fix lan-allowed-ips does not take effect 2024-02-07 18:22:54 +08:00
xishang0128
20658f6eac fix: lan-allowed-ips does not take effect 2024-02-05 22:40:06 +08:00
Larvan2
822ba5f0b5 ci: bump github-actions version 2024-02-05 14:03:21 +08:00
wwqgtxx
e6011301b2 chore: rebuild slowdown code 2024-01-30 19:41:50 +08:00
tommy
947ad9b308 chore: store latency data more reasonably (#964) 2024-01-30 15:51:12 +08:00
wwqgtxx
d1337f39ed chore: slowdown wireguard dial retry 2024-01-30 15:29:48 +08:00
wwqgtxx
c6843d6a2c fix: exclude loopback on darwin 2024-01-30 12:28:52 +08:00
wwqgtxx
f2634250a6 fix: hy2's rawConn not closed 2024-01-30 12:11:05 +08:00
wwqgtxx
9bd70e1366 fix: tfo not working with smux/yamux 2024-01-24 17:52:45 +08:00
xishang0128
1025101954 chore: add timeout option 2024-01-24 12:45:35 +08:00
wwqgtxx
80d6ca8ef9 fix: h2mux udp not working 2024-01-24 11:54:22 +08:00
xishang0128
5858384631 chore: modify initial resource update 2024-01-24 11:33:37 +08:00
wwqgtxx
5c1404f78e chore: hysteria2 add udp-mtu option
default value is `1200-3` to match old version quic-go's capability
2024-01-22 20:56:28 +08:00
Larvan2
7be6751650 fix: trigger-cmfa-update 2024-01-21 16:57:21 +08:00
xishang0128
e86567ead2 chore: limit the default url 2024-01-20 19:43:10 +08:00
wwqgtxx
c1f0ed18ef chore: dscp support range too 2024-01-20 11:00:29 +08:00
wwqgtxx
0e1bdb07d4 chore: rewrite IntRanges constructor 2024-01-20 11:00:06 +08:00
pretze
25d6ad220d feat: add DSCP rule for Tproxy UDP packets (#996)
* feat: add `DSCP` rule for Tproxy UDP packets

* fix: fix compatibility issue with non_linux platform

* chore: remove redundant lines for DSCP
2024-01-20 10:19:42 +08:00
wwqgtxx
90ea6ab278 chore: update quic-go to 0.41.0 2024-01-20 09:48:20 +08:00
snakem982
36ea09eff4 fix: Converter SIP002 parameters parse (#976) 2024-01-19 21:20:11 +08:00
riolurs
460cc240b0 fix(ntp): simplify NTP service initialization and error handling 2024-01-17 16:14:25 +08:00
Larvan2
c34a0ef8f0 chore: trigger ci 2024-01-17 14:15:49 +08:00
Larvan2
061f83d92a docs: README.md 2024-01-17 13:31:21 +08:00
wwqgtxx
edf318bae0 chore: better IPSet code 2024-01-13 18:15:30 +08:00
wwqgtxx
e860497c0c chore: cleanup IPSet code 2024-01-13 11:44:02 +08:00
Ahmad Nazari
d2d8c0115c fix: flush dns cache in android and cmfa build. (#971) 2024-01-11 10:40:04 +08:00
wwqgtxx
5ec686df08 chore: update dependencies 2024-01-11 10:00:02 +08:00
wwqgtxx
7c54775ddc chore: ipcidr direct using go4.org/netipx 2024-01-11 09:33:59 +08:00
Larvan2
425bc692ad chore: replace IpCidrTrie with binary search 2024-01-09 16:37:34 +08:00
xishang0128
ffcd672ebf chore: return more information for the api 2024-01-07 23:32:22 +08:00
Cesaryuan
7e3e38d054 fix: SUB-RULE with PROCESS-NAME rule payload not working (#953) 2024-01-06 22:29:30 +08:00
Larvan2
6bc915526f fix: resolve IPv6 rule-set issue #959. 2024-01-06 17:54:09 +08:00
Vincent.Shi
4af94df142 chore: Redundant function calls. (#956) 2024-01-05 15:07:49 +08:00
wwqgtxx
2e12ceeaed chore: stop retry when couldn't find ip 2024-01-02 21:49:27 +08:00
wwqgtxx
33bc7914e9 chore: read waiter for pipe 2024-01-02 18:34:34 +08:00
Larvan2
0404e35be8 chore: update release note 2024-01-02 16:24:44 +08:00
Larvan2
fbaa323ad4 chore: generate release note automatically 2024-01-02 16:08:19 +08:00
hunshcn
4ee267ca7e fix: add backgroundRead for plain http inbound (#952)
https://github.com/golang/go/blob/go1.21.5/src/net/http/server.go#L682
2024-01-02 13:45:40 +08:00
xishang0128
1d3e9f4889 feat: add include-all to proxy-groups 2023-12-31 09:43:52 +08:00
xishang0128
3d643cb95a chore: modify default url 2023-12-31 07:39:17 +08:00
xishang0128
22862f20cc chore: update docs 2023-12-31 03:22:01 +08:00
PuerNya
2e87c6f4da chore: add a new cors response header 2023-12-27 16:28:17 +08:00
PuerNya
fb1c0aa387 chore: change DefaultTestUrl 2023-12-27 14:24:21 +08:00
PuerNya
1701e4715d fix: stop using insert url when get urltest delay 2023-12-27 14:24:21 +08:00
xishang0128
0d07cf40b8 fix: try fixing automatic policy 2023-12-26 03:49:00 +08:00
xishang0128
41a05d96a5 chore: add some fields for override 2023-12-26 01:45:32 +08:00
PuerNya
4cea3125e6 Revert 8cf14bb6 and 9d8c3b0a 2023-12-24 22:26:18 +08:00
Larvan2
997663a4ad chore: avoid return nil. fix #930 2023-12-23 13:11:10 +08:00
H1JK
b632575e39 chore: Cleanup unused GeoSite matchers 2023-12-23 00:05:07 +08:00
Larvan2
59ab4fe745 chore: better Reject-Drop for UDP 2023-12-22 21:28:54 +08:00
Larvan2
147400fbe0 chore: cleanup code 2023-12-22 21:28:54 +08:00
Larvan2
ac381736a5 chore: restore function name to AliveForTestUrl 2023-12-22 21:18:17 +08:00
Larvan2
08a1f10af4 Merge PR #860 into Alpha 2023-12-22 21:11:07 +08:00
wwqgtxx
8822349f94 chore: support waitRead in windows 2023-12-21 21:18:26 +08:00
wwqgtxx
27635ea027 fix: hy2 missing UDP timeout 2023-12-20 23:30:06 +08:00
wwqgtxx
429a03d986 chore: add loopback detect for direct outbound 2023-12-20 13:11:00 +08:00
Larvan2
518c31dd0e ci: build loong64 2023-12-19 21:10:02 +08:00
祝子祺
16769865e4 support loong64 (#871) 2023-12-19 21:06:58 +08:00
PuerNya
9d8c3b0a3b fix: udp nat handle 2023-12-19 00:19:40 +08:00
wwqgtxx
f16ebf9bfe chore: add leading slash to ws-path 2023-12-18 23:22:50 +08:00
wwqgtxx
f29329fe80 fix: sing vectorised writer 2023-12-18 23:08:35 +08:00
PuerNya
8cf14bb67e chore: reslove udp host after rule matching 2023-12-18 17:13:53 +08:00
H1JK
2bba8aa14a feat: Add succinct matcher support for GeoSite
and use it by default
2023-12-17 00:01:01 +08:00
Larvan2
5b23b979df chore: do not always trigger upload on PR #912
Co-authored-by: bobo liu <7552030+fakeboboliu@users.noreply.github.com>
2023-12-16 22:03:32 +08:00
H1JK
78e5d3229e chore: Remove the use of curve25519 package 2023-12-16 17:02:52 +08:00
bobo liu
0ab73a9beb fix: the right way to get process in win32 format (#909) 2023-12-14 10:19:19 +08:00
Kuingsmile
7ee6809257 feat: Add LAN allowed and disallowed IP configurations (#861) 2023-12-13 00:13:17 +08:00
wwqgtxx
3cf865e5f0 fix: GSO support for TUN 2023-12-11 16:41:50 +08:00
wwqgtxx
9fc1fc4cfe chore: add GSO support for TUN
lwip had been dropped, also cgo build will be removed
2023-12-10 08:32:54 +08:00
Larvan2
d80fcb77f6 chore: health check for compatible providers after startup 2023-12-09 18:58:36 +08:00
wwqgtxx
5a2ed71bd9 chore: update uTLS to 1.5.4 2023-12-09 12:28:19 +08:00
wwqgtxx
9729c2e440 chore: don't force output color in log
but you can set `CLICOLOR_FORCE=1` environment variable
2023-12-09 10:53:19 +08:00
wwqgtxx
c5d1e20a64 chore: Update dependencies 2023-12-09 09:46:37 +08:00
wwqgtxx
da65f8f935 action: add GOTOOLCHAIN=local in build.yml 2023-12-09 09:24:41 +08:00
H1JK
582ac28728 chore: Update bandwidth convertor
Sync with 6d6a26b399
2023-12-08 21:45:00 +08:00
wwqgtxx
262d3295d1 chore: using stable api 2023-12-08 19:04:29 +08:00
wwqgtxx
8dbc5e2100 chore: limit max CopyExtendedOnce execute times to 10 2023-12-08 19:04:11 +08:00
wwqgtxx
941dd6c76d fix: CopyExtendedOnce can't exit loop 2023-12-08 13:04:17 +08:00
wwqgtxx
fdc9c01df1 fix: gvisor stack's dns hijack not working 2023-12-08 10:13:08 +08:00
wwqgtxx
b538aa6ca2 chore: code cleanup 2023-12-08 09:26:24 +08:00
wwqgtxx
1d1841f7aa fix: missing insertTriePolicy when process rule-set 2023-12-08 08:59:59 +08:00
wwqgtxx
1b527fd494 chore: windows process will return DOS format instead of NT format 2023-12-08 08:55:45 +08:00
wwqgtxx
73e16c912f fix: remove unneeded health check 2023-12-08 07:16:45 +08:00
wwqgtxx
9ac4738ef9 fix: system stack's dns hijack not working 2023-12-08 01:25:07 +08:00
wwqgtxx
cbec564af9 chore: adapt new ReadWait interfaces 2023-12-07 23:32:37 +08:00
wwqgtxx
c5d1db7905 chore: update gvisor 2023-12-07 07:55:21 +08:00
wwqgtxx
ad263f7229 fix: ss uot add thread safe wrapper 2023-12-06 21:08:04 +08:00
tommytag
f63acc0202 healthcheck latency of the provider is also stored in the extra, without compromising rest api compatibility 2023-12-06 17:11:24 +08:00
wwqgtxx
f572e7fba8 fix: avoid gobwas/ws pbytes.GetLen panic 2023-12-06 12:02:50 +08:00
wwqgtxx
ed210ee403 fix: only using xsync with pointer to avoid unaligned 64-bit atomic operation
closed #783
2023-12-06 11:01:03 +08:00
Larvan2
92129b33e7 ci: push images to docker.io for storage conservation 2023-12-05 21:07:21 +08:00
Larvan2
ee6b974c18 fix: let input prefix to lower case when parsing. Fix #868 2023-12-05 20:30:07 +08:00
giveup
2d73bcb951 chore: fix typo
chore: fix typo
2023-12-05 18:10:20 +08:00
tommytag
2d7538aca6 [fix] incorrect data save location for latency 2023-12-04 18:10:45 +08:00
Kuingsmile
aef87b29ba feat: Add GeoAutoUpdate and GeoUpdateInterval to config (#857) 2023-12-03 23:23:34 +08:00
Larvan2
5f493fbcfb fix: mount cache 2023-12-03 14:39:01 +08:00
wzdnzd
071e8488a8 [fix] latency of extra should not overwrite the history (#855) 2023-12-03 12:27:04 +08:00
snakem982
22ed13b9df feat: support external api extensions (#852) 2023-12-03 09:39:34 +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
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
Larvan2
fdd327d58d fix: fail to set KeepAliveIntervall #715 2023-09-25 14:05:13 +08:00
wwqgtxx
0dfe696300 chore: ntp service support dialer-proxy 2023-09-25 09:11:35 +08:00
wwqgtxx
c0ba798708 chore: share N.dialer code 2023-09-25 09:11:35 +08:00
Kiva
67d7e53f7a feat: recovering preHandleMetadata failure from sniffing (#769) 2023-09-24 19:27:55 +08:00
Larvan2
e6366f7442 chore: fix typo 2023-09-24 19:00:51 +08:00
汐殇
89d9cb0539 Merge pull request #767 from PuerNya/fix-delay
chore: handle provider proxies  in proxies api
2023-09-24 15:55:33 +08:00
PuerNya
0d300a3540 chore: handle provider proxies in proxies api 2023-09-24 15:39:14 +08:00
xishang0128
7c59916c22 chore: update provider proxies api 2023-09-24 00:19:10 +08:00
Larvan2
8f515ecc05 chore: updateUI API return 501 when config incomplete 2023-09-23 18:00:07 +08:00
xishang0128
34f62a0919 feat: add provider proxies api 2023-09-23 17:54:20 +08:00
wwqgtxx
0207a7ac96 chore: resolver read system hosts file 2023-09-23 14:01:18 +08:00
wwqgtxx
bf619d8586 fix: socks5 udp not working on loopback 2023-09-22 23:33:24 +08:00
wwqgtxx
d48f9c2a6c chore: rebuild ca parsing 2023-09-22 14:45:34 +08:00
wwqgtxx
90a5aa609a fix: uot read failed 2023-09-22 00:11:57 +08:00
wwqgtxx
4fe7a463c5 chore: limit tuicv5's maxUdpRelayPacketSize up to 1200-PacketOverHead 2023-09-21 23:49:45 +08:00
wwqgtxx
7f49c91267 fix: hy2 udp not working 2023-09-21 23:36:40 +08:00
Larvan2
f6bf9c0857 feat: converter support hysteria2 2023-09-21 17:25:15 +08:00
wwqgtxx
da24810da2 chore: support set cwnd for hy2 too 2023-09-21 16:41:31 +08:00
wwqgtxx
ee3213c28f fix: tuicv5 panic in ReadFrom 2023-09-21 15:10:35 +08:00
wwqgtxx
233eeb0b38 feat: inbound support Hysteria2 2023-09-21 15:10:35 +08:00
wwqgtxx
6c3b973748 doc: add Hysteria2 doc 2023-09-21 10:43:45 +08:00
wwqgtxx
9b8e2d9343 feat: support Hysteria2 2023-09-21 10:28:28 +08:00
wwqgtxx
24fd577767 chore: Update dependencies 2023-09-21 08:57:38 +08:00
wwqgtxx
42b85de83e chore: Restore go1.20 support 2023-09-21 08:29:28 +08:00
wwqgtxx
62266010ac Revert "migration: go 1.21"
This reverts commit 33d41338ef.
2023-09-21 08:29:28 +08:00
xishang0128
0d7a57fa9d Chore: update github issue template 2023-09-21 03:40:46 +08:00
汐殇
f909b3c0dc chore: Update android-ndk 2023-09-20 15:26:36 +08:00
xishang0128
8b518161a3 chore: update external-ui 2023-09-20 14:23:58 +08:00
Larvan2
20fafdca65 chore: cleanup code 2023-09-18 19:42:08 +08:00
Larvan2
fd96efd456 chore: ignore PR when Pre-releasing 2023-09-18 19:36:11 +08:00
Larvan2
7c21768e99 feat: update external-ui 2023-09-18 19:21:30 +08:00
Larvan2
6a5a94f48f chore: DNS cache policy follow upstream 2023-09-17 17:18:35 +08:00
Larvan2
33d41338ef migration: go 1.21 2023-09-17 17:05:13 +08:00
Skyxim
2d3b9364bf fix: caceh dns result 2023-09-16 12:30:11 +08:00
Larvan2
fa49fd7ba2 chore: use cmp in go 1.21
Co-authored-by: H1JK <hell0jack@protonmail.com>
2023-09-16 12:06:58 +08:00
Larvan2
c3d72f6883 feat: download/upgrade XD to external-ui 2023-09-16 11:44:15 +08:00
kunish
af99b52527 docs(README): update dashboard section 2023-09-09 13:06:49 +08:00
H1JK
f241e1f81a chore: Update dependencies 2023-09-09 09:53:14 +08:00
H1JK
90acce7fa1 feat: Add disable quic-go GSO to experimental 2023-09-08 22:58:59 +08:00
xishang0128
7286391883 feat: support users to customize download ua 2023-09-07 18:44:58 +08:00
riolu.rs
a1eab125ee fix: ntp service panic 2023-09-04 18:35:06 +08:00
Larvan2
1d4af2d92b chore: TCPKeepAlive interval set to 15s by default 2023-09-03 20:42:54 +08:00
riolu.rs
d6cf2a837f chore: ntp service dep with sing, optional synchronize system time 2023-09-03 17:49:56 +08:00
H1JK
d6b80acfbc chore: Use xsync provided map size calculation 2023-09-02 20:17:43 +08:00
wwqgtxx
1cad615b25 chore: using xsync.MapOf replace sync.Map 2023-09-02 16:54:48 +08:00
Larvan2
73fa79bf3f feat: configurable TCPKeepAlive interval 2023-09-02 16:45:16 +08:00
Larvan2
d79c13064e chore: cleanup codes 2023-09-02 14:12:53 +08:00
YanceyChiew
427a377c2a refactor: Decouple .Cleanup from ReCreateTun
The listener.Cleanup method will be called during
executor.Shutdown and route.restart, so it should serve
all kinds of listeners rather than a single tun device.

Currently listener.ReCreateTun will call it to handle
some internal affairs, This should be decoupled.

In this way, the cleanup tasks for data outside the
process life cycle that other listeners will add here
in the future will not be accidentally triggered
by configuring tun.
2023-09-02 14:12:53 +08:00
YanceyChiew
9feb4d6668 fix: RESTful api missing TunConf.device
In commit 54fee7b, due to failure to take into account that
not all required parameters of `sing_tun.server.New` have
default values provided by `LC.Tun`, the name of the tun device
cannot be obtained when `TunConf.device` is not explicitly
configured. This commit fixed the issue.
2023-09-02 14:12:53 +08:00
wwqgtxx
a366e9a4b5 fix: ntp service panic 2023-09-02 12:37:43 +08:00
riolu.rs
cbdf33c42c feat: ntp service 2023-09-02 02:15:46 +08:00
Larvan2
9ceaf20584 fix: concurrent map writes #707 2023-09-01 10:43:04 +08:00
YanceyChiew
54fee7bd3a Improve: nicer tun info for RESTful api
Let the restful api still get TunConf even when tun is off.
Otherwise the api will return the default values,
instead of the values that actually take effect after enable.

* Due to this problem, yacd changes the displayed value
back to gvisor immediately after the user selects tun stack.
2023-08-30 21:13:32 +08:00
Larvan2
414d8f2162 chore: use WaitGroup in dualStackDialContext 2023-08-30 17:28:36 +08:00
Mitt
86cf1dd54b fix: dualStack confusing error on ipv4 failed connect 2023-08-30 17:28:36 +08:00
Larvan2
d099375200 chore: rename func name 2023-08-30 15:52:41 +08:00
Alpha
9536372cfb fix: call shutdown before restart (#709) 2023-08-30 15:49:28 +08:00
Larvan2
630a17cf90 chore: cleanup codes 2023-08-26 21:20:20 +08:00
wwqgtxx
0a7b7894bd feat: proxies support direct type 2023-08-24 23:33:03 +08:00
wwqgtxx
3a9fc39cd9 chore: update quic-go to 0.38.0 2023-08-21 16:18:56 +08:00
wwqgtxx
1181fd4560 feat: add udp-over-stream for tuic
only work with meta tuic server or sing-box 1.4.0-beta.6
2023-08-21 12:37:39 +08:00
Larvan2
b8a60261ef chore: restore unselected
clear selected node in outboundgoup/URLtest when getGroupDelay triggered
2023-08-18 22:17:07 +08:00
wwqgtxx
db68d55a0e fix: sing-vmess panic 2023-08-17 22:33:07 +08:00
wwqgtxx
574efb4526 chore: Update dependencies 2023-08-16 21:30:12 +08:00
3andne
03b0252589 feat: bump restls to v0.1.6 (utls v1.4.3) (#692)
* feat: bump restls to v0.1.5 (utls v1.4.3)
* fix: rm dependency go-quic
2023-08-16 11:41:58 +08:00
H1JK
ed09df4e13 fix: TLS ALPN support 2023-08-14 15:48:13 +08:00
H1JK
f89ecd97d6 feat: Converter unofficial TUIC share link support 2023-08-14 15:11:33 +08:00
wwqgtxx
3093fc4f33 chore: update go1.21.0 release 2023-08-09 17:26:24 +08:00
wwqgtxx
984fca4726 feat: add inbound-mptcp for listeners 2023-08-09 17:09:03 +08:00
wwqgtxx
cc42d787d4 feat: add mptcp for all proxy 2023-08-09 16:57:39 +08:00
wwqgtxx
e2e0fd4eba chore: using uint16 for ports in Metadata 2023-08-09 13:51:02 +08:00
xishang0128
bad9f2e6dc fix geodata-mode 2023-08-07 01:43:23 +08:00
H1JK
68bf6f16ac refactor: Geodata initialization 2023-08-06 23:34:10 +08:00
H1JK
cca701c641 chore: Update dependencies 2023-08-06 18:38:50 +08:00
wwqgtxx
09ec7c8a62 chore: update quic-go to 0.37.3 2023-08-06 09:45:51 +08:00
wwqgtxx
68f312288d chore: update quic-go to 0.37.2 and go1.21rc4 2023-08-05 12:53:49 +08:00
wwqgtxx
191243a1d2 chore: better tuicV5 deFragger 2023-08-03 23:07:30 +08:00
YuSaki丶Kanade
b0fed73236 Fix: mapping dns should not stale (#675)
* Fix: mapping dns should not stale

* Update enhancer.go
2023-08-01 17:30:57 +08:00
wwqgtxx
f125e1ce9e chore: Update dependencies 2023-08-01 13:54:22 +08:00
wwqgtxx
e2216b7824 chore: update quic-go to 0.37.1 2023-08-01 09:55:55 +08:00
H1JK
7632827177 chore: Use Meta-geoip for default 2023-07-20 23:24:48 +08:00
H1JK
b0e76ec791 feat: Add Meta-geoip V0 database support 2023-07-17 10:33:20 +08:00
Hellojack
a82745f544 chore: Remove legacy XTLS support (#645)
* chore: Remove legacy XTLS support

* chore: Rename function
2023-07-16 23:26:07 +08:00
Skyxim
cbb8ef5dfe fix: discard http unsuccessful status 2023-07-16 11:43:55 +08:00
wwqgtxx
a181e35865 chore: structure support decode pointer 2023-07-16 11:11:30 +08:00
Skyxim
014537e1ea fix: discard http unsuccessful status 2023-07-16 11:10:07 +08:00
wwqgtxx
9b50f56e7c fix: tunnel's handleUDPToLocal panic 2023-07-16 10:35:10 +08:00
wwqgtxx
9cbca162a0 feat: tuic outbound allow set an empty ALPN array 2023-07-16 10:29:43 +08:00
Skyxim
f73f32e41c fix: parse nested sub-rules failed 2023-07-16 10:15:43 +08:00
wwqgtxx
cfc30753af chore: Update go1.21rc3 2023-07-15 16:52:44 +08:00
H1JK
081e94c738 feat: Add sing-geoip database support 2023-07-14 22:28:24 +08:00
H1JK
5dd57bab67 chore: Update dependencies 2023-07-14 11:37:15 +08:00
H1JK
492a731ec1 fix: DNS cache 2023-07-14 09:55:43 +08:00
wwqgtxx
0b1aff5759 chore: Update dependencies 2023-07-02 10:41:02 +08:00
wwqgtxx
8f1475d5d0 chore: update to go1.21rc2, drop support for go1.19 2023-07-02 09:59:18 +08:00
wwqgtxx
c6b84b0f20 chore: update quic-go to 0.36.1 2023-07-02 09:05:16 +08:00
moranno
02ba78ab90 chore: change geodata download url to fastly.jsdelivr.net (#636) 2023-06-30 18:52:39 +08:00
タイムライン
57db8dfe23 Chore: Something update from clash (#639)
Chore: add alive for proxy api
Improve: alloc using make if alloc size > 65536
2023-06-30 17:36:43 +08:00
wwqgtxx
8e16738465 chore: better env parsing 2023-06-29 16:40:08 +08:00
wwqgtxx
db6b2b7702 chore: better resolv.conf parsing 2023-06-28 09:17:54 +08:00
Skyxim
603d0809b4 fix: panic when add 4in6 ipcidr 2023-06-26 21:04:54 +08:00
wwqgtxx
614cc93cac chore: better close single connection in restful api 2023-06-26 18:25:36 +08:00
wwqgtxx
1cb75350e2 chore: statistic's Snapshot only contains TrackerInfo 2023-06-26 18:13:17 +08:00
wwqgtxx
42ef4fedfa chore: avoid unneeded map copy when close connection in restful api 2023-06-26 17:46:14 +08:00
wwqgtxx
2284acce94 chore: update quic-go to 0.36.0 2023-06-26 12:08:38 +08:00
wwqgtxx
919daf0dbb fix: tuic server cwnd parsing 2023-06-21 14:00:49 +08:00
wwqgtxx
6d824c8745 chore: tuic server can handle V4 and V5 in same port 2023-06-21 13:53:37 +08:00
Larvan2
1d94546902 chore: fix TUIC cwnd parsing 2023-06-21 00:47:05 +08:00
wwqgtxx
ad7508f203 Revert "chore: Refine adapter type name"
This reverts commit 61734e5cac.
2023-06-19 14:28:06 +08:00
wwqgtxx
d391fda051 chore: function rename 2023-06-19 08:32:11 +08:00
wwqgtxx
fe0f2d9ef9 chore: Update dependencies 2023-06-19 08:23:48 +08:00
xishang0128
b9110c164d update docs 2023-06-18 01:50:32 +08:00
Larvan2
6c8631d5cc chore: adjustable cwnd for cc in quic 2023-06-18 00:47:26 +08:00
H1JK
61734e5cac chore: Refine adapter type name 2023-06-17 00:05:03 +08:00
汐殇
77fb9a9c01 feat: optional provider path (#624) 2023-06-15 22:45:02 +08:00
H1JK
af28b99b2a Add REALITY ChaCha20-Poly1305 auth mode support 2023-06-14 17:17:46 +08:00
wwqgtxx
4f79bb7931 fix: singmux return wrong supportUDP value 2023-06-14 15:51:13 +08:00
wwqgtxx
644abcf071 fix: tuicV5's heartbeat should be a datagram packet 2023-06-13 17:50:10 +08:00
Skyxim
183f2d974c fix: dns concurrent not work 2023-06-12 18:42:46 +08:00
wwqgtxx
e914317bef feat: support tuicV5 2023-06-12 18:42:46 +08:00
wwqgtxx
5e20fedf5f chore: Update dependencies 2023-06-11 23:57:25 +08:00
H1JK
54337ecdf3 chore: Disable cache for RCode client 2023-06-11 23:01:51 +08:00
H1JK
c7de0e0253 feat: Add RCode DNS client 2023-06-11 23:01:45 +08:00
Skyxim
b72219c06a chore: allow unsafe path for provider by environment variable 2023-06-11 01:55:49 +00:00
H1JK
64b23257db chore: Replace murmur3 with maphash 2023-06-10 17:35:19 +08:00
Skyxim
c57f17d094 chore: reduce process lookup attempts when process not exist #613 2023-06-08 18:07:56 +08:00
H1JK
cd44901e90 fix: Disable XUDP global ID if source address invalid 2023-06-08 15:57:51 +08:00
wwqgtxx
766d08a8eb chore: init gopacket only when dial fake-tcp to decrease memory using 2023-06-08 11:58:51 +08:00
H1JK
c3ef05b257 feat: Add XUDP migration support 2023-06-07 23:03:36 +08:00
Larvan2
093453582f fix: Resolve delay omission in the presence of nested proxy-groups 2023-06-07 13:20:45 +08:00
wzdnzd
767aa182b9 When testing the delay through REST API, determine whether to store the delay data based on certain conditions instead of discarding it directly (#609) 2023-06-07 11:04:03 +08:00
wwqgtxx
ad11a2b813 fix: go1.19 compile 2023-06-06 10:47:50 +08:00
タイムライン
dafecebdc0 chore: Something update from clash :) (#606) 2023-06-06 09:45:05 +08:00
wzdnzd
e7174866e5 fix: nil pointer in urltest (#603) 2023-06-05 12:40:46 +08:00
Mars160
fdaa6a22a4 fix hysteria faketcp lookback in TUN mode (#601) 2023-06-04 23:43:54 +08:00
Larvan2
fd0c71a485 chore: Ignore PR in Docker build 2023-06-04 15:51:25 +08:00
wzdnzd
3c1f9a9953 ProxyProvider health check also supports specifying expected status (#600)
Co-authored-by: wwqgtxx <wwqgtxx@gmail.com>
2023-06-04 14:00:24 +08:00
wzdnzd
3ef81afc76 [Feature] Proxy stores delay data of different URLs. And supports specifying different test URLs and expected statue by group (#588)
Co-authored-by: Larvan2 <78135608+Larvan2@users.noreply.github.com>
Co-authored-by: wwqgtxx <wwqgtxx@gmail.com>
2023-06-04 11:51:30 +08:00
wwqgtxx
03d0c8620e fix: hysteria faketcp loopback in tun mode 2023-06-03 22:15:09 +08:00
wwqgtxx
63b5387164 chore: update proxy's udpConn when received a new packet 2023-06-03 21:40:09 +08:00
Skyxim
2af758e5f1 chore: Random only if the certificate and private-key are empty 2023-06-03 17:45:47 +08:00
wwqgtxx
2c44b4e170 chore: update quic-go to 0.35.1 2023-06-03 16:45:35 +08:00
H1JK
7906fbfee6 chore: Update dependencies 2023-06-03 00:24:51 +08:00
H1JK
17565ec93b chore: Reject packet conn implement wait read 2023-06-02 22:58:33 +08:00
Larvan2
26acaee424 fix: handle manually select in url-test 2023-06-02 18:26:51 +08:00
wwqgtxx
9b6e56a65e chore: update quic-go to 0.34.0 2023-06-01 16:25:02 +08:00
725 changed files with 53034 additions and 21070 deletions

83
.github/ISSUE_TEMPLATE/bug_report.yml vendored Normal file
View File

@@ -0,0 +1,83 @@
name: Bug report
description: Create a report to help us improve
title: "[Bug] "
labels: ["bug"]
body:
- type: checkboxes
id: ensure
attributes:
label: Verify steps
description: "
在提交之前,请确认
Please verify that you've followed these steps
"
options:
- label: "
确保你使用的是**本仓库**最新的的 mihomo 或 mihomo Alpha 版本
Ensure you are using the latest version of Mihomo or Mihomo Alpha from **this repository**.
"
required: true
- label: "
如果你可以自己 debug 并解决的话,提交 PR 吧
Is this something you can **debug and fix**? Send a pull request! Bug fixes and documentation fixes are welcome.
"
required: false
- label: "
我已经在 [Issue Tracker](……/) 中找过我要提出的问题
I have searched on the [issue tracker](……/) for a related issue.
"
required: true
- label: "
我已经使用 Alpha 分支版本测试过,问题依旧存在
I have tested using the dev branch, and the issue still exists.
"
required: true
- label: "
我已经仔细看过 [Documentation](https://wiki.metacubex.one/) 并无法自行解决问题
I have read the [documentation](https://wiki.metacubex.one/) and was unable to solve the issue.
"
required: true
- label: "
这是 Mihomo 核心的问题,并非我所使用的 Mihomo 衍生版本(如 OpenMihomo、KoolMihomo 等)的特定问题
This is an issue of the Mihomo core *per se*, not to the derivatives of Mihomo, like OpenMihomo or KoolMihomo.
"
required: true
- type: input
attributes:
label: Mihomo version
description: "use `mihomo -v`"
validations:
required: true
- type: dropdown
id: os
attributes:
label: What OS are you seeing the problem on?
multiple: true
options:
- macOS
- Windows
- Linux
- OpenBSD/FreeBSD
- type: textarea
attributes:
render: yaml
label: "Mihomo config"
description: "
在下方附上 Mihomo core 配置文件,请确保配置文件中没有敏感信息(比如:服务器地址,密码,端口等)
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:
required: true
- type: textarea
attributes:
render: shell
label: Mihomo log
description: "
在下方附上 Mihomo Core 的日志log level 使用 DEBUG
Paste the Mihomo core log below with the log level set to `DEBUG`.
"
- type: textarea
attributes:
label: Description
validations:
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

@@ -0,0 +1,37 @@
name: Feature request
description: Suggest an idea for this project
title: "[Feature] "
labels: ["enhancement"]
body:
- type: checkboxes
id: ensure
attributes:
label: Verify steps
description: "
在提交之前,请确认
Please verify that you've followed these steps
"
options:
- label: "
我已经在 [Issue Tracker](……/) 中找过我要提出的请求
I have searched on the [issue tracker](……/) for a related feature request.
"
required: true
- label: "
我已经仔细看过 [Documentation](https://wiki.metacubex.one/) 并无法找到这个功能
I have read the [documentation](https://wiki.metacubex.one/) and was unable to solve the issue.
"
required: true
- type: textarea
attributes:
label: Description
description: 请详细、清晰地表达你要提出的论述,例如这个问题如何影响到你?你想实现什么功能?目前 Mihomo Core 的行为是什麽?
validations:
required: true
- type: textarea
attributes:
label: Possible Solution
description: "
此项非必须,但是如果你有想法的话欢迎提出。
Not obligatory, but suggest a fix/reason for the bug, or ideas how to implement the addition or change
"

32
.github/genReleaseNote.sh vendored Executable file
View File

@@ -0,0 +1,32 @@
#!/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:"* %h %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:"* %h %s by @%an" --grep="^fix" -i $version_range | sort -f | uniq >> release.md
echo "" >> release.md
echo "## Maintenance" >> release.md
git log --pretty=format:"* %h %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/mihomo/compare/$version_range" >> release.md

182
.github/patch/go1.21.patch vendored Normal file
View File

@@ -0,0 +1,182 @@
Subject: [PATCH] Revert "[release-branch.go1.21] crypto/rand,runtime: switch RtlGenRandom for ProcessPrng"
---
Index: src/crypto/rand/rand.go
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/src/crypto/rand/rand.go b/src/crypto/rand/rand.go
--- a/src/crypto/rand/rand.go (revision 8bba868de983dd7bf55fcd121495ba8d6e2734e7)
+++ b/src/crypto/rand/rand.go (revision 7e6c963d81e14ee394402671d4044b2940c8d2c1)
@@ -15,7 +15,7 @@
// available, /dev/urandom otherwise.
// On OpenBSD and macOS, Reader uses getentropy(2).
// On other Unix-like systems, Reader reads from /dev/urandom.
-// On Windows systems, Reader uses the ProcessPrng API.
+// On Windows systems, Reader uses the RtlGenRandom API.
// On JS/Wasm, Reader uses the Web Crypto API.
// On WASIP1/Wasm, Reader uses random_get from wasi_snapshot_preview1.
var Reader io.Reader
Index: src/crypto/rand/rand_windows.go
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/src/crypto/rand/rand_windows.go b/src/crypto/rand/rand_windows.go
--- a/src/crypto/rand/rand_windows.go (revision 8bba868de983dd7bf55fcd121495ba8d6e2734e7)
+++ b/src/crypto/rand/rand_windows.go (revision 7e6c963d81e14ee394402671d4044b2940c8d2c1)
@@ -15,8 +15,11 @@
type rngReader struct{}
-func (r *rngReader) Read(b []byte) (int, error) {
- if err := windows.ProcessPrng(b); err != nil {
+func (r *rngReader) Read(b []byte) (n int, err error) {
+ // RtlGenRandom only returns 1<<32-1 bytes at a time. We only read at
+ // most 1<<31-1 bytes at a time so that this works the same on 32-bit
+ // and 64-bit systems.
+ if err := batched(windows.RtlGenRandom, 1<<31-1)(b); err != nil {
return 0, err
}
return len(b), nil
Index: src/internal/syscall/windows/syscall_windows.go
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/src/internal/syscall/windows/syscall_windows.go b/src/internal/syscall/windows/syscall_windows.go
--- a/src/internal/syscall/windows/syscall_windows.go (revision 8bba868de983dd7bf55fcd121495ba8d6e2734e7)
+++ b/src/internal/syscall/windows/syscall_windows.go (revision 7e6c963d81e14ee394402671d4044b2940c8d2c1)
@@ -384,7 +384,7 @@
//sys DestroyEnvironmentBlock(block *uint16) (err error) = userenv.DestroyEnvironmentBlock
//sys CreateEvent(eventAttrs *SecurityAttributes, manualReset uint32, initialState uint32, name *uint16) (handle syscall.Handle, err error) = kernel32.CreateEventW
-//sys ProcessPrng(buf []byte) (err error) = bcryptprimitives.ProcessPrng
+//sys RtlGenRandom(buf []byte) (err error) = advapi32.SystemFunction036
//sys RtlLookupFunctionEntry(pc uintptr, baseAddress *uintptr, table *byte) (ret uintptr) = kernel32.RtlLookupFunctionEntry
//sys RtlVirtualUnwind(handlerType uint32, baseAddress uintptr, pc uintptr, entry uintptr, ctxt uintptr, data *uintptr, frame *uintptr, ctxptrs *byte) (ret uintptr) = kernel32.RtlVirtualUnwind
Index: src/internal/syscall/windows/zsyscall_windows.go
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/src/internal/syscall/windows/zsyscall_windows.go b/src/internal/syscall/windows/zsyscall_windows.go
--- a/src/internal/syscall/windows/zsyscall_windows.go (revision 8bba868de983dd7bf55fcd121495ba8d6e2734e7)
+++ b/src/internal/syscall/windows/zsyscall_windows.go (revision 7e6c963d81e14ee394402671d4044b2940c8d2c1)
@@ -37,14 +37,13 @@
}
var (
- modadvapi32 = syscall.NewLazyDLL(sysdll.Add("advapi32.dll"))
- modbcryptprimitives = syscall.NewLazyDLL(sysdll.Add("bcryptprimitives.dll"))
- modiphlpapi = syscall.NewLazyDLL(sysdll.Add("iphlpapi.dll"))
- modkernel32 = syscall.NewLazyDLL(sysdll.Add("kernel32.dll"))
- modnetapi32 = syscall.NewLazyDLL(sysdll.Add("netapi32.dll"))
- modpsapi = syscall.NewLazyDLL(sysdll.Add("psapi.dll"))
- moduserenv = syscall.NewLazyDLL(sysdll.Add("userenv.dll"))
- modws2_32 = syscall.NewLazyDLL(sysdll.Add("ws2_32.dll"))
+ modadvapi32 = syscall.NewLazyDLL(sysdll.Add("advapi32.dll"))
+ modiphlpapi = syscall.NewLazyDLL(sysdll.Add("iphlpapi.dll"))
+ modkernel32 = syscall.NewLazyDLL(sysdll.Add("kernel32.dll"))
+ modnetapi32 = syscall.NewLazyDLL(sysdll.Add("netapi32.dll"))
+ modpsapi = syscall.NewLazyDLL(sysdll.Add("psapi.dll"))
+ moduserenv = syscall.NewLazyDLL(sysdll.Add("userenv.dll"))
+ modws2_32 = syscall.NewLazyDLL(sysdll.Add("ws2_32.dll"))
procAdjustTokenPrivileges = modadvapi32.NewProc("AdjustTokenPrivileges")
procDuplicateTokenEx = modadvapi32.NewProc("DuplicateTokenEx")
@@ -53,7 +52,7 @@
procOpenThreadToken = modadvapi32.NewProc("OpenThreadToken")
procRevertToSelf = modadvapi32.NewProc("RevertToSelf")
procSetTokenInformation = modadvapi32.NewProc("SetTokenInformation")
- procProcessPrng = modbcryptprimitives.NewProc("ProcessPrng")
+ procSystemFunction036 = modadvapi32.NewProc("SystemFunction036")
procGetAdaptersAddresses = modiphlpapi.NewProc("GetAdaptersAddresses")
procCreateEventW = modkernel32.NewProc("CreateEventW")
procGetACP = modkernel32.NewProc("GetACP")
@@ -149,12 +148,12 @@
return
}
-func ProcessPrng(buf []byte) (err error) {
+func RtlGenRandom(buf []byte) (err error) {
var _p0 *byte
if len(buf) > 0 {
_p0 = &buf[0]
}
- r1, _, e1 := syscall.Syscall(procProcessPrng.Addr(), 2, uintptr(unsafe.Pointer(_p0)), uintptr(len(buf)), 0)
+ r1, _, e1 := syscall.Syscall(procSystemFunction036.Addr(), 2, uintptr(unsafe.Pointer(_p0)), uintptr(len(buf)), 0)
if r1 == 0 {
err = errnoErr(e1)
}
Index: src/runtime/os_windows.go
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/src/runtime/os_windows.go b/src/runtime/os_windows.go
--- a/src/runtime/os_windows.go (revision 8bba868de983dd7bf55fcd121495ba8d6e2734e7)
+++ b/src/runtime/os_windows.go (revision 7e6c963d81e14ee394402671d4044b2940c8d2c1)
@@ -127,8 +127,15 @@
_AddVectoredContinueHandler,
_ stdFunction
- // Use ProcessPrng to generate cryptographically random data.
- _ProcessPrng stdFunction
+ // Use RtlGenRandom to generate cryptographically random data.
+ // This approach has been recommended by Microsoft (see issue
+ // 15589 for details).
+ // The RtlGenRandom is not listed in advapi32.dll, instead
+ // RtlGenRandom function can be found by searching for SystemFunction036.
+ // Also some versions of Mingw cannot link to SystemFunction036
+ // when building executable as Cgo. So load SystemFunction036
+ // manually during runtime startup.
+ _RtlGenRandom stdFunction
// Load ntdll.dll manually during startup, otherwise Mingw
// links wrong printf function to cgo executable (see issue
@@ -145,12 +152,12 @@
)
var (
- bcryptprimitivesdll = [...]uint16{'b', 'c', 'r', 'y', 'p', 't', 'p', 'r', 'i', 'm', 'i', 't', 'i', 'v', 'e', 's', '.', 'd', 'l', 'l', 0}
- kernel32dll = [...]uint16{'k', 'e', 'r', 'n', 'e', 'l', '3', '2', '.', 'd', 'l', 'l', 0}
- ntdlldll = [...]uint16{'n', 't', 'd', 'l', 'l', '.', 'd', 'l', 'l', 0}
- powrprofdll = [...]uint16{'p', 'o', 'w', 'r', 'p', 'r', 'o', 'f', '.', 'd', 'l', 'l', 0}
- winmmdll = [...]uint16{'w', 'i', 'n', 'm', 'm', '.', 'd', 'l', 'l', 0}
- ws2_32dll = [...]uint16{'w', 's', '2', '_', '3', '2', '.', 'd', 'l', 'l', 0}
+ advapi32dll = [...]uint16{'a', 'd', 'v', 'a', 'p', 'i', '3', '2', '.', 'd', 'l', 'l', 0}
+ kernel32dll = [...]uint16{'k', 'e', 'r', 'n', 'e', 'l', '3', '2', '.', 'd', 'l', 'l', 0}
+ ntdlldll = [...]uint16{'n', 't', 'd', 'l', 'l', '.', 'd', 'l', 'l', 0}
+ powrprofdll = [...]uint16{'p', 'o', 'w', 'r', 'p', 'r', 'o', 'f', '.', 'd', 'l', 'l', 0}
+ winmmdll = [...]uint16{'w', 'i', 'n', 'm', 'm', '.', 'd', 'l', 'l', 0}
+ ws2_32dll = [...]uint16{'w', 's', '2', '_', '3', '2', '.', 'd', 'l', 'l', 0}
)
// Function to be called by windows CreateThread
@@ -249,11 +256,11 @@
}
_AddVectoredContinueHandler = windowsFindfunc(k32, []byte("AddVectoredContinueHandler\000"))
- bcryptPrimitives := windowsLoadSystemLib(bcryptprimitivesdll[:])
- if bcryptPrimitives == 0 {
- throw("bcryptprimitives.dll not found")
+ a32 := windowsLoadSystemLib(advapi32dll[:])
+ if a32 == 0 {
+ throw("advapi32.dll not found")
}
- _ProcessPrng = windowsFindfunc(bcryptPrimitives, []byte("ProcessPrng\000"))
+ _RtlGenRandom = windowsFindfunc(a32, []byte("SystemFunction036\000"))
n32 := windowsLoadSystemLib(ntdlldll[:])
if n32 == 0 {
@@ -610,7 +617,7 @@
//go:nosplit
func getRandomData(r []byte) {
n := 0
- if stdcall2(_ProcessPrng, uintptr(unsafe.Pointer(&r[0])), uintptr(len(r)))&0xff != 0 {
+ if stdcall2(_RtlGenRandom, uintptr(unsafe.Pointer(&r[0])), uintptr(len(r)))&0xff != 0 {
n = len(r)
}
extendRandom(r, n)

645
.github/patch/go1.22.patch vendored Normal file
View File

@@ -0,0 +1,645 @@
Subject: [PATCH] Revert "runtime: always use LoadLibraryEx to load system libraries"
Revert "syscall: remove Windows 7 console handle workaround"
Revert "net: remove sysSocket fallback for Windows 7"
Revert "crypto/rand,runtime: switch RtlGenRandom for ProcessPrng"
---
Index: src/crypto/rand/rand.go
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/src/crypto/rand/rand.go b/src/crypto/rand/rand.go
--- a/src/crypto/rand/rand.go (revision cb4eee693c382bea4222f20837e26501d40ed892)
+++ b/src/crypto/rand/rand.go (revision 9779155f18b6556a034f7bb79fb7fb2aad1e26a9)
@@ -15,7 +15,7 @@
// available, /dev/urandom otherwise.
// On OpenBSD and macOS, Reader uses getentropy(2).
// On other Unix-like systems, Reader reads from /dev/urandom.
-// On Windows systems, Reader uses the ProcessPrng API.
+// On Windows systems, Reader uses the RtlGenRandom API.
// On JS/Wasm, Reader uses the Web Crypto API.
// On WASIP1/Wasm, Reader uses random_get from wasi_snapshot_preview1.
var Reader io.Reader
Index: src/crypto/rand/rand_windows.go
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/src/crypto/rand/rand_windows.go b/src/crypto/rand/rand_windows.go
--- a/src/crypto/rand/rand_windows.go (revision cb4eee693c382bea4222f20837e26501d40ed892)
+++ b/src/crypto/rand/rand_windows.go (revision 9779155f18b6556a034f7bb79fb7fb2aad1e26a9)
@@ -15,8 +15,11 @@
type rngReader struct{}
-func (r *rngReader) Read(b []byte) (int, error) {
- if err := windows.ProcessPrng(b); err != nil {
+func (r *rngReader) Read(b []byte) (n int, err error) {
+ // RtlGenRandom only returns 1<<32-1 bytes at a time. We only read at
+ // most 1<<31-1 bytes at a time so that this works the same on 32-bit
+ // and 64-bit systems.
+ if err := batched(windows.RtlGenRandom, 1<<31-1)(b); err != nil {
return 0, err
}
return len(b), nil
Index: src/internal/syscall/windows/syscall_windows.go
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/src/internal/syscall/windows/syscall_windows.go b/src/internal/syscall/windows/syscall_windows.go
--- a/src/internal/syscall/windows/syscall_windows.go (revision cb4eee693c382bea4222f20837e26501d40ed892)
+++ b/src/internal/syscall/windows/syscall_windows.go (revision 9779155f18b6556a034f7bb79fb7fb2aad1e26a9)
@@ -384,7 +384,7 @@
//sys DestroyEnvironmentBlock(block *uint16) (err error) = userenv.DestroyEnvironmentBlock
//sys CreateEvent(eventAttrs *SecurityAttributes, manualReset uint32, initialState uint32, name *uint16) (handle syscall.Handle, err error) = kernel32.CreateEventW
-//sys ProcessPrng(buf []byte) (err error) = bcryptprimitives.ProcessPrng
+//sys RtlGenRandom(buf []byte) (err error) = advapi32.SystemFunction036
type FILE_ID_BOTH_DIR_INFO struct {
NextEntryOffset uint32
Index: src/internal/syscall/windows/zsyscall_windows.go
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/src/internal/syscall/windows/zsyscall_windows.go b/src/internal/syscall/windows/zsyscall_windows.go
--- a/src/internal/syscall/windows/zsyscall_windows.go (revision cb4eee693c382bea4222f20837e26501d40ed892)
+++ b/src/internal/syscall/windows/zsyscall_windows.go (revision 9779155f18b6556a034f7bb79fb7fb2aad1e26a9)
@@ -37,14 +37,13 @@
}
var (
- modadvapi32 = syscall.NewLazyDLL(sysdll.Add("advapi32.dll"))
- modbcryptprimitives = syscall.NewLazyDLL(sysdll.Add("bcryptprimitives.dll"))
- modiphlpapi = syscall.NewLazyDLL(sysdll.Add("iphlpapi.dll"))
- modkernel32 = syscall.NewLazyDLL(sysdll.Add("kernel32.dll"))
- modnetapi32 = syscall.NewLazyDLL(sysdll.Add("netapi32.dll"))
- modpsapi = syscall.NewLazyDLL(sysdll.Add("psapi.dll"))
- moduserenv = syscall.NewLazyDLL(sysdll.Add("userenv.dll"))
- modws2_32 = syscall.NewLazyDLL(sysdll.Add("ws2_32.dll"))
+ modadvapi32 = syscall.NewLazyDLL(sysdll.Add("advapi32.dll"))
+ modiphlpapi = syscall.NewLazyDLL(sysdll.Add("iphlpapi.dll"))
+ modkernel32 = syscall.NewLazyDLL(sysdll.Add("kernel32.dll"))
+ modnetapi32 = syscall.NewLazyDLL(sysdll.Add("netapi32.dll"))
+ modpsapi = syscall.NewLazyDLL(sysdll.Add("psapi.dll"))
+ moduserenv = syscall.NewLazyDLL(sysdll.Add("userenv.dll"))
+ modws2_32 = syscall.NewLazyDLL(sysdll.Add("ws2_32.dll"))
procAdjustTokenPrivileges = modadvapi32.NewProc("AdjustTokenPrivileges")
procDuplicateTokenEx = modadvapi32.NewProc("DuplicateTokenEx")
@@ -56,7 +55,7 @@
procQueryServiceStatus = modadvapi32.NewProc("QueryServiceStatus")
procRevertToSelf = modadvapi32.NewProc("RevertToSelf")
procSetTokenInformation = modadvapi32.NewProc("SetTokenInformation")
- procProcessPrng = modbcryptprimitives.NewProc("ProcessPrng")
+ procSystemFunction036 = modadvapi32.NewProc("SystemFunction036")
procGetAdaptersAddresses = modiphlpapi.NewProc("GetAdaptersAddresses")
procCreateEventW = modkernel32.NewProc("CreateEventW")
procGetACP = modkernel32.NewProc("GetACP")
@@ -180,12 +179,12 @@
return
}
-func ProcessPrng(buf []byte) (err error) {
+func RtlGenRandom(buf []byte) (err error) {
var _p0 *byte
if len(buf) > 0 {
_p0 = &buf[0]
}
- r1, _, e1 := syscall.Syscall(procProcessPrng.Addr(), 2, uintptr(unsafe.Pointer(_p0)), uintptr(len(buf)), 0)
+ r1, _, e1 := syscall.Syscall(procSystemFunction036.Addr(), 2, uintptr(unsafe.Pointer(_p0)), uintptr(len(buf)), 0)
if r1 == 0 {
err = errnoErr(e1)
}
Index: src/runtime/os_windows.go
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/src/runtime/os_windows.go b/src/runtime/os_windows.go
--- a/src/runtime/os_windows.go (revision cb4eee693c382bea4222f20837e26501d40ed892)
+++ b/src/runtime/os_windows.go (revision 83ff9782e024cb328b690cbf0da4e7848a327f4f)
@@ -40,8 +40,8 @@
//go:cgo_import_dynamic runtime._GetSystemInfo GetSystemInfo%1 "kernel32.dll"
//go:cgo_import_dynamic runtime._GetThreadContext GetThreadContext%2 "kernel32.dll"
//go:cgo_import_dynamic runtime._SetThreadContext SetThreadContext%2 "kernel32.dll"
-//go:cgo_import_dynamic runtime._LoadLibraryExW LoadLibraryExW%3 "kernel32.dll"
//go:cgo_import_dynamic runtime._LoadLibraryW LoadLibraryW%1 "kernel32.dll"
+//go:cgo_import_dynamic runtime._LoadLibraryA LoadLibraryA%1 "kernel32.dll"
//go:cgo_import_dynamic runtime._PostQueuedCompletionStatus PostQueuedCompletionStatus%4 "kernel32.dll"
//go:cgo_import_dynamic runtime._QueryPerformanceCounter QueryPerformanceCounter%1 "kernel32.dll"
//go:cgo_import_dynamic runtime._RaiseFailFastException RaiseFailFastException%3 "kernel32.dll"
@@ -74,7 +74,6 @@
// Following syscalls are available on every Windows PC.
// All these variables are set by the Windows executable
// loader before the Go program starts.
- _AddVectoredContinueHandler,
_AddVectoredExceptionHandler,
_CloseHandle,
_CreateEventA,
@@ -98,8 +97,8 @@
_GetSystemInfo,
_GetThreadContext,
_SetThreadContext,
- _LoadLibraryExW,
_LoadLibraryW,
+ _LoadLibraryA,
_PostQueuedCompletionStatus,
_QueryPerformanceCounter,
_RaiseFailFastException,
@@ -127,8 +126,23 @@
_WriteFile,
_ stdFunction
- // Use ProcessPrng to generate cryptographically random data.
- _ProcessPrng stdFunction
+ // Following syscalls are only available on some Windows PCs.
+ // We will load syscalls, if available, before using them.
+ _AddDllDirectory,
+ _AddVectoredContinueHandler,
+ _LoadLibraryExA,
+ _LoadLibraryExW,
+ _ stdFunction
+
+ // Use RtlGenRandom to generate cryptographically random data.
+ // This approach has been recommended by Microsoft (see issue
+ // 15589 for details).
+ // The RtlGenRandom is not listed in advapi32.dll, instead
+ // RtlGenRandom function can be found by searching for SystemFunction036.
+ // Also some versions of Mingw cannot link to SystemFunction036
+ // when building executable as Cgo. So load SystemFunction036
+ // manually during runtime startup.
+ _RtlGenRandom stdFunction
// Load ntdll.dll manually during startup, otherwise Mingw
// links wrong printf function to cgo executable (see issue
@@ -143,14 +157,6 @@
_ stdFunction
)
-var (
- bcryptprimitivesdll = [...]uint16{'b', 'c', 'r', 'y', 'p', 't', 'p', 'r', 'i', 'm', 'i', 't', 'i', 'v', 'e', 's', '.', 'd', 'l', 'l', 0}
- ntdlldll = [...]uint16{'n', 't', 'd', 'l', 'l', '.', 'd', 'l', 'l', 0}
- powrprofdll = [...]uint16{'p', 'o', 'w', 'r', 'p', 'r', 'o', 'f', '.', 'd', 'l', 'l', 0}
- winmmdll = [...]uint16{'w', 'i', 'n', 'm', 'm', '.', 'd', 'l', 'l', 0}
- ws2_32dll = [...]uint16{'w', 's', '2', '_', '3', '2', '.', 'd', 'l', 'l', 0}
-)
-
// Function to be called by windows CreateThread
// to start new os thread.
func tstart_stdcall(newm *m)
@@ -239,25 +245,51 @@
return unsafe.String(&sysDirectory[0], sysDirectoryLen)
}
-func windowsLoadSystemLib(name []uint16) uintptr {
- return stdcall3(_LoadLibraryExW, uintptr(unsafe.Pointer(&name[0])), 0, _LOAD_LIBRARY_SEARCH_SYSTEM32)
+//go:linkname syscall_getSystemDirectory syscall.getSystemDirectory
+func syscall_getSystemDirectory() string {
+ return unsafe.String(&sysDirectory[0], sysDirectoryLen)
+}
+
+func windowsLoadSystemLib(name []byte) uintptr {
+ if useLoadLibraryEx {
+ return stdcall3(_LoadLibraryExA, uintptr(unsafe.Pointer(&name[0])), 0, _LOAD_LIBRARY_SEARCH_SYSTEM32)
+ } else {
+ absName := append(sysDirectory[:sysDirectoryLen], name...)
+ return stdcall1(_LoadLibraryA, uintptr(unsafe.Pointer(&absName[0])))
+ }
}
func loadOptionalSyscalls() {
- bcryptPrimitives := windowsLoadSystemLib(bcryptprimitivesdll[:])
- if bcryptPrimitives == 0 {
- throw("bcryptprimitives.dll not found")
+ var kernel32dll = []byte("kernel32.dll\000")
+ k32 := stdcall1(_LoadLibraryA, uintptr(unsafe.Pointer(&kernel32dll[0])))
+ if k32 == 0 {
+ throw("kernel32.dll not found")
}
- _ProcessPrng = windowsFindfunc(bcryptPrimitives, []byte("ProcessPrng\000"))
+ _AddDllDirectory = windowsFindfunc(k32, []byte("AddDllDirectory\000"))
+ _AddVectoredContinueHandler = windowsFindfunc(k32, []byte("AddVectoredContinueHandler\000"))
+ _LoadLibraryExA = windowsFindfunc(k32, []byte("LoadLibraryExA\000"))
+ _LoadLibraryExW = windowsFindfunc(k32, []byte("LoadLibraryExW\000"))
+ useLoadLibraryEx = (_LoadLibraryExW != nil && _LoadLibraryExA != nil && _AddDllDirectory != nil)
+
+ initSysDirectory()
- n32 := windowsLoadSystemLib(ntdlldll[:])
+ var advapi32dll = []byte("advapi32.dll\000")
+ a32 := windowsLoadSystemLib(advapi32dll)
+ if a32 == 0 {
+ throw("advapi32.dll not found")
+ }
+ _RtlGenRandom = windowsFindfunc(a32, []byte("SystemFunction036\000"))
+
+ var ntdll = []byte("ntdll.dll\000")
+ n32 := windowsLoadSystemLib(ntdll)
if n32 == 0 {
throw("ntdll.dll not found")
}
_RtlGetCurrentPeb = windowsFindfunc(n32, []byte("RtlGetCurrentPeb\000"))
_RtlGetNtVersionNumbers = windowsFindfunc(n32, []byte("RtlGetNtVersionNumbers\000"))
- m32 := windowsLoadSystemLib(winmmdll[:])
+ var winmmdll = []byte("winmm.dll\000")
+ m32 := windowsLoadSystemLib(winmmdll)
if m32 == 0 {
throw("winmm.dll not found")
}
@@ -267,7 +299,8 @@
throw("timeBegin/EndPeriod not found")
}
- ws232 := windowsLoadSystemLib(ws2_32dll[:])
+ var ws232dll = []byte("ws2_32.dll\000")
+ ws232 := windowsLoadSystemLib(ws232dll)
if ws232 == 0 {
throw("ws2_32.dll not found")
}
@@ -286,7 +319,7 @@
context uintptr
}
- powrprof := windowsLoadSystemLib(powrprofdll[:])
+ powrprof := windowsLoadSystemLib([]byte("powrprof.dll\000"))
if powrprof == 0 {
return // Running on Windows 7, where we don't need it anyway.
}
@@ -360,6 +393,22 @@
// in sys_windows_386.s and sys_windows_amd64.s:
func getlasterror() uint32
+// When loading DLLs, we prefer to use LoadLibraryEx with
+// LOAD_LIBRARY_SEARCH_* flags, if available. LoadLibraryEx is not
+// available on old Windows, though, and the LOAD_LIBRARY_SEARCH_*
+// flags are not available on some versions of Windows without a
+// security patch.
+//
+// https://msdn.microsoft.com/en-us/library/ms684179(v=vs.85).aspx says:
+// "Windows 7, Windows Server 2008 R2, Windows Vista, and Windows
+// Server 2008: The LOAD_LIBRARY_SEARCH_* flags are available on
+// systems that have KB2533623 installed. To determine whether the
+// flags are available, use GetProcAddress to get the address of the
+// AddDllDirectory, RemoveDllDirectory, or SetDefaultDllDirectories
+// function. If GetProcAddress succeeds, the LOAD_LIBRARY_SEARCH_*
+// flags can be used with LoadLibraryEx."
+var useLoadLibraryEx bool
+
var timeBeginPeriodRetValue uint32
// osRelaxMinNS indicates that sysmon shouldn't osRelax if the next
@@ -507,7 +556,6 @@
initHighResTimer()
timeBeginPeriodRetValue = osRelax(false)
- initSysDirectory()
initLongPathSupport()
ncpu = getproccount()
@@ -524,7 +572,7 @@
//go:nosplit
func readRandom(r []byte) int {
n := 0
- if stdcall2(_ProcessPrng, uintptr(unsafe.Pointer(&r[0])), uintptr(len(r)))&0xff != 0 {
+ if stdcall2(_RtlGenRandom, uintptr(unsafe.Pointer(&r[0])), uintptr(len(r)))&0xff != 0 {
n = len(r)
}
return n
Index: src/net/hook_windows.go
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/src/net/hook_windows.go b/src/net/hook_windows.go
--- a/src/net/hook_windows.go (revision 9779155f18b6556a034f7bb79fb7fb2aad1e26a9)
+++ b/src/net/hook_windows.go (revision ef0606261340e608017860b423ffae5c1ce78239)
@@ -13,6 +13,7 @@
hostsFilePath = windows.GetSystemDirectory() + "/Drivers/etc/hosts"
// Placeholders for socket system calls.
+ socketFunc func(int, int, int) (syscall.Handle, error) = syscall.Socket
wsaSocketFunc func(int32, int32, int32, *syscall.WSAProtocolInfo, uint32, uint32) (syscall.Handle, error) = windows.WSASocket
connectFunc func(syscall.Handle, syscall.Sockaddr) error = syscall.Connect
listenFunc func(syscall.Handle, int) error = syscall.Listen
Index: src/net/internal/socktest/main_test.go
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/src/net/internal/socktest/main_test.go b/src/net/internal/socktest/main_test.go
--- a/src/net/internal/socktest/main_test.go (revision 9779155f18b6556a034f7bb79fb7fb2aad1e26a9)
+++ b/src/net/internal/socktest/main_test.go (revision ef0606261340e608017860b423ffae5c1ce78239)
@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
-//go:build !js && !plan9 && !wasip1 && !windows
+//go:build !js && !plan9 && !wasip1
package socktest_test
Index: src/net/internal/socktest/main_windows_test.go
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/src/net/internal/socktest/main_windows_test.go b/src/net/internal/socktest/main_windows_test.go
new file mode 100644
--- /dev/null (revision ef0606261340e608017860b423ffae5c1ce78239)
+++ b/src/net/internal/socktest/main_windows_test.go (revision ef0606261340e608017860b423ffae5c1ce78239)
@@ -0,0 +1,22 @@
+// Copyright 2015 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 socktest_test
+
+import "syscall"
+
+var (
+ socketFunc func(int, int, int) (syscall.Handle, error)
+ closeFunc func(syscall.Handle) error
+)
+
+func installTestHooks() {
+ socketFunc = sw.Socket
+ closeFunc = sw.Closesocket
+}
+
+func uninstallTestHooks() {
+ socketFunc = syscall.Socket
+ closeFunc = syscall.Closesocket
+}
Index: src/net/internal/socktest/sys_windows.go
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/src/net/internal/socktest/sys_windows.go b/src/net/internal/socktest/sys_windows.go
--- a/src/net/internal/socktest/sys_windows.go (revision 9779155f18b6556a034f7bb79fb7fb2aad1e26a9)
+++ b/src/net/internal/socktest/sys_windows.go (revision ef0606261340e608017860b423ffae5c1ce78239)
@@ -9,6 +9,38 @@
"syscall"
)
+// Socket wraps [syscall.Socket].
+func (sw *Switch) Socket(family, sotype, proto int) (s syscall.Handle, err error) {
+ sw.once.Do(sw.init)
+
+ so := &Status{Cookie: cookie(family, sotype, proto)}
+ sw.fmu.RLock()
+ f, _ := sw.fltab[FilterSocket]
+ sw.fmu.RUnlock()
+
+ af, err := f.apply(so)
+ if err != nil {
+ return syscall.InvalidHandle, err
+ }
+ s, so.Err = syscall.Socket(family, sotype, proto)
+ if err = af.apply(so); err != nil {
+ if so.Err == nil {
+ syscall.Closesocket(s)
+ }
+ return syscall.InvalidHandle, err
+ }
+
+ sw.smu.Lock()
+ defer sw.smu.Unlock()
+ if so.Err != nil {
+ sw.stats.getLocked(so.Cookie).OpenFailed++
+ return syscall.InvalidHandle, so.Err
+ }
+ nso := sw.addLocked(s, family, sotype, proto)
+ sw.stats.getLocked(nso.Cookie).Opened++
+ return s, nil
+}
+
// WSASocket wraps [syscall.WSASocket].
func (sw *Switch) WSASocket(family, sotype, proto int32, protinfo *syscall.WSAProtocolInfo, group uint32, flags uint32) (s syscall.Handle, err error) {
sw.once.Do(sw.init)
Index: src/net/main_windows_test.go
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/src/net/main_windows_test.go b/src/net/main_windows_test.go
--- a/src/net/main_windows_test.go (revision 9779155f18b6556a034f7bb79fb7fb2aad1e26a9)
+++ b/src/net/main_windows_test.go (revision ef0606261340e608017860b423ffae5c1ce78239)
@@ -8,6 +8,7 @@
var (
// Placeholders for saving original socket system calls.
+ origSocket = socketFunc
origWSASocket = wsaSocketFunc
origClosesocket = poll.CloseFunc
origConnect = connectFunc
@@ -17,6 +18,7 @@
)
func installTestHooks() {
+ socketFunc = sw.Socket
wsaSocketFunc = sw.WSASocket
poll.CloseFunc = sw.Closesocket
connectFunc = sw.Connect
@@ -26,6 +28,7 @@
}
func uninstallTestHooks() {
+ socketFunc = origSocket
wsaSocketFunc = origWSASocket
poll.CloseFunc = origClosesocket
connectFunc = origConnect
Index: src/net/sock_windows.go
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/src/net/sock_windows.go b/src/net/sock_windows.go
--- a/src/net/sock_windows.go (revision 9779155f18b6556a034f7bb79fb7fb2aad1e26a9)
+++ b/src/net/sock_windows.go (revision ef0606261340e608017860b423ffae5c1ce78239)
@@ -20,6 +20,21 @@
func sysSocket(family, sotype, proto int) (syscall.Handle, error) {
s, err := wsaSocketFunc(int32(family), int32(sotype), int32(proto),
nil, 0, windows.WSA_FLAG_OVERLAPPED|windows.WSA_FLAG_NO_HANDLE_INHERIT)
+ if err == nil {
+ return s, nil
+ }
+ // WSA_FLAG_NO_HANDLE_INHERIT flag is not supported on some
+ // old versions of Windows, see
+ // https://msdn.microsoft.com/en-us/library/windows/desktop/ms742212(v=vs.85).aspx
+ // for details. Just use syscall.Socket, if windows.WSASocket failed.
+
+ // See ../syscall/exec_unix.go for description of ForkLock.
+ syscall.ForkLock.RLock()
+ s, err = socketFunc(family, sotype, proto)
+ if err == nil {
+ syscall.CloseOnExec(s)
+ }
+ syscall.ForkLock.RUnlock()
if err != nil {
return syscall.InvalidHandle, os.NewSyscallError("socket", err)
}
Index: src/syscall/exec_windows.go
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/src/syscall/exec_windows.go b/src/syscall/exec_windows.go
--- a/src/syscall/exec_windows.go (revision 9779155f18b6556a034f7bb79fb7fb2aad1e26a9)
+++ b/src/syscall/exec_windows.go (revision 7f83badcb925a7e743188041cb6e561fc9b5b642)
@@ -14,7 +14,6 @@
"unsafe"
)
-// ForkLock is not used on Windows.
var ForkLock sync.RWMutex
// EscapeArg rewrites command line argument s as prescribed
@@ -317,6 +316,17 @@
}
}
+ var maj, min, build uint32
+ rtlGetNtVersionNumbers(&maj, &min, &build)
+ isWin7 := maj < 6 || (maj == 6 && min <= 1)
+ // NT kernel handles are divisible by 4, with the bottom 3 bits left as
+ // a tag. The fully set tag correlates with the types of handles we're
+ // concerned about here. Except, the kernel will interpret some
+ // special handle values, like -1, -2, and so forth, so kernelbase.dll
+ // checks to see that those bottom three bits are checked, but that top
+ // bit is not checked.
+ isLegacyWin7ConsoleHandle := func(handle Handle) bool { return isWin7 && handle&0x10000003 == 3 }
+
p, _ := GetCurrentProcess()
parentProcess := p
if sys.ParentProcess != 0 {
@@ -325,7 +335,15 @@
fd := make([]Handle, len(attr.Files))
for i := range attr.Files {
if attr.Files[i] > 0 {
- err := DuplicateHandle(p, Handle(attr.Files[i]), parentProcess, &fd[i], 0, true, DUPLICATE_SAME_ACCESS)
+ destinationProcessHandle := parentProcess
+
+ // On Windows 7, console handles aren't real handles, and can only be duplicated
+ // into the current process, not a parent one, which amounts to the same thing.
+ if parentProcess != p && isLegacyWin7ConsoleHandle(Handle(attr.Files[i])) {
+ destinationProcessHandle = p
+ }
+
+ err := DuplicateHandle(p, Handle(attr.Files[i]), destinationProcessHandle, &fd[i], 0, true, DUPLICATE_SAME_ACCESS)
if err != nil {
return 0, 0, err
}
@@ -356,6 +374,14 @@
fd = append(fd, sys.AdditionalInheritedHandles...)
+ // On Windows 7, console handles aren't real handles, so don't pass them
+ // through to PROC_THREAD_ATTRIBUTE_HANDLE_LIST.
+ for i := range fd {
+ if isLegacyWin7ConsoleHandle(fd[i]) {
+ fd[i] = 0
+ }
+ }
+
// The presence of a NULL handle in the list is enough to cause PROC_THREAD_ATTRIBUTE_HANDLE_LIST
// to treat the entire list as empty, so remove NULL handles.
j := 0
Index: src/runtime/syscall_windows.go
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/src/runtime/syscall_windows.go b/src/runtime/syscall_windows.go
--- a/src/runtime/syscall_windows.go (revision 7f83badcb925a7e743188041cb6e561fc9b5b642)
+++ b/src/runtime/syscall_windows.go (revision 83ff9782e024cb328b690cbf0da4e7848a327f4f)
@@ -413,23 +413,36 @@
const _LOAD_LIBRARY_SEARCH_SYSTEM32 = 0x00000800
+// When available, this function will use LoadLibraryEx with the filename
+// parameter and the important SEARCH_SYSTEM32 argument. But on systems that
+// do not have that option, absoluteFilepath should contain a fallback
+// to the full path inside of system32 for use with vanilla LoadLibrary.
+//
//go:linkname syscall_loadsystemlibrary syscall.loadsystemlibrary
//go:nosplit
//go:cgo_unsafe_args
-func syscall_loadsystemlibrary(filename *uint16) (handle, err uintptr) {
+func syscall_loadsystemlibrary(filename *uint16, absoluteFilepath *uint16) (handle, err uintptr) {
lockOSThread()
c := &getg().m.syscall
- c.fn = getLoadLibraryEx()
- c.n = 3
- args := struct {
- lpFileName *uint16
- hFile uintptr // always 0
- flags uint32
- }{filename, 0, _LOAD_LIBRARY_SEARCH_SYSTEM32}
- c.args = uintptr(noescape(unsafe.Pointer(&args)))
+
+ if useLoadLibraryEx {
+ c.fn = getLoadLibraryEx()
+ c.n = 3
+ args := struct {
+ lpFileName *uint16
+ hFile uintptr // always 0
+ flags uint32
+ }{filename, 0, _LOAD_LIBRARY_SEARCH_SYSTEM32}
+ c.args = uintptr(noescape(unsafe.Pointer(&args)))
+ } else {
+ c.fn = getLoadLibrary()
+ c.n = 1
+ c.args = uintptr(noescape(unsafe.Pointer(&absoluteFilepath)))
+ }
cgocall(asmstdcallAddr, unsafe.Pointer(c))
KeepAlive(filename)
+ KeepAlive(absoluteFilepath)
handle = c.r1
if handle == 0 {
err = c.err
Index: src/syscall/dll_windows.go
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/src/syscall/dll_windows.go b/src/syscall/dll_windows.go
--- a/src/syscall/dll_windows.go (revision 7f83badcb925a7e743188041cb6e561fc9b5b642)
+++ b/src/syscall/dll_windows.go (revision 83ff9782e024cb328b690cbf0da4e7848a327f4f)
@@ -44,7 +44,7 @@
func SyscallN(trap uintptr, args ...uintptr) (r1, r2 uintptr, err Errno)
func loadlibrary(filename *uint16) (handle uintptr, err Errno)
-func loadsystemlibrary(filename *uint16) (handle uintptr, err Errno)
+func loadsystemlibrary(filename *uint16, absoluteFilepath *uint16) (handle uintptr, err Errno)
func getprocaddress(handle uintptr, procname *uint8) (proc uintptr, err Errno)
// A DLL implements access to a single DLL.
@@ -53,6 +53,9 @@
Handle Handle
}
+//go:linkname getSystemDirectory
+func getSystemDirectory() string // Implemented in runtime package.
+
// LoadDLL loads the named DLL file into memory.
//
// If name is not an absolute path and is not a known system DLL used by
@@ -69,7 +72,11 @@
var h uintptr
var e Errno
if sysdll.IsSystemDLL[name] {
- h, e = loadsystemlibrary(namep)
+ absoluteFilepathp, err := UTF16PtrFromString(getSystemDirectory() + name)
+ if err != nil {
+ return nil, err
+ }
+ h, e = loadsystemlibrary(namep, absoluteFilepathp)
} else {
h, e = loadlibrary(namep)
}

643
.github/patch/go1.23.patch vendored Normal file
View File

@@ -0,0 +1,643 @@
Subject: [PATCH] Revert "runtime: always use LoadLibraryEx to load system libraries"
Revert "syscall: remove Windows 7 console handle workaround"
Revert "net: remove sysSocket fallback for Windows 7"
Revert "crypto/rand,runtime: switch RtlGenRandom for ProcessPrng"
---
Index: src/crypto/rand/rand.go
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/src/crypto/rand/rand.go b/src/crypto/rand/rand.go
--- a/src/crypto/rand/rand.go (revision 6885bad7dd86880be6929c02085e5c7a67ff2887)
+++ b/src/crypto/rand/rand.go (revision 9ac42137ef6730e8b7daca016ece831297a1d75b)
@@ -16,7 +16,7 @@
// - On macOS and iOS, Reader uses arc4random_buf(3).
// - On OpenBSD and NetBSD, Reader uses getentropy(2).
// - On other Unix-like systems, Reader reads from /dev/urandom.
-// - On Windows, Reader uses the ProcessPrng API.
+// - On Windows systems, Reader uses the RtlGenRandom API.
// - On js/wasm, Reader uses the Web Crypto API.
// - On wasip1/wasm, Reader uses random_get from wasi_snapshot_preview1.
var Reader io.Reader
Index: src/crypto/rand/rand_windows.go
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/src/crypto/rand/rand_windows.go b/src/crypto/rand/rand_windows.go
--- a/src/crypto/rand/rand_windows.go (revision 6885bad7dd86880be6929c02085e5c7a67ff2887)
+++ b/src/crypto/rand/rand_windows.go (revision 9ac42137ef6730e8b7daca016ece831297a1d75b)
@@ -15,8 +15,11 @@
type rngReader struct{}
-func (r *rngReader) Read(b []byte) (int, error) {
- if err := windows.ProcessPrng(b); err != nil {
+func (r *rngReader) Read(b []byte) (n int, err error) {
+ // RtlGenRandom only returns 1<<32-1 bytes at a time. We only read at
+ // most 1<<31-1 bytes at a time so that this works the same on 32-bit
+ // and 64-bit systems.
+ if err := batched(windows.RtlGenRandom, 1<<31-1)(b); err != nil {
return 0, err
}
return len(b), nil
Index: src/internal/syscall/windows/syscall_windows.go
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/src/internal/syscall/windows/syscall_windows.go b/src/internal/syscall/windows/syscall_windows.go
--- a/src/internal/syscall/windows/syscall_windows.go (revision 6885bad7dd86880be6929c02085e5c7a67ff2887)
+++ b/src/internal/syscall/windows/syscall_windows.go (revision 9ac42137ef6730e8b7daca016ece831297a1d75b)
@@ -414,7 +414,7 @@
//sys DestroyEnvironmentBlock(block *uint16) (err error) = userenv.DestroyEnvironmentBlock
//sys CreateEvent(eventAttrs *SecurityAttributes, manualReset uint32, initialState uint32, name *uint16) (handle syscall.Handle, err error) = kernel32.CreateEventW
-//sys ProcessPrng(buf []byte) (err error) = bcryptprimitives.ProcessPrng
+//sys RtlGenRandom(buf []byte) (err error) = advapi32.SystemFunction036
type FILE_ID_BOTH_DIR_INFO struct {
NextEntryOffset uint32
Index: src/internal/syscall/windows/zsyscall_windows.go
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/src/internal/syscall/windows/zsyscall_windows.go b/src/internal/syscall/windows/zsyscall_windows.go
--- a/src/internal/syscall/windows/zsyscall_windows.go (revision 6885bad7dd86880be6929c02085e5c7a67ff2887)
+++ b/src/internal/syscall/windows/zsyscall_windows.go (revision 9ac42137ef6730e8b7daca016ece831297a1d75b)
@@ -38,7 +38,6 @@
var (
modadvapi32 = syscall.NewLazyDLL(sysdll.Add("advapi32.dll"))
- modbcryptprimitives = syscall.NewLazyDLL(sysdll.Add("bcryptprimitives.dll"))
modiphlpapi = syscall.NewLazyDLL(sysdll.Add("iphlpapi.dll"))
modkernel32 = syscall.NewLazyDLL(sysdll.Add("kernel32.dll"))
modnetapi32 = syscall.NewLazyDLL(sysdll.Add("netapi32.dll"))
@@ -57,7 +56,7 @@
procQueryServiceStatus = modadvapi32.NewProc("QueryServiceStatus")
procRevertToSelf = modadvapi32.NewProc("RevertToSelf")
procSetTokenInformation = modadvapi32.NewProc("SetTokenInformation")
- procProcessPrng = modbcryptprimitives.NewProc("ProcessPrng")
+ procSystemFunction036 = modadvapi32.NewProc("SystemFunction036")
procGetAdaptersAddresses = modiphlpapi.NewProc("GetAdaptersAddresses")
procCreateEventW = modkernel32.NewProc("CreateEventW")
procGetACP = modkernel32.NewProc("GetACP")
@@ -183,12 +182,12 @@
return
}
-func ProcessPrng(buf []byte) (err error) {
+func RtlGenRandom(buf []byte) (err error) {
var _p0 *byte
if len(buf) > 0 {
_p0 = &buf[0]
}
- r1, _, e1 := syscall.Syscall(procProcessPrng.Addr(), 2, uintptr(unsafe.Pointer(_p0)), uintptr(len(buf)), 0)
+ r1, _, e1 := syscall.Syscall(procSystemFunction036.Addr(), 2, uintptr(unsafe.Pointer(_p0)), uintptr(len(buf)), 0)
if r1 == 0 {
err = errnoErr(e1)
}
Index: src/runtime/os_windows.go
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/src/runtime/os_windows.go b/src/runtime/os_windows.go
--- a/src/runtime/os_windows.go (revision 6885bad7dd86880be6929c02085e5c7a67ff2887)
+++ b/src/runtime/os_windows.go (revision 69e2eed6dd0f6d815ebf15797761c13f31213dd6)
@@ -39,8 +39,8 @@
//go:cgo_import_dynamic runtime._GetSystemInfo GetSystemInfo%1 "kernel32.dll"
//go:cgo_import_dynamic runtime._GetThreadContext GetThreadContext%2 "kernel32.dll"
//go:cgo_import_dynamic runtime._SetThreadContext SetThreadContext%2 "kernel32.dll"
-//go:cgo_import_dynamic runtime._LoadLibraryExW LoadLibraryExW%3 "kernel32.dll"
//go:cgo_import_dynamic runtime._LoadLibraryW LoadLibraryW%1 "kernel32.dll"
+//go:cgo_import_dynamic runtime._LoadLibraryA LoadLibraryA%1 "kernel32.dll"
//go:cgo_import_dynamic runtime._PostQueuedCompletionStatus PostQueuedCompletionStatus%4 "kernel32.dll"
//go:cgo_import_dynamic runtime._QueryPerformanceCounter QueryPerformanceCounter%1 "kernel32.dll"
//go:cgo_import_dynamic runtime._QueryPerformanceFrequency QueryPerformanceFrequency%1 "kernel32.dll"
@@ -74,7 +74,6 @@
// Following syscalls are available on every Windows PC.
// All these variables are set by the Windows executable
// loader before the Go program starts.
- _AddVectoredContinueHandler,
_AddVectoredExceptionHandler,
_CloseHandle,
_CreateEventA,
@@ -97,8 +96,8 @@
_GetSystemInfo,
_GetThreadContext,
_SetThreadContext,
- _LoadLibraryExW,
_LoadLibraryW,
+ _LoadLibraryA,
_PostQueuedCompletionStatus,
_QueryPerformanceCounter,
_QueryPerformanceFrequency,
@@ -127,8 +126,23 @@
_WriteFile,
_ stdFunction
- // Use ProcessPrng to generate cryptographically random data.
- _ProcessPrng stdFunction
+ // Following syscalls are only available on some Windows PCs.
+ // We will load syscalls, if available, before using them.
+ _AddDllDirectory,
+ _AddVectoredContinueHandler,
+ _LoadLibraryExA,
+ _LoadLibraryExW,
+ _ stdFunction
+
+ // Use RtlGenRandom to generate cryptographically random data.
+ // This approach has been recommended by Microsoft (see issue
+ // 15589 for details).
+ // The RtlGenRandom is not listed in advapi32.dll, instead
+ // RtlGenRandom function can be found by searching for SystemFunction036.
+ // Also some versions of Mingw cannot link to SystemFunction036
+ // when building executable as Cgo. So load SystemFunction036
+ // manually during runtime startup.
+ _RtlGenRandom stdFunction
// Load ntdll.dll manually during startup, otherwise Mingw
// links wrong printf function to cgo executable (see issue
@@ -145,13 +159,6 @@
_ stdFunction
)
-var (
- bcryptprimitivesdll = [...]uint16{'b', 'c', 'r', 'y', 'p', 't', 'p', 'r', 'i', 'm', 'i', 't', 'i', 'v', 'e', 's', '.', 'd', 'l', 'l', 0}
- ntdlldll = [...]uint16{'n', 't', 'd', 'l', 'l', '.', 'd', 'l', 'l', 0}
- powrprofdll = [...]uint16{'p', 'o', 'w', 'r', 'p', 'r', 'o', 'f', '.', 'd', 'l', 'l', 0}
- winmmdll = [...]uint16{'w', 'i', 'n', 'm', 'm', '.', 'd', 'l', 'l', 0}
-)
-
// Function to be called by windows CreateThread
// to start new os thread.
func tstart_stdcall(newm *m)
@@ -244,8 +251,18 @@
return unsafe.String(&sysDirectory[0], sysDirectoryLen)
}
-func windowsLoadSystemLib(name []uint16) uintptr {
- return stdcall3(_LoadLibraryExW, uintptr(unsafe.Pointer(&name[0])), 0, _LOAD_LIBRARY_SEARCH_SYSTEM32)
+//go:linkname syscall_getSystemDirectory syscall.getSystemDirectory
+func syscall_getSystemDirectory() string {
+ return unsafe.String(&sysDirectory[0], sysDirectoryLen)
+}
+
+func windowsLoadSystemLib(name []byte) uintptr {
+ if useLoadLibraryEx {
+ return stdcall3(_LoadLibraryExA, uintptr(unsafe.Pointer(&name[0])), 0, _LOAD_LIBRARY_SEARCH_SYSTEM32)
+ } else {
+ absName := append(sysDirectory[:sysDirectoryLen], name...)
+ return stdcall1(_LoadLibraryA, uintptr(unsafe.Pointer(&absName[0])))
+ }
}
//go:linkname windows_QueryPerformanceCounter internal/syscall/windows.QueryPerformanceCounter
@@ -263,13 +280,28 @@
}
func loadOptionalSyscalls() {
- bcryptPrimitives := windowsLoadSystemLib(bcryptprimitivesdll[:])
- if bcryptPrimitives == 0 {
- throw("bcryptprimitives.dll not found")
+ var kernel32dll = []byte("kernel32.dll\000")
+ k32 := stdcall1(_LoadLibraryA, uintptr(unsafe.Pointer(&kernel32dll[0])))
+ if k32 == 0 {
+ throw("kernel32.dll not found")
}
- _ProcessPrng = windowsFindfunc(bcryptPrimitives, []byte("ProcessPrng\000"))
+ _AddDllDirectory = windowsFindfunc(k32, []byte("AddDllDirectory\000"))
+ _AddVectoredContinueHandler = windowsFindfunc(k32, []byte("AddVectoredContinueHandler\000"))
+ _LoadLibraryExA = windowsFindfunc(k32, []byte("LoadLibraryExA\000"))
+ _LoadLibraryExW = windowsFindfunc(k32, []byte("LoadLibraryExW\000"))
+ useLoadLibraryEx = (_LoadLibraryExW != nil && _LoadLibraryExA != nil && _AddDllDirectory != nil)
+
+ initSysDirectory()
- n32 := windowsLoadSystemLib(ntdlldll[:])
+ var advapi32dll = []byte("advapi32.dll\000")
+ a32 := windowsLoadSystemLib(advapi32dll)
+ if a32 == 0 {
+ throw("advapi32.dll not found")
+ }
+ _RtlGenRandom = windowsFindfunc(a32, []byte("SystemFunction036\000"))
+
+ var ntdll = []byte("ntdll.dll\000")
+ n32 := windowsLoadSystemLib(ntdll)
if n32 == 0 {
throw("ntdll.dll not found")
}
@@ -298,7 +330,7 @@
context uintptr
}
- powrprof := windowsLoadSystemLib(powrprofdll[:])
+ powrprof := windowsLoadSystemLib([]byte("powrprof.dll\000"))
if powrprof == 0 {
return // Running on Windows 7, where we don't need it anyway.
}
@@ -357,6 +389,22 @@
// in sys_windows_386.s and sys_windows_amd64.s:
func getlasterror() uint32
+// When loading DLLs, we prefer to use LoadLibraryEx with
+// LOAD_LIBRARY_SEARCH_* flags, if available. LoadLibraryEx is not
+// available on old Windows, though, and the LOAD_LIBRARY_SEARCH_*
+// flags are not available on some versions of Windows without a
+// security patch.
+//
+// https://msdn.microsoft.com/en-us/library/ms684179(v=vs.85).aspx says:
+// "Windows 7, Windows Server 2008 R2, Windows Vista, and Windows
+// Server 2008: The LOAD_LIBRARY_SEARCH_* flags are available on
+// systems that have KB2533623 installed. To determine whether the
+// flags are available, use GetProcAddress to get the address of the
+// AddDllDirectory, RemoveDllDirectory, or SetDefaultDllDirectories
+// function. If GetProcAddress succeeds, the LOAD_LIBRARY_SEARCH_*
+// flags can be used with LoadLibraryEx."
+var useLoadLibraryEx bool
+
var timeBeginPeriodRetValue uint32
// osRelaxMinNS indicates that sysmon shouldn't osRelax if the next
@@ -430,7 +478,8 @@
// Only load winmm.dll if we need it.
// This avoids a dependency on winmm.dll for Go programs
// that run on new Windows versions.
- m32 := windowsLoadSystemLib(winmmdll[:])
+ var winmmdll = []byte("winmm.dll\000")
+ m32 := windowsLoadSystemLib(winmmdll)
if m32 == 0 {
print("runtime: LoadLibraryExW failed; errno=", getlasterror(), "\n")
throw("winmm.dll not found")
@@ -471,6 +520,28 @@
canUseLongPaths = true
}
+var osVersionInfo struct {
+ majorVersion uint32
+ minorVersion uint32
+ buildNumber uint32
+}
+
+func initOsVersionInfo() {
+ info := _OSVERSIONINFOW{}
+ info.osVersionInfoSize = uint32(unsafe.Sizeof(info))
+ stdcall1(_RtlGetVersion, uintptr(unsafe.Pointer(&info)))
+ osVersionInfo.majorVersion = info.majorVersion
+ osVersionInfo.minorVersion = info.minorVersion
+ osVersionInfo.buildNumber = info.buildNumber
+}
+
+//go:linkname rtlGetNtVersionNumbers syscall.rtlGetNtVersionNumbers
+func rtlGetNtVersionNumbers(majorVersion *uint32, minorVersion *uint32, buildNumber *uint32) {
+ *majorVersion = osVersionInfo.majorVersion
+ *minorVersion = osVersionInfo.minorVersion
+ *buildNumber = osVersionInfo.buildNumber
+}
+
func osinit() {
asmstdcallAddr = unsafe.Pointer(abi.FuncPCABI0(asmstdcall))
@@ -483,8 +554,8 @@
initHighResTimer()
timeBeginPeriodRetValue = osRelax(false)
- initSysDirectory()
initLongPathSupport()
+ initOsVersionInfo()
ncpu = getproccount()
@@ -500,7 +571,7 @@
//go:nosplit
func readRandom(r []byte) int {
n := 0
- if stdcall2(_ProcessPrng, uintptr(unsafe.Pointer(&r[0])), uintptr(len(r)))&0xff != 0 {
+ if stdcall2(_RtlGenRandom, uintptr(unsafe.Pointer(&r[0])), uintptr(len(r)))&0xff != 0 {
n = len(r)
}
return n
Index: src/net/hook_windows.go
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/src/net/hook_windows.go b/src/net/hook_windows.go
--- a/src/net/hook_windows.go (revision 9ac42137ef6730e8b7daca016ece831297a1d75b)
+++ b/src/net/hook_windows.go (revision 21290de8a4c91408de7c2b5b68757b1e90af49dd)
@@ -13,6 +13,7 @@
hostsFilePath = windows.GetSystemDirectory() + "/Drivers/etc/hosts"
// Placeholders for socket system calls.
+ socketFunc func(int, int, int) (syscall.Handle, error) = syscall.Socket
wsaSocketFunc func(int32, int32, int32, *syscall.WSAProtocolInfo, uint32, uint32) (syscall.Handle, error) = windows.WSASocket
connectFunc func(syscall.Handle, syscall.Sockaddr) error = syscall.Connect
listenFunc func(syscall.Handle, int) error = syscall.Listen
Index: src/net/internal/socktest/main_test.go
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/src/net/internal/socktest/main_test.go b/src/net/internal/socktest/main_test.go
--- a/src/net/internal/socktest/main_test.go (revision 9ac42137ef6730e8b7daca016ece831297a1d75b)
+++ b/src/net/internal/socktest/main_test.go (revision 21290de8a4c91408de7c2b5b68757b1e90af49dd)
@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
-//go:build !js && !plan9 && !wasip1 && !windows
+//go:build !js && !plan9 && !wasip1
package socktest_test
Index: src/net/internal/socktest/main_windows_test.go
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/src/net/internal/socktest/main_windows_test.go b/src/net/internal/socktest/main_windows_test.go
new file mode 100644
--- /dev/null (revision 21290de8a4c91408de7c2b5b68757b1e90af49dd)
+++ b/src/net/internal/socktest/main_windows_test.go (revision 21290de8a4c91408de7c2b5b68757b1e90af49dd)
@@ -0,0 +1,22 @@
+// Copyright 2015 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 socktest_test
+
+import "syscall"
+
+var (
+ socketFunc func(int, int, int) (syscall.Handle, error)
+ closeFunc func(syscall.Handle) error
+)
+
+func installTestHooks() {
+ socketFunc = sw.Socket
+ closeFunc = sw.Closesocket
+}
+
+func uninstallTestHooks() {
+ socketFunc = syscall.Socket
+ closeFunc = syscall.Closesocket
+}
Index: src/net/internal/socktest/sys_windows.go
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/src/net/internal/socktest/sys_windows.go b/src/net/internal/socktest/sys_windows.go
--- a/src/net/internal/socktest/sys_windows.go (revision 9ac42137ef6730e8b7daca016ece831297a1d75b)
+++ b/src/net/internal/socktest/sys_windows.go (revision 21290de8a4c91408de7c2b5b68757b1e90af49dd)
@@ -9,6 +9,38 @@
"syscall"
)
+// Socket wraps [syscall.Socket].
+func (sw *Switch) Socket(family, sotype, proto int) (s syscall.Handle, err error) {
+ sw.once.Do(sw.init)
+
+ so := &Status{Cookie: cookie(family, sotype, proto)}
+ sw.fmu.RLock()
+ f, _ := sw.fltab[FilterSocket]
+ sw.fmu.RUnlock()
+
+ af, err := f.apply(so)
+ if err != nil {
+ return syscall.InvalidHandle, err
+ }
+ s, so.Err = syscall.Socket(family, sotype, proto)
+ if err = af.apply(so); err != nil {
+ if so.Err == nil {
+ syscall.Closesocket(s)
+ }
+ return syscall.InvalidHandle, err
+ }
+
+ sw.smu.Lock()
+ defer sw.smu.Unlock()
+ if so.Err != nil {
+ sw.stats.getLocked(so.Cookie).OpenFailed++
+ return syscall.InvalidHandle, so.Err
+ }
+ nso := sw.addLocked(s, family, sotype, proto)
+ sw.stats.getLocked(nso.Cookie).Opened++
+ return s, nil
+}
+
// WSASocket wraps [syscall.WSASocket].
func (sw *Switch) WSASocket(family, sotype, proto int32, protinfo *syscall.WSAProtocolInfo, group uint32, flags uint32) (s syscall.Handle, err error) {
sw.once.Do(sw.init)
Index: src/net/main_windows_test.go
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/src/net/main_windows_test.go b/src/net/main_windows_test.go
--- a/src/net/main_windows_test.go (revision 9ac42137ef6730e8b7daca016ece831297a1d75b)
+++ b/src/net/main_windows_test.go (revision 21290de8a4c91408de7c2b5b68757b1e90af49dd)
@@ -8,6 +8,7 @@
var (
// Placeholders for saving original socket system calls.
+ origSocket = socketFunc
origWSASocket = wsaSocketFunc
origClosesocket = poll.CloseFunc
origConnect = connectFunc
@@ -17,6 +18,7 @@
)
func installTestHooks() {
+ socketFunc = sw.Socket
wsaSocketFunc = sw.WSASocket
poll.CloseFunc = sw.Closesocket
connectFunc = sw.Connect
@@ -26,6 +28,7 @@
}
func uninstallTestHooks() {
+ socketFunc = origSocket
wsaSocketFunc = origWSASocket
poll.CloseFunc = origClosesocket
connectFunc = origConnect
Index: src/net/sock_windows.go
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/src/net/sock_windows.go b/src/net/sock_windows.go
--- a/src/net/sock_windows.go (revision 9ac42137ef6730e8b7daca016ece831297a1d75b)
+++ b/src/net/sock_windows.go (revision 21290de8a4c91408de7c2b5b68757b1e90af49dd)
@@ -20,6 +20,21 @@
func sysSocket(family, sotype, proto int) (syscall.Handle, error) {
s, err := wsaSocketFunc(int32(family), int32(sotype), int32(proto),
nil, 0, windows.WSA_FLAG_OVERLAPPED|windows.WSA_FLAG_NO_HANDLE_INHERIT)
+ if err == nil {
+ return s, nil
+ }
+ // WSA_FLAG_NO_HANDLE_INHERIT flag is not supported on some
+ // old versions of Windows, see
+ // https://msdn.microsoft.com/en-us/library/windows/desktop/ms742212(v=vs.85).aspx
+ // for details. Just use syscall.Socket, if windows.WSASocket failed.
+
+ // See ../syscall/exec_unix.go for description of ForkLock.
+ syscall.ForkLock.RLock()
+ s, err = socketFunc(family, sotype, proto)
+ if err == nil {
+ syscall.CloseOnExec(s)
+ }
+ syscall.ForkLock.RUnlock()
if err != nil {
return syscall.InvalidHandle, os.NewSyscallError("socket", err)
}
Index: src/syscall/exec_windows.go
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/src/syscall/exec_windows.go b/src/syscall/exec_windows.go
--- a/src/syscall/exec_windows.go (revision 9ac42137ef6730e8b7daca016ece831297a1d75b)
+++ b/src/syscall/exec_windows.go (revision 6a31d3fa8e47ddabc10bd97bff10d9a85f4cfb76)
@@ -14,7 +14,6 @@
"unsafe"
)
-// ForkLock is not used on Windows.
var ForkLock sync.RWMutex
// EscapeArg rewrites command line argument s as prescribed
@@ -254,6 +253,9 @@
var zeroProcAttr ProcAttr
var zeroSysProcAttr SysProcAttr
+//go:linkname rtlGetNtVersionNumbers
+func rtlGetNtVersionNumbers(majorVersion *uint32, minorVersion *uint32, buildNumber *uint32)
+
func StartProcess(argv0 string, argv []string, attr *ProcAttr) (pid int, handle uintptr, err error) {
if len(argv0) == 0 {
return 0, 0, EWINDOWS
@@ -317,6 +319,17 @@
}
}
+ var maj, min, build uint32
+ rtlGetNtVersionNumbers(&maj, &min, &build)
+ isWin7 := maj < 6 || (maj == 6 && min <= 1)
+ // NT kernel handles are divisible by 4, with the bottom 3 bits left as
+ // a tag. The fully set tag correlates with the types of handles we're
+ // concerned about here. Except, the kernel will interpret some
+ // special handle values, like -1, -2, and so forth, so kernelbase.dll
+ // checks to see that those bottom three bits are checked, but that top
+ // bit is not checked.
+ isLegacyWin7ConsoleHandle := func(handle Handle) bool { return isWin7 && handle&0x10000003 == 3 }
+
p, _ := GetCurrentProcess()
parentProcess := p
if sys.ParentProcess != 0 {
@@ -325,7 +338,15 @@
fd := make([]Handle, len(attr.Files))
for i := range attr.Files {
if attr.Files[i] > 0 {
- err := DuplicateHandle(p, Handle(attr.Files[i]), parentProcess, &fd[i], 0, true, DUPLICATE_SAME_ACCESS)
+ destinationProcessHandle := parentProcess
+
+ // On Windows 7, console handles aren't real handles, and can only be duplicated
+ // into the current process, not a parent one, which amounts to the same thing.
+ if parentProcess != p && isLegacyWin7ConsoleHandle(Handle(attr.Files[i])) {
+ destinationProcessHandle = p
+ }
+
+ err := DuplicateHandle(p, Handle(attr.Files[i]), destinationProcessHandle, &fd[i], 0, true, DUPLICATE_SAME_ACCESS)
if err != nil {
return 0, 0, err
}
@@ -356,6 +377,14 @@
fd = append(fd, sys.AdditionalInheritedHandles...)
+ // On Windows 7, console handles aren't real handles, so don't pass them
+ // through to PROC_THREAD_ATTRIBUTE_HANDLE_LIST.
+ for i := range fd {
+ if isLegacyWin7ConsoleHandle(fd[i]) {
+ fd[i] = 0
+ }
+ }
+
// The presence of a NULL handle in the list is enough to cause PROC_THREAD_ATTRIBUTE_HANDLE_LIST
// to treat the entire list as empty, so remove NULL handles.
j := 0
Index: src/runtime/syscall_windows.go
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/src/runtime/syscall_windows.go b/src/runtime/syscall_windows.go
--- a/src/runtime/syscall_windows.go (revision 6a31d3fa8e47ddabc10bd97bff10d9a85f4cfb76)
+++ b/src/runtime/syscall_windows.go (revision 69e2eed6dd0f6d815ebf15797761c13f31213dd6)
@@ -413,10 +413,20 @@
const _LOAD_LIBRARY_SEARCH_SYSTEM32 = 0x00000800
+// When available, this function will use LoadLibraryEx with the filename
+// parameter and the important SEARCH_SYSTEM32 argument. But on systems that
+// do not have that option, absoluteFilepath should contain a fallback
+// to the full path inside of system32 for use with vanilla LoadLibrary.
+//
//go:linkname syscall_loadsystemlibrary syscall.loadsystemlibrary
-func syscall_loadsystemlibrary(filename *uint16) (handle, err uintptr) {
- handle, _, err = syscall_SyscallN(uintptr(unsafe.Pointer(_LoadLibraryExW)), uintptr(unsafe.Pointer(filename)), 0, _LOAD_LIBRARY_SEARCH_SYSTEM32)
+func syscall_loadsystemlibrary(filename *uint16, absoluteFilepath *uint16) (handle, err uintptr) {
+ if useLoadLibraryEx {
+ handle, _, err = syscall_SyscallN(uintptr(unsafe.Pointer(_LoadLibraryExW)), uintptr(unsafe.Pointer(filename)), 0, _LOAD_LIBRARY_SEARCH_SYSTEM32)
+ } else {
+ handle, _, err = syscall_SyscallN(uintptr(unsafe.Pointer(_LoadLibraryW)), uintptr(unsafe.Pointer(absoluteFilepath)))
+ }
KeepAlive(filename)
+ KeepAlive(absoluteFilepath)
if handle != 0 {
err = 0
}
Index: src/syscall/dll_windows.go
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/src/syscall/dll_windows.go b/src/syscall/dll_windows.go
--- a/src/syscall/dll_windows.go (revision 6a31d3fa8e47ddabc10bd97bff10d9a85f4cfb76)
+++ b/src/syscall/dll_windows.go (revision 69e2eed6dd0f6d815ebf15797761c13f31213dd6)
@@ -44,7 +44,7 @@
func SyscallN(trap uintptr, args ...uintptr) (r1, r2 uintptr, err Errno)
func loadlibrary(filename *uint16) (handle uintptr, err Errno)
-func loadsystemlibrary(filename *uint16) (handle uintptr, err Errno)
+func loadsystemlibrary(filename *uint16, absoluteFilepath *uint16) (handle uintptr, err Errno)
func getprocaddress(handle uintptr, procname *uint8) (proc uintptr, err Errno)
// A DLL implements access to a single DLL.
@@ -53,6 +53,9 @@
Handle Handle
}
+//go:linkname getSystemDirectory
+func getSystemDirectory() string // Implemented in runtime package.
+
// LoadDLL loads the named DLL file into memory.
//
// If name is not an absolute path and is not a known system DLL used by
@@ -69,7 +72,11 @@
var h uintptr
var e Errno
if sysdll.IsSystemDLL[name] {
- h, e = loadsystemlibrary(namep)
+ absoluteFilepathp, err := UTF16PtrFromString(getSystemDirectory() + name)
+ if err != nil {
+ return nil, err
+ }
+ h, e = loadsystemlibrary(namep, absoluteFilepathp)
} else {
h, e = loadlibrary(namep)
}

657
.github/patch/go1.24.patch vendored Normal file
View File

@@ -0,0 +1,657 @@
Subject: [PATCH] Revert "runtime: always use LoadLibraryEx to load system libraries"
Revert "syscall: remove Windows 7 console handle workaround"
Revert "net: remove sysSocket fallback for Windows 7"
Revert "crypto/rand,runtime: switch RtlGenRandom for ProcessPrng"
---
Index: src/crypto/internal/sysrand/rand_windows.go
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/src/crypto/internal/sysrand/rand_windows.go b/src/crypto/internal/sysrand/rand_windows.go
--- a/src/crypto/internal/sysrand/rand_windows.go (revision 3901409b5d0fb7c85a3e6730a59943cc93b2835c)
+++ b/src/crypto/internal/sysrand/rand_windows.go (revision 2a406dc9f1ea7323d6ca9fccb2fe9ddebb6b1cc8)
@@ -7,5 +7,26 @@
import "internal/syscall/windows"
func read(b []byte) error {
- return windows.ProcessPrng(b)
+ // RtlGenRandom only returns 1<<32-1 bytes at a time. We only read at
+ // most 1<<31-1 bytes at a time so that this works the same on 32-bit
+ // and 64-bit systems.
+ return batched(windows.RtlGenRandom, 1<<31-1)(b)
+}
+
+// batched returns a function that calls f to populate a []byte by chunking it
+// into subslices of, at most, readMax bytes.
+func batched(f func([]byte) error, readMax int) func([]byte) error {
+ return func(out []byte) error {
+ for len(out) > 0 {
+ read := len(out)
+ if read > readMax {
+ read = readMax
+ }
+ if err := f(out[:read]); err != nil {
+ return err
+ }
+ out = out[read:]
+ }
+ return nil
+ }
}
Index: src/crypto/rand/rand.go
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/src/crypto/rand/rand.go b/src/crypto/rand/rand.go
--- a/src/crypto/rand/rand.go (revision 3901409b5d0fb7c85a3e6730a59943cc93b2835c)
+++ b/src/crypto/rand/rand.go (revision 2a406dc9f1ea7323d6ca9fccb2fe9ddebb6b1cc8)
@@ -22,7 +22,7 @@
// - On legacy Linux (< 3.17), Reader opens /dev/urandom on first use.
// - On macOS, iOS, and OpenBSD Reader, uses arc4random_buf(3).
// - On NetBSD, Reader uses the kern.arandom sysctl.
-// - On Windows, Reader uses the ProcessPrng API.
+// - On Windows systems, Reader uses the RtlGenRandom API.
// - On js/wasm, Reader uses the Web Crypto API.
// - On wasip1/wasm, Reader uses random_get.
//
Index: src/internal/syscall/windows/syscall_windows.go
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/src/internal/syscall/windows/syscall_windows.go b/src/internal/syscall/windows/syscall_windows.go
--- a/src/internal/syscall/windows/syscall_windows.go (revision 3901409b5d0fb7c85a3e6730a59943cc93b2835c)
+++ b/src/internal/syscall/windows/syscall_windows.go (revision 2a406dc9f1ea7323d6ca9fccb2fe9ddebb6b1cc8)
@@ -416,7 +416,7 @@
//sys DestroyEnvironmentBlock(block *uint16) (err error) = userenv.DestroyEnvironmentBlock
//sys CreateEvent(eventAttrs *SecurityAttributes, manualReset uint32, initialState uint32, name *uint16) (handle syscall.Handle, err error) = kernel32.CreateEventW
-//sys ProcessPrng(buf []byte) (err error) = bcryptprimitives.ProcessPrng
+//sys RtlGenRandom(buf []byte) (err error) = advapi32.SystemFunction036
type FILE_ID_BOTH_DIR_INFO struct {
NextEntryOffset uint32
Index: src/internal/syscall/windows/zsyscall_windows.go
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/src/internal/syscall/windows/zsyscall_windows.go b/src/internal/syscall/windows/zsyscall_windows.go
--- a/src/internal/syscall/windows/zsyscall_windows.go (revision 3901409b5d0fb7c85a3e6730a59943cc93b2835c)
+++ b/src/internal/syscall/windows/zsyscall_windows.go (revision 2a406dc9f1ea7323d6ca9fccb2fe9ddebb6b1cc8)
@@ -38,7 +38,6 @@
var (
modadvapi32 = syscall.NewLazyDLL(sysdll.Add("advapi32.dll"))
- modbcryptprimitives = syscall.NewLazyDLL(sysdll.Add("bcryptprimitives.dll"))
modiphlpapi = syscall.NewLazyDLL(sysdll.Add("iphlpapi.dll"))
modkernel32 = syscall.NewLazyDLL(sysdll.Add("kernel32.dll"))
modnetapi32 = syscall.NewLazyDLL(sysdll.Add("netapi32.dll"))
@@ -63,7 +62,7 @@
procQueryServiceStatus = modadvapi32.NewProc("QueryServiceStatus")
procRevertToSelf = modadvapi32.NewProc("RevertToSelf")
procSetTokenInformation = modadvapi32.NewProc("SetTokenInformation")
- procProcessPrng = modbcryptprimitives.NewProc("ProcessPrng")
+ procSystemFunction036 = modadvapi32.NewProc("SystemFunction036")
procGetAdaptersAddresses = modiphlpapi.NewProc("GetAdaptersAddresses")
procCreateEventW = modkernel32.NewProc("CreateEventW")
procGetACP = modkernel32.NewProc("GetACP")
@@ -236,12 +235,12 @@
return
}
-func ProcessPrng(buf []byte) (err error) {
+func RtlGenRandom(buf []byte) (err error) {
var _p0 *byte
if len(buf) > 0 {
_p0 = &buf[0]
}
- r1, _, e1 := syscall.Syscall(procProcessPrng.Addr(), 2, uintptr(unsafe.Pointer(_p0)), uintptr(len(buf)), 0)
+ r1, _, e1 := syscall.Syscall(procSystemFunction036.Addr(), 2, uintptr(unsafe.Pointer(_p0)), uintptr(len(buf)), 0)
if r1 == 0 {
err = errnoErr(e1)
}
Index: src/runtime/os_windows.go
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/src/runtime/os_windows.go b/src/runtime/os_windows.go
--- a/src/runtime/os_windows.go (revision 3901409b5d0fb7c85a3e6730a59943cc93b2835c)
+++ b/src/runtime/os_windows.go (revision ac3e93c061779dfefc0dd13a5b6e6f764a25621e)
@@ -40,8 +40,8 @@
//go:cgo_import_dynamic runtime._GetSystemInfo GetSystemInfo%1 "kernel32.dll"
//go:cgo_import_dynamic runtime._GetThreadContext GetThreadContext%2 "kernel32.dll"
//go:cgo_import_dynamic runtime._SetThreadContext SetThreadContext%2 "kernel32.dll"
-//go:cgo_import_dynamic runtime._LoadLibraryExW LoadLibraryExW%3 "kernel32.dll"
//go:cgo_import_dynamic runtime._LoadLibraryW LoadLibraryW%1 "kernel32.dll"
+//go:cgo_import_dynamic runtime._LoadLibraryA LoadLibraryA%1 "kernel32.dll"
//go:cgo_import_dynamic runtime._PostQueuedCompletionStatus PostQueuedCompletionStatus%4 "kernel32.dll"
//go:cgo_import_dynamic runtime._QueryPerformanceCounter QueryPerformanceCounter%1 "kernel32.dll"
//go:cgo_import_dynamic runtime._QueryPerformanceFrequency QueryPerformanceFrequency%1 "kernel32.dll"
@@ -75,7 +75,6 @@
// Following syscalls are available on every Windows PC.
// All these variables are set by the Windows executable
// loader before the Go program starts.
- _AddVectoredContinueHandler,
_AddVectoredExceptionHandler,
_CloseHandle,
_CreateEventA,
@@ -98,8 +97,8 @@
_GetSystemInfo,
_GetThreadContext,
_SetThreadContext,
- _LoadLibraryExW,
_LoadLibraryW,
+ _LoadLibraryA,
_PostQueuedCompletionStatus,
_QueryPerformanceCounter,
_QueryPerformanceFrequency,
@@ -128,8 +127,23 @@
_WriteFile,
_ stdFunction
- // Use ProcessPrng to generate cryptographically random data.
- _ProcessPrng stdFunction
+ // Following syscalls are only available on some Windows PCs.
+ // We will load syscalls, if available, before using them.
+ _AddDllDirectory,
+ _AddVectoredContinueHandler,
+ _LoadLibraryExA,
+ _LoadLibraryExW,
+ _ stdFunction
+
+ // Use RtlGenRandom to generate cryptographically random data.
+ // This approach has been recommended by Microsoft (see issue
+ // 15589 for details).
+ // The RtlGenRandom is not listed in advapi32.dll, instead
+ // RtlGenRandom function can be found by searching for SystemFunction036.
+ // Also some versions of Mingw cannot link to SystemFunction036
+ // when building executable as Cgo. So load SystemFunction036
+ // manually during runtime startup.
+ _RtlGenRandom stdFunction
// Load ntdll.dll manually during startup, otherwise Mingw
// links wrong printf function to cgo executable (see issue
@@ -146,13 +160,6 @@
_ stdFunction
)
-var (
- bcryptprimitivesdll = [...]uint16{'b', 'c', 'r', 'y', 'p', 't', 'p', 'r', 'i', 'm', 'i', 't', 'i', 'v', 'e', 's', '.', 'd', 'l', 'l', 0}
- ntdlldll = [...]uint16{'n', 't', 'd', 'l', 'l', '.', 'd', 'l', 'l', 0}
- powrprofdll = [...]uint16{'p', 'o', 'w', 'r', 'p', 'r', 'o', 'f', '.', 'd', 'l', 'l', 0}
- winmmdll = [...]uint16{'w', 'i', 'n', 'm', 'm', '.', 'd', 'l', 'l', 0}
-)
-
// Function to be called by windows CreateThread
// to start new os thread.
func tstart_stdcall(newm *m)
@@ -245,8 +252,18 @@
return unsafe.String(&sysDirectory[0], sysDirectoryLen)
}
-func windowsLoadSystemLib(name []uint16) uintptr {
- return stdcall3(_LoadLibraryExW, uintptr(unsafe.Pointer(&name[0])), 0, _LOAD_LIBRARY_SEARCH_SYSTEM32)
+//go:linkname syscall_getSystemDirectory syscall.getSystemDirectory
+func syscall_getSystemDirectory() string {
+ return unsafe.String(&sysDirectory[0], sysDirectoryLen)
+}
+
+func windowsLoadSystemLib(name []byte) uintptr {
+ if useLoadLibraryEx {
+ return stdcall3(_LoadLibraryExA, uintptr(unsafe.Pointer(&name[0])), 0, _LOAD_LIBRARY_SEARCH_SYSTEM32)
+ } else {
+ absName := append(sysDirectory[:sysDirectoryLen], name...)
+ return stdcall1(_LoadLibraryA, uintptr(unsafe.Pointer(&absName[0])))
+ }
}
//go:linkname windows_QueryPerformanceCounter internal/syscall/windows.QueryPerformanceCounter
@@ -264,13 +281,28 @@
}
func loadOptionalSyscalls() {
- bcryptPrimitives := windowsLoadSystemLib(bcryptprimitivesdll[:])
- if bcryptPrimitives == 0 {
- throw("bcryptprimitives.dll not found")
+ var kernel32dll = []byte("kernel32.dll\000")
+ k32 := stdcall1(_LoadLibraryA, uintptr(unsafe.Pointer(&kernel32dll[0])))
+ if k32 == 0 {
+ throw("kernel32.dll not found")
}
- _ProcessPrng = windowsFindfunc(bcryptPrimitives, []byte("ProcessPrng\000"))
+ _AddDllDirectory = windowsFindfunc(k32, []byte("AddDllDirectory\000"))
+ _AddVectoredContinueHandler = windowsFindfunc(k32, []byte("AddVectoredContinueHandler\000"))
+ _LoadLibraryExA = windowsFindfunc(k32, []byte("LoadLibraryExA\000"))
+ _LoadLibraryExW = windowsFindfunc(k32, []byte("LoadLibraryExW\000"))
+ useLoadLibraryEx = (_LoadLibraryExW != nil && _LoadLibraryExA != nil && _AddDllDirectory != nil)
+
+ initSysDirectory()
- n32 := windowsLoadSystemLib(ntdlldll[:])
+ var advapi32dll = []byte("advapi32.dll\000")
+ a32 := windowsLoadSystemLib(advapi32dll)
+ if a32 == 0 {
+ throw("advapi32.dll not found")
+ }
+ _RtlGenRandom = windowsFindfunc(a32, []byte("SystemFunction036\000"))
+
+ var ntdll = []byte("ntdll.dll\000")
+ n32 := windowsLoadSystemLib(ntdll)
if n32 == 0 {
throw("ntdll.dll not found")
}
@@ -299,7 +331,7 @@
context uintptr
}
- powrprof := windowsLoadSystemLib(powrprofdll[:])
+ powrprof := windowsLoadSystemLib([]byte("powrprof.dll\000"))
if powrprof == 0 {
return // Running on Windows 7, where we don't need it anyway.
}
@@ -358,6 +390,22 @@
// in sys_windows_386.s and sys_windows_amd64.s:
func getlasterror() uint32
+// When loading DLLs, we prefer to use LoadLibraryEx with
+// LOAD_LIBRARY_SEARCH_* flags, if available. LoadLibraryEx is not
+// available on old Windows, though, and the LOAD_LIBRARY_SEARCH_*
+// flags are not available on some versions of Windows without a
+// security patch.
+//
+// https://msdn.microsoft.com/en-us/library/ms684179(v=vs.85).aspx says:
+// "Windows 7, Windows Server 2008 R2, Windows Vista, and Windows
+// Server 2008: The LOAD_LIBRARY_SEARCH_* flags are available on
+// systems that have KB2533623 installed. To determine whether the
+// flags are available, use GetProcAddress to get the address of the
+// AddDllDirectory, RemoveDllDirectory, or SetDefaultDllDirectories
+// function. If GetProcAddress succeeds, the LOAD_LIBRARY_SEARCH_*
+// flags can be used with LoadLibraryEx."
+var useLoadLibraryEx bool
+
var timeBeginPeriodRetValue uint32
// osRelaxMinNS indicates that sysmon shouldn't osRelax if the next
@@ -431,7 +479,8 @@
// Only load winmm.dll if we need it.
// This avoids a dependency on winmm.dll for Go programs
// that run on new Windows versions.
- m32 := windowsLoadSystemLib(winmmdll[:])
+ var winmmdll = []byte("winmm.dll\000")
+ m32 := windowsLoadSystemLib(winmmdll)
if m32 == 0 {
print("runtime: LoadLibraryExW failed; errno=", getlasterror(), "\n")
throw("winmm.dll not found")
@@ -472,6 +521,28 @@
canUseLongPaths = true
}
+var osVersionInfo struct {
+ majorVersion uint32
+ minorVersion uint32
+ buildNumber uint32
+}
+
+func initOsVersionInfo() {
+ info := _OSVERSIONINFOW{}
+ info.osVersionInfoSize = uint32(unsafe.Sizeof(info))
+ stdcall1(_RtlGetVersion, uintptr(unsafe.Pointer(&info)))
+ osVersionInfo.majorVersion = info.majorVersion
+ osVersionInfo.minorVersion = info.minorVersion
+ osVersionInfo.buildNumber = info.buildNumber
+}
+
+//go:linkname rtlGetNtVersionNumbers syscall.rtlGetNtVersionNumbers
+func rtlGetNtVersionNumbers(majorVersion *uint32, minorVersion *uint32, buildNumber *uint32) {
+ *majorVersion = osVersionInfo.majorVersion
+ *minorVersion = osVersionInfo.minorVersion
+ *buildNumber = osVersionInfo.buildNumber
+}
+
func osinit() {
asmstdcallAddr = unsafe.Pointer(abi.FuncPCABI0(asmstdcall))
@@ -484,8 +555,8 @@
initHighResTimer()
timeBeginPeriodRetValue = osRelax(false)
- initSysDirectory()
initLongPathSupport()
+ initOsVersionInfo()
ncpu = getproccount()
@@ -501,7 +572,7 @@
//go:nosplit
func readRandom(r []byte) int {
n := 0
- if stdcall2(_ProcessPrng, uintptr(unsafe.Pointer(&r[0])), uintptr(len(r)))&0xff != 0 {
+ if stdcall2(_RtlGenRandom, uintptr(unsafe.Pointer(&r[0])), uintptr(len(r)))&0xff != 0 {
n = len(r)
}
return n
Index: src/net/hook_windows.go
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/src/net/hook_windows.go b/src/net/hook_windows.go
--- a/src/net/hook_windows.go (revision 2a406dc9f1ea7323d6ca9fccb2fe9ddebb6b1cc8)
+++ b/src/net/hook_windows.go (revision 7b1fd7d39c6be0185fbe1d929578ab372ac5c632)
@@ -13,6 +13,7 @@
hostsFilePath = windows.GetSystemDirectory() + "/Drivers/etc/hosts"
// Placeholders for socket system calls.
+ socketFunc func(int, int, int) (syscall.Handle, error) = syscall.Socket
wsaSocketFunc func(int32, int32, int32, *syscall.WSAProtocolInfo, uint32, uint32) (syscall.Handle, error) = windows.WSASocket
connectFunc func(syscall.Handle, syscall.Sockaddr) error = syscall.Connect
listenFunc func(syscall.Handle, int) error = syscall.Listen
Index: src/net/internal/socktest/main_test.go
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/src/net/internal/socktest/main_test.go b/src/net/internal/socktest/main_test.go
--- a/src/net/internal/socktest/main_test.go (revision 2a406dc9f1ea7323d6ca9fccb2fe9ddebb6b1cc8)
+++ b/src/net/internal/socktest/main_test.go (revision 7b1fd7d39c6be0185fbe1d929578ab372ac5c632)
@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
-//go:build !js && !plan9 && !wasip1 && !windows
+//go:build !js && !plan9 && !wasip1
package socktest_test
Index: src/net/internal/socktest/main_windows_test.go
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/src/net/internal/socktest/main_windows_test.go b/src/net/internal/socktest/main_windows_test.go
new file mode 100644
--- /dev/null (revision 7b1fd7d39c6be0185fbe1d929578ab372ac5c632)
+++ b/src/net/internal/socktest/main_windows_test.go (revision 7b1fd7d39c6be0185fbe1d929578ab372ac5c632)
@@ -0,0 +1,22 @@
+// Copyright 2015 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 socktest_test
+
+import "syscall"
+
+var (
+ socketFunc func(int, int, int) (syscall.Handle, error)
+ closeFunc func(syscall.Handle) error
+)
+
+func installTestHooks() {
+ socketFunc = sw.Socket
+ closeFunc = sw.Closesocket
+}
+
+func uninstallTestHooks() {
+ socketFunc = syscall.Socket
+ closeFunc = syscall.Closesocket
+}
Index: src/net/internal/socktest/sys_windows.go
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/src/net/internal/socktest/sys_windows.go b/src/net/internal/socktest/sys_windows.go
--- a/src/net/internal/socktest/sys_windows.go (revision 2a406dc9f1ea7323d6ca9fccb2fe9ddebb6b1cc8)
+++ b/src/net/internal/socktest/sys_windows.go (revision 7b1fd7d39c6be0185fbe1d929578ab372ac5c632)
@@ -9,6 +9,38 @@
"syscall"
)
+// Socket wraps [syscall.Socket].
+func (sw *Switch) Socket(family, sotype, proto int) (s syscall.Handle, err error) {
+ sw.once.Do(sw.init)
+
+ so := &Status{Cookie: cookie(family, sotype, proto)}
+ sw.fmu.RLock()
+ f, _ := sw.fltab[FilterSocket]
+ sw.fmu.RUnlock()
+
+ af, err := f.apply(so)
+ if err != nil {
+ return syscall.InvalidHandle, err
+ }
+ s, so.Err = syscall.Socket(family, sotype, proto)
+ if err = af.apply(so); err != nil {
+ if so.Err == nil {
+ syscall.Closesocket(s)
+ }
+ return syscall.InvalidHandle, err
+ }
+
+ sw.smu.Lock()
+ defer sw.smu.Unlock()
+ if so.Err != nil {
+ sw.stats.getLocked(so.Cookie).OpenFailed++
+ return syscall.InvalidHandle, so.Err
+ }
+ nso := sw.addLocked(s, family, sotype, proto)
+ sw.stats.getLocked(nso.Cookie).Opened++
+ return s, nil
+}
+
// WSASocket wraps [syscall.WSASocket].
func (sw *Switch) WSASocket(family, sotype, proto int32, protinfo *syscall.WSAProtocolInfo, group uint32, flags uint32) (s syscall.Handle, err error) {
sw.once.Do(sw.init)
Index: src/net/main_windows_test.go
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/src/net/main_windows_test.go b/src/net/main_windows_test.go
--- a/src/net/main_windows_test.go (revision 2a406dc9f1ea7323d6ca9fccb2fe9ddebb6b1cc8)
+++ b/src/net/main_windows_test.go (revision 7b1fd7d39c6be0185fbe1d929578ab372ac5c632)
@@ -8,6 +8,7 @@
var (
// Placeholders for saving original socket system calls.
+ origSocket = socketFunc
origWSASocket = wsaSocketFunc
origClosesocket = poll.CloseFunc
origConnect = connectFunc
@@ -17,6 +18,7 @@
)
func installTestHooks() {
+ socketFunc = sw.Socket
wsaSocketFunc = sw.WSASocket
poll.CloseFunc = sw.Closesocket
connectFunc = sw.Connect
@@ -26,6 +28,7 @@
}
func uninstallTestHooks() {
+ socketFunc = origSocket
wsaSocketFunc = origWSASocket
poll.CloseFunc = origClosesocket
connectFunc = origConnect
Index: src/net/sock_windows.go
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/src/net/sock_windows.go b/src/net/sock_windows.go
--- a/src/net/sock_windows.go (revision 2a406dc9f1ea7323d6ca9fccb2fe9ddebb6b1cc8)
+++ b/src/net/sock_windows.go (revision 7b1fd7d39c6be0185fbe1d929578ab372ac5c632)
@@ -20,6 +20,21 @@
func sysSocket(family, sotype, proto int) (syscall.Handle, error) {
s, err := wsaSocketFunc(int32(family), int32(sotype), int32(proto),
nil, 0, windows.WSA_FLAG_OVERLAPPED|windows.WSA_FLAG_NO_HANDLE_INHERIT)
+ if err == nil {
+ return s, nil
+ }
+ // WSA_FLAG_NO_HANDLE_INHERIT flag is not supported on some
+ // old versions of Windows, see
+ // https://msdn.microsoft.com/en-us/library/windows/desktop/ms742212(v=vs.85).aspx
+ // for details. Just use syscall.Socket, if windows.WSASocket failed.
+
+ // See ../syscall/exec_unix.go for description of ForkLock.
+ syscall.ForkLock.RLock()
+ s, err = socketFunc(family, sotype, proto)
+ if err == nil {
+ syscall.CloseOnExec(s)
+ }
+ syscall.ForkLock.RUnlock()
if err != nil {
return syscall.InvalidHandle, os.NewSyscallError("socket", err)
}
Index: src/syscall/exec_windows.go
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/src/syscall/exec_windows.go b/src/syscall/exec_windows.go
--- a/src/syscall/exec_windows.go (revision 2a406dc9f1ea7323d6ca9fccb2fe9ddebb6b1cc8)
+++ b/src/syscall/exec_windows.go (revision 979d6d8bab3823ff572ace26767fd2ce3cf351ae)
@@ -14,7 +14,6 @@
"unsafe"
)
-// ForkLock is not used on Windows.
var ForkLock sync.RWMutex
// EscapeArg rewrites command line argument s as prescribed
@@ -254,6 +253,9 @@
var zeroProcAttr ProcAttr
var zeroSysProcAttr SysProcAttr
+//go:linkname rtlGetNtVersionNumbers
+func rtlGetNtVersionNumbers(majorVersion *uint32, minorVersion *uint32, buildNumber *uint32)
+
func StartProcess(argv0 string, argv []string, attr *ProcAttr) (pid int, handle uintptr, err error) {
if len(argv0) == 0 {
return 0, 0, EWINDOWS
@@ -317,6 +319,17 @@
}
}
+ var maj, min, build uint32
+ rtlGetNtVersionNumbers(&maj, &min, &build)
+ isWin7 := maj < 6 || (maj == 6 && min <= 1)
+ // NT kernel handles are divisible by 4, with the bottom 3 bits left as
+ // a tag. The fully set tag correlates with the types of handles we're
+ // concerned about here. Except, the kernel will interpret some
+ // special handle values, like -1, -2, and so forth, so kernelbase.dll
+ // checks to see that those bottom three bits are checked, but that top
+ // bit is not checked.
+ isLegacyWin7ConsoleHandle := func(handle Handle) bool { return isWin7 && handle&0x10000003 == 3 }
+
p, _ := GetCurrentProcess()
parentProcess := p
if sys.ParentProcess != 0 {
@@ -325,7 +338,15 @@
fd := make([]Handle, len(attr.Files))
for i := range attr.Files {
if attr.Files[i] > 0 {
- err := DuplicateHandle(p, Handle(attr.Files[i]), parentProcess, &fd[i], 0, true, DUPLICATE_SAME_ACCESS)
+ destinationProcessHandle := parentProcess
+
+ // On Windows 7, console handles aren't real handles, and can only be duplicated
+ // into the current process, not a parent one, which amounts to the same thing.
+ if parentProcess != p && isLegacyWin7ConsoleHandle(Handle(attr.Files[i])) {
+ destinationProcessHandle = p
+ }
+
+ err := DuplicateHandle(p, Handle(attr.Files[i]), destinationProcessHandle, &fd[i], 0, true, DUPLICATE_SAME_ACCESS)
if err != nil {
return 0, 0, err
}
@@ -356,6 +377,14 @@
fd = append(fd, sys.AdditionalInheritedHandles...)
+ // On Windows 7, console handles aren't real handles, so don't pass them
+ // through to PROC_THREAD_ATTRIBUTE_HANDLE_LIST.
+ for i := range fd {
+ if isLegacyWin7ConsoleHandle(fd[i]) {
+ fd[i] = 0
+ }
+ }
+
// The presence of a NULL handle in the list is enough to cause PROC_THREAD_ATTRIBUTE_HANDLE_LIST
// to treat the entire list as empty, so remove NULL handles.
j := 0
Index: src/runtime/syscall_windows.go
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/src/runtime/syscall_windows.go b/src/runtime/syscall_windows.go
--- a/src/runtime/syscall_windows.go (revision 979d6d8bab3823ff572ace26767fd2ce3cf351ae)
+++ b/src/runtime/syscall_windows.go (revision ac3e93c061779dfefc0dd13a5b6e6f764a25621e)
@@ -413,10 +413,20 @@
const _LOAD_LIBRARY_SEARCH_SYSTEM32 = 0x00000800
+// When available, this function will use LoadLibraryEx with the filename
+// parameter and the important SEARCH_SYSTEM32 argument. But on systems that
+// do not have that option, absoluteFilepath should contain a fallback
+// to the full path inside of system32 for use with vanilla LoadLibrary.
+//
//go:linkname syscall_loadsystemlibrary syscall.loadsystemlibrary
-func syscall_loadsystemlibrary(filename *uint16) (handle, err uintptr) {
- handle, _, err = syscall_SyscallN(uintptr(unsafe.Pointer(_LoadLibraryExW)), uintptr(unsafe.Pointer(filename)), 0, _LOAD_LIBRARY_SEARCH_SYSTEM32)
+func syscall_loadsystemlibrary(filename *uint16, absoluteFilepath *uint16) (handle, err uintptr) {
+ if useLoadLibraryEx {
+ handle, _, err = syscall_SyscallN(uintptr(unsafe.Pointer(_LoadLibraryExW)), uintptr(unsafe.Pointer(filename)), 0, _LOAD_LIBRARY_SEARCH_SYSTEM32)
+ } else {
+ handle, _, err = syscall_SyscallN(uintptr(unsafe.Pointer(_LoadLibraryW)), uintptr(unsafe.Pointer(absoluteFilepath)))
+ }
KeepAlive(filename)
+ KeepAlive(absoluteFilepath)
if handle != 0 {
err = 0
}
Index: src/syscall/dll_windows.go
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/src/syscall/dll_windows.go b/src/syscall/dll_windows.go
--- a/src/syscall/dll_windows.go (revision 979d6d8bab3823ff572ace26767fd2ce3cf351ae)
+++ b/src/syscall/dll_windows.go (revision ac3e93c061779dfefc0dd13a5b6e6f764a25621e)
@@ -45,7 +45,7 @@
//go:noescape
func SyscallN(trap uintptr, args ...uintptr) (r1, r2 uintptr, err Errno)
func loadlibrary(filename *uint16) (handle uintptr, err Errno)
-func loadsystemlibrary(filename *uint16) (handle uintptr, err Errno)
+func loadsystemlibrary(filename *uint16, absoluteFilepath *uint16) (handle uintptr, err Errno)
func getprocaddress(handle uintptr, procname *uint8) (proc uintptr, err Errno)
// A DLL implements access to a single DLL.
@@ -54,6 +54,9 @@
Handle Handle
}
+//go:linkname getSystemDirectory
+func getSystemDirectory() string // Implemented in runtime package.
+
// LoadDLL loads the named DLL file into memory.
//
// If name is not an absolute path and is not a known system DLL used by
@@ -70,7 +73,11 @@
var h uintptr
var e Errno
if sysdll.IsSystemDLL[name] {
- h, e = loadsystemlibrary(namep)
+ absoluteFilepathp, err := UTF16PtrFromString(getSystemDirectory() + name)
+ if err != nil {
+ return nil, err
+ }
+ h, e = loadsystemlibrary(namep, absoluteFilepathp)
} else {
h, e = loadlibrary(namep)
}

657
.github/patch/go1.25.patch vendored Normal file
View File

@@ -0,0 +1,657 @@
Subject: [PATCH] Revert "runtime: always use LoadLibraryEx to load system libraries"
Revert "syscall: remove Windows 7 console handle workaround"
Revert "net: remove sysSocket fallback for Windows 7"
Revert "crypto/rand,runtime: switch RtlGenRandom for ProcessPrng"
---
Index: src/crypto/internal/sysrand/rand_windows.go
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/src/crypto/internal/sysrand/rand_windows.go b/src/crypto/internal/sysrand/rand_windows.go
--- a/src/crypto/internal/sysrand/rand_windows.go (revision 6e676ab2b809d46623acb5988248d95d1eb7939c)
+++ b/src/crypto/internal/sysrand/rand_windows.go (revision 8cb5472d94c34b88733a81091bd328e70ee565a4)
@@ -7,5 +7,26 @@
import "internal/syscall/windows"
func read(b []byte) error {
- return windows.ProcessPrng(b)
+ // RtlGenRandom only returns 1<<32-1 bytes at a time. We only read at
+ // most 1<<31-1 bytes at a time so that this works the same on 32-bit
+ // and 64-bit systems.
+ return batched(windows.RtlGenRandom, 1<<31-1)(b)
+}
+
+// batched returns a function that calls f to populate a []byte by chunking it
+// into subslices of, at most, readMax bytes.
+func batched(f func([]byte) error, readMax int) func([]byte) error {
+ return func(out []byte) error {
+ for len(out) > 0 {
+ read := len(out)
+ if read > readMax {
+ read = readMax
+ }
+ if err := f(out[:read]); err != nil {
+ return err
+ }
+ out = out[read:]
+ }
+ return nil
+ }
}
Index: src/crypto/rand/rand.go
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/src/crypto/rand/rand.go b/src/crypto/rand/rand.go
--- a/src/crypto/rand/rand.go (revision 6e676ab2b809d46623acb5988248d95d1eb7939c)
+++ b/src/crypto/rand/rand.go (revision 8cb5472d94c34b88733a81091bd328e70ee565a4)
@@ -22,7 +22,7 @@
// - On legacy Linux (< 3.17), Reader opens /dev/urandom on first use.
// - On macOS, iOS, and OpenBSD Reader, uses arc4random_buf(3).
// - On NetBSD, Reader uses the kern.arandom sysctl.
-// - On Windows, Reader uses the ProcessPrng API.
+// - On Windows systems, Reader uses the RtlGenRandom API.
// - On js/wasm, Reader uses the Web Crypto API.
// - On wasip1/wasm, Reader uses random_get.
//
Index: src/internal/syscall/windows/syscall_windows.go
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/src/internal/syscall/windows/syscall_windows.go b/src/internal/syscall/windows/syscall_windows.go
--- a/src/internal/syscall/windows/syscall_windows.go (revision 6e676ab2b809d46623acb5988248d95d1eb7939c)
+++ b/src/internal/syscall/windows/syscall_windows.go (revision 8cb5472d94c34b88733a81091bd328e70ee565a4)
@@ -419,7 +419,7 @@
//sys DestroyEnvironmentBlock(block *uint16) (err error) = userenv.DestroyEnvironmentBlock
//sys CreateEvent(eventAttrs *SecurityAttributes, manualReset uint32, initialState uint32, name *uint16) (handle syscall.Handle, err error) = kernel32.CreateEventW
-//sys ProcessPrng(buf []byte) (err error) = bcryptprimitives.ProcessPrng
+//sys RtlGenRandom(buf []byte) (err error) = advapi32.SystemFunction036
type FILE_ID_BOTH_DIR_INFO struct {
NextEntryOffset uint32
Index: src/internal/syscall/windows/zsyscall_windows.go
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/src/internal/syscall/windows/zsyscall_windows.go b/src/internal/syscall/windows/zsyscall_windows.go
--- a/src/internal/syscall/windows/zsyscall_windows.go (revision 6e676ab2b809d46623acb5988248d95d1eb7939c)
+++ b/src/internal/syscall/windows/zsyscall_windows.go (revision 8cb5472d94c34b88733a81091bd328e70ee565a4)
@@ -38,7 +38,6 @@
var (
modadvapi32 = syscall.NewLazyDLL(sysdll.Add("advapi32.dll"))
- modbcryptprimitives = syscall.NewLazyDLL(sysdll.Add("bcryptprimitives.dll"))
modiphlpapi = syscall.NewLazyDLL(sysdll.Add("iphlpapi.dll"))
modkernel32 = syscall.NewLazyDLL(sysdll.Add("kernel32.dll"))
modnetapi32 = syscall.NewLazyDLL(sysdll.Add("netapi32.dll"))
@@ -63,7 +62,7 @@
procQueryServiceStatus = modadvapi32.NewProc("QueryServiceStatus")
procRevertToSelf = modadvapi32.NewProc("RevertToSelf")
procSetTokenInformation = modadvapi32.NewProc("SetTokenInformation")
- procProcessPrng = modbcryptprimitives.NewProc("ProcessPrng")
+ procSystemFunction036 = modadvapi32.NewProc("SystemFunction036")
procGetAdaptersAddresses = modiphlpapi.NewProc("GetAdaptersAddresses")
procCreateEventW = modkernel32.NewProc("CreateEventW")
procCreateIoCompletionPort = modkernel32.NewProc("CreateIoCompletionPort")
@@ -242,12 +241,12 @@
return
}
-func ProcessPrng(buf []byte) (err error) {
+func RtlGenRandom(buf []byte) (err error) {
var _p0 *byte
if len(buf) > 0 {
_p0 = &buf[0]
}
- r1, _, e1 := syscall.Syscall(procProcessPrng.Addr(), 2, uintptr(unsafe.Pointer(_p0)), uintptr(len(buf)), 0)
+ r1, _, e1 := syscall.Syscall(procSystemFunction036.Addr(), 2, uintptr(unsafe.Pointer(_p0)), uintptr(len(buf)), 0)
if r1 == 0 {
err = errnoErr(e1)
}
Index: src/runtime/os_windows.go
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/src/runtime/os_windows.go b/src/runtime/os_windows.go
--- a/src/runtime/os_windows.go (revision 6e676ab2b809d46623acb5988248d95d1eb7939c)
+++ b/src/runtime/os_windows.go (revision f56f1e23507e646c85243a71bde7b9629b2f970c)
@@ -39,8 +39,8 @@
//go:cgo_import_dynamic runtime._GetSystemInfo GetSystemInfo%1 "kernel32.dll"
//go:cgo_import_dynamic runtime._GetThreadContext GetThreadContext%2 "kernel32.dll"
//go:cgo_import_dynamic runtime._SetThreadContext SetThreadContext%2 "kernel32.dll"
-//go:cgo_import_dynamic runtime._LoadLibraryExW LoadLibraryExW%3 "kernel32.dll"
//go:cgo_import_dynamic runtime._LoadLibraryW LoadLibraryW%1 "kernel32.dll"
+//go:cgo_import_dynamic runtime._LoadLibraryA LoadLibraryA%1 "kernel32.dll"
//go:cgo_import_dynamic runtime._PostQueuedCompletionStatus PostQueuedCompletionStatus%4 "kernel32.dll"
//go:cgo_import_dynamic runtime._QueryPerformanceCounter QueryPerformanceCounter%1 "kernel32.dll"
//go:cgo_import_dynamic runtime._QueryPerformanceFrequency QueryPerformanceFrequency%1 "kernel32.dll"
@@ -74,7 +74,6 @@
// Following syscalls are available on every Windows PC.
// All these variables are set by the Windows executable
// loader before the Go program starts.
- _AddVectoredContinueHandler,
_AddVectoredExceptionHandler,
_CloseHandle,
_CreateEventA,
@@ -97,8 +96,8 @@
_GetSystemInfo,
_GetThreadContext,
_SetThreadContext,
- _LoadLibraryExW,
_LoadLibraryW,
+ _LoadLibraryA,
_PostQueuedCompletionStatus,
_QueryPerformanceCounter,
_QueryPerformanceFrequency,
@@ -127,8 +126,23 @@
_WriteFile,
_ stdFunction
- // Use ProcessPrng to generate cryptographically random data.
- _ProcessPrng stdFunction
+ // Following syscalls are only available on some Windows PCs.
+ // We will load syscalls, if available, before using them.
+ _AddDllDirectory,
+ _AddVectoredContinueHandler,
+ _LoadLibraryExA,
+ _LoadLibraryExW,
+ _ stdFunction
+
+ // Use RtlGenRandom to generate cryptographically random data.
+ // This approach has been recommended by Microsoft (see issue
+ // 15589 for details).
+ // The RtlGenRandom is not listed in advapi32.dll, instead
+ // RtlGenRandom function can be found by searching for SystemFunction036.
+ // Also some versions of Mingw cannot link to SystemFunction036
+ // when building executable as Cgo. So load SystemFunction036
+ // manually during runtime startup.
+ _RtlGenRandom stdFunction
// Load ntdll.dll manually during startup, otherwise Mingw
// links wrong printf function to cgo executable (see issue
@@ -145,13 +159,6 @@
_ stdFunction
)
-var (
- bcryptprimitivesdll = [...]uint16{'b', 'c', 'r', 'y', 'p', 't', 'p', 'r', 'i', 'm', 'i', 't', 'i', 'v', 'e', 's', '.', 'd', 'l', 'l', 0}
- ntdlldll = [...]uint16{'n', 't', 'd', 'l', 'l', '.', 'd', 'l', 'l', 0}
- powrprofdll = [...]uint16{'p', 'o', 'w', 'r', 'p', 'r', 'o', 'f', '.', 'd', 'l', 'l', 0}
- winmmdll = [...]uint16{'w', 'i', 'n', 'm', 'm', '.', 'd', 'l', 'l', 0}
-)
-
// Function to be called by windows CreateThread
// to start new os thread.
func tstart_stdcall(newm *m)
@@ -244,8 +251,18 @@
return unsafe.String(&sysDirectory[0], sysDirectoryLen)
}
-func windowsLoadSystemLib(name []uint16) uintptr {
- return stdcall3(_LoadLibraryExW, uintptr(unsafe.Pointer(&name[0])), 0, _LOAD_LIBRARY_SEARCH_SYSTEM32)
+//go:linkname syscall_getSystemDirectory syscall.getSystemDirectory
+func syscall_getSystemDirectory() string {
+ return unsafe.String(&sysDirectory[0], sysDirectoryLen)
+}
+
+func windowsLoadSystemLib(name []byte) uintptr {
+ if useLoadLibraryEx {
+ return stdcall3(_LoadLibraryExA, uintptr(unsafe.Pointer(&name[0])), 0, _LOAD_LIBRARY_SEARCH_SYSTEM32)
+ } else {
+ absName := append(sysDirectory[:sysDirectoryLen], name...)
+ return stdcall1(_LoadLibraryA, uintptr(unsafe.Pointer(&absName[0])))
+ }
}
//go:linkname windows_QueryPerformanceCounter internal/syscall/windows.QueryPerformanceCounter
@@ -263,13 +280,28 @@
}
func loadOptionalSyscalls() {
- bcryptPrimitives := windowsLoadSystemLib(bcryptprimitivesdll[:])
- if bcryptPrimitives == 0 {
- throw("bcryptprimitives.dll not found")
+ var kernel32dll = []byte("kernel32.dll\000")
+ k32 := stdcall1(_LoadLibraryA, uintptr(unsafe.Pointer(&kernel32dll[0])))
+ if k32 == 0 {
+ throw("kernel32.dll not found")
}
- _ProcessPrng = windowsFindfunc(bcryptPrimitives, []byte("ProcessPrng\000"))
+ _AddDllDirectory = windowsFindfunc(k32, []byte("AddDllDirectory\000"))
+ _AddVectoredContinueHandler = windowsFindfunc(k32, []byte("AddVectoredContinueHandler\000"))
+ _LoadLibraryExA = windowsFindfunc(k32, []byte("LoadLibraryExA\000"))
+ _LoadLibraryExW = windowsFindfunc(k32, []byte("LoadLibraryExW\000"))
+ useLoadLibraryEx = (_LoadLibraryExW != nil && _LoadLibraryExA != nil && _AddDllDirectory != nil)
+
+ initSysDirectory()
- n32 := windowsLoadSystemLib(ntdlldll[:])
+ var advapi32dll = []byte("advapi32.dll\000")
+ a32 := windowsLoadSystemLib(advapi32dll)
+ if a32 == 0 {
+ throw("advapi32.dll not found")
+ }
+ _RtlGenRandom = windowsFindfunc(a32, []byte("SystemFunction036\000"))
+
+ var ntdll = []byte("ntdll.dll\000")
+ n32 := windowsLoadSystemLib(ntdll)
if n32 == 0 {
throw("ntdll.dll not found")
}
@@ -298,7 +330,7 @@
context uintptr
}
- powrprof := windowsLoadSystemLib(powrprofdll[:])
+ powrprof := windowsLoadSystemLib([]byte("powrprof.dll\000"))
if powrprof == 0 {
return // Running on Windows 7, where we don't need it anyway.
}
@@ -357,6 +389,22 @@
// in sys_windows_386.s and sys_windows_amd64.s:
func getlasterror() uint32
+// When loading DLLs, we prefer to use LoadLibraryEx with
+// LOAD_LIBRARY_SEARCH_* flags, if available. LoadLibraryEx is not
+// available on old Windows, though, and the LOAD_LIBRARY_SEARCH_*
+// flags are not available on some versions of Windows without a
+// security patch.
+//
+// https://msdn.microsoft.com/en-us/library/ms684179(v=vs.85).aspx says:
+// "Windows 7, Windows Server 2008 R2, Windows Vista, and Windows
+// Server 2008: The LOAD_LIBRARY_SEARCH_* flags are available on
+// systems that have KB2533623 installed. To determine whether the
+// flags are available, use GetProcAddress to get the address of the
+// AddDllDirectory, RemoveDllDirectory, or SetDefaultDllDirectories
+// function. If GetProcAddress succeeds, the LOAD_LIBRARY_SEARCH_*
+// flags can be used with LoadLibraryEx."
+var useLoadLibraryEx bool
+
var timeBeginPeriodRetValue uint32
// osRelaxMinNS indicates that sysmon shouldn't osRelax if the next
@@ -430,7 +478,8 @@
// Only load winmm.dll if we need it.
// This avoids a dependency on winmm.dll for Go programs
// that run on new Windows versions.
- m32 := windowsLoadSystemLib(winmmdll[:])
+ var winmmdll = []byte("winmm.dll\000")
+ m32 := windowsLoadSystemLib(winmmdll)
if m32 == 0 {
print("runtime: LoadLibraryExW failed; errno=", getlasterror(), "\n")
throw("winmm.dll not found")
@@ -471,6 +520,28 @@
canUseLongPaths = true
}
+var osVersionInfo struct {
+ majorVersion uint32
+ minorVersion uint32
+ buildNumber uint32
+}
+
+func initOsVersionInfo() {
+ info := _OSVERSIONINFOW{}
+ info.osVersionInfoSize = uint32(unsafe.Sizeof(info))
+ stdcall1(_RtlGetVersion, uintptr(unsafe.Pointer(&info)))
+ osVersionInfo.majorVersion = info.majorVersion
+ osVersionInfo.minorVersion = info.minorVersion
+ osVersionInfo.buildNumber = info.buildNumber
+}
+
+//go:linkname rtlGetNtVersionNumbers syscall.rtlGetNtVersionNumbers
+func rtlGetNtVersionNumbers(majorVersion *uint32, minorVersion *uint32, buildNumber *uint32) {
+ *majorVersion = osVersionInfo.majorVersion
+ *minorVersion = osVersionInfo.minorVersion
+ *buildNumber = osVersionInfo.buildNumber
+}
+
func osinit() {
asmstdcallAddr = unsafe.Pointer(abi.FuncPCABI0(asmstdcall))
@@ -483,8 +554,8 @@
initHighResTimer()
timeBeginPeriodRetValue = osRelax(false)
- initSysDirectory()
initLongPathSupport()
+ initOsVersionInfo()
numCPUStartup = getCPUCount()
@@ -500,7 +571,7 @@
//go:nosplit
func readRandom(r []byte) int {
n := 0
- if stdcall2(_ProcessPrng, uintptr(unsafe.Pointer(&r[0])), uintptr(len(r)))&0xff != 0 {
+ if stdcall2(_RtlGenRandom, uintptr(unsafe.Pointer(&r[0])), uintptr(len(r)))&0xff != 0 {
n = len(r)
}
return n
Index: src/net/hook_windows.go
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/src/net/hook_windows.go b/src/net/hook_windows.go
--- a/src/net/hook_windows.go (revision 8cb5472d94c34b88733a81091bd328e70ee565a4)
+++ b/src/net/hook_windows.go (revision 6788c4c6f9fafb56729bad6b660f7ee2272d699f)
@@ -13,6 +13,7 @@
hostsFilePath = windows.GetSystemDirectory() + "/Drivers/etc/hosts"
// Placeholders for socket system calls.
+ socketFunc func(int, int, int) (syscall.Handle, error) = syscall.Socket
wsaSocketFunc func(int32, int32, int32, *syscall.WSAProtocolInfo, uint32, uint32) (syscall.Handle, error) = windows.WSASocket
connectFunc func(syscall.Handle, syscall.Sockaddr) error = syscall.Connect
listenFunc func(syscall.Handle, int) error = syscall.Listen
Index: src/net/internal/socktest/main_test.go
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/src/net/internal/socktest/main_test.go b/src/net/internal/socktest/main_test.go
--- a/src/net/internal/socktest/main_test.go (revision 8cb5472d94c34b88733a81091bd328e70ee565a4)
+++ b/src/net/internal/socktest/main_test.go (revision 6788c4c6f9fafb56729bad6b660f7ee2272d699f)
@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
-//go:build !js && !plan9 && !wasip1 && !windows
+//go:build !js && !plan9 && !wasip1
package socktest_test
Index: src/net/internal/socktest/main_windows_test.go
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/src/net/internal/socktest/main_windows_test.go b/src/net/internal/socktest/main_windows_test.go
new file mode 100644
--- /dev/null (revision 6788c4c6f9fafb56729bad6b660f7ee2272d699f)
+++ b/src/net/internal/socktest/main_windows_test.go (revision 6788c4c6f9fafb56729bad6b660f7ee2272d699f)
@@ -0,0 +1,22 @@
+// Copyright 2015 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 socktest_test
+
+import "syscall"
+
+var (
+ socketFunc func(int, int, int) (syscall.Handle, error)
+ closeFunc func(syscall.Handle) error
+)
+
+func installTestHooks() {
+ socketFunc = sw.Socket
+ closeFunc = sw.Closesocket
+}
+
+func uninstallTestHooks() {
+ socketFunc = syscall.Socket
+ closeFunc = syscall.Closesocket
+}
Index: src/net/internal/socktest/sys_windows.go
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/src/net/internal/socktest/sys_windows.go b/src/net/internal/socktest/sys_windows.go
--- a/src/net/internal/socktest/sys_windows.go (revision 8cb5472d94c34b88733a81091bd328e70ee565a4)
+++ b/src/net/internal/socktest/sys_windows.go (revision 6788c4c6f9fafb56729bad6b660f7ee2272d699f)
@@ -9,6 +9,38 @@
"syscall"
)
+// Socket wraps [syscall.Socket].
+func (sw *Switch) Socket(family, sotype, proto int) (s syscall.Handle, err error) {
+ sw.once.Do(sw.init)
+
+ so := &Status{Cookie: cookie(family, sotype, proto)}
+ sw.fmu.RLock()
+ f, _ := sw.fltab[FilterSocket]
+ sw.fmu.RUnlock()
+
+ af, err := f.apply(so)
+ if err != nil {
+ return syscall.InvalidHandle, err
+ }
+ s, so.Err = syscall.Socket(family, sotype, proto)
+ if err = af.apply(so); err != nil {
+ if so.Err == nil {
+ syscall.Closesocket(s)
+ }
+ return syscall.InvalidHandle, err
+ }
+
+ sw.smu.Lock()
+ defer sw.smu.Unlock()
+ if so.Err != nil {
+ sw.stats.getLocked(so.Cookie).OpenFailed++
+ return syscall.InvalidHandle, so.Err
+ }
+ nso := sw.addLocked(s, family, sotype, proto)
+ sw.stats.getLocked(nso.Cookie).Opened++
+ return s, nil
+}
+
// WSASocket wraps [syscall.WSASocket].
func (sw *Switch) WSASocket(family, sotype, proto int32, protinfo *syscall.WSAProtocolInfo, group uint32, flags uint32) (s syscall.Handle, err error) {
sw.once.Do(sw.init)
Index: src/net/main_windows_test.go
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/src/net/main_windows_test.go b/src/net/main_windows_test.go
--- a/src/net/main_windows_test.go (revision 8cb5472d94c34b88733a81091bd328e70ee565a4)
+++ b/src/net/main_windows_test.go (revision 6788c4c6f9fafb56729bad6b660f7ee2272d699f)
@@ -12,6 +12,7 @@
var (
// Placeholders for saving original socket system calls.
+ origSocket = socketFunc
origWSASocket = wsaSocketFunc
origClosesocket = poll.CloseFunc
origConnect = connectFunc
@@ -21,6 +22,7 @@
)
func installTestHooks() {
+ socketFunc = sw.Socket
wsaSocketFunc = sw.WSASocket
poll.CloseFunc = sw.Closesocket
connectFunc = sw.Connect
@@ -30,6 +32,7 @@
}
func uninstallTestHooks() {
+ socketFunc = origSocket
wsaSocketFunc = origWSASocket
poll.CloseFunc = origClosesocket
connectFunc = origConnect
Index: src/net/sock_windows.go
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/src/net/sock_windows.go b/src/net/sock_windows.go
--- a/src/net/sock_windows.go (revision 8cb5472d94c34b88733a81091bd328e70ee565a4)
+++ b/src/net/sock_windows.go (revision 6788c4c6f9fafb56729bad6b660f7ee2272d699f)
@@ -20,6 +20,21 @@
func sysSocket(family, sotype, proto int) (syscall.Handle, error) {
s, err := wsaSocketFunc(int32(family), int32(sotype), int32(proto),
nil, 0, windows.WSA_FLAG_OVERLAPPED|windows.WSA_FLAG_NO_HANDLE_INHERIT)
+ if err == nil {
+ return s, nil
+ }
+ // WSA_FLAG_NO_HANDLE_INHERIT flag is not supported on some
+ // old versions of Windows, see
+ // https://msdn.microsoft.com/en-us/library/windows/desktop/ms742212(v=vs.85).aspx
+ // for details. Just use syscall.Socket, if windows.WSASocket failed.
+
+ // See ../syscall/exec_unix.go for description of ForkLock.
+ syscall.ForkLock.RLock()
+ s, err = socketFunc(family, sotype, proto)
+ if err == nil {
+ syscall.CloseOnExec(s)
+ }
+ syscall.ForkLock.RUnlock()
if err != nil {
return syscall.InvalidHandle, os.NewSyscallError("socket", err)
}
Index: src/syscall/exec_windows.go
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/src/syscall/exec_windows.go b/src/syscall/exec_windows.go
--- a/src/syscall/exec_windows.go (revision 8cb5472d94c34b88733a81091bd328e70ee565a4)
+++ b/src/syscall/exec_windows.go (revision a5b2168bb836ed9d6601c626f95e56c07923f906)
@@ -14,7 +14,6 @@
"unsafe"
)
-// ForkLock is not used on Windows.
var ForkLock sync.RWMutex
// EscapeArg rewrites command line argument s as prescribed
@@ -254,6 +253,9 @@
var zeroProcAttr ProcAttr
var zeroSysProcAttr SysProcAttr
+//go:linkname rtlGetNtVersionNumbers
+func rtlGetNtVersionNumbers(majorVersion *uint32, minorVersion *uint32, buildNumber *uint32)
+
func StartProcess(argv0 string, argv []string, attr *ProcAttr) (pid int, handle uintptr, err error) {
if len(argv0) == 0 {
return 0, 0, EWINDOWS
@@ -317,6 +319,17 @@
}
}
+ var maj, min, build uint32
+ rtlGetNtVersionNumbers(&maj, &min, &build)
+ isWin7 := maj < 6 || (maj == 6 && min <= 1)
+ // NT kernel handles are divisible by 4, with the bottom 3 bits left as
+ // a tag. The fully set tag correlates with the types of handles we're
+ // concerned about here. Except, the kernel will interpret some
+ // special handle values, like -1, -2, and so forth, so kernelbase.dll
+ // checks to see that those bottom three bits are checked, but that top
+ // bit is not checked.
+ isLegacyWin7ConsoleHandle := func(handle Handle) bool { return isWin7 && handle&0x10000003 == 3 }
+
p, _ := GetCurrentProcess()
parentProcess := p
if sys.ParentProcess != 0 {
@@ -325,7 +338,15 @@
fd := make([]Handle, len(attr.Files))
for i := range attr.Files {
if attr.Files[i] > 0 {
- err := DuplicateHandle(p, Handle(attr.Files[i]), parentProcess, &fd[i], 0, true, DUPLICATE_SAME_ACCESS)
+ destinationProcessHandle := parentProcess
+
+ // On Windows 7, console handles aren't real handles, and can only be duplicated
+ // into the current process, not a parent one, which amounts to the same thing.
+ if parentProcess != p && isLegacyWin7ConsoleHandle(Handle(attr.Files[i])) {
+ destinationProcessHandle = p
+ }
+
+ err := DuplicateHandle(p, Handle(attr.Files[i]), destinationProcessHandle, &fd[i], 0, true, DUPLICATE_SAME_ACCESS)
if err != nil {
return 0, 0, err
}
@@ -356,6 +377,14 @@
fd = append(fd, sys.AdditionalInheritedHandles...)
+ // On Windows 7, console handles aren't real handles, so don't pass them
+ // through to PROC_THREAD_ATTRIBUTE_HANDLE_LIST.
+ for i := range fd {
+ if isLegacyWin7ConsoleHandle(fd[i]) {
+ fd[i] = 0
+ }
+ }
+
// The presence of a NULL handle in the list is enough to cause PROC_THREAD_ATTRIBUTE_HANDLE_LIST
// to treat the entire list as empty, so remove NULL handles.
j := 0
Index: src/runtime/syscall_windows.go
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/src/runtime/syscall_windows.go b/src/runtime/syscall_windows.go
--- a/src/runtime/syscall_windows.go (revision a5b2168bb836ed9d6601c626f95e56c07923f906)
+++ b/src/runtime/syscall_windows.go (revision f56f1e23507e646c85243a71bde7b9629b2f970c)
@@ -413,10 +413,20 @@
const _LOAD_LIBRARY_SEARCH_SYSTEM32 = 0x00000800
+// When available, this function will use LoadLibraryEx with the filename
+// parameter and the important SEARCH_SYSTEM32 argument. But on systems that
+// do not have that option, absoluteFilepath should contain a fallback
+// to the full path inside of system32 for use with vanilla LoadLibrary.
+//
//go:linkname syscall_loadsystemlibrary syscall.loadsystemlibrary
-func syscall_loadsystemlibrary(filename *uint16) (handle, err uintptr) {
- handle, _, err = syscall_SyscallN(uintptr(unsafe.Pointer(_LoadLibraryExW)), uintptr(unsafe.Pointer(filename)), 0, _LOAD_LIBRARY_SEARCH_SYSTEM32)
+func syscall_loadsystemlibrary(filename *uint16, absoluteFilepath *uint16) (handle, err uintptr) {
+ if useLoadLibraryEx {
+ handle, _, err = syscall_SyscallN(uintptr(unsafe.Pointer(_LoadLibraryExW)), uintptr(unsafe.Pointer(filename)), 0, _LOAD_LIBRARY_SEARCH_SYSTEM32)
+ } else {
+ handle, _, err = syscall_SyscallN(uintptr(unsafe.Pointer(_LoadLibraryW)), uintptr(unsafe.Pointer(absoluteFilepath)))
+ }
KeepAlive(filename)
+ KeepAlive(absoluteFilepath)
if handle != 0 {
err = 0
}
Index: src/syscall/dll_windows.go
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/src/syscall/dll_windows.go b/src/syscall/dll_windows.go
--- a/src/syscall/dll_windows.go (revision a5b2168bb836ed9d6601c626f95e56c07923f906)
+++ b/src/syscall/dll_windows.go (revision f56f1e23507e646c85243a71bde7b9629b2f970c)
@@ -45,7 +45,7 @@
//go:noescape
func SyscallN(trap uintptr, args ...uintptr) (r1, r2 uintptr, err Errno)
func loadlibrary(filename *uint16) (handle uintptr, err Errno)
-func loadsystemlibrary(filename *uint16) (handle uintptr, err Errno)
+func loadsystemlibrary(filename *uint16, absoluteFilepath *uint16) (handle uintptr, err Errno)
func getprocaddress(handle uintptr, procname *uint8) (proc uintptr, err Errno)
// A DLL implements access to a single DLL.
@@ -54,6 +54,9 @@
Handle Handle
}
+//go:linkname getSystemDirectory
+func getSystemDirectory() string // Implemented in runtime package.
+
// LoadDLL loads the named DLL file into memory.
//
// If name is not an absolute path and is not a known system DLL used by
@@ -70,7 +73,11 @@
var h uintptr
var e Errno
if sysdll.IsSystemDLL[name] {
- h, e = loadsystemlibrary(namep)
+ absoluteFilepathp, err := UTF16PtrFromString(getSystemDirectory() + name)
+ if err != nil {
+ return nil, err
+ }
+ h, e = loadsystemlibrary(namep, absoluteFilepathp)
} else {
h, e = loadlibrary(namep)
}

18
.github/release/.fpm_systemd vendored Normal file
View File

@@ -0,0 +1,18 @@
-s dir
--name mihomo
--category net
--license GPL-3.0-or-later
--description "The universal proxy platform."
--url "https://wiki.metacubex.one/"
--maintainer "MetaCubeX <none@example.com>"
--deb-field "Bug: https://github.com/MetaCubeX/mihomo/issues"
--no-deb-generate-changes
--config-files /etc/mihomo/config.yaml
.github/release/config.yaml=/etc/mihomo/config.yaml
.github/release/mihomo.service=/usr/lib/systemd/system/mihomo.service
.github/release/mihomo@.service=/usr/lib/systemd/system/mihomo@.service
LICENSE=/usr/share/licenses/mihomo/LICENSE

15
.github/release/config.yaml vendored Normal file
View File

@@ -0,0 +1,15 @@
mixed-port: 7890
dns:
enable: true
ipv6: true
enhanced-mode: fake-ip
fake-ip-filter:
- "*"
- "+.lan"
- "+.local"
nameserver:
- system
rules:
- MATCH,DIRECT

17
.github/release/mihomo.service vendored Normal file
View File

@@ -0,0 +1,17 @@
[Unit]
Description=mihomo Daemon, Another Clash Kernel.
Documentation=https://wiki.metacubex.one
After=network.target nss-lookup.target network-online.target
[Service]
Type=simple
CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_RAW CAP_NET_BIND_SERVICE CAP_SYS_TIME CAP_SYS_PTRACE CAP_DAC_READ_SEARCH CAP_DAC_OVERRIDE
AmbientCapabilities=CAP_NET_ADMIN CAP_NET_RAW CAP_NET_BIND_SERVICE CAP_SYS_TIME CAP_SYS_PTRACE CAP_DAC_READ_SEARCH CAP_DAC_OVERRIDE
ExecStart=/usr/bin/mihomo -d /etc/mihomo
ExecReload=/bin/kill -HUP $MAINPID
Restart=on-failure
RestartSec=10
LimitNOFILE=infinity
[Install]
WantedBy=multi-user.target

17
.github/release/mihomo@.service vendored Normal file
View File

@@ -0,0 +1,17 @@
[Unit]
Description=mihomo Daemon, Another Clash Kernel.
Documentation=https://wiki.metacubex.one
After=network.target nss-lookup.target network-online.target
[Service]
Type=simple
CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_RAW CAP_NET_BIND_SERVICE CAP_SYS_TIME CAP_SYS_PTRACE CAP_DAC_READ_SEARCH CAP_DAC_OVERRIDE
AmbientCapabilities=CAP_NET_ADMIN CAP_NET_RAW CAP_NET_BIND_SERVICE CAP_SYS_TIME CAP_SYS_PTRACE CAP_DAC_READ_SEARCH CAP_DAC_OVERRIDE
ExecStart=/usr/bin/mihomo -d /etc/mihomo
ExecReload=/bin/kill -HUP $MAINPID
Restart=on-failure
RestartSec=10
LimitNOFILE=infinity
[Install]
WantedBy=multi-user.target

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

@@ -5,25 +5,25 @@ for FILENAME in $FILENAMES
do
if [[ $FILENAME =~ "darwin-10.16-arm64" ]];then
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
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
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
echo "rename windows amd64 $FILENAME"
mv $FILENAME clash.meta-windows-amd64-cgo.exe
elif [[ $FILENAME =~ "clash.meta-linux-arm-5" ]];then
echo "rename clash.meta-linux-arm-5 $FILENAME"
mv $FILENAME clash.meta-linux-armv5-cgo
elif [[ $FILENAME =~ "clash.meta-linux-arm-6" ]];then
echo "rename clash.meta-linux-arm-6 $FILENAME"
mv $FILENAME clash.meta-linux-armv6-cgo
elif [[ $FILENAME =~ "clash.meta-linux-arm-7" ]];then
echo "rename clash.meta-linux-arm-7 $FILENAME"
mv $FILENAME clash.meta-linux-armv7-cgo
mv $FILENAME mihomo-windows-amd64-cgo.exe
elif [[ $FILENAME =~ "mihomo-linux-arm-5" ]];then
echo "rename mihomo-linux-arm-5 $FILENAME"
mv $FILENAME mihomo-linux-armv5-cgo
elif [[ $FILENAME =~ "mihomo-linux-arm-6" ]];then
echo "rename mihomo-linux-arm-6 $FILENAME"
mv $FILENAME mihomo-linux-armv6-cgo
elif [[ $FILENAME =~ "mihomo-linux-arm-7" ]];then
echo "rename mihomo-linux-arm-7 $FILENAME"
mv $FILENAME mihomo-linux-armv7-cgo
elif [[ $FILENAME =~ "linux" ]];then
echo "rename linux $FILENAME"
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,6 +1,10 @@
name: Build
on:
workflow_dispatch:
inputs:
version:
description: "Tag version to release"
required: true
push:
paths-ignore:
- "docs/**"
@@ -10,321 +14,523 @@ on:
- Alpha
tags:
- "v*"
pull_request_target:
pull_request:
branches:
- Alpha
concurrency:
group: ${{ github.ref }}-${{ github.workflow }}
group: "${{ github.workflow }}-${{ github.ref }}"
cancel-in-progress: true
env:
REGISTRY: docker.io
jobs:
Build:
permissions: write-all
build:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
job:
- {
type: "WithoutCGO",
target: "linux-amd64 linux-amd64-compatible",
id: "1",
}
- {
type: "WithoutCGO",
target: "linux-armv5 linux-armv6 linux-armv7",
id: "2",
}
- {
type: "WithoutCGO",
target: "linux-arm64 linux-mips64 linux-mips64le",
id: "3",
}
- {
type: "WithoutCGO",
target: "linux-mips-softfloat linux-mips-hardfloat linux-mipsle-softfloat linux-mipsle-hardfloat",
id: "4",
}
- { type: "WithoutCGO", target: "linux-386 linux-riscv64", id: "5" }
- {
type: "WithoutCGO",
target: "freebsd-386 freebsd-amd64 freebsd-arm64",
id: "6",
}
- {
type: "WithoutCGO",
target: "windows-amd64-compatible windows-amd64 windows-386",
id: "7",
}
- {
type: "WithoutCGO",
target: "windows-arm64 windows-arm32v7",
id: "8",
}
- {
type: "WithoutCGO",
target: "darwin-amd64 darwin-arm64 android-arm64",
id: "9",
}
- { type: "WithCGO", target: "windows/*", id: "1" }
- { type: "WithCGO", target: "linux/386", id: "2" }
- { type: "WithCGO", target: "linux/amd64", id: "3" }
- { type: "WithCGO", target: "linux/arm64,linux/riscv64", id: "4" }
- { type: "WithCGO", target: "linux/arm,", id: "5" }
- { type: "WithCGO", target: "linux/arm-6,linux/arm-7", id: "6" }
- { type: "WithCGO", target: "linux/mips,linux/mipsle", id: "7" }
- { type: "WithCGO", target: "linux/mips64", id: "8" }
- { type: "WithCGO", target: "linux/mips64le", id: "9" }
- { type: "WithCGO", target: "darwin-10.16/*", id: "10" }
- { type: "WithCGO", target: "android", id: "11" }
jobs:
- { goos: darwin, goarch: amd64, goamd64: v1, output: amd64-compatible } # old style file name will be removed in next released
- { goos: darwin, goarch: amd64, goamd64: v3, output: amd64 }
- { goos: darwin, goarch: amd64, goamd64: v1, output: amd64-v1 }
- { goos: darwin, goarch: amd64, goamd64: v2, output: amd64-v2 }
- { goos: darwin, goarch: amd64, goamd64: v3, output: amd64-v3 }
- { goos: darwin, goarch: arm64, output: arm64 }
- { goos: linux, goarch: '386', go386: sse2, output: '386', debian: i386, rpm: i386}
- { goos: linux, goarch: '386', go386: softfloat, output: '386-softfloat' }
- { goos: linux, goarch: amd64, goamd64: v1, output: amd64-compatible} # old style file name will be removed in next released
- { goos: linux, goarch: amd64, goamd64: v3, output: amd64, debian: amd64, rpm: x86_64, pacman: x86_64}
- { goos: linux, goarch: amd64, goamd64: v1, output: amd64-v1, debian: amd64, rpm: x86_64, pacman: x86_64, test: test }
- { goos: linux, goarch: amd64, goamd64: v2, output: amd64-v2, debian: amd64, rpm: x86_64, pacman: x86_64}
- { goos: linux, goarch: amd64, goamd64: v3, output: amd64-v3, debian: amd64, rpm: x86_64, pacman: x86_64}
- { goos: linux, goarch: arm64, output: arm64, debian: arm64, rpm: aarch64, pacman: aarch64}
- { goos: linux, goarch: arm, goarm: '5', output: armv5 }
- { goos: linux, goarch: arm, goarm: '6', output: armv6, debian: armel, rpm: armv6hl}
- { goos: linux, goarch: arm, goarm: '7', output: armv7, debian: armhf, rpm: armv7hl, pacman: armv7hl}
- { goos: linux, goarch: mips, gomips: hardfloat, output: mips-hardfloat }
- { goos: linux, goarch: mips, gomips: softfloat, output: mips-softfloat }
- { goos: linux, goarch: mipsle, gomips: hardfloat, output: mipsle-hardfloat }
- { goos: linux, goarch: mipsle, gomips: softfloat, output: mipsle-softfloat }
- { goos: linux, goarch: mips64, output: mips64 }
- { goos: linux, goarch: mips64le, output: mips64le, debian: mips64el, rpm: mips64el }
- { goos: linux, goarch: loong64, output: loong64-abi1, abi: '1', debian: loongarch64, rpm: loongarch64 }
- { goos: linux, goarch: loong64, output: loong64-abi2, abi: '2', debian: loong64, rpm: loong64 }
- { goos: linux, goarch: riscv64, output: riscv64, debian: riscv64, rpm: riscv64 }
- { goos: linux, goarch: s390x, output: s390x, debian: s390x, rpm: s390x }
- { goos: linux, goarch: ppc64le, output: ppc64le, debian: ppc64el, rpm: ppc64le }
- { goos: windows, goarch: '386', output: '386' }
- { goos: windows, goarch: amd64, goamd64: v1, output: amd64-compatible } # old style file name will be removed in next released
- { goos: windows, goarch: amd64, goamd64: v3, output: amd64 }
- { goos: windows, goarch: amd64, goamd64: v1, output: amd64-v1 }
- { goos: windows, goarch: amd64, goamd64: v2, output: amd64-v2 }
- { goos: windows, goarch: amd64, goamd64: v3, output: amd64-v3 }
- { goos: windows, goarch: arm64, output: arm64 }
- { goos: freebsd, goarch: '386', output: '386' }
- { goos: freebsd, goarch: amd64, goamd64: v1, output: amd64-compatible } # old style file name will be removed in next released
- { goos: freebsd, goarch: amd64, goamd64: v3, output: amd64 }
- { goos: freebsd, goarch: amd64, goamd64: v1, output: amd64-v1 }
- { goos: freebsd, goarch: amd64, goamd64: v2, output: amd64-v2 }
- { goos: freebsd, goarch: amd64, goamd64: v3, output: amd64-v3 }
- { goos: freebsd, goarch: arm64, output: arm64 }
- { goos: android, goarch: '386', ndk: i686-linux-android34, output: '386' }
- { goos: android, goarch: amd64, ndk: x86_64-linux-android34, output: amd64 }
- { goos: android, goarch: arm, ndk: armv7a-linux-androideabi34, output: armv7 }
- { goos: android, goarch: arm64, ndk: aarch64-linux-android34, output: arm64-v8 }
# Go 1.24 with special patch can work on Windows 7
# https://github.com/MetaCubeX/go/commits/release-branch.go1.24/
- { goos: windows, goarch: '386', output: '386-go124', goversion: '1.24' }
- { goos: windows, goarch: amd64, goamd64: v1, output: amd64-v1-go124, goversion: '1.24' }
- { goos: windows, goarch: amd64, goamd64: v2, output: amd64-v2-go124, goversion: '1.24' }
- { goos: windows, goarch: amd64, goamd64: v3, output: amd64-v3-go124, goversion: '1.24' }
# Go 1.23 with special patch can work on Windows 7
# https://github.com/MetaCubeX/go/commits/release-branch.go1.23/
- { goos: windows, goarch: '386', output: '386-go123', goversion: '1.23' }
- { goos: windows, goarch: amd64, goamd64: v1, output: amd64-v1-go123, goversion: '1.23' }
- { goos: windows, goarch: amd64, goamd64: v2, output: amd64-v2-go123, goversion: '1.23' }
- { goos: windows, goarch: amd64, goamd64: v3, output: amd64-v3-go123, goversion: '1.23' }
# Go 1.22 with special patch can work on Windows 7
# https://github.com/MetaCubeX/go/commits/release-branch.go1.22/
- { goos: windows, goarch: '386', output: '386-go122', goversion: '1.22' }
- { goos: windows, goarch: amd64, goamd64: v1, output: amd64-v1-go122, goversion: '1.22' }
- { goos: windows, goarch: amd64, goamd64: v2, output: amd64-v2-go122, goversion: '1.22' }
- { goos: windows, goarch: amd64, goamd64: v3, output: amd64-v3-go122, goversion: '1.22' }
# Go 1.21 can revert commit `9e4385` to work on Windows 7
# https://github.com/golang/go/issues/64622#issuecomment-1847475161
# (OR we can just use golang1.21.4 which unneeded any patch)
- { goos: windows, goarch: '386', output: '386-go121', goversion: '1.21' }
- { goos: windows, goarch: amd64, goamd64: v1, output: amd64-v1-go121, goversion: '1.21' }
- { goos: windows, goarch: amd64, goamd64: v2, output: amd64-v2-go121, goversion: '1.21' }
- { goos: windows, goarch: amd64, goamd64: v3, output: amd64-v3-go121, goversion: '1.21' }
# 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.
- { goos: windows, goarch: '386', output: '386-go120', goversion: '1.20' }
- { goos: windows, goarch: amd64, goamd64: v1, output: amd64-v1-go120, goversion: '1.20' }
- { goos: windows, goarch: amd64, goamd64: v2, output: amd64-v2-go120, goversion: '1.20' }
- { goos: windows, goarch: amd64, goamd64: v3, output: amd64-v3-go120, goversion: '1.20' }
# Go 1.24 is the last release that will run on macOS 11 Big Sur. Go 1.25 will require macOS 12 Monterey or later.
- { goos: darwin, goarch: arm64, output: arm64-go124, goversion: '1.24' }
- { goos: darwin, goarch: amd64, goamd64: v1, output: amd64-v1-go124, goversion: '1.24' }
- { goos: darwin, goarch: amd64, goamd64: v2, output: amd64-v2-go124, goversion: '1.24' }
- { goos: darwin, goarch: amd64, goamd64: v3, output: amd64-v3-go124, goversion: '1.24' }
# Go 1.22 is the last release that will run on macOS 10.15 Catalina. Go 1.23 will require macOS 11 Big Sur or later.
- { goos: darwin, goarch: arm64, output: arm64-go122, goversion: '1.22' }
- { goos: darwin, goarch: amd64, goamd64: v1, output: amd64-v1-go122, goversion: '1.22' }
- { goos: darwin, goarch: amd64, goamd64: v2, output: amd64-v2-go122, goversion: '1.22' }
- { goos: darwin, goarch: amd64, goamd64: v3, output: amd64-v3-go122, goversion: '1.22' }
# 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.
- { goos: darwin, goarch: arm64, output: arm64-go120, goversion: '1.20' }
- { goos: darwin, goarch: amd64, goamd64: v1, output: amd64-v1-go120, goversion: '1.20' }
- { goos: darwin, goarch: amd64, goamd64: v2, output: amd64-v2-go120, goversion: '1.20' }
- { goos: darwin, goarch: amd64, goamd64: v3, output: amd64-v3-go120, goversion: '1.20' }
# Go 1.23 is the last release that requires Linux kernel version 2.6.32 or later. Go 1.24 will require Linux kernel version 3.2 or later.
- { goos: linux, goarch: '386', output: '386-go123', goversion: '1.23' }
- { goos: linux, goarch: amd64, goamd64: v1, output: amd64-v1-go123, goversion: '1.23', test: test }
- { goos: linux, goarch: amd64, goamd64: v2, output: amd64-v2-go123, goversion: '1.23' }
- { goos: linux, goarch: amd64, goamd64: v3, output: amd64-v3-go123, goversion: '1.23' }
# only for test
- { goos: linux, goarch: '386', output: '386-go120', goversion: '1.20' }
- { goos: linux, goarch: amd64, goamd64: v1, output: amd64-v1-go120, goversion: '1.20', test: test }
- { goos: linux, goarch: amd64, goamd64: v2, output: amd64-v2-go120, goversion: '1.20' }
- { goos: linux, goarch: amd64, goamd64: v3, output: amd64-v3-go120, goversion: '1.20' }
steps:
- name: Check out code into the Go module directory
uses: actions/checkout@v3
- uses: actions/checkout@v5
- name: Set variables
run: echo "VERSION=$(git rev-parse --short HEAD)" >> $GITHUB_ENV
shell: bash
- name: Set up Go
if: ${{ matrix.jobs.goversion == '' && matrix.jobs.abi != '1' }}
uses: actions/setup-go@v6
with:
go-version: '1.25'
- name: Set variables
if: ${{github.ref_name=='Alpha'}}
run: echo "VERSION=alpha-$(git rev-parse --short HEAD)" >> $GITHUB_ENV
shell: bash
- name: Set up Go
if: ${{ matrix.jobs.goversion != '' && matrix.jobs.abi != '1' }}
uses: actions/setup-go@v6
with:
go-version: ${{ matrix.jobs.goversion }}
- name: Set variables
if: ${{github.ref_name=='Beta'}}
run: echo "VERSION=beta-$(git rev-parse --short HEAD)" >> $GITHUB_ENV
shell: bash
- name: Set up Go1.24 loongarch abi1
if: ${{ matrix.jobs.goarch == 'loong64' && matrix.jobs.abi == '1' }}
run: |
wget -q https://github.com/MetaCubeX/loongarch64-golang/releases/download/1.24.0/go1.24.0.linux-amd64-abi1.tar.gz
sudo tar zxf go1.24.0.linux-amd64-abi1.tar.gz -C /usr/local
echo "/usr/local/go/bin" >> $GITHUB_PATH
- name: Set variables
if: ${{github.ref_name=='Meta'}}
run: echo "VERSION=meta-$(git rev-parse --short HEAD)" >> $GITHUB_ENV
shell: bash
# modify from https://github.com/restic/restic/issues/4636#issuecomment-1896455557
# this patch file only works on golang1.25.x
# that means after golang1.26 release it must be changed
# see: https://github.com/MetaCubeX/go/commits/release-branch.go1.25/
# revert:
# 693def151adff1af707d82d28f55dba81ceb08e1: "crypto/rand,runtime: switch RtlGenRandom for ProcessPrng"
# 7c1157f9544922e96945196b47b95664b1e39108: "net: remove sysSocket fallback for Windows 7"
# 48042aa09c2f878c4faa576948b07fe625c4707a: "syscall: remove Windows 7 console handle workaround"
# a17d959debdb04cd550016a3501dd09d50cd62e7: "runtime: always use LoadLibraryEx to load system libraries"
- name: Revert Golang1.25 commit for Windows7/8
if: ${{ matrix.jobs.goos == 'windows' && matrix.jobs.goversion == '' }}
run: |
cd $(go env GOROOT)
patch --verbose -p 1 < $GITHUB_WORKSPACE/.github/patch/go1.25.patch
- name: Set variables
if: ${{github.ref_name=='' || github.ref_type=='tag'}}
run: echo "VERSION=$(git describe --tags)" >> $GITHUB_ENV
shell: bash
# modify from https://github.com/restic/restic/issues/4636#issuecomment-1896455557
# this patch file only works on golang1.24.x
# that means after golang1.25 release it must be changed
# see: https://github.com/MetaCubeX/go/commits/release-branch.go1.24/
# revert:
# 693def151adff1af707d82d28f55dba81ceb08e1: "crypto/rand,runtime: switch RtlGenRandom for ProcessPrng"
# 7c1157f9544922e96945196b47b95664b1e39108: "net: remove sysSocket fallback for Windows 7"
# 48042aa09c2f878c4faa576948b07fe625c4707a: "syscall: remove Windows 7 console handle workaround"
# a17d959debdb04cd550016a3501dd09d50cd62e7: "runtime: always use LoadLibraryEx to load system libraries"
- name: Revert Golang1.24 commit for Windows7/8
if: ${{ matrix.jobs.goos == 'windows' && matrix.jobs.goversion == '1.24' }}
run: |
cd $(go env GOROOT)
patch --verbose -p 1 < $GITHUB_WORKSPACE/.github/patch/go1.24.patch
- name: Set ENV
run: |
sudo timedatectl set-timezone "Asia/Shanghai"
echo "NAME=clash.meta" >> $GITHUB_ENV
echo "REPO=${{ github.repository }}" >> $GITHUB_ENV
echo "ShortSHA=$(git rev-parse --short ${{ github.sha }})" >> $GITHUB_ENV
echo "BUILDTIME=$(date)" >> $GITHUB_ENV
echo "BRANCH=$(git rev-parse --abbrev-ref HEAD)" >> $GITHUB_ENV
shell: bash
# modify from https://github.com/restic/restic/issues/4636#issuecomment-1896455557
# this patch file only works on golang1.23.x
# that means after golang1.24 release it must be changed
# see: https://github.com/MetaCubeX/go/commits/release-branch.go1.23/
# revert:
# 693def151adff1af707d82d28f55dba81ceb08e1: "crypto/rand,runtime: switch RtlGenRandom for ProcessPrng"
# 7c1157f9544922e96945196b47b95664b1e39108: "net: remove sysSocket fallback for Windows 7"
# 48042aa09c2f878c4faa576948b07fe625c4707a: "syscall: remove Windows 7 console handle workaround"
# a17d959debdb04cd550016a3501dd09d50cd62e7: "runtime: always use LoadLibraryEx to load system libraries"
- name: Revert Golang1.23 commit for Windows7/8
if: ${{ matrix.jobs.goos == 'windows' && matrix.jobs.goversion == '1.23' }}
run: |
cd $(go env GOROOT)
patch --verbose -p 1 < $GITHUB_WORKSPACE/.github/patch/go1.23.patch
- name: Set ENV
run: |
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
shell: bash
# modify from https://github.com/restic/restic/issues/4636#issuecomment-1896455557
# this patch file only works on golang1.22.x
# that means after golang1.23 release it must be changed
# see: https://github.com/MetaCubeX/go/commits/release-branch.go1.22/
# revert:
# 693def151adff1af707d82d28f55dba81ceb08e1: "crypto/rand,runtime: switch RtlGenRandom for ProcessPrng"
# 7c1157f9544922e96945196b47b95664b1e39108: "net: remove sysSocket fallback for Windows 7"
# 48042aa09c2f878c4faa576948b07fe625c4707a: "syscall: remove Windows 7 console handle workaround"
# a17d959debdb04cd550016a3501dd09d50cd62e7: "runtime: always use LoadLibraryEx to load system libraries"
- name: Revert Golang1.22 commit for Windows7/8
if: ${{ matrix.jobs.goos == 'windows' && matrix.jobs.goversion == '1.22' }}
run: |
cd $(go env GOROOT)
patch --verbose -p 1 < $GITHUB_WORKSPACE/.github/patch/go1.22.patch
- name: Setup Go
uses: actions/setup-go@v4
with:
go-version: "1.20"
check-latest: true
# modify from https://github.com/restic/restic/issues/4636#issuecomment-1896455557
- name: Revert Golang1.21 commit for Windows7/8
if: ${{ matrix.jobs.goos == 'windows' && matrix.jobs.goversion == '1.21' }}
run: |
cd $(go env GOROOT)
patch --verbose -p 1 < $GITHUB_WORKSPACE/.github/patch/go1.21.patch
- name: Test
if: ${{ matrix.job.id=='1' && matrix.job.type=='WithoutCGO' }}
run: |
go test ./...
- name: Set variables
run: |
VERSION="${GITHUB_REF_NAME,,}-$(git rev-parse --short HEAD)"
VERSION="${VERSION//\//-}"
PackageVersion="$(curl -s "https://api.github.com/repos/MetaCubeX/mihomo/releases/latest" | jq -r '.tag_name' | sed 's/v//g' | awk -F '.' '{$NF = $NF + 1; print}' OFS='.').${VERSION/-/.}"
if [ -n "${{ github.event.inputs.version }}" ]; then
VERSION=${{ github.event.inputs.version }}
PackageVersion="${VERSION#v}"
fi
echo "VERSION=${VERSION}" >> $GITHUB_ENV
echo "PackageVersion=${PackageVersion}" >> $GITHUB_ENV
echo "BUILDTIME=$(date)" >> $GITHUB_ENV
echo "CGO_ENABLED=0" >> $GITHUB_ENV
echo "BUILDTAG=-extldflags --static" >> $GITHUB_ENV
echo "GOTOOLCHAIN=local" >> $GITHUB_ENV
- name: Build WithoutCGO
if: ${{ matrix.job.type=='WithoutCGO' }}
env:
NAME: Clash.Meta
BINDIR: bin
run: make -j$(($(nproc) + 1)) ${{ matrix.job.target }}
- name: Setup NDK
if: ${{ matrix.jobs.goos == 'android' }}
uses: nttld/setup-ndk@v1
id: setup-ndk
with:
ndk-version: r29-beta1
- uses: nttld/setup-ndk@v1
if: ${{ matrix.job.type=='WithCGO' && matrix.job.target=='android' }}
id: setup-ndk
with:
ndk-version: r25b
add-to-path: false
local-cache: true
- name: Set NDK path
if: ${{ matrix.jobs.goos == 'android' }}
run: |
echo "CC=${{steps.setup-ndk.outputs.ndk-path}}/toolchains/llvm/prebuilt/linux-x86_64/bin/${{matrix.jobs.ndk}}-clang" >> $GITHUB_ENV
echo "CGO_ENABLED=1" >> $GITHUB_ENV
echo "BUILDTAG=" >> $GITHUB_ENV
- name: Build Android
if: ${{ matrix.job.type=='WithCGO' && matrix.job.target=='android' }}
env:
ANDROID_NDK_HOME: ${{ steps.setup-ndk.outputs.ndk-path }}
run: |
mkdir bin
CC=${ANDROID_NDK_HOME}/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android33-clang
CGO_ENABLED=1 CC=${CC} GOARCH=arm64 GOOS=android go build -tags ${TAGS} -trimpath -ldflags "${LDFLAGS}" -o bin/${NAME}-android-arm64
- name: Test
if: ${{ matrix.jobs.test == 'test' }}
run: |
go test ./...
echo "---test with_gvisor---"
go test ./... -tags "with_gvisor" -count=1
- name: Set up xgo
if: ${{ matrix.job.type=='WithCGO' && matrix.job.target!='android' }}
run: |
docker pull techknowlogick/xgo:latest
go install src.techknowlogick.com/xgo@latest
- name: Update CA
run: |
sudo apt-get update && sudo apt-get install ca-certificates
sudo update-ca-certificates
cp -f /etc/ssl/certs/ca-certificates.crt component/ca/ca-certificates.crt
- name: Build by xgo
if: ${{ matrix.job.type=='WithCGO' && matrix.job.target!='android' }}
env:
ANDROID_NDK_HOME: ${{ steps.setup-ndk.outputs.ndk-path }}
run: |
mkdir bin
xgo --targets="${{ matrix.job.target }}" --tags="${TAGS}" -ldflags="${LDFLAGS}" --out bin/${NAME} ./
- name: Build core
env:
GOOS: ${{matrix.jobs.goos}}
GOARCH: ${{matrix.jobs.goarch}}
GOAMD64: ${{matrix.jobs.goamd64}}
GO386: ${{matrix.jobs.go386}}
GOARM: ${{matrix.jobs.goarm}}
GOMIPS: ${{matrix.jobs.gomips}}
run: |
go env
go build -v -tags "with_gvisor" -trimpath -ldflags "${BUILDTAG} -X 'github.com/metacubex/mihomo/constant.Version=${VERSION}' -X 'github.com/metacubex/mihomo/constant.BuildTime=${BUILDTIME}' -w -s -buildid="
if [ "${{matrix.jobs.goos}}" = "windows" ]; then
cp mihomo.exe mihomo-${{matrix.jobs.goos}}-${{matrix.jobs.output}}.exe
zip -r mihomo-${{matrix.jobs.goos}}-${{matrix.jobs.output}}-${VERSION}.zip mihomo-${{matrix.jobs.goos}}-${{matrix.jobs.output}}.exe
else
cp mihomo mihomo-${{matrix.jobs.goos}}-${{matrix.jobs.output}}
gzip -c mihomo-${{matrix.jobs.goos}}-${{matrix.jobs.output}} > mihomo-${{matrix.jobs.goos}}-${{matrix.jobs.output}}-${VERSION}.gz
rm mihomo-${{matrix.jobs.goos}}-${{matrix.jobs.output}}
fi
- name: Rename
if: ${{ matrix.job.type=='WithCGO' }}
run: |
cd bin
ls -la
cp ../.github/rename-cgo.sh ./
bash ./rename-cgo.sh
rm ./rename-cgo.sh
ls -la
cd ..
- name: Package DEB
if: matrix.jobs.debian != ''
run: |
set -xeuo pipefail
sudo gem install fpm
cp .github/release/.fpm_systemd .fpm
- name: Zip
if: ${{ success() }}
run: |
cd bin
ls -la
chmod +x *
cp ../.github/release.sh ./
bash ./release.sh
rm ./release.sh
ls -la
cd ..
fpm -t deb \
-v "${PackageVersion}" \
-p "mihomo-${{matrix.jobs.goos}}-${{matrix.jobs.output}}-${VERSION}.deb" \
--architecture ${{ matrix.jobs.debian }} \
mihomo=/usr/bin/mihomo
- name: Save version
run: echo ${VERSION} > bin/version.txt
shell: bash
- name: Package RPM
if: matrix.jobs.rpm != ''
run: |
set -xeuo pipefail
sudo gem install fpm
cp .github/release/.fpm_systemd .fpm
- uses: actions/upload-artifact@v3
if: ${{ success() }}
with:
name: artifact
path: bin/
fpm -t rpm \
-v "${PackageVersion}" \
-p "mihomo-${{matrix.jobs.goos}}-${{matrix.jobs.output}}-${VERSION}.rpm" \
--architecture ${{ matrix.jobs.rpm }} \
mihomo=/usr/bin/mihomo
- name: Package Pacman
if: matrix.jobs.pacman != ''
run: |
set -xeuo pipefail
sudo gem install fpm
sudo apt-get update && sudo apt-get install -y libarchive-tools
cp .github/release/.fpm_systemd .fpm
fpm -t pacman \
-v "${PackageVersion}" \
-p "mihomo-${{matrix.jobs.goos}}-${{matrix.jobs.output}}-${VERSION}.pkg.tar.zst" \
--architecture ${{ matrix.jobs.pacman }} \
mihomo=/usr/bin/mihomo
- name: Save version
run: |
echo ${VERSION} > version.txt
shell: bash
- name: Archive production artifacts
uses: actions/upload-artifact@v4
with:
name: "${{ matrix.jobs.goos }}-${{ matrix.jobs.output }}"
path: |
mihomo*.gz
mihomo*.deb
mihomo*.rpm
mihomo*.pkg.tar.zst
mihomo*.zip
version.txt
checksums.txt
Upload-Prerelease:
permissions: write-all
if: ${{ github.ref_type=='branch' }}
needs: [Build]
if: ${{ github.event_name != 'workflow_dispatch' && github.ref_type == 'branch' && !startsWith(github.event_name, 'pull_request') }}
needs: [build]
runs-on: ubuntu-latest
steps:
- uses: actions/download-artifact@v3
with:
name: artifact
path: bin/
- name: Download all workflow run artifacts
uses: actions/download-artifact@v4
with:
path: bin/
merge-multiple: true
- name: Display structure of downloaded files
run: ls -R
working-directory: bin
- name: Calculate checksums
run: |
cd bin/
find . -type f -not -name "checksums.*" -not -name "version.txt" | sort | xargs sha256sum > checksums.txt
cat checksums.txt
shell: bash
- name: Delete current release assets
uses: 8Mi-Tech/delete-release-assets-action@main
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
tag: Prerelease-${{ github.ref_name }}
deleteOnlyFromDrafts: false
- name: Delete current release assets
uses: 8Mi-Tech/delete-release-assets-action@main
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
tag: Prerelease-${{ github.ref_name }}
deleteOnlyFromDrafts: false
- name: Set Env
run: |
echo "BUILDTIME=$(TZ=Asia/Shanghai date)" >> $GITHUB_ENV
shell: bash
- name: Set Env
run: |
echo "BUILDTIME=$(TZ=Asia/Shanghai date)" >> $GITHUB_ENV
shell: bash
- name: Tag Repo
uses: richardsimko/update-tag@v1
with:
tag_name: Prerelease-${{ github.ref_name }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- run: |
cat > release.txt << 'EOF'
Release created at ${{ env.BUILDTIME }}
Synchronize ${{ github.ref_name }} branch code updates, keeping only the latest version
<br>
[我应该下载哪个文件? / Which file should I download?](https://github.com/MetaCubeX/mihomo/wiki/FAQ)
[二进制文件筛选 / Binary file selector](https://metacubex.github.io/Meta-Docs/startup/#_1)
[查看文档 / Docs](https://metacubex.github.io/Meta-Docs/)
EOF
- name: Tag Repo
uses: richardsimko/update-tag@v1.0.6
with:
tag_name: Prerelease-${{ github.ref_name }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- run: |
cat > release.txt << 'EOF'
Release created at ${{ env.BUILDTIME }}
Synchronize ${{ github.ref_name }} branch code updates, keeping only the latest version
<br>
[我应该下载哪个文件? / Which file should I download?](https://github.com/MetaCubeX/Clash.Meta/wiki/FAQ)
[查看文档 / Docs](https://metacubex.github.io/Meta-Docs/)
EOF
- name: Upload Prerelease
uses: softprops/action-gh-release@v1
if: ${{ success() }}
with:
tag_name: Prerelease-${{ github.ref_name }}
files: |
bin/*
prerelease: true
generate_release_notes: true
body_path: release.txt
- name: Upload Prerelease
uses: softprops/action-gh-release@v1
if: ${{ success() }}
with:
tag_name: Prerelease-${{ github.ref_name }}
files: |
bin/*
prerelease: true
generate_release_notes: true
body_path: release.txt
Upload-Release:
permissions: write-all
if: ${{ github.ref_type=='tag' }}
needs: [Build]
if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.version != '' }}
needs: [build]
runs-on: ubuntu-latest
steps:
- uses: actions/download-artifact@v3
- name: Checkout
uses: actions/checkout@v5
with:
name: artifact
path: bin/
ref: Meta
fetch-depth: '0'
fetch-tags: 'true'
- name: Get tags
run: |
echo "CURRENTVERSION=${{ github.event.inputs.version }}" >> $GITHUB_ENV
git fetch --tags
echo "PREVERSION=$(git describe --tags --abbrev=0 HEAD)" >> $GITHUB_ENV
- name: Force push Alpha branch to Meta
run: |
git config --global user.email "github-actions[bot]@users.noreply.github.com"
git config --global user.name "github-actions[bot]"
git fetch origin Alpha:Alpha
git push origin Alpha:Meta --force
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Tag the commit on Alpha
run: |
git checkout Alpha
git tag ${{ github.event.inputs.version }}
git push origin ${{ github.event.inputs.version }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Generate release notes
run: |
cp ./.github/genReleaseNote.sh ./
bash ./genReleaseNote.sh -v ${PREVERSION}...${CURRENTVERSION}
rm ./genReleaseNote.sh
- uses: actions/download-artifact@v4
with:
path: bin/
merge-multiple: true
- name: Display structure of downloaded files
run: ls -R
working-directory: bin
- name: Upload Release
uses: softprops/action-gh-release@v1
if: ${{ success() }}
uses: softprops/action-gh-release@v2
if: ${{ success() }}
with:
tag_name: ${{ github.ref_name }}
tag_name: ${{ github.event.inputs.version }}
files: bin/*
generate_release_notes: true
body_path: release.md
Docker:
if: ${{ !startsWith(github.event_name, 'pull_request') }}
permissions: write-all
needs: [Build]
needs: [build]
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v3
uses: actions/checkout@v5
with:
fetch-depth: 0
- uses: actions/download-artifact@v3
- uses: actions/download-artifact@v4
with:
name: artifact
path: bin/
merge-multiple: true
- name: Display structure of downloaded files
run: ls -R
working-directory: bin
- name: Set up QEMU
uses: docker/setup-qemu-action@v2
uses: docker/setup-qemu-action@v3
- name: Setup Docker buildx
uses: docker/setup-buildx-action@v2
uses: docker/setup-buildx-action@v3
with:
version: latest
# Extract metadata (tags, labels) for Docker
# https://github.com/docker/metadata-action
- name: Extract Docker metadata
id: meta
uses: docker/metadata-action@v4
if: ${{ github.event_name != 'workflow_dispatch' }}
id: meta_alpha
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ secrets.DOCKERHUB_ACCOUNT }}/${{secrets.DOCKERHUB_REPO}}
images: '${{ env.REGISTRY }}/${{ github.repository }}'
# Extract metadata (tags, labels) for Docker
# https://github.com/docker/metadata-action
- name: Extract Docker metadata
if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.version != '' }}
id: meta_release
uses: docker/metadata-action@v5
with:
images: '${{ env.REGISTRY }}/${{ github.repository }}'
tags: |
${{ github.event.inputs.version }}
flavor: |
latest=true
labels: org.opencontainers.image.version=${{ github.event.inputs.version }}
- name: Show files
run: |
ls .
ls bin/
- name: Log into registry
if: github.event_name != 'pull_request'
uses: docker/login-action@v2
- name: login to docker REGISTRY
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ secrets.DOCKER_HUB_USER }}
@@ -333,8 +539,8 @@ jobs:
# Build and push Docker image with Buildx (don't push on PR)
# https://github.com/docker/build-push-action
- name: Build and push Docker image
id: build-and-push
uses: docker/build-push-action@v4
if: ${{ github.event_name != 'workflow_dispatch' }}
uses: docker/build-push-action@v5
with:
context: .
file: ./Dockerfile
@@ -342,8 +548,22 @@ jobs:
platforms: |
linux/386
linux/amd64
linux/arm64/v8
linux/arm64
linux/arm/v7
# linux/riscv64
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
tags: ${{ steps.meta_alpha.outputs.tags }}
labels: ${{ steps.meta_alpha.outputs.labels }}
- name: Build and push Docker image
if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.version != '' }}
uses: docker/build-push-action@v5
with:
context: .
file: ./Dockerfile
push: ${{ github.event_name != 'pull_request' }}
platforms: |
linux/386
linux/amd64
linux/arm64
linux/arm/v7
tags: ${{ steps.meta_release.outputs.tags }}
labels: ${{ steps.meta_release.outputs.labels }}

66
.github/workflows/test.yml vendored Normal file
View File

@@ -0,0 +1,66 @@
name: Test
on:
push:
paths-ignore:
- "docs/**"
- "README.md"
- ".github/ISSUE_TEMPLATE/**"
branches:
- Alpha
tags:
- "v*"
pull_request:
branches:
- Alpha
jobs:
test:
strategy:
matrix:
os:
- 'ubuntu-latest' # amd64 linux
- 'windows-latest' # amd64 windows
- 'macos-latest' # arm64 macos
- 'ubuntu-24.04-arm' # arm64 linux
- 'macos-15-intel' # amd64 macos
go-version:
- '1.25'
- '1.24'
- '1.23'
- '1.22'
- '1.21'
- '1.20'
fail-fast: false
runs-on: ${{ matrix.os }}
defaults:
run:
shell: bash
env:
CGO_ENABLED: 0
GOTOOLCHAIN: local
# Fix mingw trying to be smart and converting paths https://github.com/moby/moby/issues/24029#issuecomment-250412919
MSYS_NO_PATHCONV: true
steps:
- uses: actions/checkout@v5
- name: Setup Go
uses: actions/setup-go@v6
with:
go-version: ${{ matrix.go-version }}
- name: Revert Golang commit for Windows7/8
if: ${{ runner.os == 'Windows' && matrix.go-version != '1.20' }}
run: |
cd $(go env GOROOT)
patch --verbose -p 1 < $GITHUB_WORKSPACE/.github/patch/go${{matrix.go-version}}.patch
- name: Remove inbound test for macOS
if: ${{ runner.os == 'macOS' }}
run: |
rm -rf listener/inbound/*_test.go
- name: Test
run: go test ./... -v -count=1
- name: Test with tag with_gvisor
run: go test ./... -v -count=1 -tags "with_gvisor"

View File

@@ -0,0 +1,30 @@
name: Trigger CMFA Update
on:
workflow_dispatch:
push:
paths-ignore:
- "docs/**"
- "README.md"
- ".github/ISSUE_TEMPLATE/**"
branches:
- Alpha
tags:
- "v*"
jobs:
# Send "core-updated" to MetaCubeX/ClashMetaForAndroid 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/ClashMetaForAndroid/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
sections:
- standard
- prefix(github.com/Dreamacro/clash)
- prefix(github.com/metacubex/mihomo)
- default
staticcheck:
go: '1.19'

View File

@@ -3,25 +3,24 @@ ARG TARGETPLATFORM
RUN echo "I'm building for $TARGETPLATFORM"
RUN apk add --no-cache gzip && \
mkdir /clash-config && \
wget -O /clash-config/Country.mmdb https://raw.githubusercontent.com/Loyalsoldier/geoip/release/Country.mmdb && \
wget -O /clash-config/geosite.dat https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat && \
wget -O /clash-config/geoip.dat https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat
mkdir /mihomo-config && \
wget -O /mihomo-config/geoip.metadb https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/geoip.metadb && \
wget -O /mihomo-config/geosite.dat https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/geosite.dat && \
wget -O /mihomo-config/geoip.dat https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/geoip.dat
COPY docker/file-name.sh /clash/file-name.sh
WORKDIR /clash
COPY docker/file-name.sh /mihomo/file-name.sh
WORKDIR /mihomo
COPY bin/ bin/
RUN FILE_NAME=`sh file-name.sh` && 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
FILE_NAME=`ls bin/ | egrep "$FILE_NAME.gz"|awk NR==1` && echo $FILE_NAME && \
mv bin/$FILE_NAME mihomo.gz && gzip -d mihomo.gz && chmod +x mihomo && echo "$FILE_NAME" > /mihomo-config/test
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
VOLUME ["/root/.config/clash/"]
VOLUME ["/root/.config/mihomo/"]
COPY --from=builder /clash-config/ /root/.config/clash/
COPY --from=builder /clash/clash /clash
RUN chmod +x /clash
ENTRYPOINT [ "/clash" ]
COPY --from=builder /mihomo-config/ /root/.config/mihomo/
COPY --from=builder /mihomo/mihomo /mihomo
ENTRYPOINT [ "/mihomo" ]

View File

@@ -1,4 +1,4 @@
NAME=clash.meta
NAME=mihomo
BINDIR=bin
BRANCH=$(shell git branch --show-current)
ifeq ($(BRANCH),Alpha)
@@ -12,15 +12,24 @@ VERSION=$(shell git rev-parse --short HEAD)
endif
BUILDTIME=$(shell date -u)
GOBUILD=CGO_ENABLED=0 go build -tags with_gvisor -trimpath -ldflags '-X "github.com/Dreamacro/clash/constant.Version=$(VERSION)" \
-X "github.com/Dreamacro/clash/constant.BuildTime=$(BUILDTIME)" \
GOBUILD=CGO_ENABLED=0 go build -tags with_gvisor -trimpath -ldflags '-X "github.com/metacubex/mihomo/constant.Version=$(VERSION)" \
-X "github.com/metacubex/mihomo/constant.BuildTime=$(BUILDTIME)" \
-w -s -buildid='
PLATFORM_LIST = \
darwin-386 \
darwin-amd64-compatible \
darwin-amd64 \
darwin-amd64-v1 \
darwin-amd64-v2 \
darwin-amd64-v3 \
darwin-arm64 \
linux-386 \
linux-amd64-compatible \
linux-amd64 \
linux-amd64-v1 \
linux-amd64-v2 \
linux-amd64-v3 \
linux-armv5 \
linux-armv6 \
linux-armv7 \
@@ -31,6 +40,8 @@ PLATFORM_LIST = \
linux-mips-hardfloat \
linux-mipsle-softfloat \
linux-mipsle-hardfloat \
linux-riscv64 \
linux-loong64 \
android-arm64 \
freebsd-386 \
freebsd-amd64 \
@@ -40,37 +51,61 @@ WINDOWS_ARCH_LIST = \
windows-386 \
windows-amd64-compatible \
windows-amd64 \
windows-amd64-v1 \
windows-amd64-v2 \
windows-amd64-v3 \
windows-arm64 \
windows-arm32v7
all:linux-amd64 linux-arm64\
darwin-amd64 darwin-arm64\
windows-amd64 windows-arm64\
all:linux-amd64-v3 linux-arm64\
darwin-amd64-v3 darwin-arm64\
windows-amd64-v3 windows-arm64\
darwin-all: darwin-amd64 darwin-arm64
darwin-all: darwin-amd64-v3 darwin-arm64
docker:
GOAMD64=v1 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
darwin-amd64:
GOARCH=amd64 GOOS=darwin GOAMD64=v3 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
darwin-386:
GOARCH=386 GOOS=darwin $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
darwin-amd64-compatible:
GOARCH=amd64 GOOS=darwin GOAMD64=v1 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
darwin-amd64:
GOARCH=amd64 GOOS=darwin GOAMD64=v3 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
darwin-amd64-v1:
GOARCH=amd64 GOOS=darwin GOAMD64=v1 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
darwin-amd64-v2:
GOARCH=amd64 GOOS=darwin GOAMD64=v2 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
darwin-amd64-v3:
GOARCH=amd64 GOOS=darwin GOAMD64=v3 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
darwin-arm64:
GOARCH=arm64 GOOS=darwin $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
linux-386:
GOARCH=386 GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
linux-amd64-compatible:
GOARCH=amd64 GOOS=linux GOAMD64=v1 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
linux-amd64:
GOARCH=amd64 GOOS=linux GOAMD64=v3 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
linux-amd64-compatible:
linux-amd64-v1:
GOARCH=amd64 GOOS=linux GOAMD64=v1 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
linux-amd64-v2:
GOARCH=amd64 GOOS=linux GOAMD64=v2 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
linux-amd64-v3:
GOARCH=amd64 GOOS=linux GOAMD64=v3 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
linux-arm64:
GOARCH=arm64 GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
@@ -103,6 +138,9 @@ linux-mips64le:
linux-riscv64:
GOARCH=riscv64 GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
linux-loong64:
GOARCH=loong64 GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
android-arm64:
GOARCH=arm64 GOOS=android $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
@@ -119,12 +157,21 @@ freebsd-arm64:
windows-386:
GOARCH=386 GOOS=windows $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.exe
windows-amd64-compatible:
GOARCH=amd64 GOOS=windows GOAMD64=v1 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.exe
windows-amd64:
GOARCH=amd64 GOOS=windows GOAMD64=v3 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.exe
windows-amd64-compatible:
windows-amd64-v1:
GOARCH=amd64 GOOS=windows GOAMD64=v1 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.exe
windows-amd64-v2:
GOARCH=amd64 GOOS=windows GOAMD64=v2 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.exe
windows-amd64-v3:
GOARCH=amd64 GOOS=windows GOAMD64=v3 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.exe
windows-arm64:
GOARCH=arm64 GOOS=windows $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.exe
@@ -157,7 +204,3 @@ clean:
CLANG ?= clang-14
CFLAGS := -O2 -g -Wall -Werror $(CFLAGS)
ebpf: export BPF_CLANG := $(CLANG)
ebpf: export BPF_CFLAGS := $(CFLAGS)
ebpf:
cd component/ebpf/ && go generate ./...

340
README.md
View File

@@ -3,17 +3,17 @@
<br>Meta Kernel<br>
</h1>
<h3 align="center">Another Clash Kernel.</h3>
<h3 align="center">Another Mihomo Kernel.</h3>
<p align="center">
<a href="https://goreportcard.com/report/github.com/Clash-Mini/Clash.Meta">
<img src="https://goreportcard.com/badge/github.com/Clash-Mini/Clash.Meta?style=flat-square">
<a href="https://goreportcard.com/report/github.com/MetaCubeX/mihomo">
<img src="https://goreportcard.com/badge/github.com/MetaCubeX/mihomo?style=flat-square">
</a>
<img src="https://img.shields.io/github/go-mod/go-version/Dreamacro/clash?style=flat-square">
<a href="https://github.com/Clash-Mini/Clash.Meta/releases">
<img src="https://img.shields.io/github/release/Clash-Mini/Clash.Meta/all.svg?style=flat-square">
<img src="https://img.shields.io/github/go-mod/go-version/MetaCubeX/mihomo/Alpha?style=flat-square">
<a href="https://github.com/MetaCubeX/mihomo/releases">
<img src="https://img.shields.io/github/release/MetaCubeX/mihomo/all.svg?style=flat-square">
</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">
</a>
</p>
@@ -21,261 +21,52 @@
## Features
- 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.
- Rules based off domains, GEOIP, IPCIDR or Process to forward packets to different nodes
- Remote groups allow users to implement powerful rules. Supports automatic fallback, load balancing or auto select node based off latency
- Remote providers, allowing users to get node lists remotely instead of hardcoding in config
- Netfilter TCP redirecting. Deploy Clash on your Internet gateway with `iptables`.
- Remote groups allow users to implement powerful rules. Supports automatic fallback, load balancing or auto select node
based off latency
- Remote providers, allowing users to get node lists remotely instead of hard-coding in config
- Netfilter TCP redirecting. Deploy Mihomo on your Internet gateway with `iptables`.
- Comprehensive HTTP RESTful API controller
## Wiki
Configuration examples can be found 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).
## Dashboard
## Build
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).
You should install [golang](https://go.dev) first.
## Configration example
Then get the source code of Clash.Meta:
Configuration example is located at [/docs/config.yaml](https://github.com/MetaCubeX/mihomo/blob/Alpha/docs/config.yaml).
## Docs
Documentation can be found in [mihomo Docs](https://wiki.metacubex.one/).
## For development
Requirements:
[Go 1.20 or newer](https://go.dev/dl/)
Build mihomo:
```shell
git clone https://github.com/MetaCubeX/Clash.Meta.git
cd Clash.Meta && go mod download
git clone https://github.com/MetaCubeX/mihomo.git
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
go env -w GOPROXY=https://goproxy.io,direct
```
Now you can build it:
```shell
go build
```
If you need gvisor for tun stack, build with:
Build with gvisor tun stack:
```shell
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
Work on Linux OS which supported `iptables`
@@ -289,71 +80,10 @@ iptables:
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 clashd on system startup with:
```shell
$ systemctl enable Clash-Meta
```
Launch clashd immediately with:
```shell
$ systemctl start Clash-Meta
```
### Display Process name
Clash add field `Process` to `Metadata` and prepare to get process name for Restful API `GET /connections`.
To display process name in GUI please use [Razord-meta](https://github.com/MetaCubeX/Razord-meta).
### Dashboard
We also made a custom fork of yacd provide better support for this project, check it out at [Yacd-meta](https://github.com/MetaCubeX/Yacd-meta)
## Development
If you want to build an application that uses clash as a library, check out the
the [GitHub Wiki](https://github.com/Dreamacro/clash/wiki/use-clash-as-a-library)
## Debugging
Check [wiki](https://github.com/MetaCubeX/Clash.Meta/wiki/How-to-use-debug-api) to get an instruction on using debug API.
Check [wiki](https://wiki.metacubex.one/api/#debug) to get an instruction on using debug
API.
## Credits
@@ -368,4 +98,4 @@ Check [wiki](https://github.com/MetaCubeX/Clash.Meta/wiki/How-to-use-debug-api)
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)
**In addition, any downstream projects not affiliated with `MetaCubeX` shall not contain the word `mihomo` in their names.**

View File

@@ -6,52 +6,60 @@ import (
"fmt"
"net"
"net/http"
"net/netip"
"net/url"
"strings"
"time"
"github.com/Dreamacro/clash/common/atomic"
"github.com/Dreamacro/clash/common/queue"
"github.com/Dreamacro/clash/component/dialer"
C "github.com/Dreamacro/clash/constant"
"github.com/metacubex/mihomo/common/atomic"
"github.com/metacubex/mihomo/common/queue"
"github.com/metacubex/mihomo/common/utils"
"github.com/metacubex/mihomo/common/xsync"
"github.com/metacubex/mihomo/component/ca"
C "github.com/metacubex/mihomo/constant"
"github.com/metacubex/mihomo/log"
)
var UnifiedDelay = atomic.NewBool(false)
type Proxy struct {
C.ProxyAdapter
const (
defaultHistoriesNum = 10
)
type internalProxyState struct {
alive atomic.Bool
history *queue.Queue[C.DelayHistory]
alive *atomic.Bool
}
// Alive implements C.Proxy
func (p *Proxy) Alive() bool {
type Proxy struct {
C.ProxyAdapter
alive atomic.Bool
history *queue.Queue[C.DelayHistory]
extra xsync.Map[string, *internalProxyState]
}
// Adapter implements C.Proxy
func (p *Proxy) Adapter() C.ProxyAdapter {
return p.ProxyAdapter
}
// AliveForTestUrl implements C.Proxy
func (p *Proxy) AliveForTestUrl(url string) bool {
if state, ok := p.extra.Load(url); ok {
return state.alive.Load()
}
return p.alive.Load()
}
// Dial implements C.Proxy
func (p *Proxy) Dial(metadata *C.Metadata) (C.Conn, error) {
ctx, cancel := context.WithTimeout(context.Background(), C.DefaultTCPTimeout)
defer cancel()
return p.DialContext(ctx, metadata)
}
// DialContext implements C.ProxyAdapter
func (p *Proxy) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.Conn, error) {
conn, err := p.ProxyAdapter.DialContext(ctx, metadata, opts...)
func (p *Proxy) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
conn, err := p.ProxyAdapter.DialContext(ctx, metadata)
return conn, err
}
// DialUDP implements C.ProxyAdapter
func (p *Proxy) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
ctx, cancel := context.WithTimeout(context.Background(), C.DefaultUDPTimeout)
defer cancel()
return p.ListenPacketContext(ctx, metadata)
}
// ListenPacketContext implements C.ProxyAdapter
func (p *Proxy) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) {
pc, err := p.ProxyAdapter.ListenPacketContext(ctx, metadata, opts...)
func (p *Proxy) ListenPacketContext(ctx context.Context, metadata *C.Metadata) (C.PacketConn, error) {
pc, err := p.ProxyAdapter.ListenPacketContext(ctx, metadata)
return pc, err
}
@@ -65,17 +73,60 @@ func (p *Proxy) DelayHistory() []C.DelayHistory {
return histories
}
// LastDelay return last history record. if proxy is not alive, return the max value of uint16.
// DelayHistoryForTestUrl implements C.Proxy
func (p *Proxy) DelayHistoryForTestUrl(url string) []C.DelayHistory {
var queueM []C.DelayHistory
if state, ok := p.extra.Load(url); ok {
queueM = state.history.Copy()
}
histories := []C.DelayHistory{}
for _, item := range queueM {
histories = append(histories, item)
}
return histories
}
// ExtraDelayHistories return all delay histories for each test URL
// implements C.Proxy
func (p *Proxy) LastDelay() (delay uint16) {
var max uint16 = 0xffff
if !p.alive.Load() {
return max
func (p *Proxy) ExtraDelayHistories() map[string]C.ProxyState {
histories := map[string]C.ProxyState{}
p.extra.Range(func(k string, v *internalProxyState) bool {
testUrl := k
state := v
queueM := state.history.Copy()
var history []C.DelayHistory
for _, item := range queueM {
history = append(history, item)
}
histories[testUrl] = C.ProxyState{
Alive: state.alive.Load(),
History: history,
}
return true
})
return histories
}
// LastDelayForTestUrl return last history record of the specified URL. if proxy is not alive, return the max value of uint16.
// implements C.Proxy
func (p *Proxy) LastDelayForTestUrl(url string) (delay uint16) {
var maxDelay uint16 = 0xffff
alive := false
var history C.DelayHistory
if state, ok := p.extra.Load(url); ok {
alive = state.alive.Load()
history = state.history.Last()
}
history := p.history.Last()
if history.Delay == 0 {
return max
if !alive || history.Delay == 0 {
return maxDelay
}
return history.Delay
}
@@ -90,26 +141,62 @@ func (p *Proxy) MarshalJSON() ([]byte, error) {
mapping := map[string]any{}
_ = json.Unmarshal(inner, &mapping)
mapping["history"] = p.DelayHistory()
mapping["extra"] = p.ExtraDelayHistories()
mapping["alive"] = p.alive.Load()
mapping["name"] = p.Name()
mapping["udp"] = p.SupportUDP()
mapping["xudp"] = p.SupportXUDP()
mapping["tfo"] = p.SupportTFO()
mapping["uot"] = p.SupportUOT()
proxyInfo := p.ProxyInfo()
mapping["xudp"] = proxyInfo.XUDP
mapping["tfo"] = proxyInfo.TFO
mapping["mptcp"] = proxyInfo.MPTCP
mapping["smux"] = proxyInfo.SMUX
mapping["interface"] = proxyInfo.Interface
mapping["dialer-proxy"] = proxyInfo.DialerProxy
mapping["routing-mark"] = proxyInfo.RoutingMark
return json.Marshal(mapping)
}
// URLTest get the delay for the specified URL
// implements C.Proxy
func (p *Proxy) URLTest(ctx context.Context, url string) (t uint16, err error) {
func (p *Proxy) URLTest(ctx context.Context, url string, expectedStatus utils.IntRanges[uint16]) (t uint16, err error) {
var satisfied bool
defer func() {
p.alive.Store(err == nil)
alive := err == nil
record := C.DelayHistory{Time: time.Now()}
if err == nil {
if alive {
record.Delay = t
}
p.alive.Store(alive)
p.history.Put(record)
if p.history.Len() > 10 {
if p.history.Len() > defaultHistoriesNum {
p.history.Pop()
}
state, ok := p.extra.Load(url)
if !ok {
state = &internalProxyState{
history: queue.New[C.DelayHistory](defaultHistoriesNum),
alive: atomic.NewBool(true),
}
p.extra.Store(url, state)
}
if !satisfied {
record.Delay = 0
alive = false
}
state.alive.Store(alive)
state.history.Put(record)
if state.history.Len() > defaultHistoriesNum {
state.history.Pop()
}
}()
unifiedDelay := UnifiedDelay.Load()
@@ -134,6 +221,11 @@ func (p *Proxy) URLTest(ctx context.Context, url string) (t uint16, err error) {
}
req = req.WithContext(ctx)
tlsConfig, err := ca.GetTLSConfig(ca.Option{})
if err != nil {
return
}
transport := &http.Transport{
DialContext: func(context.Context, string, string) (net.Conn, error) {
return instance, nil
@@ -143,6 +235,7 @@ func (p *Proxy) URLTest(ctx context.Context, url string) (t uint16, err error) {
IdleConnTimeout: 90 * time.Second,
TLSHandshakeTimeout: 10 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
TLSClientConfig: tlsConfig,
}
client := http.Client{
@@ -165,19 +258,32 @@ func (p *Proxy) URLTest(ctx context.Context, url string) (t uint16, err error) {
if unifiedDelay {
second := time.Now()
resp, err = client.Do(req)
if err == nil {
var ignoredErr error
var secondResp *http.Response
secondResp, ignoredErr = client.Do(req)
if ignoredErr == nil {
resp = secondResp
_ = resp.Body.Close()
start = second
} else {
if strings.HasPrefix(url, "http://") {
log.Errorln("%s failed to get the second response from %s: %v", p.Name(), url, ignoredErr)
log.Warnln("It is recommended to use HTTPS for provider.health-check.url and group.url to ensure better reliability. Due to some proxy providers hijacking test addresses and not being compatible with repeated HEAD requests, using HTTP may result in failed tests.")
}
}
}
satisfied = resp != nil && (expectedStatus == nil || expectedStatus.Check(uint16(resp.StatusCode)))
t = uint16(time.Since(start) / time.Millisecond)
return
}
func NewProxy(adapter C.ProxyAdapter) *Proxy {
return &Proxy{adapter, queue.New[C.DelayHistory](10), atomic.NewBool(true)}
return &Proxy{
ProxyAdapter: adapter,
history: queue.New[C.DelayHistory](defaultHistoriesNum),
alive: atomic.NewBool(true),
}
}
func urlToMetadata(rawURL string) (addr C.Metadata, err error) {
@@ -199,10 +305,6 @@ func urlToMetadata(rawURL string) (addr C.Metadata, err error) {
}
}
addr = C.Metadata{
Host: u.Hostname(),
DstIP: netip.Addr{},
DstPort: port,
}
err = addr.SetRemoteAddress(net.JoinHostPort(u.Hostname(), port))
return
}

View File

@@ -1,13 +1,17 @@
package inbound
import (
C "github.com/Dreamacro/clash/constant"
"net"
C "github.com/metacubex/mihomo/constant"
)
type Addition func(metadata *C.Metadata)
func (a Addition) Apply(metadata *C.Metadata) {
a(metadata)
func ApplyAdditions(metadata *C.Metadata, additions ...Addition) {
for _, addition := range additions {
addition(metadata)
}
}
func WithInName(name string) Addition {
@@ -33,3 +37,37 @@ func WithSpecialProxy(specialProxy string) Addition {
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
}
}
}
func WithDSCP(dscp uint8) Addition {
return func(metadata *C.Metadata) {
metadata.DSCP = dscp
}
}
func Placeholder(metadata *C.Metadata) {}

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

@@ -0,0 +1,38 @@
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 {
return prefixesContains(skipAuthPrefixes, addr)
}

View File

@@ -3,26 +3,18 @@ package inbound
import (
"net"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/context"
"github.com/Dreamacro/clash/transport/socks5"
C "github.com/metacubex/mihomo/constant"
"github.com/metacubex/mihomo/transport/socks5"
)
// 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.NetWork = C.TCP
metadata.Type = C.HTTP
for _, addition := range additions {
addition.Apply(metadata)
}
if ip, port, err := parseAddr(source); 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)
metadata.RawSrcAddr = srcConn.RemoteAddr()
metadata.RawDstAddr = srcConn.LocalAddr()
ApplyAdditions(metadata, WithSrcAddr(srcConn.RemoteAddr()), WithInAddr(srcConn.LocalAddr()))
ApplyAdditions(metadata, additions...)
return conn, metadata
}

View File

@@ -4,24 +4,16 @@ import (
"net"
"net/http"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/context"
C "github.com/metacubex/mihomo/constant"
)
// 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.Type = C.HTTPS
for _, addition := range additions {
addition.Apply(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)
metadata.RawSrcAddr = conn.RemoteAddr()
metadata.RawDstAddr = conn.LocalAddr()
ApplyAdditions(metadata, WithSrcAddr(conn.RemoteAddr()), WithInAddr(conn.LocalAddr()))
ApplyAdditions(metadata, additions...)
return conn, metadata
}

View File

@@ -0,0 +1,47 @@
package inbound
import (
"net"
"net/netip"
C "github.com/metacubex/mihomo/constant"
)
var lanAllowedIPs []netip.Prefix
var lanDisAllowedIPs []netip.Prefix
func SetAllowedIPs(prefixes []netip.Prefix) {
lanAllowedIPs = prefixes
}
func SetDisAllowedIPs(prefixes []netip.Prefix) {
lanDisAllowedIPs = prefixes
}
func AllowedIPs() []netip.Prefix {
return lanAllowedIPs
}
func DisAllowedIPs() []netip.Prefix {
return lanDisAllowedIPs
}
func IsRemoteAddrDisAllowed(addr net.Addr) bool {
m := C.Metadata{}
if err := m.SetRemoteAddr(addr); err != nil {
return false
}
ipAddr := m.AddrPort().Addr()
if ipAddr.IsValid() {
return isAllowed(ipAddr) && !isDisAllowed(ipAddr)
}
return false
}
func isAllowed(addr netip.Addr) bool {
return prefixesContains(lanAllowedIPs, addr)
}
func isDisAllowed(addr netip.Addr) bool {
return prefixesContains(lanDisAllowedIPs, addr)
}

View File

@@ -2,25 +2,105 @@ package inbound
import (
"context"
"fmt"
"net"
"net/netip"
"sync"
"github.com/sagernet/tfo-go"
"github.com/metacubex/mihomo/component/keepalive"
"github.com/metacubex/tfo-go"
)
var (
lc = tfo.ListenConfig{
DisableTFO: true,
}
mutex sync.RWMutex
)
func SetTfo(open bool) {
mutex.Lock()
defer mutex.Unlock()
lc.DisableTFO = !open
}
func Tfo() bool {
mutex.RLock()
defer mutex.RUnlock()
return !lc.DisableTFO
}
func SetMPTCP(open bool) {
mutex.Lock()
defer mutex.Unlock()
setMultiPathTCP(&lc.ListenConfig, open)
}
func MPTCP() bool {
mutex.RLock()
defer mutex.RUnlock()
return getMultiPathTCP(&lc.ListenConfig)
}
func preResolve(network, address string) (string, error) {
switch network { // like net.Resolver.internetAddrList but filter domain to avoid call net.Resolver.lookupIPAddr
case "tcp", "tcp4", "tcp6", "udp", "udp4", "udp6", "ip", "ip4", "ip6":
if host, port, err := net.SplitHostPort(address); err == nil {
switch host {
case "localhost":
switch network {
case "tcp6", "udp6", "ip6":
address = net.JoinHostPort("::1", port)
default:
address = net.JoinHostPort("127.0.0.1", port)
}
case "": // internetAddrList can handle this special case
break
default:
if _, err := netip.ParseAddr(host); err != nil { // not ip
return "", fmt.Errorf("invalid network address: %s", address)
}
}
}
}
return address, nil
}
func ListenContext(ctx context.Context, network, address string) (net.Listener, error) {
address, err := preResolve(network, address)
if err != nil {
return nil, err
}
mutex.RLock()
defer mutex.RUnlock()
return lc.Listen(ctx, network, address)
}
func Listen(network, address string) (net.Listener, error) {
return ListenContext(context.Background(), network, address)
}
func ListenPacketContext(ctx context.Context, network, address string) (net.PacketConn, error) {
address, err := preResolve(network, address)
if err != nil {
return nil, err
}
mutex.RLock()
defer mutex.RUnlock()
return lc.ListenPacket(ctx, network, address)
}
func ListenPacket(network, address string) (net.PacketConn, error) {
return ListenPacketContext(context.Background(), network, address)
}
func init() {
keepalive.SetDisableKeepAliveCallback.Register(func(b bool) {
mutex.Lock()
defer mutex.Unlock()
keepalive.SetNetListenConfig(&lc.ListenConfig)
})
}

View File

@@ -0,0 +1,14 @@
//go:build !windows
package inbound
import (
"net"
"os"
)
const SupportNamedPipe = false
func ListenNamedPipe(path string) (net.Listener, error) {
return nil, os.ErrInvalid
}

View File

@@ -0,0 +1,32 @@
package inbound
import (
"net"
"os"
"github.com/metacubex/wireguard-go/ipc/namedpipe"
"golang.org/x/sys/windows"
)
const SupportNamedPipe = true
// windowsSDDL is the Security Descriptor set on the namedpipe.
// It provides read/write access to all users and the local system.
const windowsSDDL = "D:PAI(A;OICI;GWGR;;;BU)(A;OICI;GWGR;;;SY)"
func ListenNamedPipe(path string) (net.Listener, error) {
sddl := os.Getenv("LISTEN_NAMEDPIPE_SDDL")
if sddl == "" {
sddl = windowsSDDL
}
securityDescriptor, err := windows.SecurityDescriptorFromString(sddl)
if err != nil {
return nil, err
}
namedpipeLC := namedpipe.ListenConfig{
SecurityDescriptor: securityDescriptor,
InputBufferSize: 256 * 1024,
OutputBufferSize: 256 * 1024,
}
return namedpipeLC.Listen(path)
}

View File

@@ -0,0 +1,14 @@
//go:build !go1.21
package inbound
import "net"
const multipathTCPAvailable = false
func setMultiPathTCP(listenConfig *net.ListenConfig, open bool) {
}
func getMultiPathTCP(listenConfig *net.ListenConfig) bool {
return false
}

View File

@@ -0,0 +1,15 @@
//go:build go1.21
package inbound
import "net"
const multipathTCPAvailable = true
func setMultiPathTCP(listenConfig *net.ListenConfig, open bool) {
listenConfig.SetMultipathTCP(open)
}
func getMultiPathTCP(listenConfig *net.ListenConfig) bool {
return listenConfig.MultipathTCP()
}

View File

@@ -1,42 +1,22 @@
package inbound
import (
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/transport/socks5"
C "github.com/metacubex/mihomo/constant"
"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
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.NetWork = C.UDP
metadata.Type = source
for _, addition := range additions {
addition.Apply(metadata)
}
if ip, port, err := parseAddr(packet.LocalAddr()); err == nil {
metadata.SrcIP = ip
metadata.SrcPort = port
}
metadata.RawSrcAddr = packet.LocalAddr()
metadata.RawDstAddr = metadata.UDPAddr()
ApplyAdditions(metadata, WithSrcAddr(packet.LocalAddr()))
if p, ok := packet.(C.UDPPacketInAddr); ok {
if ip, port, err := parseAddr(p.InAddr()); err == nil {
metadata.InIP = ip
metadata.InPort = port
}
ApplyAdditions(metadata, WithInAddr(p.InAddr()))
}
ApplyAdditions(metadata, additions...)
return &PacketAdapter{
packet,
metadata,
}
return packet, metadata
}

View File

@@ -2,48 +2,17 @@ package inbound
import (
"net"
"net/netip"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/context"
"github.com/Dreamacro/clash/transport/socks5"
C "github.com/metacubex/mihomo/constant"
"github.com/metacubex/mihomo/transport/socks5"
)
// 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.NetWork = C.TCP
metadata.Type = source
for _, addition := range additions {
addition.Apply(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 {
metadata.DstPort = port
if ip, err := netip.ParseAddr(h); err == nil {
metadata.DstIP = ip
} else {
metadata.Host = h
}
}
return context.NewConnContext(conn, metadata)
ApplyAdditions(metadata, WithSrcAddr(conn.RemoteAddr()), WithInAddr(conn.LocalAddr()))
ApplyAdditions(metadata, additions...)
return conn, metadata
}

View File

@@ -1,16 +1,13 @@
package inbound
import (
"errors"
"net"
"net/http"
"net/netip"
"strconv"
"strings"
"github.com/Dreamacro/clash/common/nnip"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/transport/socks5"
C "github.com/metacubex/mihomo/constant"
"github.com/metacubex/mihomo/transport/socks5"
)
func parseSocksAddr(target socks5.Addr) *C.Metadata {
@@ -20,15 +17,15 @@ func parseSocksAddr(target socks5.Addr) *C.Metadata {
case socks5.AtypDomainName:
// trim for FQDN
metadata.Host = strings.TrimRight(string(target[2:2+target[1]]), ".")
metadata.DstPort = strconv.Itoa((int(target[2+target[1]]) << 8) | int(target[2+target[1]+1]))
metadata.DstPort = uint16((int(target[2+target[1]]) << 8) | int(target[2+target[1]+1]))
case socks5.AtypIPv4:
metadata.DstIP = nnip.IpToAddr(net.IP(target[1 : 1+net.IPv4len]))
metadata.DstPort = strconv.Itoa((int(target[1+net.IPv4len]) << 8) | int(target[1+net.IPv4len+1]))
metadata.DstIP, _ = netip.AddrFromSlice(target[1 : 1+net.IPv4len])
metadata.DstPort = uint16((int(target[1+net.IPv4len]) << 8) | int(target[1+net.IPv4len+1]))
case socks5.AtypIPv6:
ip6, _ := netip.AddrFromSlice(target[1 : 1+net.IPv6len])
metadata.DstIP = ip6.Unmap()
metadata.DstPort = strconv.Itoa((int(target[1+net.IPv6len]) << 8) | int(target[1+net.IPv6len+1]))
metadata.DstIP, _ = netip.AddrFromSlice(target[1 : 1+net.IPv6len])
metadata.DstPort = uint16((int(target[1+net.IPv6len]) << 8) | int(target[1+net.IPv6len+1]))
}
metadata.DstIP = metadata.DstIP.Unmap()
return metadata
}
@@ -43,38 +40,23 @@ func parseHTTPAddr(request *http.Request) *C.Metadata {
// trim FQDN (#737)
host = strings.TrimRight(host, ".")
metadata := &C.Metadata{
NetWork: C.TCP,
Host: host,
DstIP: netip.Addr{},
DstPort: port,
}
ip, err := netip.ParseAddr(host)
if err == nil {
metadata.DstIP = ip
}
metadata := &C.Metadata{}
_ = metadata.SetRemoteAddress(net.JoinHostPort(host, port))
return metadata
}
func parseAddr(addr net.Addr) (netip.Addr, string, error) {
// Filter when net.Addr interface is nil
if addr == nil {
return netip.Addr{}, "", errors.New("nil addr")
func prefixesContains(prefixes []netip.Prefix, addr netip.Addr) bool {
if len(prefixes) == 0 {
return false
}
if rawAddr, ok := addr.(interface{ RawAddr() net.Addr }); ok {
ip, port, err := parseAddr(rawAddr.RawAddr())
if err == nil {
return ip, port, err
if !addr.IsValid() {
return false
}
addr = addr.Unmap().WithZone("") // netip.Prefix.Contains returns false if ip has an IPv6 zone
for _, prefix := range prefixes {
if prefix.Contains(addr) {
return true
}
}
addrStr := addr.String()
host, port, err := net.SplitHostPort(addrStr)
if err != nil {
return netip.Addr{}, "", err
}
ip, err := netip.ParseAddr(host)
return ip, port, err
return false
}

139
adapter/outbound/anytls.go Normal file
View File

@@ -0,0 +1,139 @@
package outbound
import (
"context"
"net"
"strconv"
"time"
CN "github.com/metacubex/mihomo/common/net"
"github.com/metacubex/mihomo/component/dialer"
"github.com/metacubex/mihomo/component/proxydialer"
C "github.com/metacubex/mihomo/constant"
"github.com/metacubex/mihomo/transport/anytls"
"github.com/metacubex/mihomo/transport/vmess"
M "github.com/metacubex/sing/common/metadata"
"github.com/metacubex/sing/common/uot"
)
type AnyTLS struct {
*Base
client *anytls.Client
dialer proxydialer.SingDialer
option *AnyTLSOption
}
type AnyTLSOption struct {
BasicOption
Name string `proxy:"name"`
Server string `proxy:"server"`
Port int `proxy:"port"`
Password string `proxy:"password"`
ALPN []string `proxy:"alpn,omitempty"`
SNI string `proxy:"sni,omitempty"`
ECHOpts ECHOptions `proxy:"ech-opts,omitempty"`
ClientFingerprint string `proxy:"client-fingerprint,omitempty"`
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
Fingerprint string `proxy:"fingerprint,omitempty"`
Certificate string `proxy:"certificate,omitempty"`
PrivateKey string `proxy:"private-key,omitempty"`
UDP bool `proxy:"udp,omitempty"`
IdleSessionCheckInterval int `proxy:"idle-session-check-interval,omitempty"`
IdleSessionTimeout int `proxy:"idle-session-timeout,omitempty"`
MinIdleSession int `proxy:"min-idle-session,omitempty"`
}
func (t *AnyTLS) DialContext(ctx context.Context, metadata *C.Metadata) (_ C.Conn, err error) {
c, err := t.client.CreateProxy(ctx, M.ParseSocksaddrHostPort(metadata.String(), metadata.DstPort))
if err != nil {
return nil, err
}
return NewConn(c, t), nil
}
func (t *AnyTLS) ListenPacketContext(ctx context.Context, metadata *C.Metadata) (_ C.PacketConn, err error) {
if err = t.ResolveUDP(ctx, metadata); err != nil {
return nil, err
}
// create tcp
c, err := t.client.CreateProxy(ctx, uot.RequestDestination(2))
if err != nil {
return nil, err
}
// create uot on tcp
destination := M.SocksaddrFromNet(metadata.UDPAddr())
return newPacketConn(CN.NewThreadSafePacketConn(uot.NewLazyConn(c, uot.Request{Destination: destination})), t), nil
}
// SupportUOT implements C.ProxyAdapter
func (t *AnyTLS) SupportUOT() bool {
return true
}
// ProxyInfo implements C.ProxyAdapter
func (t *AnyTLS) ProxyInfo() C.ProxyInfo {
info := t.Base.ProxyInfo()
info.DialerProxy = t.option.DialerProxy
return info
}
// Close implements C.ProxyAdapter
func (t *AnyTLS) Close() error {
return t.client.Close()
}
func NewAnyTLS(option AnyTLSOption) (*AnyTLS, error) {
addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port))
outbound := &AnyTLS{
Base: &Base{
name: option.Name,
addr: addr,
tp: C.AnyTLS,
udp: option.UDP,
tfo: option.TFO,
mpTcp: option.MPTCP,
iface: option.Interface,
rmark: option.RoutingMark,
prefer: C.NewDNSPrefer(option.IPVersion),
},
option: &option,
}
singDialer := proxydialer.NewByNameSingDialer(option.DialerProxy, dialer.NewDialer(outbound.DialOptions()...))
outbound.dialer = singDialer
tOption := anytls.ClientConfig{
Password: option.Password,
Server: M.ParseSocksaddrHostPort(option.Server, uint16(option.Port)),
Dialer: singDialer,
IdleSessionCheckInterval: time.Duration(option.IdleSessionCheckInterval) * time.Second,
IdleSessionTimeout: time.Duration(option.IdleSessionTimeout) * time.Second,
MinIdleSession: option.MinIdleSession,
}
echConfig, err := option.ECHOpts.Parse()
if err != nil {
return nil, err
}
tlsConfig := &vmess.TLSConfig{
Host: option.SNI,
SkipCertVerify: option.SkipCertVerify,
NextProtos: option.ALPN,
FingerPrint: option.Fingerprint,
Certificate: option.Certificate,
PrivateKey: option.PrivateKey,
ClientFingerprint: option.ClientFingerprint,
ECH: echConfig,
}
if tlsConfig.Host == "" {
tlsConfig.Host = option.Server
}
tOption.TLSConfig = tlsConfig
client := anytls.NewClient(context.TODO(), tOption)
outbound.client = client
return outbound, nil
}

View File

@@ -3,16 +3,26 @@ package outbound
import (
"context"
"encoding/json"
"fmt"
"net"
"strings"
"runtime"
"sync"
"syscall"
N "github.com/Dreamacro/clash/common/net"
"github.com/Dreamacro/clash/common/utils"
"github.com/Dreamacro/clash/component/dialer"
C "github.com/Dreamacro/clash/constant"
N "github.com/metacubex/mihomo/common/net"
"github.com/metacubex/mihomo/common/utils"
"github.com/metacubex/mihomo/component/dialer"
"github.com/metacubex/mihomo/component/resolver"
C "github.com/metacubex/mihomo/constant"
"github.com/metacubex/mihomo/log"
)
type ProxyAdapter interface {
C.ProxyAdapter
DialOptions() []dialer.Option
ResolveUDP(ctx context.Context, metadata *C.Metadata) error
}
type Base struct {
name string
addr string
@@ -21,6 +31,7 @@ type Base struct {
udp bool
xudp bool
tfo bool
mpTcp bool
rmark int
id string
prefer C.DNSPrefer
@@ -50,7 +61,7 @@ func (b *Base) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.Me
return c, C.ErrNotSupport
}
func (b *Base) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.Conn, error) {
func (b *Base) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
return nil, C.ErrNotSupport
}
@@ -60,7 +71,7 @@ func (b *Base) DialContextWithDialer(ctx context.Context, dialer C.Dialer, metad
}
// ListenPacketContext implements C.ProxyAdapter
func (b *Base) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) {
func (b *Base) ListenPacketContext(ctx context.Context, metadata *C.Metadata) (C.PacketConn, error) {
return nil, C.ErrNotSupport
}
@@ -84,14 +95,15 @@ func (b *Base) SupportUDP() bool {
return b.udp
}
// SupportXUDP implements C.ProxyAdapter
func (b *Base) SupportXUDP() bool {
return b.xudp
}
// SupportTFO implements C.ProxyAdapter
func (b *Base) SupportTFO() bool {
return b.tfo
// ProxyInfo implements C.ProxyAdapter
func (b *Base) ProxyInfo() (info C.ProxyInfo) {
info.XUDP = b.xudp
info.TFO = b.tfo
info.MPTCP = b.mpTcp
info.SMUX = false
info.Interface = b.iface
info.RoutingMark = b.rmark
return
}
// IsL3Protocol implements C.ProxyAdapter
@@ -118,7 +130,7 @@ func (b *Base) Unwrap(metadata *C.Metadata, touch bool) C.Proxy {
}
// DialOptions return []dialer.Option from struct
func (b *Base) DialOptions(opts ...dialer.Option) []dialer.Option {
func (b *Base) DialOptions() (opts []dialer.Option) {
if b.iface != "" {
opts = append(opts, dialer.WithInterface(b.iface))
}
@@ -143,14 +155,34 @@ func (b *Base) DialOptions(opts ...dialer.Option) []dialer.Option {
opts = append(opts, dialer.WithTFO(true))
}
if b.mpTcp {
opts = append(opts, dialer.WithMPTCP(true))
}
return opts
}
func (b *Base) ResolveUDP(ctx context.Context, metadata *C.Metadata) error {
if !metadata.Resolved() {
ip, err := resolver.ResolveIP(ctx, metadata.Host)
if err != nil {
return fmt.Errorf("can't resolve ip: %w", err)
}
metadata.DstIP = ip
}
return nil
}
func (b *Base) Close() error {
return nil
}
type BasicOption struct {
TFO bool `proxy:"tfo,omitempty" group:"tfo,omitempty"`
Interface string `proxy:"interface-name,omitempty" group:"interface-name,omitempty"`
RoutingMark int `proxy:"routing-mark,omitempty" group:"routing-mark,omitempty"`
IPVersion string `proxy:"ip-version,omitempty" group:"ip-version,omitempty"`
TFO bool `proxy:"tfo,omitempty"`
MPTCP bool `proxy:"mptcp,omitempty"`
Interface string `proxy:"interface-name,omitempty"`
RoutingMark int `proxy:"routing-mark,omitempty"`
IPVersion string `proxy:"ip-version,omitempty"`
DialerProxy string `proxy:"dialer-proxy,omitempty"` // don't apply this option into groups, but can set a group name in a proxy
}
@@ -161,6 +193,7 @@ type BaseOption struct {
UDP bool
XUDP bool
TFO bool
MPTCP bool
Interface string
RoutingMark int
Prefer C.DNSPrefer
@@ -174,6 +207,7 @@ func NewBase(opt BaseOption) *Base {
udp: opt.UDP,
xudp: opt.XUDP,
tfo: opt.TFO,
mpTcp: opt.MPTCP,
iface: opt.Interface,
rmark: opt.RoutingMark,
prefer: opt.Prefer,
@@ -182,12 +216,21 @@ func NewBase(opt BaseOption) *Base {
type conn struct {
N.ExtendedConn
chain C.Chain
actualRemoteDestination string
chain C.Chain
adapterAddr string
}
func (c *conn) RemoteDestination() string {
return c.actualRemoteDestination
if remoteAddr := c.RemoteAddr(); remoteAddr != nil {
m := C.Metadata{}
if err := m.SetRemoteAddr(remoteAddr); err == nil {
if m.Valid() {
return m.String()
}
}
}
host, _, _ := net.SplitHostPort(c.adapterAddr)
return host
}
// Chains implements C.Connection
@@ -212,23 +255,35 @@ func (c *conn) ReaderReplaceable() bool {
return true
}
func (c *conn) AddRef(ref any) {
c.ExtendedConn = N.NewRefConn(c.ExtendedConn, ref) // add ref for autoCloseProxyAdapter
}
func NewConn(c net.Conn, a C.ProxyAdapter) C.Conn {
if _, ok := c.(syscall.Conn); !ok { // exclusion system conn like *net.TCPConn
c = N.NewDeadlineConn(c) // most conn from outbound can't handle readDeadline correctly
}
return &conn{N.NewExtendedConn(c), []string{a.Name()}, parseRemoteDestination(a.Addr())}
cc := &conn{N.NewExtendedConn(c), nil, a.Addr()}
cc.AppendToChains(a)
return cc
}
type packetConn struct {
N.EnhancePacketConn
chain C.Chain
adapterName string
connID string
actualRemoteDestination string
chain C.Chain
adapterName string
connID string
adapterAddr string
resolveUDP func(ctx context.Context, metadata *C.Metadata) error
}
func (c *packetConn) ResolveUDP(ctx context.Context, metadata *C.Metadata) error {
return c.resolveUDP(ctx, metadata)
}
func (c *packetConn) RemoteDestination() string {
return c.actualRemoteDestination
host, _, _ := net.SplitHostPort(c.adapterAddr)
return host
}
// Chains implements C.Connection
@@ -258,22 +313,88 @@ func (c *packetConn) ReaderReplaceable() bool {
return true
}
func newPacketConn(pc net.PacketConn, a C.ProxyAdapter) C.PacketConn {
func (c *packetConn) AddRef(ref any) {
c.EnhancePacketConn = N.NewRefPacketConn(c.EnhancePacketConn, ref) // add ref for autoCloseProxyAdapter
}
func newPacketConn(pc net.PacketConn, a ProxyAdapter) C.PacketConn {
epc := N.NewEnhancePacketConn(pc)
if _, ok := pc.(syscall.Conn); !ok { // exclusion system conn like *net.UDPConn
epc = N.NewDeadlineEnhancePacketConn(epc) // most conn from outbound can't handle readDeadline correctly
}
return &packetConn{epc, []string{a.Name()}, a.Name(), utils.NewUUIDV4().String(), parseRemoteDestination(a.Addr())}
cpc := &packetConn{epc, nil, a.Name(), utils.NewUUIDV4().String(), a.Addr(), a.ResolveUDP}
cpc.AppendToChains(a)
return cpc
}
func parseRemoteDestination(addr string) string {
if dst, _, err := net.SplitHostPort(addr); err == nil {
return dst
} else {
if addrError, ok := err.(*net.AddrError); ok && strings.Contains(addrError.Err, "missing port") {
return dst
} else {
return ""
}
}
type AddRef interface {
AddRef(ref any)
}
type autoCloseProxyAdapter struct {
ProxyAdapter
closeOnce sync.Once
closeErr error
}
func (p *autoCloseProxyAdapter) DialContext(ctx context.Context, metadata *C.Metadata) (_ C.Conn, err error) {
c, err := p.ProxyAdapter.DialContext(ctx, metadata)
if err != nil {
return nil, err
}
if c, ok := c.(AddRef); ok {
c.AddRef(p)
}
return c, nil
}
func (p *autoCloseProxyAdapter) DialContextWithDialer(ctx context.Context, dialer C.Dialer, metadata *C.Metadata) (_ C.Conn, err error) {
c, err := p.ProxyAdapter.DialContextWithDialer(ctx, dialer, metadata)
if err != nil {
return nil, err
}
if c, ok := c.(AddRef); ok {
c.AddRef(p)
}
return c, nil
}
func (p *autoCloseProxyAdapter) ListenPacketContext(ctx context.Context, metadata *C.Metadata) (_ C.PacketConn, err error) {
pc, err := p.ProxyAdapter.ListenPacketContext(ctx, metadata)
if err != nil {
return nil, err
}
if pc, ok := pc.(AddRef); ok {
pc.AddRef(p)
}
return pc, nil
}
func (p *autoCloseProxyAdapter) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, metadata *C.Metadata) (_ C.PacketConn, err error) {
pc, err := p.ProxyAdapter.ListenPacketWithDialer(ctx, dialer, metadata)
if err != nil {
return nil, err
}
if pc, ok := pc.(AddRef); ok {
pc.AddRef(p)
}
return pc, nil
}
func (p *autoCloseProxyAdapter) Close() error {
p.closeOnce.Do(func() {
log.Debugln("Closing outdated proxy [%s]", p.Name())
runtime.SetFinalizer(p, nil)
p.closeErr = p.ProxyAdapter.Close()
})
return p.closeErr
}
func NewAutoCloseProxyAdapter(adapter ProxyAdapter) ProxyAdapter {
proxy := &autoCloseProxyAdapter{
ProxyAdapter: adapter,
}
// auto close ProxyAdapter
runtime.SetFinalizer(proxy, (*autoCloseProxyAdapter).Close)
return proxy
}

View File

@@ -2,42 +2,82 @@ package outbound
import (
"context"
"errors"
"github.com/Dreamacro/clash/component/dialer"
"github.com/Dreamacro/clash/component/resolver"
C "github.com/Dreamacro/clash/constant"
"fmt"
"github.com/metacubex/mihomo/component/dialer"
"github.com/metacubex/mihomo/component/loopback"
"github.com/metacubex/mihomo/component/resolver"
C "github.com/metacubex/mihomo/constant"
)
type Direct struct {
*Base
loopBack *loopback.Detector
}
type DirectOption struct {
BasicOption
Name string `proxy:"name"`
}
// DialContext implements C.ProxyAdapter
func (d *Direct) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.Conn, error) {
opts = append(opts, dialer.WithResolver(resolver.DefaultResolver))
c, err := dialer.DialContext(ctx, "tcp", metadata.RemoteAddress(), d.Base.DialOptions(opts...)...)
func (d *Direct) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
if err := d.loopBack.CheckConn(metadata); err != nil {
return nil, err
}
opts := d.DialOptions()
opts = append(opts, dialer.WithResolver(resolver.DirectHostResolver))
c, err := dialer.DialContext(ctx, "tcp", metadata.RemoteAddress(), opts...)
if err != nil {
return nil, err
}
tcpKeepAlive(c)
return NewConn(c, d), nil
return d.loopBack.NewConn(NewConn(c, d)), nil
}
// ListenPacketContext implements C.ProxyAdapter
func (d *Direct) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) {
// net.UDPConn.WriteTo only working with *net.UDPAddr, so we need a net.UDPAddr
if !metadata.Resolved() {
ip, err := resolver.ResolveIPWithResolver(ctx, metadata.Host, resolver.DefaultResolver)
if err != nil {
return nil, errors.New("can't resolve ip")
}
metadata.DstIP = ip
func (d *Direct) ListenPacketContext(ctx context.Context, metadata *C.Metadata) (C.PacketConn, error) {
if err := d.loopBack.CheckPacketConn(metadata); err != nil {
return nil, err
}
pc, err := dialer.ListenPacket(ctx, dialer.ParseNetwork("udp", metadata.DstIP), "", d.Base.DialOptions(opts...)...)
if err := d.ResolveUDP(ctx, metadata); err != nil {
return nil, err
}
pc, err := dialer.NewDialer(d.DialOptions()...).ListenPacket(ctx, "udp", "", metadata.AddrPort())
if err != nil {
return nil, err
}
return newPacketConn(pc, d), nil
return d.loopBack.NewPacketConn(newPacketConn(pc, d)), nil
}
func (d *Direct) ResolveUDP(ctx context.Context, metadata *C.Metadata) error {
if (!metadata.Resolved() || resolver.DirectHostResolver != resolver.DefaultResolver) && metadata.Host != "" {
ip, err := resolver.ResolveIPWithResolver(ctx, metadata.Host, resolver.DirectHostResolver)
if err != nil {
return fmt.Errorf("can't resolve ip: %w", err)
}
metadata.DstIP = ip
}
return nil
}
func (d *Direct) IsL3Protocol(metadata *C.Metadata) bool {
return true // tell DNSDialer don't send domain to DialContext, avoid lookback to DefaultResolver
}
func NewDirectWithOption(option DirectOption) *Direct {
return &Direct{
Base: &Base{
name: option.Name,
tp: C.Direct,
udp: true,
tfo: option.TFO,
mpTcp: option.MPTCP,
iface: option.Interface,
rmark: option.RoutingMark,
prefer: C.NewDNSPrefer(option.IPVersion),
},
loopBack: loopback.NewDetector(),
}
}
func NewDirect() *Direct {
@@ -48,6 +88,7 @@ func NewDirect() *Direct {
udp: true,
prefer: C.DualStack,
},
loopBack: loopback.NewDetector(),
}
}
@@ -59,5 +100,6 @@ func NewCompatible() *Direct {
udp: true,
prefer: C.DualStack,
},
loopBack: loopback.NewDetector(),
}
}

169
adapter/outbound/dns.go Normal file
View File

@@ -0,0 +1,169 @@
package outbound
import (
"context"
"net"
"net/netip"
"time"
N "github.com/metacubex/mihomo/common/net"
"github.com/metacubex/mihomo/common/pool"
"github.com/metacubex/mihomo/component/resolver"
C "github.com/metacubex/mihomo/constant"
"github.com/metacubex/mihomo/log"
)
type Dns struct {
*Base
}
type DnsOption struct {
BasicOption
Name string `proxy:"name"`
}
// DialContext implements C.ProxyAdapter
func (d *Dns) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
left, right := N.Pipe()
go resolver.RelayDnsConn(context.Background(), right, 0)
return NewConn(left, d), nil
}
// ListenPacketContext implements C.ProxyAdapter
func (d *Dns) ListenPacketContext(ctx context.Context, metadata *C.Metadata) (C.PacketConn, error) {
log.Debugln("[DNS] hijack udp:%s from %s", metadata.RemoteAddress(), metadata.SourceAddrPort())
if err := d.ResolveUDP(ctx, metadata); err != nil {
return nil, err
}
ctx, cancel := context.WithCancel(context.Background())
return newPacketConn(&dnsPacketConn{
response: make(chan dnsPacket, 1),
ctx: ctx,
cancel: cancel,
}, d), nil
}
func (d *Dns) ResolveUDP(ctx context.Context, metadata *C.Metadata) error {
if !metadata.Resolved() {
metadata.DstIP = netip.AddrFrom4([4]byte{127, 0, 0, 2})
}
return nil
}
type dnsPacket struct {
data []byte
put func()
addr net.Addr
}
// dnsPacketConn implements net.PacketConn
type dnsPacketConn struct {
response chan dnsPacket
ctx context.Context
cancel context.CancelFunc
}
func (d *dnsPacketConn) WaitReadFrom() (data []byte, put func(), addr net.Addr, err error) {
select {
case packet := <-d.response:
return packet.data, packet.put, packet.addr, nil
case <-d.ctx.Done():
return nil, nil, nil, net.ErrClosed
}
}
func (d *dnsPacketConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) {
select {
case packet := <-d.response:
n = copy(p, packet.data)
if packet.put != nil {
packet.put()
}
return n, packet.addr, nil
case <-d.ctx.Done():
return 0, nil, net.ErrClosed
}
}
func (d *dnsPacketConn) WriteTo(p []byte, addr net.Addr) (n int, err error) {
select {
case <-d.ctx.Done():
return 0, net.ErrClosed
default:
}
if len(p) > resolver.SafeDnsPacketSize {
// wtf???
return len(p), nil
}
buf := pool.Get(resolver.SafeDnsPacketSize)
put := func() { _ = pool.Put(buf) }
copy(buf, p) // avoid p be changed after WriteTo returned
go func() { // don't block the WriteTo function
ctx, cancel := context.WithTimeout(d.ctx, resolver.DefaultDnsRelayTimeout)
defer cancel()
buf, err = resolver.RelayDnsPacket(ctx, buf[:len(p)], buf)
if err != nil {
put()
return
}
packet := dnsPacket{
data: buf,
put: put,
addr: addr,
}
select {
case d.response <- packet:
break
case <-d.ctx.Done():
put()
}
}()
return len(p), nil
}
func (d *dnsPacketConn) Close() error {
d.cancel()
return nil
}
func (*dnsPacketConn) LocalAddr() net.Addr {
return &net.UDPAddr{
IP: net.IPv4(127, 0, 0, 1),
Port: 53,
Zone: "",
}
}
func (*dnsPacketConn) SetDeadline(t time.Time) error {
return nil
}
func (*dnsPacketConn) SetReadDeadline(t time.Time) error {
return nil
}
func (*dnsPacketConn) SetWriteDeadline(t time.Time) error {
return nil
}
func NewDnsWithOption(option DnsOption) *Dns {
return &Dns{
Base: &Base{
name: option.Name,
tp: C.Dns,
udp: true,
tfo: option.TFO,
mpTcp: option.MPTCP,
iface: option.Interface,
rmark: option.RoutingMark,
prefer: C.NewDNSPrefer(option.IPVersion),
},
}
}

36
adapter/outbound/ech.go Normal file
View File

@@ -0,0 +1,36 @@
package outbound
import (
"context"
"encoding/base64"
"fmt"
"github.com/metacubex/mihomo/component/ech"
"github.com/metacubex/mihomo/component/resolver"
)
type ECHOptions struct {
Enable bool `proxy:"enable,omitempty" obfs:"enable,omitempty"`
Config string `proxy:"config,omitempty" obfs:"config,omitempty"`
}
func (o ECHOptions) Parse() (*ech.Config, error) {
if !o.Enable {
return nil, nil
}
echConfig := &ech.Config{}
if o.Config != "" {
list, err := base64.StdEncoding.DecodeString(o.Config)
if err != nil {
return nil, fmt.Errorf("base64 decode ech config string failed: %v", err)
}
echConfig.GetEncryptedClientHelloConfigList = func(ctx context.Context, serverName string) ([]byte, error) {
return list, nil
}
} else {
echConfig.GetEncryptedClientHelloConfigList = func(ctx context.Context, serverName string) ([]byte, error) {
return resolver.ResolveECHWithResolver(ctx, serverName, resolver.ProxyServerHostResolver)
}
}
return echConfig, nil
}

View File

@@ -7,15 +7,15 @@ import (
"encoding/base64"
"errors"
"fmt"
"io"
"net"
"net/http"
"strconv"
"github.com/Dreamacro/clash/component/dialer"
"github.com/Dreamacro/clash/component/proxydialer"
tlsC "github.com/Dreamacro/clash/component/tls"
C "github.com/Dreamacro/clash/constant"
N "github.com/metacubex/mihomo/common/net"
"github.com/metacubex/mihomo/component/ca"
"github.com/metacubex/mihomo/component/dialer"
"github.com/metacubex/mihomo/component/proxydialer"
C "github.com/metacubex/mihomo/constant"
)
type Http struct {
@@ -37,6 +37,8 @@ type HttpOption struct {
SNI string `proxy:"sni,omitempty"`
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
Fingerprint string `proxy:"fingerprint,omitempty"`
Certificate string `proxy:"certificate,omitempty"`
PrivateKey string `proxy:"private-key,omitempty"`
Headers map[string]string `proxy:"headers,omitempty"`
}
@@ -51,15 +53,15 @@ func (h *Http) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.Me
}
}
if err := h.shakeHand(metadata, c); err != nil {
if err := h.shakeHandContext(ctx, c, metadata); err != nil {
return nil, err
}
return c, nil
}
// DialContext implements C.ProxyAdapter
func (h *Http) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) {
return h.DialContextWithDialer(ctx, dialer.NewDialer(h.Base.DialOptions(opts...)...), metadata)
func (h *Http) DialContext(ctx context.Context, metadata *C.Metadata) (_ C.Conn, err error) {
return h.DialContextWithDialer(ctx, dialer.NewDialer(h.DialOptions()...), metadata)
}
// DialContextWithDialer implements C.ProxyAdapter
@@ -74,7 +76,6 @@ func (h *Http) DialContextWithDialer(ctx context.Context, dialer C.Dialer, metad
if err != nil {
return nil, fmt.Errorf("%s connect error: %w", h.addr, err)
}
tcpKeepAlive(c)
defer func(c net.Conn) {
safeConnClose(c, err)
@@ -93,7 +94,19 @@ func (h *Http) SupportWithDialer() C.NetWork {
return C.TCP
}
func (h *Http) shakeHand(metadata *C.Metadata, rw io.ReadWriter) error {
// ProxyInfo implements C.ProxyAdapter
func (h *Http) ProxyInfo() C.ProxyInfo {
info := h.Base.ProxyInfo()
info.DialerProxy = h.option.DialerProxy
return info
}
func (h *Http) shakeHandContext(ctx context.Context, c net.Conn, metadata *C.Metadata) (err error) {
if ctx.Done() != nil {
done := N.SetupContextForConn(ctx, c)
defer done(&err)
}
addr := metadata.RemoteAddress()
HeaderString := "CONNECT " + addr + " HTTP/1.1\r\n"
tempHeaders := map[string]string{
@@ -117,13 +130,13 @@ func (h *Http) shakeHand(metadata *C.Metadata, rw io.ReadWriter) error {
HeaderString += "\r\n"
_, err := rw.Write([]byte(HeaderString))
_, err = c.Write([]byte(HeaderString))
if err != nil {
return err
}
resp, err := http.ReadResponse(bufio.NewReader(rw), nil)
resp, err := http.ReadResponse(bufio.NewReader(c), nil)
if err != nil {
return err
@@ -155,19 +168,18 @@ func NewHttp(option HttpOption) (*Http, error) {
if option.SNI != "" {
sni = option.SNI
}
if len(option.Fingerprint) == 0 {
tlsConfig = tlsC.GetGlobalTLSConfig(&tls.Config{
var err error
tlsConfig, err = ca.GetTLSConfig(ca.Option{
TLSConfig: &tls.Config{
InsecureSkipVerify: option.SkipCertVerify,
ServerName: sni,
})
} else {
var err error
if tlsConfig, err = tlsC.GetSpecifiedFingerprintTLSConfig(&tls.Config{
InsecureSkipVerify: option.SkipCertVerify,
ServerName: sni,
}, option.Fingerprint); err != nil {
return nil, err
}
},
Fingerprint: option.Fingerprint,
Certificate: option.Certificate,
PrivateKey: option.PrivateKey,
})
if err != nil {
return nil, err
}
}
@@ -177,6 +189,7 @@ func NewHttp(option HttpOption) (*Http, error) {
addr: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)),
tp: C.Http,
tfo: option.TFO,
mpTcp: option.MPTCP,
iface: option.Interface,
rmark: option.RoutingMark,
prefer: C.NewDNSPrefer(option.IPVersion),

View File

@@ -2,34 +2,31 @@ package outbound
import (
"context"
"crypto/sha256"
"crypto/tls"
"encoding/base64"
"encoding/hex"
"encoding/pem"
"fmt"
"net"
"net/netip"
"os"
"regexp"
"strconv"
"time"
"github.com/metacubex/mihomo/component/ca"
"github.com/metacubex/mihomo/component/dialer"
"github.com/metacubex/mihomo/component/ech"
"github.com/metacubex/mihomo/component/proxydialer"
tlsC "github.com/metacubex/mihomo/component/tls"
C "github.com/metacubex/mihomo/constant"
"github.com/metacubex/mihomo/log"
hyCongestion "github.com/metacubex/mihomo/transport/hysteria/congestion"
"github.com/metacubex/mihomo/transport/hysteria/core"
"github.com/metacubex/mihomo/transport/hysteria/obfs"
"github.com/metacubex/mihomo/transport/hysteria/pmtud_fix"
"github.com/metacubex/mihomo/transport/hysteria/transport"
"github.com/metacubex/mihomo/transport/hysteria/utils"
"github.com/metacubex/quic-go"
"github.com/metacubex/quic-go/congestion"
M "github.com/sagernet/sing/common/metadata"
"github.com/Dreamacro/clash/component/dialer"
"github.com/Dreamacro/clash/component/proxydialer"
tlsC "github.com/Dreamacro/clash/component/tls"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/log"
hyCongestion "github.com/Dreamacro/clash/transport/hysteria/congestion"
"github.com/Dreamacro/clash/transport/hysteria/core"
"github.com/Dreamacro/clash/transport/hysteria/obfs"
"github.com/Dreamacro/clash/transport/hysteria/pmtud_fix"
"github.com/Dreamacro/clash/transport/hysteria/transport"
"github.com/Dreamacro/clash/transport/hysteria/utils"
M "github.com/metacubex/sing/common/metadata"
)
const (
@@ -43,17 +40,18 @@ const (
DefaultHopInterval = 10
)
var rateStringRegexp = regexp.MustCompile(`^(\d+)\s*([KMGT]?)([Bb])ps$`)
type Hysteria struct {
*Base
option *HysteriaOption
client *core.Client
tlsConfig *tlsC.Config
echConfig *ech.Config
}
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...))
func (h *Hysteria) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
tcpConn, err := h.client.DialTCP(metadata.String(), metadata.DstPort, h.genHdc(ctx))
if err != nil {
return nil, err
}
@@ -61,71 +59,90 @@ func (h *Hysteria) DialContext(ctx context.Context, metadata *C.Metadata, opts .
return NewConn(tcpConn, h), nil
}
func (h *Hysteria) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) {
udpConn, err := h.client.DialUDP(h.genHdc(ctx, opts...))
func (h *Hysteria) ListenPacketContext(ctx context.Context, metadata *C.Metadata) (C.PacketConn, error) {
if err := h.ResolveUDP(ctx, metadata); err != nil {
return nil, err
}
udpConn, err := h.client.DialUDP(h.genHdc(ctx))
if err != nil {
return nil, err
}
return newPacketConn(&hyPacketConn{udpConn}, h), nil
}
func (h *Hysteria) genHdc(ctx context.Context, opts ...dialer.Option) utils.PacketDialer {
func (h *Hysteria) genHdc(ctx context.Context) utils.PacketDialer {
return &hyDialerWithContext{
ctx: context.Background(),
hyDialer: func(network string) (net.PacketConn, error) {
hyDialer: func(network string, rAddr net.Addr) (net.PacketConn, error) {
var err error
var cDialer C.Dialer = dialer.NewDialer(h.Base.DialOptions(opts...)...)
var cDialer C.Dialer = dialer.NewDialer(h.DialOptions()...)
if len(h.option.DialerProxy) > 0 {
cDialer, err = proxydialer.NewByName(h.option.DialerProxy, cDialer)
if err != nil {
return nil, err
}
}
rAddrPort, _ := netip.ParseAddrPort(h.Addr())
rAddrPort, _ := netip.ParseAddrPort(rAddr.String())
return cDialer.ListenPacket(ctx, network, "", rAddrPort)
},
remoteAddr: func(addr string) (net.Addr, error) {
return resolveUDPAddrWithPrefer(ctx, "udp", addr, h.prefer)
udpAddr, err := resolveUDPAddr(ctx, "udp", addr, h.prefer)
if err != nil {
return nil, err
}
err = h.echConfig.ClientHandle(ctx, h.tlsConfig)
if err != nil {
return nil, err
}
return udpAddr, nil
},
}
}
// ProxyInfo implements C.ProxyAdapter
func (h *Hysteria) ProxyInfo() C.ProxyInfo {
info := h.Base.ProxyInfo()
info.DialerProxy = h.option.DialerProxy
return info
}
type HysteriaOption struct {
BasicOption
Name string `proxy:"name"`
Server string `proxy:"server"`
Port int `proxy:"port,omitempty"`
Ports string `proxy:"ports,omitempty"`
Protocol string `proxy:"protocol,omitempty"`
ObfsProtocol string `proxy:"obfs-protocol,omitempty"` // compatible with Stash
Up string `proxy:"up"`
UpSpeed int `proxy:"up-speed,omitempty"` // compatible with Stash
Down string `proxy:"down"`
DownSpeed int `proxy:"down-speed,omitempty"` // compatible with Stash
Auth string `proxy:"auth,omitempty"`
AuthString string `proxy:"auth-str,omitempty"`
Obfs string `proxy:"obfs,omitempty"`
SNI string `proxy:"sni,omitempty"`
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
Fingerprint string `proxy:"fingerprint,omitempty"`
ALPN []string `proxy:"alpn,omitempty"`
CustomCA string `proxy:"ca,omitempty"`
CustomCAString string `proxy:"ca-str,omitempty"`
ReceiveWindowConn int `proxy:"recv-window-conn,omitempty"`
ReceiveWindow int `proxy:"recv-window,omitempty"`
DisableMTUDiscovery bool `proxy:"disable-mtu-discovery,omitempty"`
FastOpen bool `proxy:"fast-open,omitempty"`
HopInterval int `proxy:"hop-interval,omitempty"`
Name string `proxy:"name"`
Server string `proxy:"server"`
Port int `proxy:"port,omitempty"`
Ports string `proxy:"ports,omitempty"`
Protocol string `proxy:"protocol,omitempty"`
ObfsProtocol string `proxy:"obfs-protocol,omitempty"` // compatible with Stash
Up string `proxy:"up"`
UpSpeed int `proxy:"up-speed,omitempty"` // compatible with Stash
Down string `proxy:"down"`
DownSpeed int `proxy:"down-speed,omitempty"` // compatible with Stash
Auth string `proxy:"auth,omitempty"`
AuthString string `proxy:"auth-str,omitempty"`
Obfs string `proxy:"obfs,omitempty"`
SNI string `proxy:"sni,omitempty"`
ECHOpts ECHOptions `proxy:"ech-opts,omitempty"`
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
Fingerprint string `proxy:"fingerprint,omitempty"`
Certificate string `proxy:"certificate,omitempty"`
PrivateKey string `proxy:"private-key,omitempty"`
ALPN []string `proxy:"alpn,omitempty"`
ReceiveWindowConn int `proxy:"recv-window-conn,omitempty"`
ReceiveWindow int `proxy:"recv-window,omitempty"`
DisableMTUDiscovery bool `proxy:"disable-mtu-discovery,omitempty"`
FastOpen bool `proxy:"fast-open,omitempty"`
HopInterval int `proxy:"hop-interval,omitempty"`
}
func (c *HysteriaOption) Speed() (uint64, uint64, error) {
var up, down uint64
up = stringToBps(c.Up)
up = StringToBps(c.Up)
if up == 0 {
return 0, 0, fmt.Errorf("invaild upload speed: %s", c.Up)
}
down = stringToBps(c.Down)
down = StringToBps(c.Down)
if down == 0 {
return 0, 0, fmt.Errorf("invaild download speed: %s", c.Down)
}
@@ -134,11 +151,7 @@ func (c *HysteriaOption) Speed() (uint64, uint64, error) {
}
func NewHysteria(option HysteriaOption) (*Hysteria, error) {
clientTransport := &transport.ClientTransport{
Dialer: &net.Dialer{
Timeout: 8 * time.Second,
},
}
clientTransport := &transport.ClientTransport{}
addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port))
ports := option.Ports
@@ -147,50 +160,32 @@ func NewHysteria(option HysteriaOption) (*Hysteria, error) {
serverName = option.SNI
}
tlsConfig := &tls.Config{
ServerName: serverName,
InsecureSkipVerify: option.SkipCertVerify,
MinVersion: tls.VersionTLS13,
tlsConfig, err := ca.GetTLSConfig(ca.Option{
TLSConfig: &tls.Config{
ServerName: serverName,
InsecureSkipVerify: option.SkipCertVerify,
MinVersion: tls.VersionTLS13,
},
Fingerprint: option.Fingerprint,
Certificate: option.Certificate,
PrivateKey: option.PrivateKey,
})
if err != nil {
return nil, err
}
var bs []byte
var err error
if len(option.CustomCA) > 0 {
bs, err = os.ReadFile(option.CustomCA)
if err != nil {
return nil, fmt.Errorf("hysteria %s load ca error: %w", addr, err)
}
} else if option.CustomCAString != "" {
bs = []byte(option.CustomCAString)
}
if len(bs) > 0 {
block, _ := pem.Decode(bs)
if block == nil {
return nil, fmt.Errorf("CA cert is not PEM")
}
fpBytes := sha256.Sum256(block.Bytes)
if len(option.Fingerprint) == 0 {
option.Fingerprint = hex.EncodeToString(fpBytes[:])
}
}
if len(option.Fingerprint) != 0 {
var err error
tlsConfig, err = tlsC.GetSpecifiedFingerprintTLSConfig(tlsConfig, option.Fingerprint)
if err != nil {
return nil, err
}
} else {
tlsConfig = tlsC.GetGlobalTLSConfig(tlsConfig)
}
if len(option.ALPN) > 0 {
if option.ALPN != nil { // structure's Decode will ensure value not nil when input has value even it was set an empty array
tlsConfig.NextProtos = option.ALPN
} else {
tlsConfig.NextProtos = []string{DefaultALPN}
}
echConfig, err := option.ECHOpts.Parse()
if err != nil {
return nil, err
}
tlsClientConfig := tlsC.UConfig(tlsConfig)
quicConfig := &quic.Config{
InitialStreamReceiveWindow: uint64(option.ReceiveWindowConn),
MaxStreamReceiveWindow: uint64(option.ReceiveWindowConn),
@@ -245,14 +240,14 @@ func NewHysteria(option HysteriaOption) (*Hysteria, error) {
down = uint64(option.DownSpeed * mbpsToBps)
}
client, err := core.NewClient(
addr, ports, option.Protocol, auth, tlsConfig, quicConfig, clientTransport, up, down, func(refBPS uint64) congestion.CongestionControl {
addr, ports, option.Protocol, auth, tlsClientConfig, quicConfig, clientTransport, up, down, func(refBPS uint64) congestion.CongestionControl {
return hyCongestion.NewBrutalSender(congestion.ByteCount(refBPS))
}, obfuscator, hopInterval, option.FastOpen,
)
if err != nil {
return nil, fmt.Errorf("hysteria %s create error: %w", addr, err)
}
return &Hysteria{
outbound := &Hysteria{
Base: &Base{
name: option.Name,
addr: addr,
@@ -263,45 +258,21 @@ func NewHysteria(option HysteriaOption) (*Hysteria, error) {
rmark: option.RoutingMark,
prefer: C.NewDNSPrefer(option.IPVersion),
},
option: &option,
client: client,
}, nil
option: &option,
client: client,
tlsConfig: tlsClientConfig,
echConfig: echConfig,
}
return outbound, nil
}
func stringToBps(s string) uint64 {
if s == "" {
return 0
// Close implements C.ProxyAdapter
func (h *Hysteria) Close() error {
if h.client != nil {
return h.client.Close()
}
// when have not unit, use Mbps
if v, err := strconv.Atoi(s); err == nil {
return stringToBps(fmt.Sprintf("%d Mbps", v))
}
m := rateStringRegexp.FindStringSubmatch(s)
if m == nil {
return 0
}
var n uint64
switch m[2] {
case "K":
n = 1 << 10
case "M":
n = 1 << 20
case "G":
n = 1 << 30
case "T":
n = 1 << 40
default:
n = 1
}
v, _ := strconv.ParseUint(m[1], 10, 64)
n = v * n
if m[3] == "b" {
// Bits, need to convert to bytes
n = n >> 3
}
return n
return nil
}
type hyPacketConn struct {
@@ -338,7 +309,7 @@ func (c *hyPacketConn) WriteTo(p []byte, addr net.Addr) (n int, err error) {
}
type hyDialerWithContext struct {
hyDialer func(network string) (net.PacketConn, error)
hyDialer func(network string, rAddr net.Addr) (net.PacketConn, error)
ctx context.Context
remoteAddr func(host string) (net.Addr, error)
}
@@ -348,7 +319,7 @@ func (h *hyDialerWithContext) ListenPacket(rAddr net.Addr) (net.PacketConn, erro
if addrPort, err := netip.ParseAddrPort(rAddr.String()); err == nil {
network = dialer.ParseNetwork(network, addrPort.Addr())
}
return h.hyDialer(network)
return h.hyDialer(network, rAddr)
}
func (h *hyDialerWithContext) Context() context.Context {

View File

@@ -0,0 +1,239 @@
package outbound
import (
"context"
"crypto/tls"
"errors"
"fmt"
"net"
"strconv"
"time"
CN "github.com/metacubex/mihomo/common/net"
"github.com/metacubex/mihomo/common/utils"
"github.com/metacubex/mihomo/component/ca"
"github.com/metacubex/mihomo/component/dialer"
"github.com/metacubex/mihomo/component/proxydialer"
tlsC "github.com/metacubex/mihomo/component/tls"
C "github.com/metacubex/mihomo/constant"
"github.com/metacubex/mihomo/log"
tuicCommon "github.com/metacubex/mihomo/transport/tuic/common"
"github.com/metacubex/quic-go"
"github.com/metacubex/sing-quic/hysteria2"
M "github.com/metacubex/sing/common/metadata"
)
func init() {
hysteria2.SetCongestionController = tuicCommon.SetCongestionController
}
const minHopInterval = 5
const defaultHopInterval = 30
type Hysteria2 struct {
*Base
option *Hysteria2Option
client *hysteria2.Client
dialer proxydialer.SingDialer
}
type Hysteria2Option struct {
BasicOption
Name string `proxy:"name"`
Server string `proxy:"server"`
Port int `proxy:"port,omitempty"`
Ports string `proxy:"ports,omitempty"`
HopInterval int `proxy:"hop-interval,omitempty"`
Up string `proxy:"up,omitempty"`
Down string `proxy:"down,omitempty"`
Password string `proxy:"password,omitempty"`
Obfs string `proxy:"obfs,omitempty"`
ObfsPassword string `proxy:"obfs-password,omitempty"`
SNI string `proxy:"sni,omitempty"`
ECHOpts ECHOptions `proxy:"ech-opts,omitempty"`
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
Fingerprint string `proxy:"fingerprint,omitempty"`
Certificate string `proxy:"certificate,omitempty"`
PrivateKey string `proxy:"private-key,omitempty"`
ALPN []string `proxy:"alpn,omitempty"`
CWND int `proxy:"cwnd,omitempty"`
UdpMTU int `proxy:"udp-mtu,omitempty"`
// quic-go special config
InitialStreamReceiveWindow uint64 `proxy:"initial-stream-receive-window,omitempty"`
MaxStreamReceiveWindow uint64 `proxy:"max-stream-receive-window,omitempty"`
InitialConnectionReceiveWindow uint64 `proxy:"initial-connection-receive-window,omitempty"`
MaxConnectionReceiveWindow uint64 `proxy:"max-connection-receive-window,omitempty"`
}
func (h *Hysteria2) DialContext(ctx context.Context, metadata *C.Metadata) (_ C.Conn, err error) {
c, err := h.client.DialConn(ctx, M.ParseSocksaddrHostPort(metadata.String(), metadata.DstPort))
if err != nil {
return nil, err
}
return NewConn(c, h), nil
}
func (h *Hysteria2) ListenPacketContext(ctx context.Context, metadata *C.Metadata) (_ C.PacketConn, err error) {
if err = h.ResolveUDP(ctx, metadata); err != nil {
return nil, err
}
pc, err := h.client.ListenPacket(ctx)
if err != nil {
return nil, err
}
if pc == nil {
return nil, errors.New("packetConn is nil")
}
return newPacketConn(CN.NewThreadSafePacketConn(pc), h), nil
}
// Close implements C.ProxyAdapter
func (h *Hysteria2) Close() error {
if h.client != nil {
return h.client.CloseWithError(errors.New("proxy removed"))
}
return nil
}
// ProxyInfo implements C.ProxyAdapter
func (h *Hysteria2) ProxyInfo() C.ProxyInfo {
info := h.Base.ProxyInfo()
info.DialerProxy = h.option.DialerProxy
return info
}
func NewHysteria2(option Hysteria2Option) (*Hysteria2, error) {
addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port))
outbound := &Hysteria2{
Base: &Base{
name: option.Name,
addr: addr,
tp: C.Hysteria2,
udp: true,
iface: option.Interface,
rmark: option.RoutingMark,
prefer: C.NewDNSPrefer(option.IPVersion),
},
option: &option,
}
singDialer := proxydialer.NewByNameSingDialer(option.DialerProxy, dialer.NewDialer(outbound.DialOptions()...))
outbound.dialer = singDialer
var salamanderPassword string
if len(option.Obfs) > 0 {
if option.ObfsPassword == "" {
return nil, errors.New("missing obfs password")
}
switch option.Obfs {
case hysteria2.ObfsTypeSalamander:
salamanderPassword = option.ObfsPassword
default:
return nil, fmt.Errorf("unknown obfs type: %s", option.Obfs)
}
}
serverName := option.Server
if option.SNI != "" {
serverName = option.SNI
}
tlsConfig, err := ca.GetTLSConfig(ca.Option{
TLSConfig: &tls.Config{
ServerName: serverName,
InsecureSkipVerify: option.SkipCertVerify,
MinVersion: tls.VersionTLS13,
},
Fingerprint: option.Fingerprint,
Certificate: option.Certificate,
PrivateKey: option.PrivateKey,
})
if err != nil {
return nil, err
}
if option.ALPN != nil { // structure's Decode will ensure value not nil when input has value even it was set an empty array
tlsConfig.NextProtos = option.ALPN
}
tlsClientConfig := tlsC.UConfig(tlsConfig)
echConfig, err := option.ECHOpts.Parse()
if err != nil {
return nil, err
}
if option.UdpMTU == 0 {
// "1200" from quic-go's MaxDatagramSize
// "-3" from quic-go's DatagramFrame.MaxDataLen
option.UdpMTU = 1200 - 3
}
quicConfig := &quic.Config{
InitialStreamReceiveWindow: option.InitialStreamReceiveWindow,
MaxStreamReceiveWindow: option.MaxStreamReceiveWindow,
InitialConnectionReceiveWindow: option.InitialConnectionReceiveWindow,
MaxConnectionReceiveWindow: option.MaxConnectionReceiveWindow,
}
clientOptions := hysteria2.ClientOptions{
Context: context.TODO(),
Dialer: singDialer,
Logger: log.SingLogger,
SendBPS: StringToBps(option.Up),
ReceiveBPS: StringToBps(option.Down),
SalamanderPassword: salamanderPassword,
Password: option.Password,
TLSConfig: tlsClientConfig,
QUICConfig: quicConfig,
UDPDisabled: false,
CWND: option.CWND,
UdpMTU: option.UdpMTU,
ServerAddress: func(ctx context.Context) (*net.UDPAddr, error) {
udpAddr, err := resolveUDPAddr(ctx, "udp", addr, C.NewDNSPrefer(option.IPVersion))
if err != nil {
return nil, err
}
err = echConfig.ClientHandle(ctx, tlsClientConfig)
if err != nil {
return nil, err
}
return udpAddr, nil
},
}
var ranges utils.IntRanges[uint16]
var serverPorts []uint16
if option.Ports != "" {
ranges, err = utils.NewUnsignedRanges[uint16](option.Ports)
if err != nil {
return nil, err
}
ranges.Range(func(port uint16) bool {
serverPorts = append(serverPorts, port)
return true
})
if len(serverPorts) > 0 {
if option.HopInterval == 0 {
option.HopInterval = defaultHopInterval
} else if option.HopInterval < minHopInterval {
option.HopInterval = minHopInterval
}
clientOptions.HopInterval = time.Duration(option.HopInterval) * time.Second
clientOptions.ServerPorts = serverPorts
}
}
if option.Port == 0 && len(serverPorts) == 0 {
return nil, errors.New("invalid port")
}
client, err := hysteria2.NewClient(clientOptions)
if err != nil {
return nil, err
}
outbound.client = client
return outbound, nil
}

363
adapter/outbound/mieru.go Normal file
View File

@@ -0,0 +1,363 @@
package outbound
import (
"context"
"fmt"
"net"
"net/netip"
"strconv"
"sync"
CN "github.com/metacubex/mihomo/common/net"
"github.com/metacubex/mihomo/component/dialer"
"github.com/metacubex/mihomo/component/proxydialer"
"github.com/metacubex/mihomo/component/resolver"
C "github.com/metacubex/mihomo/constant"
mieruclient "github.com/enfein/mieru/v3/apis/client"
mierucommon "github.com/enfein/mieru/v3/apis/common"
mierumodel "github.com/enfein/mieru/v3/apis/model"
mierupb "github.com/enfein/mieru/v3/pkg/appctl/appctlpb"
"google.golang.org/protobuf/proto"
)
type Mieru struct {
*Base
option *MieruOption
client mieruclient.Client
mu sync.Mutex
}
type MieruOption struct {
BasicOption
Name string `proxy:"name"`
Server string `proxy:"server"`
Port int `proxy:"port,omitempty"`
PortRange string `proxy:"port-range,omitempty"`
Transport string `proxy:"transport"`
UDP bool `proxy:"udp,omitempty"`
UserName string `proxy:"username"`
Password string `proxy:"password"`
Multiplexing string `proxy:"multiplexing,omitempty"`
HandshakeMode string `proxy:"handshake-mode,omitempty"`
}
type mieruPacketDialer struct {
C.Dialer
}
var _ mierucommon.PacketDialer = (*mieruPacketDialer)(nil)
func (pd mieruPacketDialer) ListenPacket(ctx context.Context, network, laddr, raddr string) (net.PacketConn, error) {
rAddrPort, err := netip.ParseAddrPort(raddr)
if err != nil {
return nil, fmt.Errorf("invalid address %s: %w", raddr, err)
}
return pd.Dialer.ListenPacket(ctx, network, laddr, rAddrPort)
}
type mieruDNSResolver struct {
prefer C.DNSPrefer
}
var _ mierucommon.DNSResolver = (*mieruDNSResolver)(nil)
func (dr mieruDNSResolver) LookupIP(ctx context.Context, network, host string) (_ []net.IP, err error) {
var ip netip.Addr
switch dr.prefer {
case C.IPv4Only:
ip, err = resolver.ResolveIPv4WithResolver(ctx, host, resolver.ProxyServerHostResolver)
case C.IPv6Only:
ip, err = resolver.ResolveIPv6WithResolver(ctx, host, resolver.ProxyServerHostResolver)
case C.IPv6Prefer:
ip, err = resolver.ResolveIPPrefer6WithResolver(ctx, host, resolver.ProxyServerHostResolver)
default:
ip, err = resolver.ResolveIPWithResolver(ctx, host, resolver.ProxyServerHostResolver)
}
if err != nil {
return nil, fmt.Errorf("can't resolve ip: %w", err)
}
// TODO: handle IP4P (due to interface limitations, it's currently impossible to modify the port here)
return []net.IP{ip.AsSlice()}, nil
}
// DialContext implements C.ProxyAdapter
func (m *Mieru) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
if err := m.ensureClientIsRunning(); err != nil {
return nil, err
}
addr := metadataToMieruNetAddrSpec(metadata)
c, err := m.client.DialContext(ctx, addr)
if err != nil {
return nil, fmt.Errorf("dial to %s failed: %w", addr, err)
}
return NewConn(c, m), nil
}
// ListenPacketContext implements C.ProxyAdapter
func (m *Mieru) ListenPacketContext(ctx context.Context, metadata *C.Metadata) (_ C.PacketConn, err error) {
if err = m.ResolveUDP(ctx, metadata); err != nil {
return nil, err
}
if err := m.ensureClientIsRunning(); err != nil {
return nil, err
}
c, err := m.client.DialContext(ctx, metadata.UDPAddr())
if err != nil {
return nil, fmt.Errorf("dial to %s failed: %w", metadata.UDPAddr(), err)
}
return newPacketConn(CN.NewThreadSafePacketConn(mierucommon.NewUDPAssociateWrapper(mierucommon.NewPacketOverStreamTunnel(c))), m), nil
}
// SupportUOT implements C.ProxyAdapter
func (m *Mieru) SupportUOT() bool {
return true
}
// ProxyInfo implements C.ProxyAdapter
func (m *Mieru) ProxyInfo() C.ProxyInfo {
info := m.Base.ProxyInfo()
info.DialerProxy = m.option.DialerProxy
return info
}
func (m *Mieru) ensureClientIsRunning() error {
m.mu.Lock()
defer m.mu.Unlock()
if m.client.IsRunning() {
return nil
}
// Create a dialer and add it to the client config, before starting the client.
var dialer C.Dialer = dialer.NewDialer(m.DialOptions()...)
var err error
if len(m.option.DialerProxy) > 0 {
dialer, err = proxydialer.NewByName(m.option.DialerProxy, dialer)
if err != nil {
return err
}
}
config, err := m.client.Load()
if err != nil {
return err
}
config.Dialer = dialer
config.PacketDialer = mieruPacketDialer{Dialer: dialer}
config.Resolver = mieruDNSResolver{prefer: m.prefer}
if err := m.client.Store(config); err != nil {
return err
}
if err := m.client.Start(); err != nil {
return fmt.Errorf("failed to start mieru client: %w", err)
}
return nil
}
func NewMieru(option MieruOption) (*Mieru, error) {
config, err := buildMieruClientConfig(option)
if err != nil {
return nil, fmt.Errorf("failed to build mieru client config: %w", err)
}
c := mieruclient.NewClient()
if err := c.Store(config); err != nil {
return nil, fmt.Errorf("failed to store mieru client config: %w", err)
}
// Client is started lazily on the first use.
var addr string
if option.Port != 0 {
addr = net.JoinHostPort(option.Server, strconv.Itoa(option.Port))
} else {
beginPort, _, _ := beginAndEndPortFromPortRange(option.PortRange)
addr = net.JoinHostPort(option.Server, strconv.Itoa(beginPort))
}
outbound := &Mieru{
Base: &Base{
name: option.Name,
addr: addr,
iface: option.Interface,
tp: C.Mieru,
udp: option.UDP,
xudp: false,
rmark: option.RoutingMark,
prefer: C.NewDNSPrefer(option.IPVersion),
},
option: &option,
client: c,
}
return outbound, nil
}
// Close implements C.ProxyAdapter
func (m *Mieru) Close() error {
m.mu.Lock()
defer m.mu.Unlock()
if m.client != nil && m.client.IsRunning() {
return m.client.Stop()
}
return nil
}
func metadataToMieruNetAddrSpec(metadata *C.Metadata) mierumodel.NetAddrSpec {
spec := mierumodel.NetAddrSpec{
Net: metadata.NetWork.String(),
}
if metadata.Host != "" {
spec.AddrSpec = mierumodel.AddrSpec{
FQDN: metadata.Host,
Port: int(metadata.DstPort),
}
} else {
spec.AddrSpec = mierumodel.AddrSpec{
IP: metadata.DstIP.AsSlice(),
Port: int(metadata.DstPort),
}
}
return spec
}
func buildMieruClientConfig(option MieruOption) (*mieruclient.ClientConfig, error) {
if err := validateMieruOption(option); err != nil {
return nil, fmt.Errorf("failed to validate mieru option: %w", err)
}
var transportProtocol = mierupb.TransportProtocol_UNKNOWN_TRANSPORT_PROTOCOL.Enum()
switch option.Transport {
case "TCP":
transportProtocol = mierupb.TransportProtocol_TCP.Enum()
case "UDP":
transportProtocol = mierupb.TransportProtocol_UDP.Enum()
}
var server *mierupb.ServerEndpoint
if net.ParseIP(option.Server) != nil {
// server is an IP address
if option.PortRange != "" {
server = &mierupb.ServerEndpoint{
IpAddress: proto.String(option.Server),
PortBindings: []*mierupb.PortBinding{
{
PortRange: proto.String(option.PortRange),
Protocol: transportProtocol,
},
},
}
} else {
server = &mierupb.ServerEndpoint{
IpAddress: proto.String(option.Server),
PortBindings: []*mierupb.PortBinding{
{
Port: proto.Int32(int32(option.Port)),
Protocol: transportProtocol,
},
},
}
}
} else {
// server is a domain name
if option.PortRange != "" {
server = &mierupb.ServerEndpoint{
DomainName: proto.String(option.Server),
PortBindings: []*mierupb.PortBinding{
{
PortRange: proto.String(option.PortRange),
Protocol: transportProtocol,
},
},
}
} else {
server = &mierupb.ServerEndpoint{
DomainName: proto.String(option.Server),
PortBindings: []*mierupb.PortBinding{
{
Port: proto.Int32(int32(option.Port)),
Protocol: transportProtocol,
},
},
}
}
}
config := &mieruclient.ClientConfig{
Profile: &mierupb.ClientProfile{
ProfileName: proto.String(option.Name),
User: &mierupb.User{
Name: proto.String(option.UserName),
Password: proto.String(option.Password),
},
Servers: []*mierupb.ServerEndpoint{server},
},
DNSConfig: &mierucommon.ClientDNSConfig{
BypassDialerDNS: true,
},
}
if multiplexing, ok := mierupb.MultiplexingLevel_value[option.Multiplexing]; ok {
config.Profile.Multiplexing = &mierupb.MultiplexingConfig{
Level: mierupb.MultiplexingLevel(multiplexing).Enum(),
}
}
if handshakeMode, ok := mierupb.HandshakeMode_value[option.HandshakeMode]; ok {
config.Profile.HandshakeMode = (*mierupb.HandshakeMode)(&handshakeMode)
}
return config, nil
}
func validateMieruOption(option MieruOption) error {
if option.Name == "" {
return fmt.Errorf("name is empty")
}
if option.Server == "" {
return fmt.Errorf("server is empty")
}
if option.Port == 0 && option.PortRange == "" {
return fmt.Errorf("either port or port-range must be set")
}
if option.Port != 0 && option.PortRange != "" {
return fmt.Errorf("port and port-range cannot be set at the same time")
}
if option.Port != 0 && (option.Port < 1 || option.Port > 65535) {
return fmt.Errorf("port must be between 1 and 65535")
}
if option.PortRange != "" {
begin, end, err := beginAndEndPortFromPortRange(option.PortRange)
if err != nil {
return fmt.Errorf("invalid port-range format")
}
if begin < 1 || begin > 65535 {
return fmt.Errorf("begin port must be between 1 and 65535")
}
if end < 1 || end > 65535 {
return fmt.Errorf("end port must be between 1 and 65535")
}
if begin > end {
return fmt.Errorf("begin port must be less than or equal to end port")
}
}
if option.Transport != "TCP" && option.Transport != "UDP" {
return fmt.Errorf("transport must be TCP or UDP")
}
if option.UserName == "" {
return fmt.Errorf("username is empty")
}
if option.Password == "" {
return fmt.Errorf("password is empty")
}
if option.Multiplexing != "" {
if _, ok := mierupb.MultiplexingLevel_value[option.Multiplexing]; !ok {
return fmt.Errorf("invalid multiplexing level: %s", option.Multiplexing)
}
}
if option.HandshakeMode != "" {
if _, ok := mierupb.HandshakeMode_value[option.HandshakeMode]; !ok {
return fmt.Errorf("invalid handshake mode: %s", option.HandshakeMode)
}
}
return nil
}
func beginAndEndPortFromPortRange(portRange string) (int, int, error) {
var begin, end int
_, err := fmt.Sscanf(portRange, "%d-%d", &begin, &end)
return begin, end, err
}

View File

@@ -0,0 +1,92 @@
package outbound
import "testing"
func TestNewMieru(t *testing.T) {
testCases := []struct {
option MieruOption
wantBaseAddr string
}{
{
option: MieruOption{
Name: "test",
Server: "1.2.3.4",
Port: 10000,
Transport: "TCP",
UserName: "test",
Password: "test",
},
wantBaseAddr: "1.2.3.4:10000",
},
{
option: MieruOption{
Name: "test",
Server: "2001:db8::1",
PortRange: "10001-10002",
Transport: "TCP",
UserName: "test",
Password: "test",
},
wantBaseAddr: "[2001:db8::1]:10001",
},
{
option: MieruOption{
Name: "test",
Server: "example.com",
Port: 10003,
Transport: "UDP",
UserName: "test",
Password: "test",
},
wantBaseAddr: "example.com:10003",
},
}
for _, testCase := range testCases {
mieru, err := NewMieru(testCase.option)
if err != nil {
t.Error(err)
}
if mieru.addr != testCase.wantBaseAddr {
t.Errorf("got addr %q, want %q", mieru.addr, testCase.wantBaseAddr)
}
}
}
func TestBeginAndEndPortFromPortRange(t *testing.T) {
testCases := []struct {
input string
begin int
end int
hasErr bool
}{
{"1-10", 1, 10, false},
{"1000-2000", 1000, 2000, false},
{"65535-65535", 65535, 65535, false},
{"1", 0, 0, true},
{"1-", 0, 0, true},
{"-10", 0, 0, true},
{"a-b", 0, 0, true},
{"1-b", 0, 0, true},
{"a-10", 0, 0, true},
}
for _, testCase := range testCases {
begin, end, err := beginAndEndPortFromPortRange(testCase.input)
if testCase.hasErr {
if err == nil {
t.Errorf("beginAndEndPortFromPortRange(%s) should return an error", testCase.input)
}
} else {
if err != nil {
t.Errorf("beginAndEndPortFromPortRange(%s) should not return an error, but got %v", testCase.input, err)
}
if begin != testCase.begin {
t.Errorf("beginAndEndPortFromPortRange(%s) begin port mismatch, got %d, want %d", testCase.input, begin, testCase.begin)
}
if end != testCase.end {
t.Errorf("beginAndEndPortFromPortRange(%s) end port mismatch, got %d, want %d", testCase.input, end, testCase.end)
}
}
}
}

View File

@@ -1,29 +1,41 @@
package outbound
import (
"crypto/ecdh"
"encoding/base64"
"encoding/hex"
"errors"
"fmt"
tlsC "github.com/Dreamacro/clash/component/tls"
"golang.org/x/crypto/curve25519"
tlsC "github.com/metacubex/mihomo/component/tls"
)
type RealityOptions struct {
PublicKey string `proxy:"public-key"`
ShortID string `proxy:"short-id"`
SupportX25519MLKEM768 bool `proxy:"support-x25519mlkem768"`
}
func (o RealityOptions) Parse() (*tlsC.RealityConfig, error) {
if o.PublicKey != "" {
config := new(tlsC.RealityConfig)
config.SupportX25519MLKEM768 = o.SupportX25519MLKEM768
n, err := base64.RawURLEncoding.Decode(config.PublicKey[:], []byte(o.PublicKey))
if err != nil || n != curve25519.ScalarSize {
const x25519ScalarSize = 32
publicKey, err := base64.RawURLEncoding.DecodeString(o.PublicKey)
if err != nil || len(publicKey) != x25519ScalarSize {
return nil, errors.New("invalid REALITY public key")
}
config.PublicKey, err = ecdh.X25519().NewPublicKey(publicKey)
if err != nil {
return nil, fmt.Errorf("fail to create REALITY public key: %w", err)
}
n := hex.DecodedLen(len(o.ShortID))
if n > tlsC.RealityMaxShortIDLen {
return nil, errors.New("invalid REALITY short id")
}
n, err = hex.Decode(config.ShortID[:], []byte(o.ShortID))
if err != nil || n > tlsC.RealityMaxShortIDLen {
return nil, errors.New("invalid REALITY short ID")

View File

@@ -4,25 +4,53 @@ import (
"context"
"io"
"net"
"net/netip"
"time"
"github.com/Dreamacro/clash/common/buf"
"github.com/Dreamacro/clash/component/dialer"
C "github.com/Dreamacro/clash/constant"
"github.com/metacubex/mihomo/common/buf"
C "github.com/metacubex/mihomo/constant"
)
type Reject struct {
*Base
drop bool
}
type RejectOption struct {
Name string `proxy:"name"`
}
// 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) (C.Conn, error) {
if r.drop {
return NewConn(dropConn{}, r), nil
}
return NewConn(nopConn{}, r), nil
}
// ListenPacketContext implements C.ProxyAdapter
func (r *Reject) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) {
return newPacketConn(nopPacketConn{}, r), nil
func (r *Reject) ListenPacketContext(ctx context.Context, metadata *C.Metadata) (C.PacketConn, error) {
if err := r.ResolveUDP(ctx, metadata); err != nil {
return nil, err
}
return newPacketConn(&nopPacketConn{}, r), nil
}
func (r *Reject) ResolveUDP(ctx context.Context, metadata *C.Metadata) error {
if !metadata.Resolved() {
metadata.DstIP = netip.IPv4Unspecified()
}
return nil
}
func NewRejectWithOption(option RejectOption) *Reject {
return &Reject{
Base: &Base{
name: option.Name,
tp: C.Reject,
udp: true,
},
}
}
func NewReject() *Reject {
@@ -36,6 +64,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 {
return &Reject{
Base: &Base{
@@ -49,37 +89,50 @@ func NewPass() *Reject {
type nopConn struct{}
func (rw nopConn) Read(b []byte) (int, error) {
return 0, io.EOF
}
func (rw nopConn) Read(b []byte) (int, error) { return 0, io.EOF }
func (rw nopConn) ReadBuffer(buffer *buf.Buffer) error {
return io.EOF
}
func (rw nopConn) ReadBuffer(buffer *buf.Buffer) error { return io.EOF }
func (rw nopConn) Write(b []byte) (int, error) {
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) 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 }
func (rw nopConn) Write(b []byte) (int, error) { 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) 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}
type nopPacketConn struct{}
func (npc nopPacketConn) WriteTo(b []byte, addr net.Addr) (n int, err error) { return len(b), nil }
func (npc nopPacketConn) ReadFrom(b []byte) (int, net.Addr, error) { return 0, nil, io.EOF }
func (npc nopPacketConn) Close() error { return nil }
func (npc nopPacketConn) LocalAddr() net.Addr { return udpAddrIPv4Unspecified }
func (npc nopPacketConn) SetDeadline(time.Time) error { return nil }
func (npc nopPacketConn) SetReadDeadline(time.Time) error { return nil }
func (npc nopPacketConn) SetWriteDeadline(time.Time) error { return nil }
func (npc nopPacketConn) WriteTo(b []byte, addr net.Addr) (n int, err error) {
return len(b), nil
}
func (npc nopPacketConn) ReadFrom(b []byte) (int, net.Addr, error) {
return 0, nil, io.EOF
}
func (npc nopPacketConn) WaitReadFrom() ([]byte, func(), net.Addr, error) {
return nil, nil, nil, io.EOF
}
func (npc nopPacketConn) Close() error { return nil }
func (npc nopPacketConn) LocalAddr() net.Addr { return udpAddrIPv4Unspecified }
func (npc nopPacketConn) SetDeadline(time.Time) error { return nil }
func (npc nopPacketConn) SetReadDeadline(time.Time) error { return nil }
func (npc nopPacketConn) SetWriteDeadline(time.Time) error { return nil }
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 }

View File

@@ -2,26 +2,27 @@ package outbound
import (
"context"
"errors"
"fmt"
"net"
"strconv"
N "github.com/Dreamacro/clash/common/net"
"github.com/Dreamacro/clash/common/structure"
"github.com/Dreamacro/clash/component/dialer"
"github.com/Dreamacro/clash/component/proxydialer"
"github.com/Dreamacro/clash/component/resolver"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/transport/restls"
obfs "github.com/Dreamacro/clash/transport/simple-obfs"
shadowtls "github.com/Dreamacro/clash/transport/sing-shadowtls"
v2rayObfs "github.com/Dreamacro/clash/transport/v2ray-plugin"
N "github.com/metacubex/mihomo/common/net"
"github.com/metacubex/mihomo/common/structure"
"github.com/metacubex/mihomo/component/dialer"
"github.com/metacubex/mihomo/component/proxydialer"
C "github.com/metacubex/mihomo/constant"
"github.com/metacubex/mihomo/ntp"
gost "github.com/metacubex/mihomo/transport/gost-plugin"
"github.com/metacubex/mihomo/transport/kcptun"
"github.com/metacubex/mihomo/transport/restls"
obfs "github.com/metacubex/mihomo/transport/simple-obfs"
shadowtls "github.com/metacubex/mihomo/transport/sing-shadowtls"
v2rayObfs "github.com/metacubex/mihomo/transport/v2ray-plugin"
restlsC "github.com/3andne/restls-client-go"
"github.com/metacubex/sing-shadowsocks2"
M "github.com/sagernet/sing/common/metadata"
"github.com/sagernet/sing/common/uot"
shadowsocks "github.com/metacubex/sing-shadowsocks2"
"github.com/metacubex/sing/common/bufio"
M "github.com/metacubex/sing/common/metadata"
"github.com/metacubex/sing/common/uot"
)
type ShadowSocks struct {
@@ -33,8 +34,10 @@ type ShadowSocks struct {
obfsMode string
obfsOption *simpleObfsOption
v2rayOption *v2rayObfs.Option
gostOption *gost.Option
shadowTLSOption *shadowtls.ShadowTLSOption
restlsConfig *restlsC.Config
restlsConfig *restls.Config
kcptunClient *kcptun.Client
}
type ShadowSocksOption struct {
@@ -58,22 +61,44 @@ type simpleObfsOption struct {
}
type v2rayObfsOption struct {
Mode string `obfs:"mode"`
Host string `obfs:"host,omitempty"`
Path string `obfs:"path,omitempty"`
TLS bool `obfs:"tls,omitempty"`
ECHOpts ECHOptions `obfs:"ech-opts,omitempty"`
Fingerprint string `obfs:"fingerprint,omitempty"`
Certificate string `obfs:"certificate,omitempty"`
PrivateKey string `obfs:"private-key,omitempty"`
Headers map[string]string `obfs:"headers,omitempty"`
SkipCertVerify bool `obfs:"skip-cert-verify,omitempty"`
Mux bool `obfs:"mux,omitempty"`
V2rayHttpUpgrade bool `obfs:"v2ray-http-upgrade,omitempty"`
V2rayHttpUpgradeFastOpen bool `obfs:"v2ray-http-upgrade-fast-open,omitempty"`
}
type gostObfsOption struct {
Mode string `obfs:"mode"`
Host string `obfs:"host,omitempty"`
Path string `obfs:"path,omitempty"`
TLS bool `obfs:"tls,omitempty"`
ECHOpts ECHOptions `obfs:"ech-opts,omitempty"`
Fingerprint string `obfs:"fingerprint,omitempty"`
Certificate string `obfs:"certificate,omitempty"`
PrivateKey string `obfs:"private-key,omitempty"`
Headers map[string]string `obfs:"headers,omitempty"`
SkipCertVerify bool `obfs:"skip-cert-verify,omitempty"`
Mux bool `obfs:"mux,omitempty"`
}
type shadowTLSOption struct {
Password string `obfs:"password"`
Host string `obfs:"host"`
Fingerprint string `obfs:"fingerprint,omitempty"`
SkipCertVerify bool `obfs:"skip-cert-verify,omitempty"`
Version int `obfs:"version,omitempty"`
Password string `obfs:"password,omitempty"`
Host string `obfs:"host"`
Fingerprint string `obfs:"fingerprint,omitempty"`
Certificate string `obfs:"certificate,omitempty"`
PrivateKey string `obfs:"private-key,omitempty"`
SkipCertVerify bool `obfs:"skip-cert-verify,omitempty"`
Version int `obfs:"version,omitempty"`
ALPN []string `obfs:"alpn,omitempty"`
}
type restlsOption struct {
@@ -83,8 +108,34 @@ type restlsOption struct {
RestlsScript string `obfs:"restls-script,omitempty"`
}
type kcpTunOption struct {
Key string `obfs:"key,omitempty"`
Crypt string `obfs:"crypt,omitempty"`
Mode string `obfs:"mode,omitempty"`
Conn int `obfs:"conn,omitempty"`
AutoExpire int `obfs:"autoexpire,omitempty"`
ScavengeTTL int `obfs:"scavengettl,omitempty"`
MTU int `obfs:"mtu,omitempty"`
SndWnd int `obfs:"sndwnd,omitempty"`
RcvWnd int `obfs:"rcvwnd,omitempty"`
DataShard int `obfs:"datashard,omitempty"`
ParityShard int `obfs:"parityshard,omitempty"`
DSCP int `obfs:"dscp,omitempty"`
NoComp bool `obfs:"nocomp,omitempty"`
AckNodelay bool `obfs:"acknodelay,omitempty"`
NoDelay int `obfs:"nodelay,omitempty"`
Interval int `obfs:"interval,omitempty"`
Resend int `obfs:"resend,omitempty"`
NoCongestion int `obfs:"nc,omitempty"`
SockBuf int `obfs:"sockbuf,omitempty"`
SmuxVer int `obfs:"smuxver,omitempty"`
SmuxBuf int `obfs:"smuxbuf,omitempty"`
StreamBuf int `obfs:"streambuf,omitempty"`
KeepAlive int `obfs:"keepalive,omitempty"`
}
// StreamConnContext implements C.ProxyAdapter
func (ss *ShadowSocks) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.Metadata) (net.Conn, error) {
func (ss *ShadowSocks) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.Metadata) (_ net.Conn, err error) {
useEarly := false
switch ss.obfsMode {
case "tls":
@@ -93,20 +144,23 @@ func (ss *ShadowSocks) StreamConnContext(ctx context.Context, c net.Conn, metada
_, port, _ := net.SplitHostPort(ss.addr)
c = obfs.NewHTTPObfs(c, ss.obfsOption.Host, port)
case "websocket":
var err error
c, err = v2rayObfs.NewV2rayObfs(ctx, c, ss.v2rayOption)
if ss.v2rayOption != nil {
c, err = v2rayObfs.NewV2rayObfs(ctx, c, ss.v2rayOption)
} else if ss.gostOption != nil {
c, err = gost.NewGostWebsocket(ctx, c, ss.gostOption)
} else {
return nil, fmt.Errorf("plugin options is required")
}
if err != nil {
return nil, fmt.Errorf("%s connect error: %w", ss.addr, err)
}
case shadowtls.Mode:
var err error
c, err = shadowtls.NewShadowTLS(ctx, c, ss.shadowTLSOption)
if err != nil {
return nil, err
}
useEarly = true
case restls.Mode:
var err error
c, err = restls.NewRestls(ctx, c, ss.restlsConfig)
if err != nil {
return nil, fmt.Errorf("%s (restls) connect error: %w", ss.addr, err)
@@ -114,6 +168,12 @@ func (ss *ShadowSocks) StreamConnContext(ctx context.Context, c net.Conn, metada
useEarly = true
}
useEarly = useEarly || N.NeedHandshake(c)
if !useEarly {
if ctx.Done() != nil {
done := N.SetupContextForConn(ctx, c)
defer done(&err)
}
}
if metadata.NetWork == C.UDP && ss.option.UDPOverTCP {
uotDestination := uot.RequestDestination(uint8(ss.option.UDPOverTCPVersion))
if useEarly {
@@ -123,15 +183,15 @@ func (ss *ShadowSocks) StreamConnContext(ctx context.Context, c net.Conn, metada
}
}
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 {
return ss.method.DialConn(c, M.ParseSocksaddr(metadata.RemoteAddress()))
return ss.method.DialConn(c, M.ParseSocksaddrHostPort(metadata.String(), metadata.DstPort))
}
}
// DialContext implements C.ProxyAdapter
func (ss *ShadowSocks) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) {
return ss.DialContextWithDialer(ctx, dialer.NewDialer(ss.Base.DialOptions(opts...)...), metadata)
func (ss *ShadowSocks) DialContext(ctx context.Context, metadata *C.Metadata) (_ C.Conn, err error) {
return ss.DialContextWithDialer(ctx, dialer.NewDialer(ss.DialOptions()...), metadata)
}
// DialContextWithDialer implements C.ProxyAdapter
@@ -142,11 +202,30 @@ func (ss *ShadowSocks) DialContextWithDialer(ctx context.Context, dialer C.Diale
return nil, err
}
}
c, err := dialer.DialContext(ctx, "tcp", ss.addr)
var c net.Conn
if ss.kcptunClient != nil {
c, err = ss.kcptunClient.OpenStream(ctx, func(ctx context.Context) (net.PacketConn, net.Addr, error) {
if err = ss.ResolveUDP(ctx, metadata); err != nil {
return nil, nil, err
}
addr, err := resolveUDPAddr(ctx, "udp", ss.addr, ss.prefer)
if err != nil {
return nil, nil, err
}
pc, err := dialer.ListenPacket(ctx, "udp", "", addr.AddrPort())
if err != nil {
return nil, nil, err
}
return pc, addr, nil
})
} else {
c, err = dialer.DialContext(ctx, "tcp", ss.addr)
}
if err != nil {
return nil, fmt.Errorf("%s connect error: %w", ss.addr, err)
}
tcpKeepAlive(c)
defer func(c net.Conn) {
safeConnClose(c, err)
@@ -157,18 +236,12 @@ func (ss *ShadowSocks) DialContextWithDialer(ctx context.Context, dialer C.Diale
}
// ListenPacketContext implements C.ProxyAdapter
func (ss *ShadowSocks) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) {
return ss.ListenPacketWithDialer(ctx, dialer.NewDialer(ss.Base.DialOptions(opts...)...), metadata)
func (ss *ShadowSocks) ListenPacketContext(ctx context.Context, metadata *C.Metadata) (C.PacketConn, error) {
return ss.ListenPacketWithDialer(ctx, dialer.NewDialer(ss.DialOptions()...), metadata)
}
// ListenPacketWithDialer implements C.ProxyAdapter
func (ss *ShadowSocks) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, metadata *C.Metadata) (_ C.PacketConn, err error) {
if len(ss.option.DialerProxy) > 0 {
dialer, err = proxydialer.NewByName(ss.option.DialerProxy, dialer)
if err != nil {
return nil, err
}
}
if ss.option.UDPOverTCP {
tcpConn, err := ss.DialContextWithDialer(ctx, dialer, metadata)
if err != nil {
@@ -176,7 +249,16 @@ func (ss *ShadowSocks) ListenPacketWithDialer(ctx context.Context, dialer C.Dial
}
return ss.ListenPacketOnStreamConn(ctx, tcpConn, metadata)
}
addr, err := resolveUDPAddrWithPrefer(ctx, "udp", ss.addr, ss.prefer)
if len(ss.option.DialerProxy) > 0 {
dialer, err = proxydialer.NewByName(ss.option.DialerProxy, dialer)
if err != nil {
return nil, err
}
}
if err = ss.ResolveUDP(ctx, metadata); err != nil {
return nil, err
}
addr, err := resolveUDPAddr(ctx, "udp", ss.addr, ss.prefer)
if err != nil {
return nil, err
}
@@ -185,7 +267,7 @@ func (ss *ShadowSocks) ListenPacketWithDialer(ctx context.Context, dialer C.Dial
if err != nil {
return nil, err
}
pc = ss.method.DialPacketConn(N.NewBindPacketConn(pc, addr))
pc = ss.method.DialPacketConn(bufio.NewBindPacketConn(pc, addr))
return newPacketConn(pc, ss), nil
}
@@ -194,23 +276,24 @@ func (ss *ShadowSocks) SupportWithDialer() C.NetWork {
return C.ALLNet
}
// ProxyInfo implements C.ProxyAdapter
func (ss *ShadowSocks) ProxyInfo() C.ProxyInfo {
info := ss.Base.ProxyInfo()
info.DialerProxy = ss.option.DialerProxy
return info
}
// ListenPacketOnStreamConn implements C.ProxyAdapter
func (ss *ShadowSocks) ListenPacketOnStreamConn(ctx context.Context, c net.Conn, metadata *C.Metadata) (_ C.PacketConn, err error) {
if ss.option.UDPOverTCP {
// ss uot use stream-oriented udp with a special address, so we need a net.UDPAddr
if !metadata.Resolved() {
ip, err := resolver.ResolveIP(ctx, metadata.Host)
if err != nil {
return nil, errors.New("can't resolve ip")
}
metadata.DstIP = ip
if err = ss.ResolveUDP(ctx, metadata); err != nil {
return nil, err
}
destination := M.SocksaddrFromNet(metadata.UDPAddr())
if ss.option.UDPOverTCPVersion == uot.LegacyVersion {
return newPacketConn(uot.NewConn(c, uot.Request{Destination: destination}), ss), nil
return newPacketConn(N.NewThreadSafePacketConn(uot.NewConn(c, uot.Request{Destination: destination})), ss), nil
} else {
return newPacketConn(uot.NewLazyConn(c, uot.Request{Destination: destination}), ss), nil
return newPacketConn(N.NewThreadSafePacketConn(uot.NewLazyConn(c, uot.Request{Destination: destination})), ss), nil
}
}
return nil, C.ErrNotSupport
@@ -221,19 +304,29 @@ func (ss *ShadowSocks) SupportUOT() bool {
return ss.option.UDPOverTCP
}
func (ss *ShadowSocks) Close() error {
if ss.kcptunClient != nil {
return ss.kcptunClient.Close()
}
return nil
}
func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) {
addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port))
method, err := shadowsocks.CreateMethod(context.Background(), option.Cipher, shadowsocks.MethodOptions{
method, err := shadowsocks.CreateMethod(option.Cipher, shadowsocks.MethodOptions{
Password: option.Password,
TimeFunc: ntp.Now,
})
if err != nil {
return nil, fmt.Errorf("ss %s initialize error: %w", addr, err)
return nil, fmt.Errorf("ss %s cipher: %s initialize error: %w", addr, option.Cipher, err)
}
var v2rayOption *v2rayObfs.Option
var gostOption *gost.Option
var obfsOption *simpleObfsOption
var shadowTLSOpt *shadowtls.ShadowTLSOption
var restlsConfig *restlsC.Config
var restlsConfig *restls.Config
var kcptunClient *kcptun.Client
obfsMode := ""
decoder := structure.NewDecoder(structure.Option{TagName: "obfs", WeaklyTypedInput: true})
@@ -259,6 +352,38 @@ func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) {
}
obfsMode = opts.Mode
v2rayOption = &v2rayObfs.Option{
Host: opts.Host,
Path: opts.Path,
Headers: opts.Headers,
Mux: opts.Mux,
V2rayHttpUpgrade: opts.V2rayHttpUpgrade,
V2rayHttpUpgradeFastOpen: opts.V2rayHttpUpgradeFastOpen,
}
if opts.TLS {
v2rayOption.TLS = true
v2rayOption.SkipCertVerify = opts.SkipCertVerify
v2rayOption.Fingerprint = opts.Fingerprint
v2rayOption.Certificate = opts.Certificate
v2rayOption.PrivateKey = opts.PrivateKey
echConfig, err := opts.ECHOpts.Parse()
if err != nil {
return nil, fmt.Errorf("ss %s initialize v2ray-plugin error: %w", addr, err)
}
v2rayOption.ECHConfig = echConfig
}
} else if option.Plugin == "gost-plugin" {
opts := gostObfsOption{Host: "bing.com", Mux: true}
if err := decoder.Decode(option.PluginOpts, &opts); err != nil {
return nil, fmt.Errorf("ss %s initialize gost-plugin error: %w", addr, err)
}
if opts.Mode != "websocket" {
return nil, fmt.Errorf("ss %s obfs mode error: %s", addr, opts.Mode)
}
obfsMode = opts.Mode
gostOption = &gost.Option{
Host: opts.Host,
Path: opts.Path,
Headers: opts.Headers,
@@ -266,8 +391,17 @@ func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) {
}
if opts.TLS {
v2rayOption.TLS = true
v2rayOption.SkipCertVerify = opts.SkipCertVerify
gostOption.TLS = true
gostOption.SkipCertVerify = opts.SkipCertVerify
gostOption.Fingerprint = opts.Fingerprint
gostOption.Certificate = opts.Certificate
gostOption.PrivateKey = opts.PrivateKey
echConfig, err := opts.ECHOpts.Parse()
if err != nil {
return nil, fmt.Errorf("ss %s initialize gost-plugin error: %w", addr, err)
}
gostOption.ECHConfig = echConfig
}
} else if option.Plugin == shadowtls.Mode {
obfsMode = shadowtls.Mode
@@ -282,10 +416,18 @@ func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) {
Password: opt.Password,
Host: opt.Host,
Fingerprint: opt.Fingerprint,
Certificate: opt.Certificate,
PrivateKey: opt.PrivateKey,
ClientFingerprint: option.ClientFingerprint,
SkipCertVerify: opt.SkipCertVerify,
Version: opt.Version,
}
if opt.ALPN != nil { // structure's Decode will ensure value not nil when input has value even it was set an empty array
shadowTLSOpt.ALPN = opt.ALPN
} else {
shadowTLSOpt.ALPN = shadowtls.DefaultALPN
}
} else if option.Plugin == restls.Mode {
obfsMode = restls.Mode
restlsOpt := &restlsOption{}
@@ -293,12 +435,44 @@ func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) {
return nil, fmt.Errorf("ss %s initialize restls-plugin error: %w", addr, err)
}
restlsConfig, err = restlsC.NewRestlsConfig(restlsOpt.Host, restlsOpt.Password, restlsOpt.VersionHint, restlsOpt.RestlsScript, option.ClientFingerprint)
restlsConfig.SessionTicketsDisabled = true
restlsConfig, err = restls.NewRestlsConfig(restlsOpt.Host, restlsOpt.Password, restlsOpt.VersionHint, restlsOpt.RestlsScript, option.ClientFingerprint)
if err != nil {
return nil, fmt.Errorf("ss %s initialize restls-plugin error: %w", addr, err)
}
} else if option.Plugin == kcptun.Mode {
obfsMode = kcptun.Mode
kcptunOpt := &kcpTunOption{}
if err := decoder.Decode(option.PluginOpts, kcptunOpt); err != nil {
return nil, fmt.Errorf("ss %s initialize kcptun-plugin error: %w", addr, err)
}
kcptunClient = kcptun.NewClient(kcptun.Config{
Key: kcptunOpt.Key,
Crypt: kcptunOpt.Crypt,
Mode: kcptunOpt.Mode,
Conn: kcptunOpt.Conn,
AutoExpire: kcptunOpt.AutoExpire,
ScavengeTTL: kcptunOpt.ScavengeTTL,
MTU: kcptunOpt.MTU,
SndWnd: kcptunOpt.SndWnd,
RcvWnd: kcptunOpt.RcvWnd,
DataShard: kcptunOpt.DataShard,
ParityShard: kcptunOpt.ParityShard,
DSCP: kcptunOpt.DSCP,
NoComp: kcptunOpt.NoComp,
AckNodelay: kcptunOpt.AckNodelay,
NoDelay: kcptunOpt.NoDelay,
Interval: kcptunOpt.Interval,
Resend: kcptunOpt.Resend,
NoCongestion: kcptunOpt.NoCongestion,
SockBuf: kcptunOpt.SockBuf,
SmuxVer: kcptunOpt.SmuxVer,
SmuxBuf: kcptunOpt.SmuxBuf,
StreamBuf: kcptunOpt.StreamBuf,
KeepAlive: kcptunOpt.KeepAlive,
})
option.UDPOverTCP = true // must open uot
}
switch option.UDPOverTCPVersion {
case uot.Version, uot.LegacyVersion:
@@ -315,6 +489,7 @@ func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) {
tp: C.Shadowsocks,
udp: option.UDP,
tfo: option.TFO,
mpTcp: option.MPTCP,
iface: option.Interface,
rmark: option.RoutingMark,
prefer: C.NewDNSPrefer(option.IPVersion),
@@ -324,8 +499,10 @@ func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) {
option: &option,
obfsMode: obfsMode,
v2rayOption: v2rayOption,
gostOption: gostOption,
obfsOption: obfsOption,
shadowTLSOption: shadowTLSOpt,
restlsConfig: restlsConfig,
kcptunClient: kcptunClient,
}, nil
}

View File

@@ -7,16 +7,16 @@ import (
"net"
"strconv"
N "github.com/Dreamacro/clash/common/net"
"github.com/Dreamacro/clash/component/dialer"
"github.com/Dreamacro/clash/component/proxydialer"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/transport/shadowsocks/core"
"github.com/Dreamacro/clash/transport/shadowsocks/shadowaead"
"github.com/Dreamacro/clash/transport/shadowsocks/shadowstream"
"github.com/Dreamacro/clash/transport/socks5"
"github.com/Dreamacro/clash/transport/ssr/obfs"
"github.com/Dreamacro/clash/transport/ssr/protocol"
N "github.com/metacubex/mihomo/common/net"
"github.com/metacubex/mihomo/component/dialer"
"github.com/metacubex/mihomo/component/proxydialer"
C "github.com/metacubex/mihomo/constant"
"github.com/metacubex/mihomo/transport/shadowsocks/core"
"github.com/metacubex/mihomo/transport/shadowsocks/shadowaead"
"github.com/metacubex/mihomo/transport/shadowsocks/shadowstream"
"github.com/metacubex/mihomo/transport/socks5"
"github.com/metacubex/mihomo/transport/ssr/obfs"
"github.com/metacubex/mihomo/transport/ssr/protocol"
)
type ShadowSocksR struct {
@@ -42,12 +42,15 @@ type ShadowSocksROption struct {
}
// StreamConnContext implements C.ProxyAdapter
func (ssr *ShadowSocksR) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.Metadata) (net.Conn, error) {
func (ssr *ShadowSocksR) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.Metadata) (_ net.Conn, err error) {
if ctx.Done() != nil {
done := N.SetupContextForConn(ctx, c)
defer done(&err)
}
c = ssr.obfs.StreamConn(c)
c = ssr.cipher.StreamConn(c)
var (
iv []byte
err error
iv []byte
)
switch conn := c.(type) {
case *shadowstream.Conn:
@@ -64,8 +67,8 @@ func (ssr *ShadowSocksR) StreamConnContext(ctx context.Context, c net.Conn, meta
}
// DialContext implements C.ProxyAdapter
func (ssr *ShadowSocksR) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) {
return ssr.DialContextWithDialer(ctx, dialer.NewDialer(ssr.Base.DialOptions(opts...)...), metadata)
func (ssr *ShadowSocksR) DialContext(ctx context.Context, metadata *C.Metadata) (_ C.Conn, err error) {
return ssr.DialContextWithDialer(ctx, dialer.NewDialer(ssr.DialOptions()...), metadata)
}
// DialContextWithDialer implements C.ProxyAdapter
@@ -80,7 +83,6 @@ func (ssr *ShadowSocksR) DialContextWithDialer(ctx context.Context, dialer C.Dia
if err != nil {
return nil, fmt.Errorf("%s connect error: %w", ssr.addr, err)
}
tcpKeepAlive(c)
defer func(c net.Conn) {
safeConnClose(c, err)
@@ -91,8 +93,8 @@ func (ssr *ShadowSocksR) DialContextWithDialer(ctx context.Context, dialer C.Dia
}
// ListenPacketContext implements C.ProxyAdapter
func (ssr *ShadowSocksR) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) {
return ssr.ListenPacketWithDialer(ctx, dialer.NewDialer(ssr.Base.DialOptions(opts...)...), metadata)
func (ssr *ShadowSocksR) ListenPacketContext(ctx context.Context, metadata *C.Metadata) (C.PacketConn, error) {
return ssr.ListenPacketWithDialer(ctx, dialer.NewDialer(ssr.DialOptions()...), metadata)
}
// ListenPacketWithDialer implements C.ProxyAdapter
@@ -103,7 +105,10 @@ func (ssr *ShadowSocksR) ListenPacketWithDialer(ctx context.Context, dialer C.Di
return nil, err
}
}
addr, err := resolveUDPAddrWithPrefer(ctx, "udp", ssr.addr, ssr.prefer)
if err = ssr.ResolveUDP(ctx, metadata); err != nil {
return nil, err
}
addr, err := resolveUDPAddr(ctx, "udp", ssr.addr, ssr.prefer)
if err != nil {
return nil, err
}
@@ -123,9 +128,16 @@ func (ssr *ShadowSocksR) SupportWithDialer() C.NetWork {
return C.ALLNet
}
// ProxyInfo implements C.ProxyAdapter
func (ssr *ShadowSocksR) ProxyInfo() C.ProxyInfo {
info := ssr.Base.ProxyInfo()
info.DialerProxy = ssr.option.DialerProxy
return info
}
func NewShadowSocksR(option ShadowSocksROption) (*ShadowSocksR, error) {
// SSR protocol compatibility
// https://github.com/Dreamacro/clash/pull/2056
// https://github.com/metacubex/mihomo/pull/2056
if option.Cipher == "none" {
option.Cipher = "dummy"
}
@@ -135,7 +147,7 @@ func NewShadowSocksR(option ShadowSocksROption) (*ShadowSocksR, error) {
password := option.Password
coreCiph, err := core.PickCipher(cipher, nil, password)
if err != nil {
return nil, fmt.Errorf("ssr %s initialize error: %w", addr, err)
return nil, fmt.Errorf("ssr %s cipher: %s initialize error: %w", addr, cipher, err)
}
var (
ivSize int
@@ -181,6 +193,7 @@ func NewShadowSocksR(option ShadowSocksROption) (*ShadowSocksR, error) {
tp: C.ShadowsocksR,
udp: option.UDP,
tfo: option.TFO,
mpTcp: option.MPTCP,
iface: option.Interface,
rmark: option.RoutingMark,
prefer: C.NewDNSPrefer(option.IPVersion),
@@ -239,13 +252,14 @@ func (spc *ssrPacketConn) WaitReadFrom() (data []byte, put func(), addr net.Addr
return nil, nil, nil, errors.New("parse addr error")
}
addr = _addr.UDPAddr()
if addr == nil {
udpAddr := _addr.UDPAddr()
if udpAddr == nil {
if put != nil {
put()
}
return nil, nil, nil, errors.New("parse addr error")
}
addr = udpAddr
data = data[len(_addr):]
return

View File

@@ -2,89 +2,58 @@ package outbound
import (
"context"
"errors"
"net"
"runtime"
CN "github.com/Dreamacro/clash/common/net"
"github.com/Dreamacro/clash/component/dialer"
"github.com/Dreamacro/clash/component/proxydialer"
"github.com/Dreamacro/clash/component/resolver"
C "github.com/Dreamacro/clash/constant"
CN "github.com/metacubex/mihomo/common/net"
"github.com/metacubex/mihomo/component/dialer"
"github.com/metacubex/mihomo/component/proxydialer"
C "github.com/metacubex/mihomo/constant"
"github.com/metacubex/mihomo/log"
mux "github.com/sagernet/sing-mux"
E "github.com/sagernet/sing/common/exceptions"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
mux "github.com/metacubex/sing-mux"
E "github.com/metacubex/sing/common/exceptions"
M "github.com/metacubex/sing/common/metadata"
)
type SingMux struct {
C.ProxyAdapter
base ProxyBase
ProxyAdapter
client *mux.Client
dialer *muxSingDialer
dialer proxydialer.SingDialer
onlyTcp bool
}
type SingMuxOption struct {
Enabled bool `proxy:"enabled,omitempty"`
Protocol string `proxy:"protocol,omitempty"`
MaxConnections int `proxy:"max-connections,omitempty"`
MinStreams int `proxy:"min-streams,omitempty"`
MaxStreams int `proxy:"max-streams,omitempty"`
Padding bool `proxy:"padding,omitempty"`
Statistic bool `proxy:"statistic,omitempty"`
OnlyTcp bool `proxy:"only-tcp,omitempty"`
Enabled bool `proxy:"enabled,omitempty"`
Protocol string `proxy:"protocol,omitempty"`
MaxConnections int `proxy:"max-connections,omitempty"`
MinStreams int `proxy:"min-streams,omitempty"`
MaxStreams int `proxy:"max-streams,omitempty"`
Padding bool `proxy:"padding,omitempty"`
Statistic bool `proxy:"statistic,omitempty"`
OnlyTcp bool `proxy:"only-tcp,omitempty"`
BrutalOpts BrutalOption `proxy:"brutal-opts,omitempty"`
}
type ProxyBase interface {
DialOptions(opts ...dialer.Option) []dialer.Option
type BrutalOption struct {
Enabled bool `proxy:"enabled,omitempty"`
Up string `proxy:"up,omitempty"`
Down string `proxy:"down,omitempty"`
}
type muxSingDialer struct {
dialer dialer.Dialer
proxy C.ProxyAdapter
statistic bool
}
var _ N.Dialer = (*muxSingDialer)(nil)
func (d *muxSingDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
var cDialer C.Dialer = proxydialer.New(d.proxy, d.dialer, d.statistic)
return cDialer.DialContext(ctx, network, destination.String())
}
func (d *muxSingDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
var cDialer C.Dialer = proxydialer.New(d.proxy, d.dialer, d.statistic)
return cDialer.ListenPacket(ctx, "udp", "", destination.AddrPort())
}
func (s *SingMux) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) {
options := s.base.DialOptions(opts...)
s.dialer.dialer = dialer.NewDialer(options...)
c, err := s.client.DialContext(ctx, "tcp", M.ParseSocksaddr(metadata.RemoteAddress()))
func (s *SingMux) DialContext(ctx context.Context, metadata *C.Metadata) (_ C.Conn, err error) {
c, err := s.client.DialContext(ctx, "tcp", M.ParseSocksaddrHostPort(metadata.String(), metadata.DstPort))
if err != nil {
return nil, err
}
return NewConn(CN.NewRefConn(c, s), s.ProxyAdapter), err
return NewConn(c, s), err
}
func (s *SingMux) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.PacketConn, err error) {
func (s *SingMux) ListenPacketContext(ctx context.Context, metadata *C.Metadata) (_ C.PacketConn, err error) {
if s.onlyTcp {
return s.ProxyAdapter.ListenPacketContext(ctx, metadata, opts...)
return s.ProxyAdapter.ListenPacketContext(ctx, metadata)
}
options := s.base.DialOptions(opts...)
s.dialer.dialer = dialer.NewDialer(options...)
// sing-mux use stream-oriented udp with a special address, so we need a net.UDPAddr
if !metadata.Resolved() {
ip, err := resolver.ResolveIP(ctx, metadata.Host)
if err != nil {
return nil, errors.New("can't resolve ip")
}
metadata.DstIP = ip
if err = s.ProxyAdapter.ResolveUDP(ctx, metadata); err != nil {
return nil, err
}
pc, err := s.client.ListenPacket(ctx, M.SocksaddrFromNet(metadata.UDPAddr()))
if err != nil {
return nil, err
@@ -92,12 +61,12 @@ func (s *SingMux) ListenPacketContext(ctx context.Context, metadata *C.Metadata,
if pc == nil {
return nil, E.New("packetConn is nil")
}
return newPacketConn(CN.NewRefPacketConn(CN.NewThreadSafePacketConn(pc), s), s.ProxyAdapter), nil
return newPacketConn(CN.NewThreadSafePacketConn(pc), s), nil
}
func (s *SingMux) SupportUDP() bool {
if s.onlyTcp {
return s.ProxyAdapter.SupportUOT()
return s.ProxyAdapter.SupportUDP()
}
return true
}
@@ -109,30 +78,47 @@ func (s *SingMux) SupportUOT() bool {
return true
}
func closeSingMux(s *SingMux) {
_ = s.client.Close()
func (s *SingMux) ProxyInfo() C.ProxyInfo {
info := s.ProxyAdapter.ProxyInfo()
info.SMUX = true
return info
}
func NewSingMux(option SingMuxOption, proxy C.ProxyAdapter, base ProxyBase) (C.ProxyAdapter, error) {
singDialer := &muxSingDialer{dialer: dialer.NewDialer(), proxy: proxy, statistic: option.Statistic}
// Close implements C.ProxyAdapter
func (s *SingMux) Close() error {
if s.client != nil {
_ = s.client.Close()
}
return s.ProxyAdapter.Close()
}
func NewSingMux(option SingMuxOption, proxy ProxyAdapter) (ProxyAdapter, error) {
// TODO
// "TCP Brutal is only supported on Linux-based systems"
singDialer := proxydialer.NewSingDialer(proxy, dialer.NewDialer(proxy.DialOptions()...), option.Statistic)
client, err := mux.NewClient(mux.Options{
Dialer: singDialer,
Logger: log.SingLogger,
Protocol: option.Protocol,
MaxConnections: option.MaxConnections,
MinStreams: option.MinStreams,
MaxStreams: option.MaxStreams,
Padding: option.Padding,
Brutal: mux.BrutalOptions{
Enabled: option.BrutalOpts.Enabled,
SendBPS: StringToBps(option.BrutalOpts.Up),
ReceiveBPS: StringToBps(option.BrutalOpts.Down),
},
})
if err != nil {
return nil, err
}
outbound := &SingMux{
ProxyAdapter: proxy,
base: base,
client: client,
dialer: singDialer,
onlyTcp: option.OnlyTcp,
}
runtime.SetFinalizer(outbound, closeSingMux)
return outbound, nil
}

View File

@@ -6,12 +6,13 @@ import (
"net"
"strconv"
"github.com/Dreamacro/clash/common/structure"
"github.com/Dreamacro/clash/component/dialer"
"github.com/Dreamacro/clash/component/proxydialer"
C "github.com/Dreamacro/clash/constant"
obfs "github.com/Dreamacro/clash/transport/simple-obfs"
"github.com/Dreamacro/clash/transport/snell"
N "github.com/metacubex/mihomo/common/net"
"github.com/metacubex/mihomo/common/structure"
"github.com/metacubex/mihomo/component/dialer"
"github.com/metacubex/mihomo/component/proxydialer"
C "github.com/metacubex/mihomo/constant"
obfs "github.com/metacubex/mihomo/transport/simple-obfs"
"github.com/metacubex/mihomo/transport/snell"
)
type Snell struct {
@@ -41,7 +42,7 @@ type streamOption struct {
obfsOption *simpleObfsOption
}
func streamConn(c net.Conn, option streamOption) *snell.Snell {
func snellStreamConn(c net.Conn, option streamOption) *snell.Snell {
switch option.obfsOption.Mode {
case "tls":
c = obfs.NewTLSObfs(c, option.obfsOption.Host)
@@ -54,33 +55,41 @@ func streamConn(c net.Conn, option streamOption) *snell.Snell {
// StreamConnContext implements C.ProxyAdapter
func (s *Snell) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.Metadata) (net.Conn, error) {
c = streamConn(c, streamOption{s.psk, s.version, s.addr, s.obfsOption})
if metadata.NetWork == C.UDP {
err := snell.WriteUDPHeader(c, s.version)
return c, err
}
port, _ := strconv.ParseUint(metadata.DstPort, 10, 16)
err := snell.WriteHeader(c, metadata.String(), uint(port), s.version)
c = snellStreamConn(c, streamOption{s.psk, s.version, s.addr, s.obfsOption})
err := s.writeHeaderContext(ctx, c, metadata)
return c, err
}
func (s *Snell) writeHeaderContext(ctx context.Context, c net.Conn, metadata *C.Metadata) (err error) {
if ctx.Done() != nil {
done := N.SetupContextForConn(ctx, c)
defer done(&err)
}
if metadata.NetWork == C.UDP {
err = snell.WriteUDPHeader(c, s.version)
return
}
err = snell.WriteHeader(c, metadata.String(), uint(metadata.DstPort), s.version)
return
}
// DialContext implements C.ProxyAdapter
func (s *Snell) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) {
if s.version == snell.Version2 && len(opts) == 0 {
func (s *Snell) DialContext(ctx context.Context, metadata *C.Metadata) (_ C.Conn, err error) {
if s.version == snell.Version2 {
c, err := s.pool.Get()
if err != nil {
return nil, err
}
port, _ := strconv.ParseUint(metadata.DstPort, 10, 16)
if err = snell.WriteHeader(c, metadata.String(), uint(port), s.version); err != nil {
c.Close()
if err = s.writeHeaderContext(ctx, c, metadata); err != nil {
_ = c.Close()
return nil, err
}
return NewConn(c, s), err
}
return s.DialContextWithDialer(ctx, dialer.NewDialer(s.Base.DialOptions(opts...)...), metadata)
return s.DialContextWithDialer(ctx, dialer.NewDialer(s.DialOptions()...), metadata)
}
// DialContextWithDialer implements C.ProxyAdapter
@@ -95,7 +104,6 @@ func (s *Snell) DialContextWithDialer(ctx context.Context, dialer C.Dialer, meta
if err != nil {
return nil, fmt.Errorf("%s connect error: %w", s.addr, err)
}
tcpKeepAlive(c)
defer func(c net.Conn) {
safeConnClose(c, err)
@@ -106,8 +114,8 @@ func (s *Snell) DialContextWithDialer(ctx context.Context, dialer C.Dialer, meta
}
// ListenPacketContext implements C.ProxyAdapter
func (s *Snell) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) {
return s.ListenPacketWithDialer(ctx, dialer.NewDialer(s.Base.DialOptions(opts...)...), metadata)
func (s *Snell) ListenPacketContext(ctx context.Context, metadata *C.Metadata) (C.PacketConn, error) {
return s.ListenPacketWithDialer(ctx, dialer.NewDialer(s.DialOptions()...), metadata)
}
// ListenPacketWithDialer implements C.ProxyAdapter
@@ -119,17 +127,15 @@ func (s *Snell) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, met
return nil, err
}
}
if err = s.ResolveUDP(ctx, metadata); err != nil {
return nil, err
}
c, err := dialer.DialContext(ctx, "tcp", s.addr)
if err != nil {
return nil, err
}
tcpKeepAlive(c)
c = streamConn(c, streamOption{s.psk, s.version, s.addr, s.obfsOption})
err = snell.WriteUDPHeader(c, s.version)
if err != nil {
return nil, err
}
c, err = s.StreamConnContext(ctx, c, metadata)
pc := snell.PacketConn(c)
return newPacketConn(pc, s), nil
@@ -145,6 +151,13 @@ func (s *Snell) SupportUOT() bool {
return true
}
// ProxyInfo implements C.ProxyAdapter
func (s *Snell) ProxyInfo() C.ProxyInfo {
info := s.Base.ProxyInfo()
info.DialerProxy = s.option.DialerProxy
return info
}
func NewSnell(option SnellOption) (*Snell, error) {
addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port))
psk := []byte(option.Psk)
@@ -183,6 +196,7 @@ func NewSnell(option SnellOption) (*Snell, error) {
tp: C.Snell,
udp: option.UDP,
tfo: option.TFO,
mpTcp: option.MPTCP,
iface: option.Interface,
rmark: option.RoutingMark,
prefer: C.NewDNSPrefer(option.IPVersion),
@@ -196,7 +210,7 @@ func NewSnell(option SnellOption) (*Snell, error) {
if option.Version == snell.Version2 {
s.pool = snell.NewPool(func(ctx context.Context) (*snell.Snell, error) {
var err error
var cDialer C.Dialer = dialer.NewDialer(s.Base.DialOptions()...)
var cDialer C.Dialer = dialer.NewDialer(s.DialOptions()...)
if len(s.option.DialerProxy) > 0 {
cDialer, err = proxydialer.NewByName(s.option.DialerProxy, cDialer)
if err != nil {
@@ -208,8 +222,7 @@ func NewSnell(option SnellOption) (*Snell, error) {
return nil, err
}
tcpKeepAlive(c)
return streamConn(c, streamOption{psk, option.Version, addr, obfsOption}), nil
return snellStreamConn(c, streamOption{psk, option.Version, addr, obfsOption}), nil
})
}
return s, nil

View File

@@ -7,13 +7,15 @@ import (
"fmt"
"io"
"net"
"net/netip"
"strconv"
"github.com/Dreamacro/clash/component/dialer"
"github.com/Dreamacro/clash/component/proxydialer"
tlsC "github.com/Dreamacro/clash/component/tls"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/transport/socks5"
N "github.com/metacubex/mihomo/common/net"
"github.com/metacubex/mihomo/component/ca"
"github.com/metacubex/mihomo/component/dialer"
"github.com/metacubex/mihomo/component/proxydialer"
C "github.com/metacubex/mihomo/constant"
"github.com/metacubex/mihomo/transport/socks5"
)
type Socks5 struct {
@@ -37,6 +39,8 @@ type Socks5Option struct {
UDP bool `proxy:"udp,omitempty"`
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
Fingerprint string `proxy:"fingerprint,omitempty"`
Certificate string `proxy:"certificate,omitempty"`
PrivateKey string `proxy:"private-key,omitempty"`
}
// StreamConnContext implements C.ProxyAdapter
@@ -57,15 +61,15 @@ func (ss *Socks5) StreamConnContext(ctx context.Context, c net.Conn, metadata *C
Password: ss.pass,
}
}
if _, err := socks5.ClientHandshake(c, serializesSocksAddr(metadata), socks5.CmdConnect, user); err != nil {
if _, err := ss.clientHandshakeContext(ctx, c, serializesSocksAddr(metadata), socks5.CmdConnect, user); err != nil {
return nil, err
}
return c, nil
}
// DialContext implements C.ProxyAdapter
func (ss *Socks5) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) {
return ss.DialContextWithDialer(ctx, dialer.NewDialer(ss.Base.DialOptions(opts...)...), metadata)
func (ss *Socks5) DialContext(ctx context.Context, metadata *C.Metadata) (_ C.Conn, err error) {
return ss.DialContextWithDialer(ctx, dialer.NewDialer(ss.DialOptions()...), metadata)
}
// DialContextWithDialer implements C.ProxyAdapter
@@ -80,7 +84,6 @@ func (ss *Socks5) DialContextWithDialer(ctx context.Context, dialer C.Dialer, me
if err != nil {
return nil, fmt.Errorf("%s connect error: %w", ss.addr, err)
}
tcpKeepAlive(c)
defer func(c net.Conn) {
safeConnClose(c, err)
@@ -100,14 +103,17 @@ func (ss *Socks5) SupportWithDialer() C.NetWork {
}
// ListenPacketContext implements C.ProxyAdapter
func (ss *Socks5) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.PacketConn, err error) {
var cDialer C.Dialer = dialer.NewDialer(ss.Base.DialOptions(opts...)...)
func (ss *Socks5) ListenPacketContext(ctx context.Context, metadata *C.Metadata) (_ C.PacketConn, err error) {
var cDialer C.Dialer = dialer.NewDialer(ss.DialOptions()...)
if len(ss.option.DialerProxy) > 0 {
cDialer, err = proxydialer.NewByName(ss.option.DialerProxy, cDialer)
if err != nil {
return nil, err
}
}
if err = ss.ResolveUDP(ctx, metadata); err != nil {
return nil, err
}
c, err := cDialer.DialContext(ctx, "tcp", ss.addr)
if err != nil {
err = fmt.Errorf("%s connect error: %w", ss.addr, err)
@@ -126,7 +132,6 @@ func (ss *Socks5) ListenPacketContext(ctx context.Context, metadata *C.Metadata,
safeConnClose(c, err)
}(c)
tcpKeepAlive(c)
var user *socks5.User
if ss.user != "" {
user = &socks5.User{
@@ -135,7 +140,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 := ss.clientHandshakeContext(ctx, c, udpAssocateAddr, socks5.CmdUDPAssociate, user)
if err != nil {
err = fmt.Errorf("client hanshake error: %w", err)
return
@@ -147,7 +153,7 @@ func (ss *Socks5) ListenPacketContext(ctx context.Context, metadata *C.Metadata,
err = errors.New("invalid UDP bind address")
return
} else if bindUDPAddr.IP.IsUnspecified() {
serverAddr, err := resolveUDPAddr(ctx, "udp", ss.Addr())
serverAddr, err := resolveUDPAddr(ctx, "udp", ss.Addr(), C.IPv4Prefer)
if err != nil {
return nil, err
}
@@ -155,7 +161,7 @@ func (ss *Socks5) ListenPacketContext(ctx context.Context, metadata *C.Metadata,
bindUDPAddr.IP = serverAddr.IP
}
pc, err := dialer.ListenPacket(ctx, dialer.ParseNetwork("udp", bindUDPAddr.AddrPort().Addr()), "", ss.Base.DialOptions(opts...)...)
pc, err := cDialer.ListenPacket(ctx, "udp", "", bindUDPAddr.AddrPort())
if err != nil {
return
}
@@ -171,21 +177,36 @@ func (ss *Socks5) ListenPacketContext(ctx context.Context, metadata *C.Metadata,
return newPacketConn(&socksPacketConn{PacketConn: pc, rAddr: bindUDPAddr, tcpConn: c}, ss), nil
}
// ProxyInfo implements C.ProxyAdapter
func (ss *Socks5) ProxyInfo() C.ProxyInfo {
info := ss.Base.ProxyInfo()
info.DialerProxy = ss.option.DialerProxy
return info
}
func (ss *Socks5) clientHandshakeContext(ctx context.Context, c net.Conn, addr socks5.Addr, command socks5.Command, user *socks5.User) (_ socks5.Addr, err error) {
if ctx.Done() != nil {
done := N.SetupContextForConn(ctx, c)
defer done(&err)
}
return socks5.ClientHandshake(c, addr, command, user)
}
func NewSocks5(option Socks5Option) (*Socks5, error) {
var tlsConfig *tls.Config
if option.TLS {
tlsConfig = &tls.Config{
InsecureSkipVerify: option.SkipCertVerify,
ServerName: option.Server,
}
if len(option.Fingerprint) == 0 {
tlsConfig = tlsC.GetGlobalTLSConfig(tlsConfig)
} else {
var err error
if tlsConfig, err = tlsC.GetSpecifiedFingerprintTLSConfig(tlsConfig, option.Fingerprint); err != nil {
return nil, err
}
var err error
tlsConfig, err = ca.GetTLSConfig(ca.Option{
TLSConfig: &tls.Config{
InsecureSkipVerify: option.SkipCertVerify,
ServerName: option.Server,
},
Fingerprint: option.Fingerprint,
Certificate: option.Certificate,
PrivateKey: option.PrivateKey,
})
if err != nil {
return nil, err
}
}
@@ -196,6 +217,7 @@ func NewSocks5(option Socks5Option) (*Socks5, error) {
tp: C.Socks5,
udp: option.UDP,
tfo: option.TFO,
mpTcp: option.MPTCP,
iface: option.Interface,
rmark: option.RoutingMark,
prefer: C.NewDNSPrefer(option.IPVersion),

208
adapter/outbound/ssh.go Normal file
View File

@@ -0,0 +1,208 @@
package outbound
import (
"bytes"
"context"
"encoding/base64"
"fmt"
"net"
"os"
"strconv"
"strings"
"sync"
N "github.com/metacubex/mihomo/common/net"
"github.com/metacubex/mihomo/component/dialer"
"github.com/metacubex/mihomo/component/proxydialer"
C "github.com/metacubex/mihomo/constant"
"github.com/metacubex/randv2"
"golang.org/x/crypto/ssh"
)
type Ssh struct {
*Base
option *SshOption
config *ssh.ClientConfig
client *ssh.Client
cMutex sync.Mutex
}
type SshOption struct {
BasicOption
Name string `proxy:"name"`
Server string `proxy:"server"`
Port int `proxy:"port"`
UserName string `proxy:"username"`
Password string `proxy:"password,omitempty"`
PrivateKey string `proxy:"private-key,omitempty"`
PrivateKeyPassphrase string `proxy:"private-key-passphrase,omitempty"`
HostKey []string `proxy:"host-key,omitempty"`
HostKeyAlgorithms []string `proxy:"host-key-algorithms,omitempty"`
}
func (s *Ssh) DialContext(ctx context.Context, metadata *C.Metadata) (_ C.Conn, err error) {
var cDialer C.Dialer = dialer.NewDialer(s.DialOptions()...)
if len(s.option.DialerProxy) > 0 {
cDialer, err = proxydialer.NewByName(s.option.DialerProxy, cDialer)
if err != nil {
return nil, err
}
}
client, err := s.connect(ctx, cDialer, s.addr)
if err != nil {
return nil, err
}
c, err := client.DialContext(ctx, "tcp", metadata.RemoteAddress())
if err != nil {
return nil, err
}
return NewConn(c, s), nil
}
func (s *Ssh) connect(ctx context.Context, cDialer C.Dialer, addr string) (client *ssh.Client, err error) {
s.cMutex.Lock()
defer s.cMutex.Unlock()
if s.client != nil {
return s.client, nil
}
c, err := cDialer.DialContext(ctx, "tcp", addr)
if err != nil {
return nil, err
}
defer func(c net.Conn) {
safeConnClose(c, err)
}(c)
if ctx.Done() != nil {
done := N.SetupContextForConn(ctx, c)
defer done(&err)
}
clientConn, chans, reqs, err := ssh.NewClientConn(c, addr, s.config)
if err != nil {
return nil, err
}
client = ssh.NewClient(clientConn, chans, reqs)
s.client = client
go func() {
_ = client.Wait() // wait shutdown
_ = client.Close()
s.cMutex.Lock()
defer s.cMutex.Unlock()
if s.client == client {
s.client = nil
}
}()
return client, nil
}
// ProxyInfo implements C.ProxyAdapter
func (s *Ssh) ProxyInfo() C.ProxyInfo {
info := s.Base.ProxyInfo()
info.DialerProxy = s.option.DialerProxy
return info
}
// Close implements C.ProxyAdapter
func (s *Ssh) Close() error {
s.cMutex.Lock()
defer s.cMutex.Unlock()
if s.client != nil {
return s.client.Close()
}
return nil
}
func NewSsh(option SshOption) (*Ssh, error) {
addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port))
config := ssh.ClientConfig{
User: option.UserName,
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
HostKeyAlgorithms: option.HostKeyAlgorithms,
}
if option.PrivateKey != "" {
var b []byte
var err error
if strings.Contains(option.PrivateKey, "PRIVATE KEY") {
b = []byte(option.PrivateKey)
} else {
path := C.Path.Resolve(option.PrivateKey)
if !C.Path.IsSafePath(path) {
return nil, C.Path.ErrNotSafePath(path)
}
b, err = os.ReadFile(path)
if err != nil {
return nil, err
}
}
var pKey ssh.Signer
if option.PrivateKeyPassphrase != "" {
pKey, err = ssh.ParsePrivateKeyWithPassphrase(b, []byte(option.PrivateKeyPassphrase))
} else {
pKey, err = ssh.ParsePrivateKey(b)
}
if err != nil {
return nil, err
}
config.Auth = append(config.Auth, ssh.PublicKeys(pKey))
}
if option.Password != "" {
config.Auth = append(config.Auth, ssh.Password(option.Password))
}
if len(option.HostKey) != 0 {
keys := make([]ssh.PublicKey, len(option.HostKey))
for i, hostKey := range option.HostKey {
key, _, _, _, err := ssh.ParseAuthorizedKey([]byte(hostKey))
if err != nil {
return nil, fmt.Errorf("parse host key :%s", key)
}
keys[i] = key
}
config.HostKeyCallback = func(hostname string, remote net.Addr, key ssh.PublicKey) error {
serverKey := key.Marshal()
for _, hostKey := range keys {
if bytes.Equal(serverKey, hostKey.Marshal()) {
return nil
}
}
return fmt.Errorf("host key mismatch, server send :%s %s", key.Type(), base64.StdEncoding.EncodeToString(serverKey))
}
}
version := "SSH-2.0-OpenSSH_"
if randv2.IntN(2) == 0 {
version += "7." + strconv.Itoa(randv2.IntN(10))
} else {
version += "8." + strconv.Itoa(randv2.IntN(9))
}
config.ClientVersion = version
outbound := &Ssh{
Base: &Base{
name: option.Name,
addr: addr,
tp: C.Ssh,
udp: false,
iface: option.Interface,
rmark: option.RoutingMark,
prefer: C.NewDNSPrefer(option.IPVersion),
},
option: &option,
config: &config,
}
return outbound, nil
}

270
adapter/outbound/sudoku.go Normal file
View File

@@ -0,0 +1,270 @@
package outbound
import (
"context"
"crypto/sha256"
"encoding/binary"
"fmt"
"io"
"net"
"strconv"
"strings"
"time"
"github.com/metacubex/mihomo/log"
"github.com/saba-futai/sudoku/apis"
"github.com/saba-futai/sudoku/pkg/crypto"
"github.com/saba-futai/sudoku/pkg/obfs/httpmask"
"github.com/saba-futai/sudoku/pkg/obfs/sudoku"
N "github.com/metacubex/mihomo/common/net"
"github.com/metacubex/mihomo/component/dialer"
"github.com/metacubex/mihomo/component/proxydialer"
C "github.com/metacubex/mihomo/constant"
)
type Sudoku struct {
*Base
option *SudokuOption
table *sudoku.Table
baseConf apis.ProtocolConfig
}
type SudokuOption struct {
BasicOption
Name string `proxy:"name"`
Server string `proxy:"server"`
Port int `proxy:"port"`
Key string `proxy:"key"`
AEADMethod string `proxy:"aead-method,omitempty"`
PaddingMin *int `proxy:"padding-min,omitempty"`
PaddingMax *int `proxy:"padding-max,omitempty"`
TableType string `proxy:"table-type,omitempty"` // "prefer_ascii" or "prefer_entropy"
HTTPMask bool `proxy:"http-mask,omitempty"`
}
// DialContext implements C.ProxyAdapter
func (s *Sudoku) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
return s.DialContextWithDialer(ctx, dialer.NewDialer(s.DialOptions()...), metadata)
}
// DialContextWithDialer implements C.ProxyAdapter
func (s *Sudoku) DialContextWithDialer(ctx context.Context, d C.Dialer, metadata *C.Metadata) (_ C.Conn, err error) {
if len(s.option.DialerProxy) > 0 {
d, err = proxydialer.NewByName(s.option.DialerProxy, d)
if err != nil {
return nil, err
}
}
cfg, err := s.buildConfig(metadata)
if err != nil {
return nil, err
}
c, err := d.DialContext(ctx, "tcp", s.addr)
if err != nil {
return nil, fmt.Errorf("%s connect error: %w", s.addr, err)
}
defer func() {
safeConnClose(c, err)
}()
if ctx.Done() != nil {
done := N.SetupContextForConn(ctx, c)
defer done(&err)
}
c, err = s.streamConn(c, cfg)
if err != nil {
return nil, err
}
return NewConn(c, s), nil
}
// ListenPacketContext implements C.ProxyAdapter
func (s *Sudoku) ListenPacketContext(ctx context.Context, metadata *C.Metadata) (C.PacketConn, error) {
return nil, C.ErrNotSupport
}
// SupportUOT implements C.ProxyAdapter
func (s *Sudoku) SupportUOT() bool {
return false // Sudoku protocol only supports TCP
}
// SupportWithDialer implements C.ProxyAdapter
func (s *Sudoku) SupportWithDialer() C.NetWork {
return C.TCP
}
// ProxyInfo implements C.ProxyAdapter
func (s *Sudoku) ProxyInfo() C.ProxyInfo {
info := s.Base.ProxyInfo()
info.DialerProxy = s.option.DialerProxy
return info
}
func (s *Sudoku) buildConfig(metadata *C.Metadata) (*apis.ProtocolConfig, error) {
if metadata == nil || metadata.DstPort == 0 || !metadata.Valid() {
return nil, fmt.Errorf("invalid metadata for sudoku outbound")
}
cfg := s.baseConf
cfg.TargetAddress = metadata.RemoteAddress()
if err := cfg.ValidateClient(); err != nil {
return nil, err
}
return &cfg, nil
}
func (s *Sudoku) streamConn(rawConn net.Conn, cfg *apis.ProtocolConfig) (_ net.Conn, err error) {
if !cfg.DisableHTTPMask {
if err = httpmask.WriteRandomRequestHeader(rawConn, cfg.ServerAddress); err != nil {
return nil, fmt.Errorf("write http mask failed: %w", err)
}
}
obfsConn := sudoku.NewConn(rawConn, cfg.Table, cfg.PaddingMin, cfg.PaddingMax, false)
cConn, err := crypto.NewAEADConn(obfsConn, cfg.Key, cfg.AEADMethod)
if err != nil {
return nil, fmt.Errorf("setup crypto failed: %w", err)
}
handshake := buildSudokuHandshakePayload(cfg.Key)
if _, err = cConn.Write(handshake[:]); err != nil {
cConn.Close()
return nil, fmt.Errorf("send handshake failed: %w", err)
}
if err = writeTargetAddress(cConn, cfg.TargetAddress); err != nil {
cConn.Close()
return nil, fmt.Errorf("send target address failed: %w", err)
}
return cConn, nil
}
func NewSudoku(option SudokuOption) (*Sudoku, error) {
if option.Server == "" {
return nil, fmt.Errorf("server is required")
}
if option.Port <= 0 || option.Port > 65535 {
return nil, fmt.Errorf("invalid port: %d", option.Port)
}
if option.Key == "" {
return nil, fmt.Errorf("key is required")
}
tableType := strings.ToLower(option.TableType)
if tableType == "" {
tableType = "prefer_ascii"
}
if tableType != "prefer_ascii" && tableType != "prefer_entropy" {
return nil, fmt.Errorf("table-type must be prefer_ascii or prefer_entropy")
}
seed := option.Key
if recoveredFromKey, err := crypto.RecoverPublicKey(option.Key); err == nil {
seed = crypto.EncodePoint(recoveredFromKey)
}
start := time.Now()
table := sudoku.NewTable(seed, tableType)
log.Infoln("[Sudoku] Tables initialized (%s) in %v", tableType, time.Since(start))
defaultConf := apis.DefaultConfig()
paddingMin := defaultConf.PaddingMin
paddingMax := defaultConf.PaddingMax
if option.PaddingMin != nil {
paddingMin = *option.PaddingMin
}
if option.PaddingMax != nil {
paddingMax = *option.PaddingMax
}
if option.PaddingMin == nil && option.PaddingMax != nil && paddingMax < paddingMin {
paddingMin = paddingMax
}
if option.PaddingMax == nil && option.PaddingMin != nil && paddingMax < paddingMin {
paddingMax = paddingMin
}
baseConf := apis.ProtocolConfig{
ServerAddress: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)),
Key: option.Key,
AEADMethod: defaultConf.AEADMethod,
Table: table,
PaddingMin: paddingMin,
PaddingMax: paddingMax,
HandshakeTimeoutSeconds: defaultConf.HandshakeTimeoutSeconds,
DisableHTTPMask: !option.HTTPMask,
}
if option.AEADMethod != "" {
baseConf.AEADMethod = option.AEADMethod
}
return &Sudoku{
Base: &Base{
name: option.Name,
addr: baseConf.ServerAddress,
tp: C.Sudoku,
udp: false,
tfo: option.TFO,
mpTcp: option.MPTCP,
iface: option.Interface,
rmark: option.RoutingMark,
prefer: C.NewDNSPrefer(option.IPVersion),
},
option: &option,
table: table,
baseConf: baseConf,
}, nil
}
func buildSudokuHandshakePayload(key string) [16]byte {
var payload [16]byte
binary.BigEndian.PutUint64(payload[:8], uint64(time.Now().Unix()))
hash := sha256.Sum256([]byte(key))
copy(payload[8:], hash[:8])
return payload
}
func writeTargetAddress(w io.Writer, rawAddr string) error {
host, portStr, err := net.SplitHostPort(rawAddr)
if err != nil {
return err
}
portInt, err := net.LookupPort("tcp", portStr)
if err != nil {
return err
}
var buf []byte
if ip := net.ParseIP(host); ip != nil {
if ip4 := ip.To4(); ip4 != nil {
buf = append(buf, 0x01) // IPv4
buf = append(buf, ip4...)
} else {
buf = append(buf, 0x04) // IPv6
buf = append(buf, ip...)
}
} else {
if len(host) > 255 {
return fmt.Errorf("domain too long")
}
buf = append(buf, 0x03) // domain
buf = append(buf, byte(len(host)))
buf = append(buf, host...)
}
var portBytes [2]byte
binary.BigEndian.PutUint16(portBytes[:], uint16(portInt))
buf = append(buf, portBytes[:]...)
_, err = w.Write(buf)
return err
}

View File

@@ -3,24 +3,29 @@ package outbound
import (
"context"
"crypto/tls"
"errors"
"fmt"
"net"
"net/http"
"strconv"
"github.com/Dreamacro/clash/component/dialer"
"github.com/Dreamacro/clash/component/proxydialer"
tlsC "github.com/Dreamacro/clash/component/tls"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/transport/gun"
"github.com/Dreamacro/clash/transport/trojan"
"github.com/Dreamacro/clash/transport/vless"
N "github.com/metacubex/mihomo/common/net"
"github.com/metacubex/mihomo/component/ca"
"github.com/metacubex/mihomo/component/dialer"
"github.com/metacubex/mihomo/component/ech"
"github.com/metacubex/mihomo/component/proxydialer"
tlsC "github.com/metacubex/mihomo/component/tls"
C "github.com/metacubex/mihomo/constant"
"github.com/metacubex/mihomo/transport/gun"
"github.com/metacubex/mihomo/transport/shadowsocks/core"
"github.com/metacubex/mihomo/transport/trojan"
"github.com/metacubex/mihomo/transport/vmess"
)
type Trojan struct {
*Base
instance *trojan.Trojan
option *TrojanOption
option *TrojanOption
hexPassword [trojan.KeyLength]byte
// for gun mux
gunTLSConfig *tls.Config
@@ -28,6 +33,9 @@ type Trojan struct {
transport *gun.TransportWrap
realityConfig *tlsC.RealityConfig
echConfig *ech.Config
ssCipher core.Cipher
}
type TrojanOption struct {
@@ -40,23 +48,42 @@ type TrojanOption struct {
SNI string `proxy:"sni,omitempty"`
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
Fingerprint string `proxy:"fingerprint,omitempty"`
Certificate string `proxy:"certificate,omitempty"`
PrivateKey string `proxy:"private-key,omitempty"`
UDP bool `proxy:"udp,omitempty"`
Network string `proxy:"network,omitempty"`
ECHOpts ECHOptions `proxy:"ech-opts,omitempty"`
RealityOpts RealityOptions `proxy:"reality-opts,omitempty"`
GrpcOpts GrpcOptions `proxy:"grpc-opts,omitempty"`
WSOpts WSOptions `proxy:"ws-opts,omitempty"`
Flow string `proxy:"flow,omitempty"`
FlowShow bool `proxy:"flow-show,omitempty"`
SSOpts TrojanSSOption `proxy:"ss-opts,omitempty"`
ClientFingerprint string `proxy:"client-fingerprint,omitempty"`
}
func (t *Trojan) plainStream(ctx context.Context, c net.Conn) (net.Conn, error) {
if t.option.Network == "ws" {
// TrojanSSOption from https://github.com/p4gefau1t/trojan-go/blob/v0.10.6/tunnel/shadowsocks/config.go#L5
type TrojanSSOption struct {
Enabled bool `proxy:"enabled,omitempty"`
Method string `proxy:"method,omitempty"`
Password string `proxy:"password,omitempty"`
}
// StreamConnContext implements C.ProxyAdapter
func (t *Trojan) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.Metadata) (_ net.Conn, err error) {
switch t.option.Network {
case "ws":
host, port, _ := net.SplitHostPort(t.addr)
wsOpts := &trojan.WebsocketOption{
Host: host,
Port: port,
Path: t.option.WSOpts.Path,
wsOpts := &vmess.WebsocketConfig{
Host: host,
Port: port,
Path: t.option.WSOpts.Path,
MaxEarlyData: t.option.WSOpts.MaxEarlyData,
EarlyDataHeaderName: t.option.WSOpts.EarlyDataHeaderName,
V2rayHttpUpgrade: t.option.WSOpts.V2rayHttpUpgrade,
V2rayHttpUpgradeFastOpen: t.option.WSOpts.V2rayHttpUpgradeFastOpen,
ClientFingerprint: t.option.ClientFingerprint,
ECHConfig: t.echConfig,
Headers: http.Header{},
}
if t.option.SNI != "" {
@@ -64,73 +91,112 @@ func (t *Trojan) plainStream(ctx context.Context, c net.Conn) (net.Conn, error)
}
if len(t.option.WSOpts.Headers) != 0 {
header := http.Header{}
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)
alpn := trojan.DefaultWebsocketALPN
if t.option.ALPN != nil { // structure's Decode will ensure value not nil when input has value even it was set an empty array
alpn = t.option.ALPN
}
wsOpts.TLS = true
wsOpts.TLSConfig, err = ca.GetTLSConfig(ca.Option{
TLSConfig: &tls.Config{
NextProtos: alpn,
MinVersion: tls.VersionTLS12,
InsecureSkipVerify: t.option.SkipCertVerify,
ServerName: t.option.SNI,
},
Fingerprint: t.option.Fingerprint,
Certificate: t.option.Certificate,
PrivateKey: t.option.PrivateKey,
})
if err != nil {
return nil, err
}
c, err = vmess.StreamWebsocketConn(ctx, c, wsOpts)
case "grpc":
c, err = gun.StreamGunWithConn(c, t.gunTLSConfig, t.gunConfig, t.echConfig, t.realityConfig)
default:
// default tcp network
// handle TLS
alpn := trojan.DefaultALPN
if t.option.ALPN != nil { // structure's Decode will ensure value not nil when input has value even it was set an empty array
alpn = t.option.ALPN
}
c, err = vmess.StreamTLSConn(ctx, c, &vmess.TLSConfig{
Host: t.option.SNI,
SkipCertVerify: t.option.SkipCertVerify,
FingerPrint: t.option.Fingerprint,
Certificate: t.option.Certificate,
PrivateKey: t.option.PrivateKey,
ClientFingerprint: t.option.ClientFingerprint,
NextProtos: alpn,
ECH: t.echConfig,
Reality: t.realityConfig,
})
}
return t.instance.StreamConn(ctx, c)
}
// StreamConnContext implements C.ProxyAdapter
func (t *Trojan) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.Metadata) (net.Conn, error) {
var err error
if tlsC.HaveGlobalFingerprint() && len(t.option.ClientFingerprint) == 0 {
t.option.ClientFingerprint = tlsC.GetGlobalFingerprint()
}
if t.transport != nil {
c, err = gun.StreamGunWithConn(c, t.gunTLSConfig, t.gunConfig, t.realityConfig)
} else {
c, err = t.plainStream(ctx, c)
}
if err != nil {
return nil, fmt.Errorf("%s connect error: %w", t.addr, err)
}
c, err = t.instance.PresetXTLSConn(c)
if err != nil {
return nil, err
return t.streamConnContext(ctx, c, metadata)
}
func (t *Trojan) streamConnContext(ctx context.Context, c net.Conn, metadata *C.Metadata) (_ net.Conn, err error) {
if t.ssCipher != nil {
c = t.ssCipher.StreamConn(c)
}
if metadata.NetWork == C.UDP {
err = t.instance.WriteHeader(c, trojan.CommandUDP, serializesSocksAddr(metadata))
return c, err
if ctx.Done() != nil {
done := N.SetupContextForConn(ctx, c)
defer done(&err)
}
err = t.instance.WriteHeader(c, trojan.CommandTCP, serializesSocksAddr(metadata))
command := trojan.CommandTCP
if metadata.NetWork == C.UDP {
command = trojan.CommandUDP
}
err = trojan.WriteHeader(c, t.hexPassword, command, serializesSocksAddr(metadata))
return c, err
}
func (t *Trojan) writeHeaderContext(ctx context.Context, c net.Conn, metadata *C.Metadata) (err error) {
if ctx.Done() != nil {
done := N.SetupContextForConn(ctx, c)
defer done(&err)
}
command := trojan.CommandTCP
if metadata.NetWork == C.UDP {
command = trojan.CommandUDP
}
err = trojan.WriteHeader(c, t.hexPassword, command, serializesSocksAddr(metadata))
return err
}
// DialContext implements C.ProxyAdapter
func (t *Trojan) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) {
func (t *Trojan) DialContext(ctx context.Context, metadata *C.Metadata) (_ C.Conn, err error) {
var c net.Conn
// gun transport
if t.transport != nil && len(opts) == 0 {
c, err := gun.StreamGunWithTransport(t.transport, t.gunConfig)
if t.transport != nil {
c, err = gun.StreamGunWithTransport(t.transport, t.gunConfig)
if err != nil {
return nil, err
}
defer func(c net.Conn) {
safeConnClose(c, err)
}(c)
c, err = t.instance.PresetXTLSConn(c)
c, err = t.streamConnContext(ctx, c, metadata)
if err != nil {
c.Close()
return nil, err
}
if err = t.instance.WriteHeader(c, trojan.CommandTCP, serializesSocksAddr(metadata)); err != nil {
c.Close()
return nil, err
}
return NewConn(c, t), nil
}
return t.DialContextWithDialer(ctx, dialer.NewDialer(t.Base.DialOptions(opts...)...), metadata)
return t.DialContextWithDialer(ctx, dialer.NewDialer(t.DialOptions()...), metadata)
}
// DialContextWithDialer implements C.ProxyAdapter
@@ -145,7 +211,6 @@ func (t *Trojan) DialContextWithDialer(ctx context.Context, dialer C.Dialer, met
if err != nil {
return nil, fmt.Errorf("%s connect error: %w", t.addr, err)
}
tcpKeepAlive(c)
defer func(c net.Conn) {
safeConnClose(c, err)
@@ -160,11 +225,15 @@ func (t *Trojan) DialContextWithDialer(ctx context.Context, dialer C.Dialer, met
}
// ListenPacketContext implements C.ProxyAdapter
func (t *Trojan) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.PacketConn, err error) {
func (t *Trojan) ListenPacketContext(ctx context.Context, metadata *C.Metadata) (_ C.PacketConn, err error) {
if err = t.ResolveUDP(ctx, metadata); err != nil {
return nil, err
}
var c net.Conn
// grpc transport
if t.transport != nil && len(opts) == 0 {
if t.transport != nil {
c, err = gun.StreamGunWithTransport(t.transport, t.gunConfig)
if err != nil {
return nil, fmt.Errorf("%s connect error: %w", t.addr, err)
@@ -172,15 +241,16 @@ func (t *Trojan) ListenPacketContext(ctx context.Context, metadata *C.Metadata,
defer func(c net.Conn) {
safeConnClose(c, err)
}(c)
err = t.instance.WriteHeader(c, trojan.CommandUDP, serializesSocksAddr(metadata))
c, err = t.streamConnContext(ctx, c, metadata)
if err != nil {
return nil, err
}
pc := t.instance.PacketConn(c)
pc := trojan.NewPacketConn(c)
return newPacketConn(pc, t), err
}
return t.ListenPacketWithDialer(ctx, dialer.NewDialer(t.Base.DialOptions(opts...)...), metadata)
return t.ListenPacketWithDialer(ctx, dialer.NewDialer(t.DialOptions()...), metadata)
}
// ListenPacketWithDialer implements C.ProxyAdapter
@@ -191,6 +261,9 @@ func (t *Trojan) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, me
return nil, err
}
}
if err = t.ResolveUDP(ctx, metadata); err != nil {
return nil, err
}
c, err := dialer.DialContext(ctx, "tcp", t.addr)
if err != nil {
return nil, fmt.Errorf("%s connect error: %w", t.addr, err)
@@ -198,18 +271,12 @@ func (t *Trojan) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, me
defer func(c net.Conn) {
safeConnClose(c, err)
}(c)
tcpKeepAlive(c)
c, err = t.plainStream(ctx, c)
if err != nil {
return nil, fmt.Errorf("%s connect error: %w", t.addr, err)
}
err = t.instance.WriteHeader(c, trojan.CommandUDP, serializesSocksAddr(metadata))
c, err = t.StreamConnContext(ctx, c, metadata)
if err != nil {
return nil, err
}
pc := t.instance.PacketConn(c)
pc := trojan.NewPacketConn(c)
return newPacketConn(pc, t), err
}
@@ -218,45 +285,31 @@ func (t *Trojan) SupportWithDialer() C.NetWork {
return C.ALLNet
}
// ListenPacketOnStreamConn implements C.ProxyAdapter
func (t *Trojan) ListenPacketOnStreamConn(c net.Conn, metadata *C.Metadata) (_ C.PacketConn, err error) {
pc := t.instance.PacketConn(c)
return newPacketConn(pc, t), err
}
// SupportUOT implements C.ProxyAdapter
func (t *Trojan) SupportUOT() bool {
return true
}
// ProxyInfo implements C.ProxyAdapter
func (t *Trojan) ProxyInfo() C.ProxyInfo {
info := t.Base.ProxyInfo()
info.DialerProxy = t.option.DialerProxy
return info
}
// Close implements C.ProxyAdapter
func (t *Trojan) Close() error {
if t.transport != nil {
return t.transport.Close()
}
return nil
}
func NewTrojan(option TrojanOption) (*Trojan, error) {
addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port))
tOption := &trojan.Option{
Password: option.Password,
ALPN: option.ALPN,
ServerName: option.Server,
SkipCertVerify: option.SkipCertVerify,
FlowShow: option.FlowShow,
Fingerprint: option.Fingerprint,
ClientFingerprint: option.ClientFingerprint,
}
switch option.Network {
case "", "tcp":
if len(option.Flow) >= 16 {
option.Flow = option.Flow[:16]
switch option.Flow {
case vless.XRO, vless.XRD, vless.XRS:
tOption.Flow = option.Flow
default:
return nil, fmt.Errorf("unsupported xtls flow type: %s", option.Flow)
}
}
}
if option.SNI != "" {
tOption.ServerName = option.SNI
if option.SNI == "" {
option.SNI = option.Server
}
t := &Trojan{
@@ -266,12 +319,13 @@ func NewTrojan(option TrojanOption) (*Trojan, error) {
tp: C.Trojan,
udp: option.UDP,
tfo: option.TFO,
mpTcp: option.MPTCP,
iface: option.Interface,
rmark: option.RoutingMark,
prefer: C.NewDNSPrefer(option.IPVersion),
},
instance: trojan.New(tOption),
option: &option,
option: &option,
hexPassword: trojan.Key(option.Password),
}
var err error
@@ -279,48 +333,65 @@ func NewTrojan(option TrojanOption) (*Trojan, error) {
if err != nil {
return nil, err
}
tOption.Reality = t.realityConfig
t.echConfig, err = option.ECHOpts.Parse()
if err != nil {
return nil, err
}
if option.SSOpts.Enabled {
if option.SSOpts.Password == "" {
return nil, errors.New("empty password")
}
if option.SSOpts.Method == "" {
option.SSOpts.Method = "AES-128-GCM"
}
ciph, err := core.PickCipher(option.SSOpts.Method, nil, option.SSOpts.Password)
if err != nil {
return nil, err
}
t.ssCipher = ciph
}
if option.Network == "grpc" {
dialFn := func(network, addr string) (net.Conn, error) {
dialFn := func(ctx context.Context, network, addr string) (net.Conn, error) {
var err error
var cDialer C.Dialer = dialer.NewDialer(t.Base.DialOptions()...)
var cDialer C.Dialer = dialer.NewDialer(t.DialOptions()...)
if len(t.option.DialerProxy) > 0 {
cDialer, err = proxydialer.NewByName(t.option.DialerProxy, cDialer)
if err != nil {
return nil, err
}
}
c, err := cDialer.DialContext(context.Background(), "tcp", t.addr)
c, err := cDialer.DialContext(ctx, "tcp", t.addr)
if err != nil {
return nil, fmt.Errorf("%s connect error: %s", t.addr, err.Error())
}
tcpKeepAlive(c)
return c, nil
}
tlsConfig := &tls.Config{
NextProtos: option.ALPN,
MinVersion: tls.VersionTLS12,
InsecureSkipVerify: tOption.SkipCertVerify,
ServerName: tOption.ServerName,
tlsConfig, err := ca.GetTLSConfig(ca.Option{
TLSConfig: &tls.Config{
NextProtos: option.ALPN,
MinVersion: tls.VersionTLS12,
InsecureSkipVerify: option.SkipCertVerify,
ServerName: option.SNI,
},
Fingerprint: option.Fingerprint,
Certificate: option.Certificate,
PrivateKey: option.PrivateKey,
})
if err != nil {
return nil, err
}
if len(option.Fingerprint) == 0 {
tlsConfig = tlsC.GetGlobalTLSConfig(tlsConfig)
} else {
var err error
if tlsConfig, err = tlsC.GetSpecifiedFingerprintTLSConfig(tlsConfig, option.Fingerprint); err != nil {
return nil, err
}
}
t.transport = gun.NewHTTP2Client(dialFn, tlsConfig, tOption.ClientFingerprint, t.realityConfig)
t.transport = gun.NewHTTP2Client(dialFn, tlsConfig, option.ClientFingerprint, t.echConfig, t.realityConfig)
t.gunTLSConfig = tlsConfig
t.gunConfig = &gun.Config{
ServiceName: option.GrpcOpts.GrpcServiceName,
Host: tOption.ServerName,
ServiceName: option.GrpcOpts.GrpcServiceName,
Host: option.SNI,
ClientFingerprint: option.ClientFingerprint,
}
}

View File

@@ -2,30 +2,34 @@ package outbound
import (
"context"
"crypto/sha256"
"crypto/tls"
"encoding/hex"
"encoding/pem"
"fmt"
"math"
"net"
"os"
"strconv"
"time"
"github.com/metacubex/quic-go"
"github.com/metacubex/mihomo/component/ca"
"github.com/metacubex/mihomo/component/dialer"
"github.com/metacubex/mihomo/component/ech"
"github.com/metacubex/mihomo/component/proxydialer"
tlsC "github.com/metacubex/mihomo/component/tls"
C "github.com/metacubex/mihomo/constant"
"github.com/metacubex/mihomo/transport/tuic"
"github.com/Dreamacro/clash/component/dialer"
"github.com/Dreamacro/clash/component/proxydialer"
tlsC "github.com/Dreamacro/clash/component/tls"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/transport/tuic"
"github.com/gofrs/uuid/v5"
"github.com/metacubex/quic-go"
M "github.com/metacubex/sing/common/metadata"
"github.com/metacubex/sing/common/uot"
)
type Tuic struct {
*Base
option *TuicOption
client *tuic.PoolClient
tlsConfig *tlsC.Config
echConfig *ech.Config
}
type TuicOption struct {
@@ -33,7 +37,9 @@ type TuicOption struct {
Name string `proxy:"name"`
Server string `proxy:"server"`
Port int `proxy:"port"`
Token string `proxy:"token"`
Token string `proxy:"token,omitempty"`
UUID string `proxy:"uuid,omitempty"`
Password string `proxy:"password,omitempty"`
Ip string `proxy:"ip,omitempty"`
HeartbeatInterval int `proxy:"heartbeat-interval,omitempty"`
ALPN []string `proxy:"alpn,omitempty"`
@@ -44,22 +50,27 @@ type TuicOption struct {
DisableSni bool `proxy:"disable-sni,omitempty"`
MaxUdpRelayPacketSize int `proxy:"max-udp-relay-packet-size,omitempty"`
FastOpen bool `proxy:"fast-open,omitempty"`
MaxOpenStreams int `proxy:"max-open-streams,omitempty"`
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
Fingerprint string `proxy:"fingerprint,omitempty"`
CustomCA string `proxy:"ca,omitempty"`
CustomCAString string `proxy:"ca-str,omitempty"`
ReceiveWindowConn int `proxy:"recv-window-conn,omitempty"`
ReceiveWindow int `proxy:"recv-window,omitempty"`
DisableMTUDiscovery bool `proxy:"disable-mtu-discovery,omitempty"`
MaxDatagramFrameSize int `proxy:"max-datagram-frame-size,omitempty"`
SNI string `proxy:"sni,omitempty"`
FastOpen bool `proxy:"fast-open,omitempty"`
MaxOpenStreams int `proxy:"max-open-streams,omitempty"`
CWND int `proxy:"cwnd,omitempty"`
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
Fingerprint string `proxy:"fingerprint,omitempty"`
Certificate string `proxy:"certificate,omitempty"`
PrivateKey string `proxy:"private-key,omitempty"`
ReceiveWindowConn int `proxy:"recv-window-conn,omitempty"`
ReceiveWindow int `proxy:"recv-window,omitempty"`
DisableMTUDiscovery bool `proxy:"disable-mtu-discovery,omitempty"`
MaxDatagramFrameSize int `proxy:"max-datagram-frame-size,omitempty"`
SNI string `proxy:"sni,omitempty"`
ECHOpts ECHOptions `proxy:"ech-opts,omitempty"`
UDPOverStream bool `proxy:"udp-over-stream,omitempty"`
UDPOverStreamVersion int `proxy:"udp-over-stream-version,omitempty"`
}
// DialContext implements C.ProxyAdapter
func (t *Tuic) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.Conn, error) {
return t.DialContextWithDialer(ctx, dialer.NewDialer(t.Base.DialOptions(opts...)...), metadata)
func (t *Tuic) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
return t.DialContextWithDialer(ctx, dialer.NewDialer(t.DialOptions()...), metadata)
}
// DialContextWithDialer implements C.ProxyAdapter
@@ -72,12 +83,35 @@ func (t *Tuic) DialContextWithDialer(ctx context.Context, dialer C.Dialer, metad
}
// ListenPacketContext implements C.ProxyAdapter
func (t *Tuic) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.PacketConn, err error) {
return t.ListenPacketWithDialer(ctx, dialer.NewDialer(t.Base.DialOptions(opts...)...), metadata)
func (t *Tuic) ListenPacketContext(ctx context.Context, metadata *C.Metadata) (_ C.PacketConn, err error) {
return t.ListenPacketWithDialer(ctx, dialer.NewDialer(t.DialOptions()...), metadata)
}
// ListenPacketWithDialer implements C.ProxyAdapter
func (t *Tuic) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, metadata *C.Metadata) (_ C.PacketConn, err error) {
if err = t.ResolveUDP(ctx, metadata); err != nil {
return nil, err
}
if t.option.UDPOverStream {
uotDestination := uot.RequestDestination(uint8(t.option.UDPOverStreamVersion))
uotMetadata := *metadata
uotMetadata.Host = uotDestination.Fqdn
uotMetadata.DstPort = uotDestination.Port
c, err := t.DialContextWithDialer(ctx, dialer, &uotMetadata)
if err != nil {
return nil, err
}
// tuic uos use stream-oriented udp with a special address, so we need a net.UDPAddr
destination := M.SocksaddrFromNet(metadata.UDPAddr())
if t.option.UDPOverStreamVersion == uot.LegacyVersion {
return newPacketConn(uot.NewConn(c, uot.Request{Destination: destination}), t), nil
} else {
return newPacketConn(uot.NewLazyConn(c, uot.Request{Destination: destination}), t), nil
}
}
pc, err := t.client.ListenPacketWithDialer(ctx, metadata, dialer, t.dialWithDialer)
if err != nil {
return nil, err
@@ -90,75 +124,62 @@ func (t *Tuic) SupportWithDialer() C.NetWork {
return C.ALLNet
}
func (t *Tuic) dial(ctx context.Context, opts ...dialer.Option) (pc net.PacketConn, addr net.Addr, err error) {
return t.dialWithDialer(ctx, dialer.NewDialer(opts...))
}
func (t *Tuic) dialWithDialer(ctx context.Context, dialer C.Dialer) (pc net.PacketConn, addr net.Addr, err error) {
func (t *Tuic) dialWithDialer(ctx context.Context, dialer C.Dialer) (transport *quic.Transport, addr net.Addr, err error) {
if len(t.option.DialerProxy) > 0 {
dialer, err = proxydialer.NewByName(t.option.DialerProxy, dialer)
if err != nil {
return nil, nil, err
}
}
udpAddr, err := resolveUDPAddrWithPrefer(ctx, "udp", t.addr, t.prefer)
udpAddr, err := resolveUDPAddr(ctx, "udp", t.addr, t.prefer)
if err != nil {
return nil, nil, err
}
err = t.echConfig.ClientHandle(ctx, t.tlsConfig)
if err != nil {
return nil, nil, err
}
addr = udpAddr
var pc net.PacketConn
pc, err = dialer.ListenPacket(ctx, "udp", "", udpAddr.AddrPort())
if err != nil {
return nil, nil, err
}
transport = &quic.Transport{Conn: pc}
transport.SetCreatedConn(true) // auto close conn
transport.SetSingleUse(true) // auto close transport
return
}
// ProxyInfo implements C.ProxyAdapter
func (t *Tuic) ProxyInfo() C.ProxyInfo {
info := t.Base.ProxyInfo()
info.DialerProxy = t.option.DialerProxy
return info
}
func NewTuic(option TuicOption) (*Tuic, error) {
addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port))
serverName := option.Server
tlsConfig := &tls.Config{
ServerName: serverName,
InsecureSkipVerify: option.SkipCertVerify,
MinVersion: tls.VersionTLS13,
}
if option.SNI != "" {
tlsConfig.ServerName = option.SNI
serverName = option.SNI
}
var bs []byte
var err error
if len(option.CustomCA) > 0 {
bs, err = os.ReadFile(option.CustomCA)
if err != nil {
return nil, fmt.Errorf("tuic %s load ca error: %w", addr, err)
}
} else if option.CustomCAString != "" {
bs = []byte(option.CustomCAString)
tlsConfig, err := ca.GetTLSConfig(ca.Option{
TLSConfig: &tls.Config{
ServerName: serverName,
InsecureSkipVerify: option.SkipCertVerify,
MinVersion: tls.VersionTLS13,
},
Fingerprint: option.Fingerprint,
Certificate: option.Certificate,
PrivateKey: option.PrivateKey,
})
if err != nil {
return nil, err
}
if len(bs) > 0 {
block, _ := pem.Decode(bs)
if block == nil {
return nil, fmt.Errorf("CA cert is not PEM")
}
fpBytes := sha256.Sum256(block.Bytes)
if len(option.Fingerprint) == 0 {
option.Fingerprint = hex.EncodeToString(fpBytes[:])
}
}
if len(option.Fingerprint) != 0 {
var err error
tlsConfig, err = tlsC.GetSpecifiedFingerprintTLSConfig(tlsConfig, option.Fingerprint)
if err != nil {
return nil, err
}
} else {
tlsConfig = tlsC.GetGlobalTLSConfig(tlsConfig)
}
if len(option.ALPN) > 0 {
if option.ALPN != nil { // structure's Decode will ensure value not nil when input has value even it was set an empty array
tlsConfig.NextProtos = option.ALPN
} else {
tlsConfig.NextProtos = []string{"h3"}
@@ -172,8 +193,9 @@ func NewTuic(option TuicOption) (*Tuic, error) {
option.HeartbeatInterval = 10000
}
udpRelayMode := tuic.QUIC
if option.UdpRelayMode != "quic" {
option.UdpRelayMode = "native"
udpRelayMode = tuic.NATIVE
}
if option.MaxUdpRelayPacketSize == 0 {
@@ -184,14 +206,23 @@ func NewTuic(option TuicOption) (*Tuic, error) {
option.MaxOpenStreams = 100
}
if option.CWND == 0 {
option.CWND = 32
}
packetOverHead := tuic.PacketOverHeadV4
if len(option.Token) == 0 {
packetOverHead = tuic.PacketOverHeadV5
}
if option.MaxDatagramFrameSize == 0 {
option.MaxDatagramFrameSize = option.MaxUdpRelayPacketSize + tuic.PacketOverHead
option.MaxDatagramFrameSize = option.MaxUdpRelayPacketSize + packetOverHead
}
if option.MaxDatagramFrameSize > 1400 {
option.MaxDatagramFrameSize = 1400
}
option.MaxUdpRelayPacketSize = option.MaxDatagramFrameSize - tuic.PacketOverHead
option.MaxUdpRelayPacketSize = option.MaxDatagramFrameSize - packetOverHead
// ensure server's incoming stream can handle correctly, increase to 1.1x
quicMaxOpenStreams := int64(option.MaxOpenStreams)
@@ -220,12 +251,24 @@ func NewTuic(option TuicOption) (*Tuic, error) {
if len(option.Ip) > 0 {
addr = net.JoinHostPort(option.Ip, strconv.Itoa(option.Port))
}
host := option.Server
if option.DisableSni {
host = ""
tlsConfig.ServerName = ""
tlsConfig.InsecureSkipVerify = true // tls: either ServerName or InsecureSkipVerify must be specified in the tls.Config
}
tlsClientConfig := tlsC.UConfig(tlsConfig)
echConfig, err := option.ECHOpts.Parse()
if err != nil {
return nil, err
}
switch option.UDPOverStreamVersion {
case uot.Version, uot.LegacyVersion:
case 0:
option.UDPOverStreamVersion = uot.LegacyVersion
default:
return nil, fmt.Errorf("tuic %s unknown udp over stream protocol version: %d", addr, option.UDPOverStreamVersion)
}
tkn := tuic.GenTKN(option.Token)
t := &Tuic{
Base: &Base{
@@ -238,7 +281,9 @@ func NewTuic(option TuicOption) (*Tuic, error) {
rmark: option.RoutingMark,
prefer: C.NewDNSPrefer(option.IPVersion),
},
option: &option,
option: &option,
tlsConfig: tlsClientConfig,
echConfig: echConfig,
}
clientMaxOpenStreams := int64(option.MaxOpenStreams)
@@ -251,21 +296,44 @@ func NewTuic(option TuicOption) (*Tuic, error) {
if clientMaxOpenStreams < 1 {
clientMaxOpenStreams = 1
}
clientOption := &tuic.ClientOption{
TlsConfig: tlsConfig,
QuicConfig: quicConfig,
Host: host,
Token: tkn,
UdpRelayMode: option.UdpRelayMode,
CongestionController: option.CongestionController,
ReduceRtt: option.ReduceRtt,
RequestTimeout: time.Duration(option.RequestTimeout) * time.Millisecond,
MaxUdpRelayPacketSize: option.MaxUdpRelayPacketSize,
FastOpen: option.FastOpen,
MaxOpenStreams: clientMaxOpenStreams,
}
t.client = tuic.NewPoolClient(clientOption)
if len(option.Token) > 0 {
tkn := tuic.GenTKN(option.Token)
clientOption := &tuic.ClientOptionV4{
TlsConfig: tlsClientConfig,
QuicConfig: quicConfig,
Token: tkn,
UdpRelayMode: udpRelayMode,
CongestionController: option.CongestionController,
ReduceRtt: option.ReduceRtt,
RequestTimeout: time.Duration(option.RequestTimeout) * time.Millisecond,
MaxUdpRelayPacketSize: option.MaxUdpRelayPacketSize,
FastOpen: option.FastOpen,
MaxOpenStreams: clientMaxOpenStreams,
CWND: option.CWND,
}
t.client = tuic.NewPoolClientV4(clientOption)
} else {
maxUdpRelayPacketSize := option.MaxUdpRelayPacketSize
if maxUdpRelayPacketSize > tuic.MaxFragSizeV5 {
maxUdpRelayPacketSize = tuic.MaxFragSizeV5
}
clientOption := &tuic.ClientOptionV5{
TlsConfig: tlsClientConfig,
QuicConfig: quicConfig,
Uuid: uuid.FromStringOrNil(option.UUID),
Password: option.Password,
UdpRelayMode: udpRelayMode,
CongestionController: option.CongestionController,
ReduceRtt: option.ReduceRtt,
MaxUdpRelayPacketSize: maxUdpRelayPacketSize,
MaxOpenStreams: clientMaxOpenStreams,
CWND: option.CWND,
}
t.client = tuic.NewPoolClientV5(clientOption)
}
return t, nil
}

View File

@@ -3,134 +3,71 @@ package outbound
import (
"bytes"
"context"
"crypto/tls"
xtls "github.com/xtls/go"
"fmt"
"net"
"net/netip"
"regexp"
"strconv"
"sync"
"time"
"github.com/Dreamacro/clash/component/resolver"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/transport/socks5"
"github.com/metacubex/mihomo/component/resolver"
C "github.com/metacubex/mihomo/constant"
"github.com/metacubex/mihomo/transport/socks5"
)
var (
globalClientSessionCache tls.ClientSessionCache
globalClientXSessionCache xtls.ClientSessionCache
once sync.Once
)
func tcpKeepAlive(c net.Conn) {
if tcp, ok := c.(*net.TCPConn); ok {
_ = tcp.SetKeepAlive(true)
_ = tcp.SetKeepAlivePeriod(30 * time.Second)
}
}
func getClientSessionCache() tls.ClientSessionCache {
once.Do(func() {
globalClientSessionCache = tls.NewLRUClientSessionCache(128)
})
return globalClientSessionCache
}
func getClientXSessionCache() xtls.ClientSessionCache {
once.Do(func() {
globalClientXSessionCache = xtls.NewLRUClientSessionCache(128)
})
return globalClientXSessionCache
}
func serializesSocksAddr(metadata *C.Metadata) []byte {
var buf [][]byte
addrType := metadata.AddrType()
aType := uint8(addrType)
p, _ := strconv.ParseUint(metadata.DstPort, 10, 16)
p := uint(metadata.DstPort)
port := []byte{uint8(p >> 8), uint8(p & 0xff)}
switch addrType {
case socks5.AtypDomainName:
case C.AtypDomainName:
lenM := uint8(len(metadata.Host))
host := []byte(metadata.Host)
buf = [][]byte{{aType, lenM}, host, port}
case socks5.AtypIPv4:
buf = [][]byte{{socks5.AtypDomainName, lenM}, host, port}
case C.AtypIPv4:
host := metadata.DstIP.AsSlice()
buf = [][]byte{{aType}, host, port}
case socks5.AtypIPv6:
buf = [][]byte{{socks5.AtypIPv4}, host, port}
case C.AtypIPv6:
host := metadata.DstIP.AsSlice()
buf = [][]byte{{aType}, host, port}
buf = [][]byte{{socks5.AtypIPv6}, host, port}
}
return bytes.Join(buf, nil)
}
func resolveUDPAddr(ctx context.Context, network, address string) (*net.UDPAddr, error) {
func resolveUDPAddr(ctx context.Context, network, address string, prefer C.DNSPrefer) (*net.UDPAddr, error) {
host, port, err := net.SplitHostPort(address)
if err != nil {
return nil, err
}
ip, err := resolver.ResolveProxyServerHost(ctx, host)
if err != nil {
return nil, err
}
return net.ResolveUDPAddr(network, net.JoinHostPort(ip.String(), port))
}
func resolveUDPAddrWithPrefer(ctx context.Context, network, address string, prefer C.DNSPrefer) (*net.UDPAddr, error) {
host, port, err := net.SplitHostPort(address)
if err != nil {
return nil, err
}
var ip netip.Addr
var fallback netip.Addr
switch prefer {
case C.IPv4Only:
ip, err = resolver.ResolveIPv4ProxyServerHost(ctx, host)
ip, err = resolver.ResolveIPv4WithResolver(ctx, host, resolver.ProxyServerHostResolver)
case C.IPv6Only:
ip, err = resolver.ResolveIPv6ProxyServerHost(ctx, host)
ip, err = resolver.ResolveIPv6WithResolver(ctx, host, resolver.ProxyServerHostResolver)
case C.IPv6Prefer:
var ips []netip.Addr
ips, err = resolver.LookupIPProxyServerHost(ctx, host)
if err == nil {
for _, addr := range ips {
if addr.Is6() {
ip = addr
break
} else {
if !fallback.IsValid() {
fallback = addr
}
}
}
}
ip, err = resolver.ResolveIPPrefer6WithResolver(ctx, host, resolver.ProxyServerHostResolver)
default:
// C.IPv4Prefer, C.DualStack and other
var ips []netip.Addr
ips, err = resolver.LookupIPProxyServerHost(ctx, host)
if err == nil {
for _, addr := range ips {
if addr.Is4() {
ip = addr
break
} else {
if !fallback.IsValid() {
fallback = addr
}
}
}
}
}
if !ip.IsValid() && fallback.IsValid() {
ip = fallback
ip, err = resolver.ResolveIPWithResolver(ctx, host, resolver.ProxyServerHostResolver)
}
if err != nil {
return nil, err
}
return net.ResolveUDPAddr(network, net.JoinHostPort(ip.String(), port))
ip, port = resolver.LookupIP4P(ip, port)
var uint16Port uint16
if port, err := strconv.ParseUint(port, 10, 16); err == nil {
uint16Port = uint16(port)
} else {
return nil, err
}
// our resolver always unmap before return, so unneeded unmap at here
// which is different with net.ResolveUDPAddr maybe return 4in6 address
// 4in6 addresses can cause some strange effects on sing-based code
return net.UDPAddrFromAddrPort(netip.AddrPortFrom(ip, uint16Port)), nil
}
func safeConnClose(c net.Conn, err error) {
@@ -138,3 +75,42 @@ func safeConnClose(c net.Conn, err error) {
_ = c.Close()
}
}
var rateStringRegexp = regexp.MustCompile(`^(\d+)\s*([KMGT]?)([Bb])ps$`)
func StringToBps(s string) uint64 {
if s == "" {
return 0
}
// when have not unit, use Mbps
if v, err := strconv.Atoi(s); err == nil {
return StringToBps(fmt.Sprintf("%d Mbps", v))
}
m := rateStringRegexp.FindStringSubmatch(s)
if m == nil {
return 0
}
var n uint64 = 1
switch m[2] {
case "T":
n *= 1000
fallthrough
case "G":
n *= 1000
fallthrough
case "M":
n *= 1000
fallthrough
case "K":
n *= 1000
}
v, _ := strconv.ParseUint(m[1], 10, 64)
n *= v
if m[3] == "b" {
// Bits, need to convert to bytes
n /= 8
}
return n
}

View File

@@ -3,36 +3,28 @@ package outbound
import (
"context"
"crypto/tls"
"encoding/binary"
"errors"
"fmt"
"io"
"net"
"net/http"
"strconv"
"sync"
"github.com/Dreamacro/clash/common/convert"
N "github.com/Dreamacro/clash/common/net"
"github.com/Dreamacro/clash/component/dialer"
"github.com/Dreamacro/clash/component/proxydialer"
"github.com/Dreamacro/clash/component/resolver"
tlsC "github.com/Dreamacro/clash/component/tls"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/log"
"github.com/Dreamacro/clash/transport/gun"
"github.com/Dreamacro/clash/transport/socks5"
"github.com/Dreamacro/clash/transport/vless"
"github.com/Dreamacro/clash/transport/vmess"
"github.com/metacubex/mihomo/common/convert"
N "github.com/metacubex/mihomo/common/net"
"github.com/metacubex/mihomo/common/utils"
"github.com/metacubex/mihomo/component/ca"
"github.com/metacubex/mihomo/component/dialer"
"github.com/metacubex/mihomo/component/ech"
"github.com/metacubex/mihomo/component/proxydialer"
tlsC "github.com/metacubex/mihomo/component/tls"
C "github.com/metacubex/mihomo/constant"
"github.com/metacubex/mihomo/transport/gun"
"github.com/metacubex/mihomo/transport/vless"
"github.com/metacubex/mihomo/transport/vless/encryption"
"github.com/metacubex/mihomo/transport/vmess"
vmessSing "github.com/sagernet/sing-vmess"
"github.com/sagernet/sing-vmess/packetaddr"
M "github.com/sagernet/sing/common/metadata"
)
const (
// max packet length
maxLength = 1024 << 3
vmessSing "github.com/metacubex/sing-vmess"
"github.com/metacubex/sing-vmess/packetaddr"
M "github.com/metacubex/sing/common/metadata"
)
type Vless struct {
@@ -40,12 +32,15 @@ type Vless struct {
client *vless.Client
option *VlessOption
encryption *encryption.ClientInstance
// for gun mux
gunTLSConfig *tls.Config
gunConfig *gun.Config
transport *gun.TransportWrap
realityConfig *tlsC.RealityConfig
echConfig *ech.Config
}
type VlessOption struct {
@@ -55,44 +50,44 @@ type VlessOption struct {
Port int `proxy:"port"`
UUID string `proxy:"uuid"`
Flow string `proxy:"flow,omitempty"`
FlowShow bool `proxy:"flow-show,omitempty"`
TLS bool `proxy:"tls,omitempty"`
ALPN []string `proxy:"alpn,omitempty"`
UDP bool `proxy:"udp,omitempty"`
PacketAddr bool `proxy:"packet-addr,omitempty"`
XUDP bool `proxy:"xudp,omitempty"`
PacketEncoding string `proxy:"packet-encoding,omitempty"`
Encryption string `proxy:"encryption,omitempty"`
Network string `proxy:"network,omitempty"`
ECHOpts ECHOptions `proxy:"ech-opts,omitempty"`
RealityOpts RealityOptions `proxy:"reality-opts,omitempty"`
HTTPOpts HTTPOptions `proxy:"http-opts,omitempty"`
HTTP2Opts HTTP2Options `proxy:"h2-opts,omitempty"`
GrpcOpts GrpcOptions `proxy:"grpc-opts,omitempty"`
WSOpts WSOptions `proxy:"ws-opts,omitempty"`
WSPath string `proxy:"ws-path,omitempty"`
WSHeaders map[string]string `proxy:"ws-headers,omitempty"`
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
Fingerprint string `proxy:"fingerprint,omitempty"`
Certificate string `proxy:"certificate,omitempty"`
PrivateKey string `proxy:"private-key,omitempty"`
ServerName string `proxy:"servername,omitempty"`
ClientFingerprint string `proxy:"client-fingerprint,omitempty"`
}
func (v *Vless) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.Metadata) (net.Conn, error) {
var err error
if tlsC.HaveGlobalFingerprint() && len(v.option.ClientFingerprint) == 0 {
v.option.ClientFingerprint = tlsC.GetGlobalFingerprint()
}
func (v *Vless) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.Metadata) (_ net.Conn, err error) {
switch v.option.Network {
case "ws":
host, port, _ := net.SplitHostPort(v.addr)
wsOpts := &vmess.WebsocketConfig{
Host: host,
Port: port,
Path: v.option.WSOpts.Path,
MaxEarlyData: v.option.WSOpts.MaxEarlyData,
EarlyDataHeaderName: v.option.WSOpts.EarlyDataHeaderName,
ClientFingerprint: v.option.ClientFingerprint,
Headers: http.Header{},
Host: host,
Port: port,
Path: v.option.WSOpts.Path,
MaxEarlyData: v.option.WSOpts.MaxEarlyData,
EarlyDataHeaderName: v.option.WSOpts.EarlyDataHeaderName,
V2rayHttpUpgrade: v.option.WSOpts.V2rayHttpUpgrade,
V2rayHttpUpgradeFastOpen: v.option.WSOpts.V2rayHttpUpgradeFastOpen,
ClientFingerprint: v.option.ClientFingerprint,
ECHConfig: v.echConfig,
Headers: http.Header{},
}
if len(v.option.WSOpts.Headers) != 0 {
@@ -102,20 +97,19 @@ func (v *Vless) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.M
}
if v.option.TLS {
wsOpts.TLS = true
tlsConfig := &tls.Config{
MinVersion: tls.VersionTLS12,
ServerName: host,
InsecureSkipVerify: v.option.SkipCertVerify,
NextProtos: []string{"http/1.1"},
}
if len(v.option.Fingerprint) == 0 {
wsOpts.TLSConfig = tlsC.GetGlobalTLSConfig(tlsConfig)
} else {
wsOpts.TLSConfig, err = tlsC.GetSpecifiedFingerprintTLSConfig(tlsConfig, v.option.Fingerprint)
if err != nil {
return nil, err
}
wsOpts.TLSConfig, err = ca.GetTLSConfig(ca.Option{
TLSConfig: &tls.Config{
MinVersion: tls.VersionTLS12,
ServerName: host,
InsecureSkipVerify: v.option.SkipCertVerify,
NextProtos: []string{"http/1.1"},
},
Fingerprint: v.option.Fingerprint,
Certificate: v.option.Certificate,
PrivateKey: v.option.PrivateKey,
})
if err != nil {
return nil, err
}
if v.option.ServerName != "" {
@@ -132,7 +126,7 @@ func (v *Vless) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.M
c, err = vmess.StreamWebsocketConn(ctx, c, wsOpts)
case "http":
// readability first, so just copy default TLS logic
c, err = v.streamTLSOrXTLSConn(ctx, c, false)
c, err = v.streamTLSConn(ctx, c, false)
if err != nil {
return nil, err
}
@@ -147,7 +141,7 @@ func (v *Vless) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.M
c = vmess.StreamHTTPConn(c, httpOpts)
case "h2":
c, err = v.streamTLSOrXTLSConn(ctx, c, true)
c, err = v.streamTLSConn(ctx, c, true)
if err != nil {
return nil, err
}
@@ -157,29 +151,39 @@ func (v *Vless) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.M
Path: v.option.HTTP2Opts.Path,
}
c, err = vmess.StreamH2Conn(c, h2Opts)
c, err = vmess.StreamH2Conn(ctx, c, h2Opts)
case "grpc":
c, err = gun.StreamGunWithConn(c, v.gunTLSConfig, v.gunConfig, v.realityConfig)
c, err = gun.StreamGunWithConn(c, v.gunTLSConfig, v.gunConfig, v.echConfig, v.realityConfig)
default:
// default tcp network
// handle TLS And XTLS
c, err = v.streamTLSOrXTLSConn(ctx, c, false)
// handle TLS
c, err = v.streamTLSConn(ctx, c, false)
}
if err != nil {
return nil, err
}
return v.streamConn(c, metadata)
return v.streamConnContext(ctx, c, metadata)
}
func (v *Vless) streamConn(c net.Conn, metadata *C.Metadata) (conn net.Conn, err error) {
func (v *Vless) streamConnContext(ctx context.Context, c net.Conn, metadata *C.Metadata) (conn net.Conn, err error) {
if ctx.Done() != nil {
done := N.SetupContextForConn(ctx, c)
defer done(&err)
}
if v.encryption != nil {
c, err = v.encryption.Handshake(c)
if err != nil {
return
}
}
if metadata.NetWork == C.UDP {
if v.option.PacketAddr {
metadata = &C.Metadata{
NetWork: C.UDP,
Host: packetaddr.SeqPacketMagicAddress,
DstPort: "443",
DstPort: 443,
}
} else {
metadata = &C.Metadata{ // a clear metadata only contains ip
@@ -189,9 +193,6 @@ func (v *Vless) streamConn(c net.Conn, metadata *C.Metadata) (conn net.Conn, err
}
}
conn, err = v.client.StreamConn(c, parseVlessAddr(metadata, v.option.XUDP))
if v.option.PacketAddr {
conn = packetaddr.NewBindConn(conn)
}
} else {
conn, err = v.client.StreamConn(c, parseVlessAddr(metadata, false))
}
@@ -201,29 +202,20 @@ func (v *Vless) streamConn(c net.Conn, metadata *C.Metadata) (conn net.Conn, err
return
}
func (v *Vless) streamTLSOrXTLSConn(ctx context.Context, conn net.Conn, isH2 bool) (net.Conn, error) {
host, _, _ := net.SplitHostPort(v.addr)
func (v *Vless) streamTLSConn(ctx context.Context, conn net.Conn, isH2 bool) (net.Conn, error) {
if v.option.TLS {
host, _, _ := net.SplitHostPort(v.addr)
if v.isLegacyXTLSEnabled() && !isH2 {
xtlsOpts := vless.XTLSConfig{
Host: host,
SkipCertVerify: v.option.SkipCertVerify,
Fingerprint: v.option.Fingerprint,
}
if v.option.ServerName != "" {
xtlsOpts.Host = v.option.ServerName
}
return vless.StreamXTLSConn(ctx, conn, &xtlsOpts)
} else if v.option.TLS {
tlsOpts := vmess.TLSConfig{
Host: host,
SkipCertVerify: v.option.SkipCertVerify,
FingerPrint: v.option.Fingerprint,
Certificate: v.option.Certificate,
PrivateKey: v.option.PrivateKey,
ClientFingerprint: v.option.ClientFingerprint,
ECH: v.echConfig,
Reality: v.realityConfig,
NextProtos: v.option.ALPN,
}
if isH2 {
@@ -240,15 +232,12 @@ func (v *Vless) streamTLSOrXTLSConn(ctx context.Context, conn net.Conn, isH2 boo
return conn, nil
}
func (v *Vless) isLegacyXTLSEnabled() bool {
return v.client.Addons != nil && v.client.Addons.Flow != vless.XRV
}
// DialContext implements C.ProxyAdapter
func (v *Vless) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) {
func (v *Vless) DialContext(ctx context.Context, metadata *C.Metadata) (_ C.Conn, err error) {
var c net.Conn
// gun transport
if v.transport != nil && len(opts) == 0 {
c, err := gun.StreamGunWithTransport(v.transport, v.gunConfig)
if v.transport != nil {
c, err = gun.StreamGunWithTransport(v.transport, v.gunConfig)
if err != nil {
return nil, err
}
@@ -256,14 +245,14 @@ func (v *Vless) DialContext(ctx context.Context, metadata *C.Metadata, opts ...d
safeConnClose(c, err)
}(c)
c, err = v.client.StreamConn(c, parseVlessAddr(metadata, v.option.XUDP))
c, err = v.streamConnContext(ctx, c, metadata)
if err != nil {
return nil, err
}
return NewConn(c, v), nil
}
return v.DialContextWithDialer(ctx, dialer.NewDialer(v.Base.DialOptions(opts...)...), metadata)
return v.DialContextWithDialer(ctx, dialer.NewDialer(v.DialOptions()...), metadata)
}
// DialContextWithDialer implements C.ProxyAdapter
@@ -278,7 +267,6 @@ func (v *Vless) DialContextWithDialer(ctx context.Context, dialer C.Dialer, meta
if err != nil {
return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error())
}
tcpKeepAlive(c)
defer func(c net.Conn) {
safeConnClose(c, err)
}(c)
@@ -291,18 +279,13 @@ func (v *Vless) DialContextWithDialer(ctx context.Context, dialer C.Dialer, meta
}
// ListenPacketContext implements C.ProxyAdapter
func (v *Vless) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.PacketConn, err error) {
// vless use stream-oriented udp with a special address, so we need a net.UDPAddr
if !metadata.Resolved() {
ip, err := resolver.ResolveIP(ctx, metadata.Host)
if err != nil {
return nil, errors.New("can't resolve ip")
}
metadata.DstIP = ip
func (v *Vless) ListenPacketContext(ctx context.Context, metadata *C.Metadata) (_ C.PacketConn, err error) {
if err = v.ResolveUDP(ctx, metadata); err != nil {
return nil, err
}
var c net.Conn
// gun transport
if v.transport != nil && len(opts) == 0 {
if v.transport != nil {
c, err = gun.StreamGunWithTransport(v.transport, v.gunConfig)
if err != nil {
return nil, err
@@ -311,14 +294,14 @@ func (v *Vless) ListenPacketContext(ctx context.Context, metadata *C.Metadata, o
safeConnClose(c, err)
}(c)
c, err = v.streamConn(c, metadata)
c, err = v.streamConnContext(ctx, c, metadata)
if err != nil {
return nil, fmt.Errorf("new vless client error: %v", err)
}
return v.ListenPacketOnStreamConn(ctx, c, metadata)
}
return v.ListenPacketWithDialer(ctx, dialer.NewDialer(v.Base.DialOptions(opts...)...), metadata)
return v.ListenPacketWithDialer(ctx, dialer.NewDialer(v.DialOptions()...), metadata)
}
// ListenPacketWithDialer implements C.ProxyAdapter
@@ -330,20 +313,14 @@ func (v *Vless) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, met
}
}
// vless use stream-oriented udp with a special address, so we need a net.UDPAddr
if !metadata.Resolved() {
ip, err := resolver.ResolveIP(ctx, metadata.Host)
if err != nil {
return nil, errors.New("can't resolve ip")
}
metadata.DstIP = ip
if err = v.ResolveUDP(ctx, metadata); err != nil {
return nil, err
}
c, err := dialer.DialContext(ctx, "tcp", v.addr)
if err != nil {
return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error())
}
tcpKeepAlive(c)
defer func(c net.Conn) {
safeConnClose(c, err)
}(c)
@@ -363,27 +340,27 @@ func (v *Vless) SupportWithDialer() C.NetWork {
// ListenPacketOnStreamConn implements C.ProxyAdapter
func (v *Vless) ListenPacketOnStreamConn(ctx context.Context, c net.Conn, metadata *C.Metadata) (_ C.PacketConn, err error) {
// vless use stream-oriented udp with a special address, so we need a net.UDPAddr
if !metadata.Resolved() {
ip, err := resolver.ResolveIP(ctx, metadata.Host)
if err != nil {
return nil, errors.New("can't resolve ip")
}
metadata.DstIP = ip
if err = v.ResolveUDP(ctx, metadata); err != nil {
return nil, err
}
if v.option.XUDP {
var globalID [8]byte
if metadata.SourceValid() {
globalID = utils.GlobalID(metadata.SourceAddress())
}
return newPacketConn(N.NewThreadSafePacketConn(
vmessSing.NewXUDPConn(c, M.SocksaddrFromNet(metadata.UDPAddr())),
vmessSing.NewXUDPConn(c,
globalID,
M.SocksaddrFromNet(metadata.UDPAddr())),
), v), nil
} else if v.option.PacketAddr {
return newPacketConn(N.NewThreadSafePacketConn(
packetaddr.NewConn(&vlessPacketConn{
Conn: c, rAddr: metadata.UDPAddr(),
}, M.SocksaddrFromNet(metadata.UDPAddr())),
packetaddr.NewConn(v.client.PacketConn(c, metadata.UDPAddr()),
M.SocksaddrFromNet(metadata.UDPAddr())),
), v), nil
}
return newPacketConn(&vlessPacketConn{Conn: c, rAddr: metadata.UDPAddr()}, v), nil
return newPacketConn(N.NewThreadSafePacketConn(v.client.PacketConn(c, metadata.UDPAddr())), v), nil
}
// SupportUOT implements C.ProxyAdapter
@@ -391,142 +368,59 @@ func (v *Vless) SupportUOT() bool {
return true
}
// ProxyInfo implements C.ProxyAdapter
func (v *Vless) ProxyInfo() C.ProxyInfo {
info := v.Base.ProxyInfo()
info.DialerProxy = v.option.DialerProxy
return info
}
// Close implements C.ProxyAdapter
func (v *Vless) Close() error {
if v.transport != nil {
return v.transport.Close()
}
return nil
}
func parseVlessAddr(metadata *C.Metadata, xudp bool) *vless.DstAddr {
var addrType byte
var addr []byte
switch metadata.AddrType() {
case socks5.AtypIPv4:
case C.AtypIPv4:
addrType = vless.AtypIPv4
addr = make([]byte, net.IPv4len)
copy(addr[:], metadata.DstIP.AsSlice())
case socks5.AtypIPv6:
case C.AtypIPv6:
addrType = vless.AtypIPv6
addr = make([]byte, net.IPv6len)
copy(addr[:], metadata.DstIP.AsSlice())
case socks5.AtypDomainName:
case C.AtypDomainName:
addrType = vless.AtypDomainName
addr = make([]byte, len(metadata.Host)+1)
addr[0] = byte(len(metadata.Host))
copy(addr[1:], metadata.Host)
}
port, _ := strconv.ParseUint(metadata.DstPort, 10, 16)
return &vless.DstAddr{
UDP: metadata.NetWork == C.UDP,
AddrType: addrType,
Addr: addr,
Port: uint16(port),
Port: metadata.DstPort,
Mux: metadata.NetWork == C.UDP && xudp,
}
}
type vlessPacketConn struct {
net.Conn
rAddr net.Addr
remain int
mux sync.Mutex
cache [2]byte
}
func (c *vlessPacketConn) writePacket(payload []byte) (int, error) {
binary.BigEndian.PutUint16(c.cache[:], uint16(len(payload)))
if _, err := c.Conn.Write(c.cache[:]); err != nil {
return 0, err
}
return c.Conn.Write(payload)
}
func (c *vlessPacketConn) WriteTo(b []byte, addr net.Addr) (int, error) {
total := len(b)
if total == 0 {
return 0, nil
}
if total <= maxLength {
return c.writePacket(b)
}
offset := 0
for offset < total {
cursor := offset + maxLength
if cursor > total {
cursor = total
}
n, err := c.writePacket(b[offset:cursor])
if err != nil {
return offset + n, err
}
offset = cursor
if offset == total {
break
}
}
return total, nil
}
func (c *vlessPacketConn) ReadFrom(b []byte) (int, net.Addr, error) {
c.mux.Lock()
defer c.mux.Unlock()
if c.remain > 0 {
length := len(b)
if c.remain < length {
length = c.remain
}
n, err := c.Conn.Read(b[:length])
if err != nil {
return 0, c.rAddr, err
}
c.remain -= n
return n, c.rAddr, nil
}
if _, err := c.Conn.Read(b[:2]); err != nil {
return 0, c.rAddr, err
}
total := int(binary.BigEndian.Uint16(b[:2]))
if total == 0 {
return 0, c.rAddr, nil
}
length := len(b)
if length > total {
length = total
}
if _, err := io.ReadFull(c.Conn, b[:length]); err != nil {
return 0, c.rAddr, errors.New("read packet error")
}
c.remain = total - length
return length, c.rAddr, nil
}
func NewVless(option VlessOption) (*Vless, error) {
var addons *vless.Addons
if option.Network != "ws" && len(option.Flow) >= 16 {
if len(option.Flow) >= 16 {
option.Flow = option.Flow[:16]
switch option.Flow {
case vless.XRV:
log.Warnln("To use %s, ensure your server is upgrade to Xray-core v1.8.0+", vless.XRV)
fallthrough
case vless.XRO, vless.XRD, vless.XRS:
addons = &vless.Addons{
Flow: option.Flow,
}
default:
if option.Flow != vless.XRV {
return nil, fmt.Errorf("unsupported xtls flow type: %s", option.Flow)
}
addons = &vless.Addons{
Flow: option.Flow,
}
}
switch option.PacketEncoding {
@@ -542,7 +436,7 @@ func NewVless(option VlessOption) (*Vless, error) {
option.PacketAddr = false
}
client, err := vless.NewClient(option.UUID, addons, option.FlowShow)
client, err := vless.NewClient(option.UUID, addons)
if err != nil {
return nil, err
}
@@ -555,6 +449,7 @@ func NewVless(option VlessOption) (*Vless, error) {
udp: option.UDP,
xudp: option.XUDP,
tfo: option.TFO,
mpTcp: option.MPTCP,
iface: option.Interface,
rmark: option.RoutingMark,
prefer: C.NewDNSPrefer(option.IPVersion),
@@ -563,31 +458,40 @@ func NewVless(option VlessOption) (*Vless, error) {
option: &option,
}
v.encryption, err = encryption.NewClient(option.Encryption)
if err != nil {
return nil, err
}
v.realityConfig, err = v.option.RealityOpts.Parse()
if err != nil {
return nil, err
}
v.echConfig, err = v.option.ECHOpts.Parse()
if err != nil {
return nil, err
}
switch option.Network {
case "h2":
if len(option.HTTP2Opts.Host) == 0 {
option.HTTP2Opts.Host = append(option.HTTP2Opts.Host, "www.example.com")
}
case "grpc":
dialFn := func(network, addr string) (net.Conn, error) {
dialFn := func(ctx context.Context, network, addr string) (net.Conn, error) {
var err error
var cDialer C.Dialer = dialer.NewDialer(v.Base.DialOptions()...)
var cDialer C.Dialer = dialer.NewDialer(v.DialOptions()...)
if len(v.option.DialerProxy) > 0 {
cDialer, err = proxydialer.NewByName(v.option.DialerProxy, cDialer)
if err != nil {
return nil, err
}
}
c, err := cDialer.DialContext(context.Background(), "tcp", v.addr)
c, err := cDialer.DialContext(ctx, "tcp", v.addr)
if err != nil {
return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error())
}
tcpKeepAlive(c)
return c, nil
}
@@ -601,10 +505,18 @@ func NewVless(option VlessOption) (*Vless, error) {
}
var tlsConfig *tls.Config
if option.TLS {
tlsConfig = tlsC.GetGlobalTLSConfig(&tls.Config{
InsecureSkipVerify: v.option.SkipCertVerify,
ServerName: v.option.ServerName,
tlsConfig, err = ca.GetTLSConfig(ca.Option{
TLSConfig: &tls.Config{
InsecureSkipVerify: v.option.SkipCertVerify,
ServerName: v.option.ServerName,
},
Fingerprint: v.option.Fingerprint,
Certificate: v.option.Certificate,
PrivateKey: v.option.PrivateKey,
})
if err != nil {
return nil, err
}
if option.ServerName == "" {
host, _, _ := net.SplitHostPort(v.addr)
tlsConfig.ServerName = host
@@ -614,7 +526,7 @@ func NewVless(option VlessOption) (*Vless, error) {
v.gunTLSConfig = tlsConfig
v.gunConfig = gunConfig
v.transport = gun.NewHTTP2Client(dialFn, tlsConfig, v.option.ClientFingerprint, v.realityConfig)
v.transport = gun.NewHTTP2Client(dialFn, tlsConfig, v.option.ClientFingerprint, v.echConfig, v.realityConfig)
}
return v, nil

View File

@@ -11,18 +11,21 @@ import (
"strings"
"sync"
N "github.com/Dreamacro/clash/common/net"
"github.com/Dreamacro/clash/component/dialer"
"github.com/Dreamacro/clash/component/proxydialer"
"github.com/Dreamacro/clash/component/resolver"
tlsC "github.com/Dreamacro/clash/component/tls"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/transport/gun"
clashVMess "github.com/Dreamacro/clash/transport/vmess"
N "github.com/metacubex/mihomo/common/net"
"github.com/metacubex/mihomo/common/utils"
"github.com/metacubex/mihomo/component/ca"
"github.com/metacubex/mihomo/component/dialer"
"github.com/metacubex/mihomo/component/ech"
"github.com/metacubex/mihomo/component/proxydialer"
tlsC "github.com/metacubex/mihomo/component/tls"
C "github.com/metacubex/mihomo/constant"
"github.com/metacubex/mihomo/ntp"
"github.com/metacubex/mihomo/transport/gun"
mihomoVMess "github.com/metacubex/mihomo/transport/vmess"
vmess "github.com/sagernet/sing-vmess"
"github.com/sagernet/sing-vmess/packetaddr"
M "github.com/sagernet/sing/common/metadata"
vmess "github.com/metacubex/sing-vmess"
"github.com/metacubex/sing-vmess/packetaddr"
M "github.com/metacubex/sing/common/metadata"
)
var ErrUDPRemoteAddrMismatch = errors.New("udp packet dropped due to mismatched remote address")
@@ -38,6 +41,7 @@ type Vmess struct {
transport *gun.TransportWrap
realityConfig *tlsC.RealityConfig
echConfig *ech.Config
}
type VmessOption struct {
@@ -51,9 +55,13 @@ type VmessOption struct {
UDP bool `proxy:"udp,omitempty"`
Network string `proxy:"network,omitempty"`
TLS bool `proxy:"tls,omitempty"`
ALPN []string `proxy:"alpn,omitempty"`
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
Fingerprint string `proxy:"fingerprint,omitempty"`
Certificate string `proxy:"certificate,omitempty"`
PrivateKey string `proxy:"private-key,omitempty"`
ServerName string `proxy:"servername,omitempty"`
ECHOpts ECHOptions `proxy:"ech-opts,omitempty"`
RealityOpts RealityOptions `proxy:"reality-opts,omitempty"`
HTTPOpts HTTPOptions `proxy:"http-opts,omitempty"`
HTTP2Opts HTTP2Options `proxy:"h2-opts,omitempty"`
@@ -83,31 +91,30 @@ type GrpcOptions struct {
}
type WSOptions struct {
Path string `proxy:"path,omitempty"`
Headers map[string]string `proxy:"headers,omitempty"`
MaxEarlyData int `proxy:"max-early-data,omitempty"`
EarlyDataHeaderName string `proxy:"early-data-header-name,omitempty"`
Path string `proxy:"path,omitempty"`
Headers map[string]string `proxy:"headers,omitempty"`
MaxEarlyData int `proxy:"max-early-data,omitempty"`
EarlyDataHeaderName string `proxy:"early-data-header-name,omitempty"`
V2rayHttpUpgrade bool `proxy:"v2ray-http-upgrade,omitempty"`
V2rayHttpUpgradeFastOpen bool `proxy:"v2ray-http-upgrade-fast-open,omitempty"`
}
// StreamConnContext implements C.ProxyAdapter
func (v *Vmess) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.Metadata) (net.Conn, error) {
var err error
if tlsC.HaveGlobalFingerprint() && (len(v.option.ClientFingerprint) == 0) {
v.option.ClientFingerprint = tlsC.GetGlobalFingerprint()
}
func (v *Vmess) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.Metadata) (_ net.Conn, err error) {
switch v.option.Network {
case "ws":
host, port, _ := net.SplitHostPort(v.addr)
wsOpts := &clashVMess.WebsocketConfig{
Host: host,
Port: port,
Path: v.option.WSOpts.Path,
MaxEarlyData: v.option.WSOpts.MaxEarlyData,
EarlyDataHeaderName: v.option.WSOpts.EarlyDataHeaderName,
ClientFingerprint: v.option.ClientFingerprint,
Headers: http.Header{},
wsOpts := &mihomoVMess.WebsocketConfig{
Host: host,
Port: port,
Path: v.option.WSOpts.Path,
MaxEarlyData: v.option.WSOpts.MaxEarlyData,
EarlyDataHeaderName: v.option.WSOpts.EarlyDataHeaderName,
V2rayHttpUpgrade: v.option.WSOpts.V2rayHttpUpgrade,
V2rayHttpUpgradeFastOpen: v.option.WSOpts.V2rayHttpUpgradeFastOpen,
ClientFingerprint: v.option.ClientFingerprint,
ECHConfig: v.echConfig,
Headers: http.Header{},
}
if len(v.option.WSOpts.Headers) != 0 {
@@ -118,18 +125,18 @@ func (v *Vmess) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.M
if v.option.TLS {
wsOpts.TLS = true
tlsConfig := &tls.Config{
ServerName: host,
InsecureSkipVerify: v.option.SkipCertVerify,
NextProtos: []string{"http/1.1"},
}
if len(v.option.Fingerprint) == 0 {
wsOpts.TLSConfig = tlsC.GetGlobalTLSConfig(tlsConfig)
} else {
if wsOpts.TLSConfig, err = tlsC.GetSpecifiedFingerprintTLSConfig(tlsConfig, v.option.Fingerprint); err != nil {
return nil, err
}
wsOpts.TLSConfig, err = ca.GetTLSConfig(ca.Option{
TLSConfig: &tls.Config{
ServerName: host,
InsecureSkipVerify: v.option.SkipCertVerify,
NextProtos: []string{"http/1.1"},
},
Fingerprint: v.option.Fingerprint,
Certificate: v.option.Certificate,
PrivateKey: v.option.PrivateKey,
})
if err != nil {
return nil, err
}
if v.option.ServerName != "" {
@@ -138,41 +145,46 @@ func (v *Vmess) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.M
wsOpts.TLSConfig.ServerName = host
}
}
c, err = clashVMess.StreamWebsocketConn(ctx, c, wsOpts)
c, err = mihomoVMess.StreamWebsocketConn(ctx, c, wsOpts)
case "http":
// readability first, so just copy default TLS logic
if v.option.TLS {
host, _, _ := net.SplitHostPort(v.addr)
tlsOpts := &clashVMess.TLSConfig{
tlsOpts := &mihomoVMess.TLSConfig{
Host: host,
SkipCertVerify: v.option.SkipCertVerify,
ClientFingerprint: v.option.ClientFingerprint,
ECH: v.echConfig,
Reality: v.realityConfig,
NextProtos: v.option.ALPN,
}
if 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 {
return nil, err
}
}
host, _, _ := net.SplitHostPort(v.addr)
httpOpts := &clashVMess.HTTPConfig{
httpOpts := &mihomoVMess.HTTPConfig{
Host: host,
Method: v.option.HTTPOpts.Method,
Path: v.option.HTTPOpts.Path,
Headers: v.option.HTTPOpts.Headers,
}
c = clashVMess.StreamHTTPConn(c, httpOpts)
c = mihomoVMess.StreamHTTPConn(c, httpOpts)
case "h2":
host, _, _ := net.SplitHostPort(v.addr)
tlsOpts := clashVMess.TLSConfig{
tlsOpts := mihomoVMess.TLSConfig{
Host: host,
SkipCertVerify: v.option.SkipCertVerify,
FingerPrint: v.option.Fingerprint,
Certificate: v.option.Certificate,
PrivateKey: v.option.PrivateKey,
NextProtos: []string{"h2"},
ClientFingerprint: v.option.ClientFingerprint,
Reality: v.realityConfig,
@@ -182,71 +194,97 @@ func (v *Vmess) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.M
tlsOpts.Host = v.option.ServerName
}
c, err = clashVMess.StreamTLSConn(ctx, c, &tlsOpts)
c, err = mihomoVMess.StreamTLSConn(ctx, c, &tlsOpts)
if err != nil {
return nil, err
}
h2Opts := &clashVMess.H2Config{
h2Opts := &mihomoVMess.H2Config{
Hosts: v.option.HTTP2Opts.Host,
Path: v.option.HTTP2Opts.Path,
}
c, err = clashVMess.StreamH2Conn(c, h2Opts)
c, err = mihomoVMess.StreamH2Conn(ctx, c, h2Opts)
case "grpc":
c, err = gun.StreamGunWithConn(c, v.gunTLSConfig, v.gunConfig, v.realityConfig)
c, err = gun.StreamGunWithConn(c, v.gunTLSConfig, v.gunConfig, v.echConfig, v.realityConfig)
default:
// handle TLS
if v.option.TLS {
host, _, _ := net.SplitHostPort(v.addr)
tlsOpts := &clashVMess.TLSConfig{
tlsOpts := &mihomoVMess.TLSConfig{
Host: host,
SkipCertVerify: v.option.SkipCertVerify,
FingerPrint: v.option.Fingerprint,
Certificate: v.option.Certificate,
PrivateKey: v.option.PrivateKey,
ClientFingerprint: v.option.ClientFingerprint,
ECH: v.echConfig,
Reality: v.realityConfig,
NextProtos: v.option.ALPN,
}
if 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 {
return nil, err
}
return v.streamConn(c, metadata)
return v.streamConnContext(ctx, c, metadata)
}
func (v *Vmess) streamConn(c net.Conn, metadata *C.Metadata) (conn net.Conn, err error) {
func (v *Vmess) streamConnContext(ctx context.Context, c net.Conn, metadata *C.Metadata) (conn net.Conn, err error) {
useEarly := N.NeedHandshake(c)
if !useEarly {
if ctx.Done() != nil {
done := N.SetupContextForConn(ctx, c)
defer done(&err)
}
}
if metadata.NetWork == C.UDP {
if v.option.XUDP {
if N.NeedHandshake(c) {
conn = v.client.DialEarlyXUDPPacketConn(c, M.SocksaddrFromNet(metadata.UDPAddr()))
var globalID [8]byte
if metadata.SourceValid() {
globalID = utils.GlobalID(metadata.SourceAddress())
}
if useEarly {
conn = v.client.DialEarlyXUDPPacketConn(c,
globalID,
M.SocksaddrFromNet(metadata.UDPAddr()))
} else {
conn, err = v.client.DialXUDPPacketConn(c, M.SocksaddrFromNet(metadata.UDPAddr()))
conn, err = v.client.DialXUDPPacketConn(c,
globalID,
M.SocksaddrFromNet(metadata.UDPAddr()))
}
} else if v.option.PacketAddr {
if N.NeedHandshake(c) {
conn = v.client.DialEarlyPacketConn(c, M.ParseSocksaddrHostPort(packetaddr.SeqPacketMagicAddress, 443))
if useEarly {
conn = v.client.DialEarlyPacketConn(c,
M.ParseSocksaddrHostPort(packetaddr.SeqPacketMagicAddress, 443))
} else {
conn, err = v.client.DialPacketConn(c, M.ParseSocksaddrHostPort(packetaddr.SeqPacketMagicAddress, 443))
conn, err = v.client.DialPacketConn(c,
M.ParseSocksaddrHostPort(packetaddr.SeqPacketMagicAddress, 443))
}
conn = packetaddr.NewBindConn(conn)
} else {
if N.NeedHandshake(c) {
conn = v.client.DialEarlyPacketConn(c, M.SocksaddrFromNet(metadata.UDPAddr()))
if useEarly {
conn = v.client.DialEarlyPacketConn(c,
M.SocksaddrFromNet(metadata.UDPAddr()))
} else {
conn, err = v.client.DialPacketConn(c, M.SocksaddrFromNet(metadata.UDPAddr()))
conn, err = v.client.DialPacketConn(c,
M.SocksaddrFromNet(metadata.UDPAddr()))
}
}
} else {
if N.NeedHandshake(c) {
conn = v.client.DialEarlyConn(c, M.ParseSocksaddr(metadata.RemoteAddress()))
if useEarly {
conn = v.client.DialEarlyConn(c,
M.ParseSocksaddrHostPort(metadata.String(), metadata.DstPort))
} else {
conn, err = v.client.DialConn(c, M.ParseSocksaddr(metadata.RemoteAddress()))
conn, err = v.client.DialConn(c,
M.ParseSocksaddrHostPort(metadata.String(), metadata.DstPort))
}
}
if err != nil {
@@ -256,10 +294,11 @@ func (v *Vmess) streamConn(c net.Conn, metadata *C.Metadata) (conn net.Conn, err
}
// DialContext implements C.ProxyAdapter
func (v *Vmess) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) {
func (v *Vmess) DialContext(ctx context.Context, metadata *C.Metadata) (_ C.Conn, err error) {
var c net.Conn
// gun transport
if v.transport != nil && len(opts) == 0 {
c, err := gun.StreamGunWithTransport(v.transport, v.gunConfig)
if v.transport != nil {
c, err = gun.StreamGunWithTransport(v.transport, v.gunConfig)
if err != nil {
return nil, err
}
@@ -267,14 +306,14 @@ func (v *Vmess) DialContext(ctx context.Context, metadata *C.Metadata, opts ...d
safeConnClose(c, err)
}(c)
c, err = v.client.DialConn(c, M.ParseSocksaddr(metadata.RemoteAddress()))
c, err = v.streamConnContext(ctx, c, metadata)
if err != nil {
return nil, err
}
return NewConn(c, v), nil
}
return v.DialContextWithDialer(ctx, dialer.NewDialer(v.Base.DialOptions(opts...)...), metadata)
return v.DialContextWithDialer(ctx, dialer.NewDialer(v.DialOptions()...), metadata)
}
// DialContextWithDialer implements C.ProxyAdapter
@@ -289,7 +328,6 @@ func (v *Vmess) DialContextWithDialer(ctx context.Context, dialer C.Dialer, meta
if err != nil {
return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error())
}
tcpKeepAlive(c)
defer func(c net.Conn) {
safeConnClose(c, err)
}(c)
@@ -299,18 +337,13 @@ func (v *Vmess) DialContextWithDialer(ctx context.Context, dialer C.Dialer, meta
}
// ListenPacketContext implements C.ProxyAdapter
func (v *Vmess) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.PacketConn, err error) {
// vmess use stream-oriented udp with a special address, so we need a net.UDPAddr
if !metadata.Resolved() {
ip, err := resolver.ResolveIP(ctx, metadata.Host)
if err != nil {
return nil, errors.New("can't resolve ip")
}
metadata.DstIP = ip
func (v *Vmess) ListenPacketContext(ctx context.Context, metadata *C.Metadata) (_ C.PacketConn, err error) {
if err = v.ResolveUDP(ctx, metadata); err != nil {
return nil, err
}
var c net.Conn
// gun transport
if v.transport != nil && len(opts) == 0 {
if v.transport != nil {
c, err = gun.StreamGunWithTransport(v.transport, v.gunConfig)
if err != nil {
return nil, err
@@ -319,13 +352,13 @@ func (v *Vmess) ListenPacketContext(ctx context.Context, metadata *C.Metadata, o
safeConnClose(c, err)
}(c)
c, err = v.streamConn(c, metadata)
c, err = v.streamConnContext(ctx, c, metadata)
if err != nil {
return nil, fmt.Errorf("new vmess client error: %v", err)
}
return v.ListenPacketOnStreamConn(ctx, c, metadata)
}
return v.ListenPacketWithDialer(ctx, dialer.NewDialer(v.Base.DialOptions(opts...)...), metadata)
return v.ListenPacketWithDialer(ctx, dialer.NewDialer(v.DialOptions()...), metadata)
}
// ListenPacketWithDialer implements C.ProxyAdapter
@@ -337,20 +370,14 @@ func (v *Vmess) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, met
}
}
// vmess use stream-oriented udp with a special address, so we need a net.UDPAddr
if !metadata.Resolved() {
ip, err := resolver.ResolveIP(ctx, metadata.Host)
if err != nil {
return nil, errors.New("can't resolve ip")
}
metadata.DstIP = ip
if err = v.ResolveUDP(ctx, metadata); err != nil {
return nil, err
}
c, err := dialer.DialContext(ctx, "tcp", v.addr)
if err != nil {
return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error())
}
tcpKeepAlive(c)
defer func(c net.Conn) {
safeConnClose(c, err)
}(c)
@@ -367,15 +394,25 @@ func (v *Vmess) SupportWithDialer() C.NetWork {
return C.ALLNet
}
// ProxyInfo implements C.ProxyAdapter
func (v *Vmess) ProxyInfo() C.ProxyInfo {
info := v.Base.ProxyInfo()
info.DialerProxy = v.option.DialerProxy
return info
}
// Close implements C.ProxyAdapter
func (v *Vmess) Close() error {
if v.transport != nil {
return v.transport.Close()
}
return nil
}
// ListenPacketOnStreamConn implements C.ProxyAdapter
func (v *Vmess) ListenPacketOnStreamConn(ctx context.Context, c net.Conn, metadata *C.Metadata) (_ C.PacketConn, err error) {
// vmess use stream-oriented udp with a special address, so we need a net.UDPAddr
if !metadata.Resolved() {
ip, err := resolver.ResolveIP(ctx, metadata.Host)
if err != nil {
return nil, errors.New("can't resolve ip")
}
metadata.DstIP = ip
if err = v.ResolveUDP(ctx, metadata); err != nil {
return nil, err
}
if pc, ok := c.(net.PacketConn); ok {
@@ -398,6 +435,7 @@ func NewVmess(option VmessOption) (*Vmess, error) {
if option.AuthenticatedLength {
options = append(options, vmess.ClientWithAuthenticatedLength())
}
options = append(options, vmess.ClientWithTimeFunc(ntp.Now))
client, err := vmess.NewClient(option.UUID, security, option.AlterID, options...)
if err != nil {
return nil, err
@@ -421,6 +459,7 @@ func NewVmess(option VmessOption) (*Vmess, error) {
udp: option.UDP,
xudp: option.XUDP,
tfo: option.TFO,
mpTcp: option.MPTCP,
iface: option.Interface,
rmark: option.RoutingMark,
prefer: C.NewDNSPrefer(option.IPVersion),
@@ -429,26 +468,35 @@ func NewVmess(option VmessOption) (*Vmess, error) {
option: &option,
}
v.realityConfig, err = v.option.RealityOpts.Parse()
if err != nil {
return nil, err
}
v.echConfig, err = v.option.ECHOpts.Parse()
if err != nil {
return nil, err
}
switch option.Network {
case "h2":
if len(option.HTTP2Opts.Host) == 0 {
option.HTTP2Opts.Host = append(option.HTTP2Opts.Host, "www.example.com")
}
case "grpc":
dialFn := func(network, addr string) (net.Conn, error) {
dialFn := func(ctx context.Context, network, addr string) (net.Conn, error) {
var err error
var cDialer C.Dialer = dialer.NewDialer(v.Base.DialOptions()...)
var cDialer C.Dialer = dialer.NewDialer(v.DialOptions()...)
if len(v.option.DialerProxy) > 0 {
cDialer, err = proxydialer.NewByName(v.option.DialerProxy, cDialer)
if err != nil {
return nil, err
}
}
c, err := cDialer.DialContext(context.Background(), "tcp", v.addr)
c, err := cDialer.DialContext(ctx, "tcp", v.addr)
if err != nil {
return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error())
}
tcpKeepAlive(c)
return c, nil
}
@@ -462,10 +510,18 @@ func NewVmess(option VmessOption) (*Vmess, error) {
}
var tlsConfig *tls.Config
if option.TLS {
tlsConfig = tlsC.GetGlobalTLSConfig(&tls.Config{
InsecureSkipVerify: v.option.SkipCertVerify,
ServerName: v.option.ServerName,
tlsConfig, err = ca.GetTLSConfig(ca.Option{
TLSConfig: &tls.Config{
InsecureSkipVerify: v.option.SkipCertVerify,
ServerName: v.option.ServerName,
},
Fingerprint: v.option.Fingerprint,
Certificate: v.option.Certificate,
PrivateKey: v.option.PrivateKey,
})
if err != nil {
return nil, err
}
if option.ServerName == "" {
host, _, _ := net.SplitHostPort(v.addr)
tlsConfig.ServerName = host
@@ -475,12 +531,7 @@ func NewVmess(option VmessOption) (*Vmess, error) {
v.gunTLSConfig = tlsConfig
v.gunConfig = gunConfig
v.transport = gun.NewHTTP2Client(dialFn, tlsConfig, v.option.ClientFingerprint, v.realityConfig)
}
v.realityConfig, err = v.option.RealityOpts.Parse()
if err != nil {
return nil, err
v.transport = gun.NewHTTP2Client(dialFn, tlsConfig, v.option.ClientFingerprint, v.echConfig, v.realityConfig)
}
return v, nil

View File

@@ -4,101 +4,109 @@ import (
"context"
"encoding/base64"
"encoding/hex"
"errors"
"fmt"
"net"
"net/netip"
"runtime"
"strconv"
"strings"
"sync"
"time"
CN "github.com/Dreamacro/clash/common/net"
"github.com/Dreamacro/clash/component/dialer"
"github.com/Dreamacro/clash/component/proxydialer"
"github.com/Dreamacro/clash/component/resolver"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/dns"
"github.com/Dreamacro/clash/log"
"github.com/metacubex/mihomo/common/atomic"
"github.com/metacubex/mihomo/component/dialer"
"github.com/metacubex/mihomo/component/proxydialer"
"github.com/metacubex/mihomo/component/resolver"
"github.com/metacubex/mihomo/component/slowdown"
C "github.com/metacubex/mihomo/constant"
"github.com/metacubex/mihomo/dns"
"github.com/metacubex/mihomo/log"
amnezia "github.com/metacubex/amneziawg-go/device"
wireguard "github.com/metacubex/sing-wireguard"
"github.com/metacubex/wireguard-go/device"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/debug"
E "github.com/sagernet/sing/common/exceptions"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
"github.com/sagernet/wireguard-go/device"
"github.com/metacubex/sing/common/debug"
E "github.com/metacubex/sing/common/exceptions"
M "github.com/metacubex/sing/common/metadata"
)
type wireguardGoDevice interface {
Close()
IpcSet(uapiConf string) error
}
type WireGuard struct {
*Base
bind *wireguard.ClientBind
device *device.Device
device wireguardGoDevice
tunDevice wireguard.Device
dialer *wgSingDialer
startOnce sync.Once
startErr error
resolver *dns.Resolver
refP *refProxyAdapter
dialer proxydialer.SingDialer
resolver resolver.Resolver
initOk atomic.Bool
initMutex sync.Mutex
initErr error
option WireGuardOption
connectAddr M.Socksaddr
localPrefixes []netip.Prefix
serverAddrMap map[M.Socksaddr]netip.AddrPort
serverAddrTime atomic.TypedValue[time.Time]
serverAddrMutex sync.Mutex
}
type WireGuardOption struct {
BasicOption
WireGuardPeerOption
Name string `proxy:"name"`
Ip string `proxy:"ip,omitempty"`
Ipv6 string `proxy:"ipv6,omitempty"`
PrivateKey string `proxy:"private-key"`
Workers int `proxy:"workers,omitempty"`
MTU int `proxy:"mtu,omitempty"`
UDP bool `proxy:"udp,omitempty"`
PersistentKeepalive int `proxy:"persistent-keepalive,omitempty"`
AmneziaWGOption *AmneziaWGOption `proxy:"amnezia-wg-option,omitempty"`
Peers []WireGuardPeerOption `proxy:"peers,omitempty"`
RemoteDnsResolve bool `proxy:"remote-dns-resolve,omitempty"`
Dns []string `proxy:"dns,omitempty"`
RefreshServerIPInterval int `proxy:"refresh-server-ip-interval,omitempty"`
}
type WireGuardPeerOption struct {
Server string `proxy:"server"`
Port int `proxy:"port"`
Ip string `proxy:"ip,omitempty"`
Ipv6 string `proxy:"ipv6,omitempty"`
PublicKey string `proxy:"public-key,omitempty"`
PreSharedKey string `proxy:"pre-shared-key,omitempty"`
Reserved []uint8 `proxy:"reserved,omitempty"`
AllowedIPs []string `proxy:"allowed-ips,omitempty"`
}
type wgSingDialer struct {
dialer dialer.Dialer
proxyName string
}
var _ N.Dialer = (*wgSingDialer)(nil)
func (d *wgSingDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
var cDialer C.Dialer = d.dialer
if len(d.proxyName) > 0 {
pd, err := proxydialer.NewByName(d.proxyName, d.dialer)
if err != nil {
return nil, err
}
cDialer = pd
}
return cDialer.DialContext(ctx, network, destination.String())
}
func (d *wgSingDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
var cDialer C.Dialer = d.dialer
if len(d.proxyName) > 0 {
pd, err := proxydialer.NewByName(d.proxyName, d.dialer)
if err != nil {
return nil, err
}
cDialer = pd
}
return cDialer.ListenPacket(ctx, "udp", "", destination.AddrPort())
type AmneziaWGOption struct {
JC int `proxy:"jc,omitempty"`
JMin int `proxy:"jmin,omitempty"`
JMax int `proxy:"jmax,omitempty"`
S1 int `proxy:"s1,omitempty"`
S2 int `proxy:"s2,omitempty"`
S3 int `proxy:"s3,omitempty"` // AmneziaWG v1.5 and v2
S4 int `proxy:"s4,omitempty"` // AmneziaWG v1.5 and v2
H1 string `proxy:"h1,omitempty"` // In AmneziaWG v1.x, it was uint32, but our WeaklyTypedInput can handle this situation
H2 string `proxy:"h2,omitempty"` // In AmneziaWG v1.x, it was uint32, but our WeaklyTypedInput can handle this situation
H3 string `proxy:"h3,omitempty"` // In AmneziaWG v1.x, it was uint32, but our WeaklyTypedInput can handle this situation
H4 string `proxy:"h4,omitempty"` // In AmneziaWG v1.x, it was uint32, but our WeaklyTypedInput can handle this situation
I1 string `proxy:"i1,omitempty"` // AmneziaWG v1.5 and v2
I2 string `proxy:"i2,omitempty"` // AmneziaWG v1.5 and v2
I3 string `proxy:"i3,omitempty"` // AmneziaWG v1.5 and v2
I4 string `proxy:"i4,omitempty"` // AmneziaWG v1.5 and v2
I5 string `proxy:"i5,omitempty"` // AmneziaWG v1.5 and v2
J1 string `proxy:"j1,omitempty"` // AmneziaWG v1.5 only (removed in v2)
J2 string `proxy:"j2,omitempty"` // AmneziaWG v1.5 only (removed in v2)
J3 string `proxy:"j3,omitempty"` // AmneziaWG v1.5 only (removed in v2)
Itime int64 `proxy:"itime,omitempty"` // AmneziaWG v1.5 only (removed in v2)
}
type wgSingErrorHandler struct {
@@ -129,7 +137,7 @@ func (option WireGuardPeerOption) Addr() M.Socksaddr {
return M.ParseSocksaddrHostPort(option.Server, uint16(option.Port))
}
func (option WireGuardPeerOption) Prefixes() ([]netip.Prefix, error) {
func (option WireGuardOption) Prefixes() ([]netip.Prefix, error) {
localPrefixes := make([]netip.Prefix, 0, 2)
if len(option.Ip) > 0 {
if !strings.Contains(option.Ip, "/") {
@@ -168,9 +176,9 @@ func NewWireGuard(option WireGuardOption) (*WireGuard, error) {
rmark: option.RoutingMark,
prefer: C.NewDNSPrefer(option.IPVersion),
},
dialer: &wgSingDialer{dialer: dialer.NewDialer(), proxyName: option.DialerProxy},
}
runtime.SetFinalizer(outbound, closeWireGuard)
singDialer := proxydialer.NewSlowDownSingDialer(proxydialer.NewByNameSingDialer(option.DialerProxy, dialer.NewDialer(outbound.DialOptions()...)), slowdown.New())
outbound.dialer = singDialer
var reserved [3]uint8
if len(option.Reserved) > 0 {
@@ -180,162 +188,116 @@ func NewWireGuard(option WireGuardOption) (*WireGuard, error) {
copy(reserved[:], option.Reserved)
}
var isConnect bool
var connectAddr M.Socksaddr
if len(option.Peers) < 2 {
isConnect = true
if len(option.Peers) == 1 {
connectAddr = option.Peers[0].Addr()
outbound.connectAddr = option.Peers[0].Addr()
} else {
connectAddr = option.Addr()
outbound.connectAddr = option.Addr()
}
}
outbound.bind = wireguard.NewClientBind(context.Background(), wgSingErrorHandler{outbound.Name()}, outbound.dialer, isConnect, connectAddr, reserved)
outbound.bind = wireguard.NewClientBind(context.Background(), wgSingErrorHandler{outbound.Name()}, outbound.dialer, isConnect, outbound.connectAddr.AddrPort(), reserved)
var localPrefixes []netip.Prefix
var err error
outbound.localPrefixes, err = option.Prefixes()
if err != nil {
return nil, err
}
var privateKey string
{
bytes, err := base64.StdEncoding.DecodeString(option.PrivateKey)
if err != nil {
return nil, E.Cause(err, "decode private key")
}
privateKey = hex.EncodeToString(bytes)
option.PrivateKey = hex.EncodeToString(bytes)
}
ipcConf := "private_key=" + privateKey
if peersLen := len(option.Peers); peersLen > 0 {
localPrefixes = make([]netip.Prefix, 0, peersLen*2)
for i, peer := range option.Peers {
var peerPublicKey, preSharedKey string
{
bytes, err := base64.StdEncoding.DecodeString(peer.PublicKey)
if err != nil {
return nil, E.Cause(err, "decode public key for peer ", i)
}
peerPublicKey = hex.EncodeToString(bytes)
if len(option.Peers) > 0 {
for i := range option.Peers {
peer := &option.Peers[i] // we need modify option here
bytes, err := base64.StdEncoding.DecodeString(peer.PublicKey)
if err != nil {
return nil, E.Cause(err, "decode public key for peer ", i)
}
peer.PublicKey = hex.EncodeToString(bytes)
if peer.PreSharedKey != "" {
bytes, err := base64.StdEncoding.DecodeString(peer.PreSharedKey)
if err != nil {
return nil, E.Cause(err, "decode pre shared key for peer ", i)
}
preSharedKey = hex.EncodeToString(bytes)
}
destination := peer.Addr()
ipcConf += "\npublic_key=" + peerPublicKey
ipcConf += "\nendpoint=" + destination.String()
if preSharedKey != "" {
ipcConf += "\npreshared_key=" + preSharedKey
peer.PreSharedKey = hex.EncodeToString(bytes)
}
if len(peer.AllowedIPs) == 0 {
return nil, E.New("missing allowed_ips for peer ", i)
}
for _, allowedIP := range peer.AllowedIPs {
ipcConf += "\nallowed_ip=" + allowedIP
}
if len(peer.Reserved) > 0 {
if len(peer.Reserved) != 3 {
return nil, E.New("invalid reserved value for peer ", i, ", required 3 bytes, got ", len(peer.Reserved))
}
copy(reserved[:], option.Reserved)
outbound.bind.SetReservedForEndpoint(destination, reserved)
}
prefixes, err := peer.Prefixes()
if err != nil {
return nil, err
}
localPrefixes = append(localPrefixes, prefixes...)
}
} else {
var peerPublicKey, preSharedKey string
{
bytes, err := base64.StdEncoding.DecodeString(option.PublicKey)
if err != nil {
return nil, E.Cause(err, "decode peer public key")
}
peerPublicKey = hex.EncodeToString(bytes)
option.PublicKey = hex.EncodeToString(bytes)
}
if option.PreSharedKey != "" {
bytes, err := base64.StdEncoding.DecodeString(option.PreSharedKey)
if err != nil {
return nil, E.Cause(err, "decode pre shared key")
}
preSharedKey = hex.EncodeToString(bytes)
}
ipcConf += "\npublic_key=" + peerPublicKey
ipcConf += "\nendpoint=" + connectAddr.String()
if preSharedKey != "" {
ipcConf += "\npreshared_key=" + preSharedKey
}
var err error
localPrefixes, err = option.Prefixes()
if err != nil {
return nil, err
}
var has4, has6 bool
for _, address := range localPrefixes {
if address.Addr().Is4() {
has4 = true
} else {
has6 = true
}
}
if has4 {
ipcConf += "\nallowed_ip=0.0.0.0/0"
}
if has6 {
ipcConf += "\nallowed_ip=::/0"
option.PreSharedKey = hex.EncodeToString(bytes)
}
}
outbound.option = option
if option.PersistentKeepalive != 0 {
ipcConf += fmt.Sprintf("\npersistent_keepalive_interval=%d", option.PersistentKeepalive)
}
mtu := option.MTU
if mtu == 0 {
mtu = 1408
}
if len(localPrefixes) == 0 {
if len(outbound.localPrefixes) == 0 {
return nil, E.New("missing local address")
}
var err error
outbound.tunDevice, err = wireguard.NewStackDevice(localPrefixes, uint32(mtu))
outbound.tunDevice, err = wireguard.NewStackDevice(outbound.localPrefixes, uint32(mtu))
if err != nil {
return nil, E.Cause(err, "create WireGuard device")
}
outbound.device = device.NewDevice(outbound.tunDevice, outbound.bind, &device.Logger{
logger := &device.Logger{
Verbosef: func(format string, args ...interface{}) {
log.SingLogger.Debug(fmt.Sprintf("[WG](%s) %s", option.Name, fmt.Sprintf(format, args...)))
},
Errorf: func(format string, args ...interface{}) {
log.SingLogger.Error(fmt.Sprintf("[WG](%s) %s", option.Name, fmt.Sprintf(format, args...)))
},
}, option.Workers)
if debug.Enabled {
log.SingLogger.Trace(fmt.Sprintf("[WG](%s) created wireguard ipc conf: \n %s", option.Name, ipcConf))
}
err = outbound.device.IpcSet(ipcConf)
if err != nil {
return nil, E.Cause(err, "setup wireguard")
if option.AmneziaWGOption != nil {
outbound.bind.SetParseReserved(false) // AmneziaWG don't need parse reserved
outbound.device = amnezia.NewDevice(outbound.tunDevice, outbound.bind, logger, option.Workers)
} else {
outbound.device = device.NewDevice(outbound.tunDevice, outbound.bind, logger, option.Workers)
}
//err = outbound.tunDevice.Start()
var has6 bool
for _, address := range localPrefixes {
for _, address := range outbound.localPrefixes {
if !address.Addr().Unmap().Is4() {
has6 = true
break
}
}
refP := &refProxyAdapter{}
outbound.refP = refP
if option.RemoteDnsResolve && len(option.Dns) > 0 {
nss, err := dns.ParseNameServer(option.Dns)
if err != nil {
return nil, err
}
for i := range nss {
nss[i].ProxyAdapter = refP
nss[i].ProxyAdapter = outbound
}
outbound.resolver = dns.NewResolver(dns.Config{
Main: nss,
@@ -346,36 +308,264 @@ func NewWireGuard(option WireGuardOption) (*WireGuard, error) {
return outbound, nil
}
func closeWireGuard(w *WireGuard) {
func (w *WireGuard) resolve(ctx context.Context, address M.Socksaddr) (netip.AddrPort, error) {
if address.Addr.IsValid() {
return address.AddrPort(), nil
}
udpAddr, err := resolveUDPAddr(ctx, "udp", address.String(), w.prefer)
if err != nil {
return netip.AddrPort{}, err
}
// net.ResolveUDPAddr maybe return 4in6 address, so unmap at here
addrPort := udpAddr.AddrPort()
return netip.AddrPortFrom(addrPort.Addr().Unmap(), addrPort.Port()), nil
}
func (w *WireGuard) init(ctx context.Context) error {
err := w.init0(ctx)
if err != nil {
return err
}
w.updateServerAddr(ctx)
return nil
}
func (w *WireGuard) init0(ctx context.Context) error {
if w.initOk.Load() {
return nil
}
w.initMutex.Lock()
defer w.initMutex.Unlock()
// double check like sync.Once
if w.initOk.Load() {
return nil
}
if w.initErr != nil {
return w.initErr
}
w.bind.ResetReservedForEndpoint()
w.serverAddrMap = make(map[M.Socksaddr]netip.AddrPort)
ipcConf, err := w.genIpcConf(ctx, false)
if err != nil {
// !!! do not set initErr here !!!
// let us can retry domain resolve in next time
return err
}
if debug.Enabled {
log.SingLogger.Trace(fmt.Sprintf("[WG](%s) created wireguard ipc conf: \n %s", w.option.Name, ipcConf))
}
err = w.device.IpcSet(ipcConf)
if err != nil {
w.initErr = E.Cause(err, "setup wireguard")
return w.initErr
}
w.serverAddrTime.Store(time.Now())
err = w.tunDevice.Start()
if err != nil {
w.initErr = err
return w.initErr
}
w.initOk.Store(true)
return nil
}
func (w *WireGuard) updateServerAddr(ctx context.Context) {
if w.option.RefreshServerIPInterval != 0 && time.Since(w.serverAddrTime.Load()) > time.Second*time.Duration(w.option.RefreshServerIPInterval) {
if w.serverAddrMutex.TryLock() {
defer w.serverAddrMutex.Unlock()
ipcConf, err := w.genIpcConf(ctx, true)
if err != nil {
log.Warnln("[WG](%s)UpdateServerAddr failed to generate wireguard ipc conf: %s", w.option.Name, err)
return
}
err = w.device.IpcSet(ipcConf)
if err != nil {
log.Warnln("[WG](%s)UpdateServerAddr failed to update wireguard ipc conf: %s", w.option.Name, err)
return
}
w.serverAddrTime.Store(time.Now())
}
}
}
func (w *WireGuard) genIpcConf(ctx context.Context, updateOnly bool) (string, error) {
ipcConf := ""
if !updateOnly {
ipcConf += "private_key=" + w.option.PrivateKey + "\n"
if w.option.AmneziaWGOption != nil {
if w.option.AmneziaWGOption.JC != 0 {
ipcConf += "jc=" + strconv.Itoa(w.option.AmneziaWGOption.JC) + "\n"
}
if w.option.AmneziaWGOption.JMin != 0 {
ipcConf += "jmin=" + strconv.Itoa(w.option.AmneziaWGOption.JMin) + "\n"
}
if w.option.AmneziaWGOption.JMax != 0 {
ipcConf += "jmax=" + strconv.Itoa(w.option.AmneziaWGOption.JMax) + "\n"
}
if w.option.AmneziaWGOption.S1 != 0 {
ipcConf += "s1=" + strconv.Itoa(w.option.AmneziaWGOption.S1) + "\n"
}
if w.option.AmneziaWGOption.S2 != 0 {
ipcConf += "s2=" + strconv.Itoa(w.option.AmneziaWGOption.S2) + "\n"
}
if w.option.AmneziaWGOption.S3 != 0 {
ipcConf += "s3=" + strconv.Itoa(w.option.AmneziaWGOption.S3) + "\n"
}
if w.option.AmneziaWGOption.S4 != 0 {
ipcConf += "s4=" + strconv.Itoa(w.option.AmneziaWGOption.S4) + "\n"
}
if w.option.AmneziaWGOption.H1 != "" {
ipcConf += "h1=" + w.option.AmneziaWGOption.H1 + "\n"
}
if w.option.AmneziaWGOption.H2 != "" {
ipcConf += "h2=" + w.option.AmneziaWGOption.H2 + "\n"
}
if w.option.AmneziaWGOption.H3 != "" {
ipcConf += "h3=" + w.option.AmneziaWGOption.H3 + "\n"
}
if w.option.AmneziaWGOption.H4 != "" {
ipcConf += "h4=" + w.option.AmneziaWGOption.H4 + "\n"
}
if w.option.AmneziaWGOption.I1 != "" {
ipcConf += "i1=" + w.option.AmneziaWGOption.I1 + "\n"
}
if w.option.AmneziaWGOption.I2 != "" {
ipcConf += "i2=" + w.option.AmneziaWGOption.I2 + "\n"
}
if w.option.AmneziaWGOption.I3 != "" {
ipcConf += "i3=" + w.option.AmneziaWGOption.I3 + "\n"
}
if w.option.AmneziaWGOption.I4 != "" {
ipcConf += "i4=" + w.option.AmneziaWGOption.I4 + "\n"
}
if w.option.AmneziaWGOption.I5 != "" {
ipcConf += "i5=" + w.option.AmneziaWGOption.I5 + "\n"
}
if w.option.AmneziaWGOption.J1 != "" {
ipcConf += "j1=" + w.option.AmneziaWGOption.J1 + "\n"
}
if w.option.AmneziaWGOption.J2 != "" {
ipcConf += "j2=" + w.option.AmneziaWGOption.J2 + "\n"
}
if w.option.AmneziaWGOption.J3 != "" {
ipcConf += "j3=" + w.option.AmneziaWGOption.J3 + "\n"
}
if w.option.AmneziaWGOption.Itime != 0 {
ipcConf += "itime=" + strconv.FormatInt(int64(w.option.AmneziaWGOption.Itime), 10) + "\n"
}
}
}
if len(w.option.Peers) > 0 {
for i, peer := range w.option.Peers {
peerAddr := peer.Addr()
destination, err := w.resolve(ctx, peerAddr)
if err != nil {
return "", E.Cause(err, "resolve endpoint domain for peer ", i)
}
if w.serverAddrMap[peerAddr] != destination {
w.serverAddrMap[peerAddr] = destination
} else if updateOnly {
continue
}
if len(w.option.Peers) == 1 { // must call SetConnectAddr if isConnect == true
w.bind.SetConnectAddr(destination)
}
ipcConf += "public_key=" + peer.PublicKey + "\n"
if updateOnly {
ipcConf += "update_only=true\n"
}
ipcConf += "endpoint=" + destination.String() + "\n"
if len(peer.Reserved) > 0 {
var reserved [3]uint8
copy(reserved[:], w.option.Reserved)
w.bind.SetReservedForEndpoint(destination, reserved)
}
if updateOnly {
continue
}
if peer.PreSharedKey != "" {
ipcConf += "preshared_key=" + peer.PreSharedKey + "\n"
}
for _, allowedIP := range peer.AllowedIPs {
ipcConf += "allowed_ip=" + allowedIP + "\n"
}
if w.option.PersistentKeepalive != 0 {
ipcConf += fmt.Sprintf("persistent_keepalive_interval=%d\n", w.option.PersistentKeepalive)
}
}
} else {
destination, err := w.resolve(ctx, w.connectAddr)
if err != nil {
return "", E.Cause(err, "resolve endpoint domain")
}
if w.serverAddrMap[w.connectAddr] != destination {
w.serverAddrMap[w.connectAddr] = destination
} else if updateOnly {
return "", nil
}
w.bind.SetConnectAddr(destination) // must call SetConnectAddr if isConnect == true
ipcConf += "public_key=" + w.option.PublicKey + "\n"
if updateOnly {
ipcConf += "update_only=true\n"
}
ipcConf += "endpoint=" + destination.String() + "\n"
if updateOnly {
return ipcConf, nil
}
if w.option.PreSharedKey != "" {
ipcConf += "preshared_key=" + w.option.PreSharedKey + "\n"
}
var has4, has6 bool
for _, address := range w.localPrefixes {
if address.Addr().Is4() {
has4 = true
} else {
has6 = true
}
}
if has4 {
ipcConf += "allowed_ip=0.0.0.0/0\n"
}
if has6 {
ipcConf += "allowed_ip=::/0\n"
}
if w.option.PersistentKeepalive != 0 {
ipcConf += fmt.Sprintf("persistent_keepalive_interval=%d\n", w.option.PersistentKeepalive)
}
}
return ipcConf, nil
}
// Close implements C.ProxyAdapter
func (w *WireGuard) Close() error {
if w.device != nil {
w.device.Close()
}
_ = common.Close(w.tunDevice)
return nil
}
func (w *WireGuard) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) {
options := w.Base.DialOptions(opts...)
w.dialer.dialer = dialer.NewDialer(options...)
func (w *WireGuard) DialContext(ctx context.Context, metadata *C.Metadata) (_ C.Conn, err error) {
var conn net.Conn
w.startOnce.Do(func() {
w.startErr = w.tunDevice.Start()
})
if w.startErr != nil {
return nil, w.startErr
if err = w.init(ctx); err != nil {
return nil, err
}
if !metadata.Resolved() || w.resolver != nil {
r := resolver.DefaultResolver
if w.resolver != nil {
w.refP.SetProxyAdapter(w)
defer w.refP.ClearProxyAdapter()
r = w.resolver
}
options := w.DialOptions()
options = append(options, dialer.WithResolver(r))
options = append(options, dialer.WithNetDialer(wgNetDialer{tunDevice: w.tunDevice}))
conn, err = dialer.NewDialer(options...).DialContext(ctx, "tcp", metadata.RemoteAddress())
} else {
port, _ := strconv.Atoi(metadata.DstPort)
conn, err = w.tunDevice.DialContext(ctx, "tcp", M.SocksaddrFrom(metadata.DstIP, uint16(port)).Unwrap())
conn, err = w.tunDevice.DialContext(ctx, "tcp", M.SocksaddrFrom(metadata.DstIP, metadata.DstPort).Unwrap())
}
if err != nil {
return nil, err
@@ -383,183 +573,43 @@ func (w *WireGuard) DialContext(ctx context.Context, metadata *C.Metadata, opts
if conn == nil {
return nil, E.New("conn is nil")
}
return NewConn(CN.NewRefConn(conn, w), w), nil
return NewConn(conn, w), nil
}
func (w *WireGuard) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.PacketConn, err error) {
options := w.Base.DialOptions(opts...)
w.dialer.dialer = dialer.NewDialer(options...)
func (w *WireGuard) ListenPacketContext(ctx context.Context, metadata *C.Metadata) (_ C.PacketConn, err error) {
var pc net.PacketConn
w.startOnce.Do(func() {
w.startErr = w.tunDevice.Start()
})
if w.startErr != nil {
return nil, w.startErr
}
if err != nil {
if err = w.init(ctx); err != nil {
return nil, err
}
if (!metadata.Resolved() || w.resolver != nil) && metadata.Host != "" {
r := resolver.DefaultResolver
if w.resolver != nil {
w.refP.SetProxyAdapter(w)
defer w.refP.ClearProxyAdapter()
r = w.resolver
}
ip, err := resolver.ResolveIPWithResolver(ctx, metadata.Host, r)
if err != nil {
return nil, errors.New("can't resolve ip")
}
metadata.DstIP = ip
if err = w.ResolveUDP(ctx, metadata); err != nil {
return nil, err
}
port, _ := strconv.Atoi(metadata.DstPort)
pc, err = w.tunDevice.ListenPacket(ctx, M.SocksaddrFrom(metadata.DstIP, uint16(port)).Unwrap())
pc, err = w.tunDevice.ListenPacket(ctx, M.SocksaddrFrom(metadata.DstIP, metadata.DstPort).Unwrap())
if err != nil {
return nil, err
}
if pc == nil {
return nil, E.New("packetConn is nil")
}
return newPacketConn(CN.NewRefPacketConn(pc, w), w), nil
return newPacketConn(pc, w), nil
}
func (w *WireGuard) ResolveUDP(ctx context.Context, metadata *C.Metadata) error {
if (!metadata.Resolved() || w.resolver != nil) && metadata.Host != "" {
r := resolver.DefaultResolver
if w.resolver != nil {
r = w.resolver
}
ip, err := resolver.ResolveIPWithResolver(ctx, metadata.Host, r)
if err != nil {
return fmt.Errorf("can't resolve ip: %w", err)
}
metadata.DstIP = ip
}
return nil
}
// IsL3Protocol implements C.ProxyAdapter
func (w *WireGuard) IsL3Protocol(metadata *C.Metadata) bool {
return true
}
type refProxyAdapter struct {
proxyAdapter C.ProxyAdapter
count int
mutex sync.Mutex
}
func (r *refProxyAdapter) SetProxyAdapter(proxyAdapter C.ProxyAdapter) {
r.mutex.Lock()
defer r.mutex.Unlock()
r.proxyAdapter = proxyAdapter
r.count++
}
func (r *refProxyAdapter) ClearProxyAdapter() {
r.mutex.Lock()
defer r.mutex.Unlock()
r.count--
if r.count == 0 {
r.proxyAdapter = nil
}
}
func (r *refProxyAdapter) Name() string {
if r.proxyAdapter != nil {
return r.proxyAdapter.Name()
}
return ""
}
func (r *refProxyAdapter) Type() C.AdapterType {
if r.proxyAdapter != nil {
return r.proxyAdapter.Type()
}
return C.AdapterType(0)
}
func (r *refProxyAdapter) Addr() string {
if r.proxyAdapter != nil {
return r.proxyAdapter.Addr()
}
return ""
}
func (r *refProxyAdapter) SupportUDP() bool {
if r.proxyAdapter != nil {
return r.proxyAdapter.SupportUDP()
}
return false
}
func (r *refProxyAdapter) SupportXUDP() bool {
if r.proxyAdapter != nil {
return r.proxyAdapter.SupportXUDP()
}
return false
}
func (r *refProxyAdapter) SupportTFO() bool {
if r.proxyAdapter != nil {
return r.proxyAdapter.SupportTFO()
}
return false
}
func (r *refProxyAdapter) MarshalJSON() ([]byte, error) {
if r.proxyAdapter != nil {
return r.proxyAdapter.MarshalJSON()
}
return nil, C.ErrNotSupport
}
func (r *refProxyAdapter) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.Metadata) (net.Conn, error) {
if r.proxyAdapter != nil {
return r.proxyAdapter.StreamConnContext(ctx, c, metadata)
}
return nil, C.ErrNotSupport
}
func (r *refProxyAdapter) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.Conn, error) {
if r.proxyAdapter != nil {
return r.proxyAdapter.DialContext(ctx, metadata, opts...)
}
return nil, C.ErrNotSupport
}
func (r *refProxyAdapter) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) {
if r.proxyAdapter != nil {
return r.proxyAdapter.ListenPacketContext(ctx, metadata, opts...)
}
return nil, C.ErrNotSupport
}
func (r *refProxyAdapter) SupportUOT() bool {
if r.proxyAdapter != nil {
return r.proxyAdapter.SupportUOT()
}
return false
}
func (r *refProxyAdapter) SupportWithDialer() C.NetWork {
if r.proxyAdapter != nil {
return r.proxyAdapter.SupportWithDialer()
}
return C.InvalidNet
}
func (r *refProxyAdapter) DialContextWithDialer(ctx context.Context, dialer C.Dialer, metadata *C.Metadata) (C.Conn, error) {
if r.proxyAdapter != nil {
return r.proxyAdapter.DialContextWithDialer(ctx, dialer, metadata)
}
return nil, C.ErrNotSupport
}
func (r *refProxyAdapter) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, metadata *C.Metadata) (C.PacketConn, error) {
if r.proxyAdapter != nil {
return r.proxyAdapter.ListenPacketWithDialer(ctx, dialer, metadata)
}
return nil, C.ErrNotSupport
}
func (r *refProxyAdapter) IsL3Protocol(metadata *C.Metadata) bool {
if r.proxyAdapter != nil {
return r.proxyAdapter.IsL3Protocol(metadata)
}
return false
}
func (r *refProxyAdapter) Unwrap(metadata *C.Metadata, touch bool) C.Proxy {
if r.proxyAdapter != nil {
return r.proxyAdapter.Unwrap(metadata, touch)
}
return nil
}
var _ C.ProxyAdapter = (*refProxyAdapter)(nil)

View File

@@ -6,19 +6,21 @@ import (
"errors"
"time"
"github.com/Dreamacro/clash/adapter/outbound"
"github.com/Dreamacro/clash/common/callback"
N "github.com/Dreamacro/clash/common/net"
"github.com/Dreamacro/clash/component/dialer"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/constant/provider"
"github.com/metacubex/mihomo/common/callback"
N "github.com/metacubex/mihomo/common/net"
"github.com/metacubex/mihomo/common/utils"
C "github.com/metacubex/mihomo/constant"
P "github.com/metacubex/mihomo/constant/provider"
)
type Fallback struct {
*GroupBase
disableUDP bool
testUrl string
selected string
disableUDP bool
testUrl string
selected string
expectedStatus string
Hidden bool
Icon string
}
func (f *Fallback) Now() string {
@@ -27,13 +29,13 @@ func (f *Fallback) Now() string {
}
// DialContext implements C.ProxyAdapter
func (f *Fallback) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.Conn, error) {
func (f *Fallback) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
proxy := f.findAliveProxy(true)
c, err := proxy.DialContext(ctx, metadata, f.Base.DialOptions(opts...)...)
c, err := proxy.DialContext(ctx, metadata)
if err == nil {
c.AppendToChains(f)
} else {
f.onDialFailed(proxy.Type(), err)
f.onDialFailed(proxy.Type(), err, f.healthCheck)
}
if N.NeedHandshake(c) {
@@ -41,7 +43,7 @@ func (f *Fallback) DialContext(ctx context.Context, metadata *C.Metadata, opts .
if err == nil {
f.onDialSuccess()
} else {
f.onDialFailed(proxy.Type(), err)
f.onDialFailed(proxy.Type(), err, f.healthCheck)
}
})
}
@@ -50,9 +52,9 @@ func (f *Fallback) DialContext(ctx context.Context, metadata *C.Metadata, opts .
}
// ListenPacketContext implements C.ProxyAdapter
func (f *Fallback) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) {
func (f *Fallback) ListenPacketContext(ctx context.Context, metadata *C.Metadata) (C.PacketConn, error) {
proxy := f.findAliveProxy(true)
pc, err := proxy.ListenPacketContext(ctx, metadata, f.Base.DialOptions(opts...)...)
pc, err := proxy.ListenPacketContext(ctx, metadata)
if err == nil {
pc.AppendToChains(f)
}
@@ -82,9 +84,14 @@ func (f *Fallback) MarshalJSON() ([]byte, error) {
all = append(all, proxy.Name())
}
return json.Marshal(map[string]any{
"type": f.Type().String(),
"now": f.Now(),
"all": all,
"type": f.Type().String(),
"now": f.Now(),
"all": all,
"testUrl": f.testUrl,
"expectedStatus": f.expectedStatus,
"fixed": f.selected,
"hidden": f.Hidden,
"icon": f.Icon,
})
}
@@ -98,12 +105,12 @@ func (f *Fallback) findAliveProxy(touch bool) C.Proxy {
proxies := f.GetProxies(touch)
for _, proxy := range proxies {
if len(f.selected) == 0 {
if proxy.Alive() {
if proxy.AliveForTestUrl(f.testUrl) {
return proxy
}
} else {
if proxy.Name() == f.selected {
if proxy.Alive() {
if proxy.AliveForTestUrl(f.testUrl) {
return proxy
} else {
f.selected = ""
@@ -129,10 +136,11 @@ func (f *Fallback) Set(name string) error {
}
f.selected = name
if !p.Alive() {
if !p.AliveForTestUrl(f.testUrl) {
ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond*time.Duration(5000))
defer cancel()
_, _ = p.URLTest(ctx, f.testUrl)
expectedStatus, _ := utils.NewUnsignedRanges[uint16](f.expectedStatus)
_, _ = p.URLTest(ctx, f.testUrl, expectedStatus)
}
return nil
@@ -142,21 +150,22 @@ func (f *Fallback) ForceSet(name string) {
f.selected = name
}
func NewFallback(option *GroupCommonOption, providers []provider.ProxyProvider) *Fallback {
func NewFallback(option *GroupCommonOption, providers []P.ProxyProvider) *Fallback {
return &Fallback{
GroupBase: NewGroupBase(GroupBaseOption{
outbound.BaseOption{
Name: option.Name,
Type: C.Fallback,
Interface: option.Interface,
RoutingMark: option.RoutingMark,
},
option.Filter,
option.ExcludeFilter,
option.ExcludeType,
providers,
Name: option.Name,
Type: C.Fallback,
Filter: option.Filter,
ExcludeFilter: option.ExcludeFilter,
ExcludeType: option.ExcludeType,
TestTimeout: option.TestTimeout,
MaxFailedTimes: option.MaxFailedTimes,
Providers: providers,
}),
disableUDP: option.DisableUDP,
testUrl: option.URL,
disableUDP: option.DisableUDP,
testUrl: option.URL,
expectedStatus: option.ExpectedStatus,
Hidden: option.Hidden,
Icon: option.Icon,
}
}

View File

@@ -2,73 +2,93 @@ package outboundgroup
import (
"context"
"errors"
"fmt"
"strings"
"sync"
"time"
"github.com/Dreamacro/clash/adapter/outbound"
"github.com/Dreamacro/clash/common/atomic"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/constant/provider"
types "github.com/Dreamacro/clash/constant/provider"
"github.com/Dreamacro/clash/log"
"github.com/Dreamacro/clash/tunnel"
"github.com/metacubex/mihomo/adapter/outbound"
"github.com/metacubex/mihomo/common/atomic"
"github.com/metacubex/mihomo/common/utils"
C "github.com/metacubex/mihomo/constant"
P "github.com/metacubex/mihomo/constant/provider"
"github.com/metacubex/mihomo/log"
"github.com/metacubex/mihomo/tunnel"
"github.com/dlclark/regexp2"
"golang.org/x/exp/slices"
)
type GroupBase struct {
*outbound.Base
filterRegs []*regexp2.Regexp
excludeFilterReg *regexp2.Regexp
excludeTypeArray []string
providers []provider.ProxyProvider
failedTestMux sync.Mutex
failedTimes int
failedTime time.Time
failedTesting *atomic.Bool
proxies [][]C.Proxy
versions []atomic.Uint32
filterRegs []*regexp2.Regexp
excludeFilterRegs []*regexp2.Regexp
excludeTypeArray []string
providers []P.ProxyProvider
failedTestMux sync.Mutex
failedTimes int
failedTime time.Time
failedTesting atomic.Bool
TestTimeout int
maxFailedTimes int
// for GetProxies
getProxiesMutex sync.Mutex
providerVersions []uint32
providerProxies []C.Proxy
}
type GroupBaseOption struct {
outbound.BaseOption
filter string
excludeFilter string
excludeType string
providers []provider.ProxyProvider
Name string
Type C.AdapterType
Filter string
ExcludeFilter string
ExcludeType string
TestTimeout int
MaxFailedTimes int
Providers []P.ProxyProvider
}
func NewGroupBase(opt GroupBaseOption) *GroupBase {
var excludeFilterReg *regexp2.Regexp
if opt.excludeFilter != "" {
excludeFilterReg = regexp2.MustCompile(opt.excludeFilter, 0)
}
var excludeTypeArray []string
if opt.excludeType != "" {
excludeTypeArray = strings.Split(opt.excludeType, "|")
if opt.ExcludeType != "" {
excludeTypeArray = strings.Split(opt.ExcludeType, "|")
}
var excludeFilterRegs []*regexp2.Regexp
if opt.ExcludeFilter != "" {
for _, excludeFilter := range strings.Split(opt.ExcludeFilter, "`") {
excludeFilterReg := regexp2.MustCompile(excludeFilter, regexp2.None)
excludeFilterRegs = append(excludeFilterRegs, excludeFilterReg)
}
}
var filterRegs []*regexp2.Regexp
if opt.filter != "" {
for _, filter := range strings.Split(opt.filter, "`") {
filterReg := regexp2.MustCompile(filter, 0)
if opt.Filter != "" {
for _, filter := range strings.Split(opt.Filter, "`") {
filterReg := regexp2.MustCompile(filter, regexp2.None)
filterRegs = append(filterRegs, filterReg)
}
}
gb := &GroupBase{
Base: outbound.NewBase(opt.BaseOption),
filterRegs: filterRegs,
excludeFilterReg: excludeFilterReg,
excludeTypeArray: excludeTypeArray,
providers: opt.providers,
failedTesting: atomic.NewBool(false),
Base: outbound.NewBase(outbound.BaseOption{Name: opt.Name, Type: opt.Type}),
filterRegs: filterRegs,
excludeFilterRegs: excludeFilterRegs,
excludeTypeArray: excludeTypeArray,
providers: opt.Providers,
failedTesting: atomic.NewBool(false),
TestTimeout: opt.TestTimeout,
maxFailedTimes: opt.MaxFailedTimes,
}
gb.proxies = make([][]C.Proxy, len(opt.providers))
gb.versions = make([]atomic.Uint32, len(opt.providers))
if gb.TestTimeout == 0 {
gb.TestTimeout = 5000
}
if gb.maxFailedTimes == 0 {
gb.maxFailedTimes = 5
}
return gb
}
@@ -80,63 +100,62 @@ func (gb *GroupBase) Touch() {
}
func (gb *GroupBase) GetProxies(touch bool) []C.Proxy {
providerVersions := make([]uint32, len(gb.providers))
for i, pd := range gb.providers {
if touch { // touch first
pd.Touch()
}
providerVersions[i] = pd.Version()
}
// thread safe
gb.getProxiesMutex.Lock()
defer gb.getProxiesMutex.Unlock()
// return the cached proxies if version not changed
if slices.Equal(providerVersions, gb.providerVersions) {
return gb.providerProxies
}
var proxies []C.Proxy
if len(gb.filterRegs) == 0 {
for _, pd := range gb.providers {
if touch {
pd.Touch()
}
proxies = append(proxies, pd.Proxies()...)
}
} else {
for i, pd := range gb.providers {
if touch {
pd.Touch()
}
if pd.VehicleType() == types.Compatible {
gb.versions[i].Store(pd.Version())
gb.proxies[i] = pd.Proxies()
for _, pd := range gb.providers {
if pd.VehicleType() == P.Compatible { // compatible provider unneeded filter
proxies = append(proxies, pd.Proxies()...)
continue
}
version := gb.versions[i].Load()
if version != pd.Version() && gb.versions[i].CompareAndSwap(version, pd.Version()) {
var (
proxies []C.Proxy
newProxies []C.Proxy
)
proxies = pd.Proxies()
proxiesSet := map[string]struct{}{}
for _, filterReg := range gb.filterRegs {
for _, p := range proxies {
name := p.Name()
if mat, _ := filterReg.FindStringMatch(name); mat != nil {
if _, ok := proxiesSet[name]; !ok {
proxiesSet[name] = struct{}{}
newProxies = append(newProxies, p)
}
var newProxies []C.Proxy
proxiesSet := map[string]struct{}{}
for _, filterReg := range gb.filterRegs {
for _, p := range pd.Proxies() {
name := p.Name()
if mat, _ := filterReg.MatchString(name); mat {
if _, ok := proxiesSet[name]; !ok {
proxiesSet[name] = struct{}{}
newProxies = append(newProxies, p)
}
}
}
gb.proxies[i] = newProxies
}
}
for _, p := range gb.proxies {
proxies = append(proxies, p...)
proxies = append(proxies, newProxies...)
}
}
// Multiple filers means that proxies are sorted in the order in which the filers appear.
// Although the filter has been performed once in the previous process,
// when there are multiple providers, the array needs to be reordered as a whole.
if len(gb.providers) > 1 && len(gb.filterRegs) > 1 {
var newProxies []C.Proxy
proxiesSet := map[string]struct{}{}
for _, filterReg := range gb.filterRegs {
for _, p := range proxies {
name := p.Name()
if mat, _ := filterReg.FindStringMatch(name); mat != nil {
if mat, _ := filterReg.MatchString(name); mat {
if _, ok := proxiesSet[name]; !ok {
proxiesSet[name] = struct{}{}
newProxies = append(newProxies, p)
@@ -153,32 +172,31 @@ func (gb *GroupBase) GetProxies(touch bool) []C.Proxy {
}
proxies = newProxies
}
if gb.excludeTypeArray != nil {
var newProxies []C.Proxy
for _, p := range proxies {
mType := p.Type().String()
flag := false
for i := range gb.excludeTypeArray {
if strings.EqualFold(mType, gb.excludeTypeArray[i]) {
flag = true
break
}
}
if flag {
continue
if len(gb.excludeFilterRegs) > 0 {
var newProxies []C.Proxy
LOOP1:
for _, p := range proxies {
name := p.Name()
for _, excludeFilterReg := range gb.excludeFilterRegs {
if mat, _ := excludeFilterReg.MatchString(name); mat {
continue LOOP1
}
}
newProxies = append(newProxies, p)
}
proxies = newProxies
}
if gb.excludeFilterReg != nil {
if gb.excludeTypeArray != nil {
var newProxies []C.Proxy
LOOP2:
for _, p := range proxies {
name := p.Name()
if mat, _ := gb.excludeFilterReg.FindStringMatch(name); mat != nil {
continue
mType := p.Type().String()
for _, excludeType := range gb.excludeTypeArray {
if strings.EqualFold(mType, excludeType) {
continue LOOP2
}
}
newProxies = append(newProxies, p)
}
@@ -186,13 +204,17 @@ func (gb *GroupBase) GetProxies(touch bool) []C.Proxy {
}
if len(proxies) == 0 {
return append(proxies, tunnel.Proxies()["COMPATIBLE"])
return []C.Proxy{tunnel.Proxies()["COMPATIBLE"]}
}
// only cache when proxies not empty
gb.providerVersions = providerVersions
gb.providerProxies = proxies
return proxies
}
func (gb *GroupBase) URLTest(ctx context.Context, url string) (map[string]uint16, error) {
func (gb *GroupBase) URLTest(ctx context.Context, url string, expectedStatus utils.IntRanges[uint16]) (map[string]uint16, error) {
var wg sync.WaitGroup
var lock sync.Mutex
mp := map[string]uint16{}
@@ -201,7 +223,7 @@ func (gb *GroupBase) URLTest(ctx context.Context, url string) (map[string]uint16
proxy := proxy
wg.Add(1)
go func() {
delay, err := proxy.URLTest(ctx, url)
delay, err := proxy.URLTest(ctx, url, expectedStatus)
if err == nil {
lock.Lock()
mp[proxy.Name()] = delay
@@ -220,17 +242,21 @@ func (gb *GroupBase) URLTest(ctx context.Context, url string) (map[string]uint16
}
}
func (gb *GroupBase) onDialFailed(adapterType C.AdapterType, err error) {
if adapterType == C.Direct || adapterType == C.Compatible || adapterType == C.Reject || adapterType == C.Pass {
func (gb *GroupBase) onDialFailed(adapterType C.AdapterType, err error, fn func()) {
if adapterType == C.Direct || adapterType == C.Compatible || adapterType == C.Reject || adapterType == C.Pass || adapterType == C.RejectDrop {
return
}
if strings.Contains(err.Error(), "connection refused") {
go gb.healthCheck()
if errors.Is(err, C.ErrNotSupport) {
return
}
go func() {
if strings.Contains(err.Error(), "connection refused") {
fn()
return
}
gb.failedTestMux.Lock()
defer gb.failedTestMux.Unlock()
@@ -239,15 +265,15 @@ func (gb *GroupBase) onDialFailed(adapterType C.AdapterType, err error) {
log.Debugln("ProxyGroup: %s first failed", gb.Name())
gb.failedTime = time.Now()
} else {
if time.Since(gb.failedTime) > gb.failedTimeoutInterval() {
if time.Since(gb.failedTime) > time.Duration(gb.TestTimeout)*time.Millisecond {
gb.failedTimes = 0
return
}
log.Debugln("ProxyGroup: %s failed count: %d", gb.Name(), gb.failedTimes)
if gb.failedTimes >= gb.maxFailedTimes() {
if gb.failedTimes >= gb.maxFailedTimes {
log.Warnln("because %s failed multiple times, active health check", gb.Name())
gb.healthCheck()
fn()
}
}
}()
@@ -274,20 +300,8 @@ func (gb *GroupBase) healthCheck() {
gb.failedTimes = 0
}
func (gb *GroupBase) failedIntervalTime() int64 {
return 5 * time.Second.Milliseconds()
}
func (gb *GroupBase) onDialSuccess() {
if !gb.failedTesting.Load() {
gb.failedTimes = 0
}
}
func (gb *GroupBase) maxFailedTimes() int {
return 5
}
func (gb *GroupBase) failedTimeoutInterval() time.Duration {
return 5 * time.Second
}

View File

@@ -9,14 +9,12 @@ import (
"sync"
"time"
"github.com/Dreamacro/clash/adapter/outbound"
"github.com/Dreamacro/clash/common/cache"
"github.com/Dreamacro/clash/common/callback"
"github.com/Dreamacro/clash/common/murmur3"
N "github.com/Dreamacro/clash/common/net"
"github.com/Dreamacro/clash/component/dialer"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/constant/provider"
"github.com/metacubex/mihomo/common/callback"
"github.com/metacubex/mihomo/common/lru"
N "github.com/metacubex/mihomo/common/net"
"github.com/metacubex/mihomo/common/utils"
C "github.com/metacubex/mihomo/constant"
P "github.com/metacubex/mihomo/constant/provider"
"golang.org/x/net/publicsuffix"
)
@@ -25,8 +23,12 @@ type strategyFn = func(proxies []C.Proxy, metadata *C.Metadata, touch bool) C.Pr
type LoadBalance struct {
*GroupBase
disableUDP bool
strategyFn strategyFn
disableUDP bool
strategyFn strategyFn
testUrl string
expectedStatus string
Hidden bool
Icon string
}
var errStrategy = errors.New("unsupported strategy")
@@ -84,14 +86,14 @@ func jumpHash(key uint64, buckets int32) int32 {
}
// DialContext implements C.ProxyAdapter
func (lb *LoadBalance) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (c C.Conn, err error) {
func (lb *LoadBalance) DialContext(ctx context.Context, metadata *C.Metadata) (c C.Conn, err error) {
proxy := lb.Unwrap(metadata, true)
c, err = proxy.DialContext(ctx, metadata, lb.Base.DialOptions(opts...)...)
c, err = proxy.DialContext(ctx, metadata)
if err == nil {
c.AppendToChains(lb)
} else {
lb.onDialFailed(proxy.Type(), err)
lb.onDialFailed(proxy.Type(), err, lb.healthCheck)
}
if N.NeedHandshake(c) {
@@ -99,7 +101,7 @@ func (lb *LoadBalance) DialContext(ctx context.Context, metadata *C.Metadata, op
if err == nil {
lb.onDialSuccess()
} else {
lb.onDialFailed(proxy.Type(), err)
lb.onDialFailed(proxy.Type(), err, lb.healthCheck)
}
})
}
@@ -108,7 +110,7 @@ func (lb *LoadBalance) DialContext(ctx context.Context, metadata *C.Metadata, op
}
// ListenPacketContext implements C.ProxyAdapter
func (lb *LoadBalance) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (pc C.PacketConn, err error) {
func (lb *LoadBalance) ListenPacketContext(ctx context.Context, metadata *C.Metadata) (pc C.PacketConn, err error) {
defer func() {
if err == nil {
pc.AppendToChains(lb)
@@ -116,7 +118,7 @@ func (lb *LoadBalance) ListenPacketContext(ctx context.Context, metadata *C.Meta
}()
proxy := lb.Unwrap(metadata, true)
return proxy.ListenPacketContext(ctx, metadata, lb.Base.DialOptions(opts...)...)
return proxy.ListenPacketContext(ctx, metadata)
}
// SupportUDP implements C.ProxyAdapter
@@ -129,7 +131,7 @@ func (lb *LoadBalance) IsL3Protocol(metadata *C.Metadata) bool {
return lb.Unwrap(metadata, false).IsL3Protocol(metadata)
}
func strategyRoundRobin() strategyFn {
func strategyRoundRobin(url string) strategyFn {
idx := 0
idxMutex := sync.Mutex{}
return func(proxies []C.Proxy, metadata *C.Metadata, touch bool) C.Proxy {
@@ -148,7 +150,7 @@ func strategyRoundRobin() strategyFn {
for ; i < length; i++ {
id := (idx + i) % length
proxy := proxies[id]
if proxy.Alive() {
if proxy.AliveForTestUrl(url) {
i++
return proxy
}
@@ -158,22 +160,22 @@ func strategyRoundRobin() strategyFn {
}
}
func strategyConsistentHashing() strategyFn {
func strategyConsistentHashing(url string) strategyFn {
maxRetry := 5
return func(proxies []C.Proxy, metadata *C.Metadata, touch bool) C.Proxy {
key := uint64(murmur3.Sum32([]byte(getKey(metadata))))
key := utils.MapHash(getKey(metadata))
buckets := int32(len(proxies))
for i := 0; i < maxRetry; i, key = i+1, key+1 {
idx := jumpHash(key, buckets)
proxy := proxies[idx]
if proxy.Alive() {
if proxy.AliveForTestUrl(url) {
return proxy
}
}
// when availability is poor, traverse the entire list to get the available nodes
for _, proxy := range proxies {
if proxy.Alive() {
if proxy.AliveForTestUrl(url) {
return proxy
}
}
@@ -182,26 +184,25 @@ func strategyConsistentHashing() strategyFn {
}
}
func strategyStickySessions() strategyFn {
func strategyStickySessions(url string) strategyFn {
ttl := time.Minute * 10
maxRetry := 5
lruCache := cache.New[uint64, int](
cache.WithAge[uint64, int](int64(ttl.Seconds())),
cache.WithSize[uint64, int](1000))
lruCache := lru.New[uint64, int](
lru.WithAge[uint64, int](int64(ttl.Seconds())),
lru.WithSize[uint64, int](1000))
return func(proxies []C.Proxy, metadata *C.Metadata, touch bool) C.Proxy {
key := uint64(murmur3.Sum32([]byte(getKeyWithSrcAndDst(metadata))))
key := utils.MapHash(getKeyWithSrcAndDst(metadata))
length := len(proxies)
idx, has := lruCache.Get(key)
if !has {
if !has || idx >= length {
idx = int(jumpHash(key+uint64(time.Now().UnixNano()), int32(length)))
}
nowIdx := idx
for i := 1; i < maxRetry; i++ {
proxy := proxies[nowIdx]
if proxy.Alive() {
if nowIdx != idx {
lruCache.Delete(key)
if proxy.AliveForTestUrl(url) {
if !has || nowIdx != idx {
lruCache.Set(key, nowIdx)
}
@@ -211,7 +212,6 @@ func strategyStickySessions() strategyFn {
}
}
lruCache.Delete(key)
lruCache.Set(key, 0)
return proxies[0]
}
@@ -230,37 +230,43 @@ func (lb *LoadBalance) MarshalJSON() ([]byte, error) {
all = append(all, proxy.Name())
}
return json.Marshal(map[string]any{
"type": lb.Type().String(),
"all": all,
"type": lb.Type().String(),
"all": all,
"testUrl": lb.testUrl,
"expectedStatus": lb.expectedStatus,
"hidden": lb.Hidden,
"icon": lb.Icon,
})
}
func NewLoadBalance(option *GroupCommonOption, providers []provider.ProxyProvider, strategy string) (lb *LoadBalance, err error) {
func NewLoadBalance(option *GroupCommonOption, providers []P.ProxyProvider, strategy string) (lb *LoadBalance, err error) {
var strategyFn strategyFn
switch strategy {
case "consistent-hashing":
strategyFn = strategyConsistentHashing()
strategyFn = strategyConsistentHashing(option.URL)
case "round-robin":
strategyFn = strategyRoundRobin()
strategyFn = strategyRoundRobin(option.URL)
case "sticky-sessions":
strategyFn = strategyStickySessions()
strategyFn = strategyStickySessions(option.URL)
default:
return nil, fmt.Errorf("%w: %s", errStrategy, strategy)
}
return &LoadBalance{
GroupBase: NewGroupBase(GroupBaseOption{
outbound.BaseOption{
Name: option.Name,
Type: C.LoadBalance,
Interface: option.Interface,
RoutingMark: option.RoutingMark,
},
option.Filter,
option.ExcludeFilter,
option.ExcludeType,
providers,
Name: option.Name,
Type: C.LoadBalance,
Filter: option.Filter,
ExcludeFilter: option.ExcludeFilter,
ExcludeType: option.ExcludeType,
TestTimeout: option.TestTimeout,
MaxFailedTimes: option.MaxFailedTimes,
Providers: providers,
}),
strategyFn: strategyFn,
disableUDP: option.DisableUDP,
strategyFn: strategyFn,
disableUDP: option.DisableUDP,
testUrl: option.URL,
expectedStatus: option.ExpectedStatus,
Hidden: option.Hidden,
Icon: option.Icon,
}, nil
}

View File

@@ -3,38 +3,52 @@ package outboundgroup
import (
"errors"
"fmt"
"strings"
"github.com/Dreamacro/clash/adapter/outbound"
"github.com/Dreamacro/clash/adapter/provider"
"github.com/Dreamacro/clash/common/structure"
C "github.com/Dreamacro/clash/constant"
types "github.com/Dreamacro/clash/constant/provider"
"github.com/dlclark/regexp2"
"github.com/metacubex/mihomo/adapter/provider"
"github.com/metacubex/mihomo/common/structure"
"github.com/metacubex/mihomo/common/utils"
C "github.com/metacubex/mihomo/constant"
P "github.com/metacubex/mihomo/constant/provider"
"github.com/metacubex/mihomo/log"
)
var (
errFormat = errors.New("format error")
errType = errors.New("unsupport type")
errType = errors.New("unsupported type")
errMissProxy = errors.New("`use` or `proxies` missing")
errMissHealthCheck = errors.New("`url` or `interval` missing")
errDuplicateProvider = errors.New("duplicate provider name")
)
type GroupCommonOption struct {
outbound.BasicOption
Name string `group:"name"`
Type string `group:"type"`
Proxies []string `group:"proxies,omitempty"`
Use []string `group:"use,omitempty"`
URL string `group:"url,omitempty"`
Interval int `group:"interval,omitempty"`
Lazy bool `group:"lazy,omitempty"`
DisableUDP bool `group:"disable-udp,omitempty"`
Filter string `group:"filter,omitempty"`
ExcludeFilter string `group:"exclude-filter,omitempty"`
ExcludeType string `group:"exclude-type,omitempty"`
Name string `group:"name"`
Type string `group:"type"`
Proxies []string `group:"proxies,omitempty"`
Use []string `group:"use,omitempty"`
URL string `group:"url,omitempty"`
Interval int `group:"interval,omitempty"`
TestTimeout int `group:"timeout,omitempty"`
MaxFailedTimes int `group:"max-failed-times,omitempty"`
Lazy bool `group:"lazy,omitempty"`
DisableUDP bool `group:"disable-udp,omitempty"`
Filter string `group:"filter,omitempty"`
ExcludeFilter string `group:"exclude-filter,omitempty"`
ExcludeType string `group:"exclude-type,omitempty"`
ExpectedStatus string `group:"expected-status,omitempty"`
IncludeAll bool `group:"include-all,omitempty"`
IncludeAllProxies bool `group:"include-all-proxies,omitempty"`
IncludeAllProviders bool `group:"include-all-providers,omitempty"`
Hidden bool `group:"hidden,omitempty"`
Icon string `group:"icon,omitempty"`
// removed configs, only for error logging
Interface string `group:"interface-name,omitempty"`
RoutingMark int `group:"routing-mark,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]P.ProxyProvider, AllProxies []string, AllProviders []string) (C.ProxyAdapter, error) {
decoder := structure.NewDecoder(structure.Option{TagName: "group", WeaklyTypedInput: true})
groupOption := &GroupCommonOption{
@@ -48,62 +62,115 @@ func ParseProxyGroup(config map[string]any, proxyMap map[string]C.Proxy, provide
return nil, errFormat
}
if groupOption.RoutingMark != 0 {
log.Errorln("The group [%s] with routing-mark configuration was removed, please set it directly on the proxy instead", groupOption.Name)
}
if groupOption.Interface != "" {
log.Errorln("The group [%s] with interface-name configuration was removed, please set it directly on the proxy instead", groupOption.Name)
}
groupName := groupOption.Name
providers := []types.ProxyProvider{}
providers := []P.ProxyProvider{}
if groupOption.IncludeAll {
groupOption.IncludeAllProviders = true
groupOption.IncludeAllProxies = true
}
if groupOption.IncludeAllProviders {
groupOption.Use = AllProviders
}
if groupOption.IncludeAllProxies {
if groupOption.Filter != "" {
var filterRegs []*regexp2.Regexp
for _, filter := range strings.Split(groupOption.Filter, "`") {
filterReg := regexp2.MustCompile(filter, regexp2.None)
filterRegs = append(filterRegs, filterReg)
}
for _, p := range AllProxies {
for _, filterReg := range filterRegs {
if mat, _ := filterReg.MatchString(p); mat {
groupOption.Proxies = append(groupOption.Proxies, p)
}
}
}
} else {
groupOption.Proxies = append(groupOption.Proxies, AllProxies...)
}
if len(groupOption.Proxies) == 0 && len(groupOption.Use) == 0 {
groupOption.Proxies = []string{"COMPATIBLE"}
}
}
if len(groupOption.Proxies) == 0 && len(groupOption.Use) == 0 {
return nil, errMissProxy
return nil, fmt.Errorf("%s: %w", groupName, errMissProxy)
}
expectedStatus, err := utils.NewUnsignedRanges[uint16](groupOption.ExpectedStatus)
if err != nil {
return nil, fmt.Errorf("%s: %w", groupName, err)
}
status := strings.TrimSpace(groupOption.ExpectedStatus)
if status == "" {
status = "*"
}
groupOption.ExpectedStatus = status
if len(groupOption.Use) != 0 {
PDs, err := getProviders(providersMap, groupOption.Use)
if err != nil {
return nil, fmt.Errorf("%s: %w", groupName, err)
}
// if test URL is empty, use the first health check URL of providers
if groupOption.URL == "" {
for _, pd := range PDs {
if pd.HealthCheckURL() != "" {
groupOption.URL = pd.HealthCheckURL()
break
}
}
if groupOption.URL == "" {
groupOption.URL = C.DefaultTestURL
}
} else {
addTestUrlToProviders(PDs, groupOption.URL, expectedStatus, groupOption.Filter, uint(groupOption.Interval))
}
providers = append(providers, PDs...)
}
if len(groupOption.Proxies) != 0 {
ps, err := getProxies(proxyMap, groupOption.Proxies)
if err != nil {
return nil, err
return nil, fmt.Errorf("%s: %w", groupName, err)
}
if _, ok := providersMap[groupName]; ok {
return nil, errDuplicateProvider
return nil, fmt.Errorf("%s: %w", groupName, errDuplicateProvider)
}
// select don't need health check
if groupOption.Type == "select" || groupOption.Type == "relay" {
hc := provider.NewHealthCheck(ps, "", 0, true)
pd, err := provider.NewCompatibleProvider(groupName, ps, hc)
if err != nil {
return nil, err
}
providers = append(providers, pd)
providersMap[groupName] = pd
} else {
if groupOption.URL == "" {
groupOption.URL = "https://cp.cloudflare.com/generate_204"
}
if groupOption.URL == "" {
groupOption.URL = C.DefaultTestURL
}
// select don't need auto health check
if groupOption.Type != "select" && groupOption.Type != "relay" {
if groupOption.Interval == 0 {
groupOption.Interval = 300
}
hc := provider.NewHealthCheck(ps, groupOption.URL, uint(groupOption.Interval), groupOption.Lazy)
pd, err := provider.NewCompatibleProvider(groupName, ps, hc)
if err != nil {
return nil, err
}
providers = append(providers, pd)
providersMap[groupName] = pd
}
}
if len(groupOption.Use) != 0 {
list, err := getProviders(providersMap, groupOption.Use)
hc := provider.NewHealthCheck(ps, groupOption.URL, uint(groupOption.TestTimeout), uint(groupOption.Interval), groupOption.Lazy, expectedStatus)
pd, err := provider.NewCompatibleProvider(groupName, ps, hc)
if err != nil {
return nil, err
return nil, fmt.Errorf("%s: %w", groupName, err)
}
providers = append(providers, list...)
} else {
groupOption.Filter = ""
providers = append([]P.ProxyProvider{pd}, providers...)
providersMap[groupName] = pd
}
var group C.ProxyAdapter
@@ -119,7 +186,7 @@ func ParseProxyGroup(config map[string]any, proxyMap map[string]C.Proxy, provide
strategy := parseStrategy(config)
return NewLoadBalance(groupOption, providers, strategy)
case "relay":
group = NewRelay(groupOption, providers)
return nil, fmt.Errorf("%w: The group [%s] with relay type was removed, please using dialer-proxy instead", errType, groupName)
default:
return nil, fmt.Errorf("%w: %s", errType, groupOption.Type)
}
@@ -139,18 +206,28 @@ func getProxies(mapping map[string]C.Proxy, list []string) ([]C.Proxy, error) {
return ps, nil
}
func getProviders(mapping map[string]types.ProxyProvider, list []string) ([]types.ProxyProvider, error) {
var ps []types.ProxyProvider
func getProviders(mapping map[string]P.ProxyProvider, list []string) ([]P.ProxyProvider, error) {
var ps []P.ProxyProvider
for _, name := range list {
p, ok := mapping[name]
if !ok {
return nil, fmt.Errorf("'%s' not found", name)
}
if p.VehicleType() == types.Compatible {
if p.VehicleType() == P.Compatible {
return nil, fmt.Errorf("proxy group %s can't contains in `use`", name)
}
ps = append(ps, p)
}
return ps, nil
}
func addTestUrlToProviders(providers []P.ProxyProvider, url string, expectedStatus utils.IntRanges[uint16], filter string, interval uint) {
if len(providers) == 0 || len(url) == 0 {
return
}
for _, pd := range providers {
pd.RegisterHealthCheckTask(url, expectedStatus, filter, interval)
}
}

View File

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

View File

@@ -3,29 +3,33 @@ package outboundgroup
import (
"context"
"encoding/json"
"github.com/Dreamacro/clash/adapter/outbound"
"github.com/Dreamacro/clash/component/dialer"
"github.com/Dreamacro/clash/component/proxydialer"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/constant/provider"
"github.com/metacubex/mihomo/adapter/outbound"
"github.com/metacubex/mihomo/component/dialer"
"github.com/metacubex/mihomo/component/proxydialer"
C "github.com/metacubex/mihomo/constant"
P "github.com/metacubex/mihomo/constant/provider"
"github.com/metacubex/mihomo/log"
)
type Relay struct {
*GroupBase
Hidden bool
Icon string
}
// DialContext implements C.ProxyAdapter
func (r *Relay) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.Conn, error) {
func (r *Relay) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
proxies, chainProxies := r.proxies(metadata, true)
switch len(proxies) {
case 0:
return outbound.NewDirect().DialContext(ctx, metadata, r.Base.DialOptions(opts...)...)
return outbound.NewDirect().DialContext(ctx, metadata)
case 1:
return proxies[0].DialContext(ctx, metadata, r.Base.DialOptions(opts...)...)
return proxies[0].DialContext(ctx, metadata)
}
var d C.Dialer
d = dialer.NewDialer(r.Base.DialOptions(opts...)...)
d = dialer.NewDialer()
for _, proxy := range proxies[:len(proxies)-1] {
d = proxydialer.New(proxy, d, false)
}
@@ -45,18 +49,18 @@ func (r *Relay) DialContext(ctx context.Context, metadata *C.Metadata, opts ...d
}
// ListenPacketContext implements C.ProxyAdapter
func (r *Relay) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.PacketConn, err error) {
func (r *Relay) ListenPacketContext(ctx context.Context, metadata *C.Metadata) (_ C.PacketConn, err error) {
proxies, chainProxies := r.proxies(metadata, true)
switch len(proxies) {
case 0:
return outbound.NewDirect().ListenPacketContext(ctx, metadata, r.Base.DialOptions(opts...)...)
return outbound.NewDirect().ListenPacketContext(ctx, metadata)
case 1:
return proxies[0].ListenPacketContext(ctx, metadata, r.Base.DialOptions(opts...)...)
return proxies[0].ListenPacketContext(ctx, metadata)
}
var d C.Dialer
d = dialer.NewDialer(r.Base.DialOptions(opts...)...)
d = dialer.NewDialer()
for _, proxy := range proxies[:len(proxies)-1] {
d = proxydialer.New(proxy, d, false)
}
@@ -106,8 +110,10 @@ func (r *Relay) MarshalJSON() ([]byte, error) {
all = append(all, proxy.Name())
}
return json.Marshal(map[string]any{
"type": r.Type().String(),
"all": all,
"type": r.Type().String(),
"all": all,
"hidden": r.Hidden,
"icon": r.Icon,
})
}
@@ -143,19 +149,15 @@ func (r *Relay) Addr() string {
return proxies[len(proxies)-1].Addr()
}
func NewRelay(option *GroupCommonOption, providers []provider.ProxyProvider) *Relay {
func NewRelay(option *GroupCommonOption, providers []P.ProxyProvider) *Relay {
log.Warnln("The group [%s] with relay type is deprecated, please using dialer-proxy instead", option.Name)
return &Relay{
GroupBase: NewGroupBase(GroupBaseOption{
outbound.BaseOption{
Name: option.Name,
Type: C.Relay,
Interface: option.Interface,
RoutingMark: option.RoutingMark,
},
"",
"",
"",
providers,
Name: option.Name,
Type: C.Relay,
Providers: providers,
}),
Hidden: option.Hidden,
Icon: option.Icon,
}
}

View File

@@ -5,21 +5,22 @@ import (
"encoding/json"
"errors"
"github.com/Dreamacro/clash/adapter/outbound"
"github.com/Dreamacro/clash/component/dialer"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/constant/provider"
C "github.com/metacubex/mihomo/constant"
P "github.com/metacubex/mihomo/constant/provider"
)
type Selector struct {
*GroupBase
disableUDP bool
selected string
testUrl string
Hidden bool
Icon string
}
// DialContext implements C.ProxyAdapter
func (s *Selector) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.Conn, error) {
c, err := s.selectedProxy(true).DialContext(ctx, metadata, s.Base.DialOptions(opts...)...)
func (s *Selector) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
c, err := s.selectedProxy(true).DialContext(ctx, metadata)
if err == nil {
c.AppendToChains(s)
}
@@ -27,8 +28,8 @@ func (s *Selector) DialContext(ctx context.Context, metadata *C.Metadata, opts .
}
// ListenPacketContext implements C.ProxyAdapter
func (s *Selector) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) {
pc, err := s.selectedProxy(true).ListenPacketContext(ctx, metadata, s.Base.DialOptions(opts...)...)
func (s *Selector) ListenPacketContext(ctx context.Context, metadata *C.Metadata) (C.PacketConn, error) {
pc, err := s.selectedProxy(true).ListenPacketContext(ctx, metadata)
if err == nil {
pc.AppendToChains(s)
}
@@ -55,11 +56,20 @@ func (s *Selector) MarshalJSON() ([]byte, error) {
for _, proxy := range s.GetProxies(false) {
all = append(all, proxy.Name())
}
// When testurl is the default value
// do not append a value to ensure that the web dashboard follows the settings of the dashboard
var url string
if s.testUrl != C.DefaultTestURL {
url = s.testUrl
}
return json.Marshal(map[string]any{
"type": s.Type().String(),
"now": s.Now(),
"all": all,
"type": s.Type().String(),
"now": s.Now(),
"all": all,
"testUrl": url,
"hidden": s.Hidden,
"icon": s.Icon,
})
}
@@ -98,21 +108,22 @@ func (s *Selector) selectedProxy(touch bool) C.Proxy {
return proxies[0]
}
func NewSelector(option *GroupCommonOption, providers []provider.ProxyProvider) *Selector {
func NewSelector(option *GroupCommonOption, providers []P.ProxyProvider) *Selector {
return &Selector{
GroupBase: NewGroupBase(GroupBaseOption{
outbound.BaseOption{
Name: option.Name,
Type: C.Selector,
Interface: option.Interface,
RoutingMark: option.RoutingMark,
},
option.Filter,
option.ExcludeFilter,
option.ExcludeType,
providers,
Name: option.Name,
Type: C.Selector,
Filter: option.Filter,
ExcludeFilter: option.ExcludeFilter,
ExcludeType: option.ExcludeType,
TestTimeout: option.TestTimeout,
MaxFailedTimes: option.MaxFailedTimes,
Providers: providers,
}),
selected: "COMPATIBLE",
disableUDP: option.DisableUDP,
testUrl: option.URL,
Hidden: option.Hidden,
Icon: option.Icon,
}
}

View File

@@ -6,13 +6,12 @@ import (
"errors"
"time"
"github.com/Dreamacro/clash/adapter/outbound"
"github.com/Dreamacro/clash/common/callback"
N "github.com/Dreamacro/clash/common/net"
"github.com/Dreamacro/clash/common/singledo"
"github.com/Dreamacro/clash/component/dialer"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/constant/provider"
"github.com/metacubex/mihomo/common/callback"
N "github.com/metacubex/mihomo/common/net"
"github.com/metacubex/mihomo/common/singledo"
"github.com/metacubex/mihomo/common/utils"
C "github.com/metacubex/mihomo/constant"
P "github.com/metacubex/mihomo/constant/provider"
)
type urlTestOption func(*URLTest)
@@ -25,12 +24,15 @@ func urlTestWithTolerance(tolerance uint16) urlTestOption {
type URLTest struct {
*GroupBase
selected string
testUrl string
tolerance uint16
disableUDP bool
fastNode C.Proxy
fastSingle *singledo.Single[C.Proxy]
selected string
testUrl string
expectedStatus string
tolerance uint16
disableUDP bool
Hidden bool
Icon string
fastNode C.Proxy
fastSingle *singledo.Single[C.Proxy]
}
func (u *URLTest) Now() string {
@@ -48,23 +50,23 @@ func (u *URLTest) Set(name string) error {
if p == nil {
return errors.New("proxy not exist")
}
u.selected = name
u.fast(false)
u.ForceSet(name)
return nil
}
func (u *URLTest) ForceSet(name string) {
u.selected = name
u.fastSingle.Reset()
}
// DialContext implements C.ProxyAdapter
func (u *URLTest) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (c C.Conn, err error) {
func (u *URLTest) DialContext(ctx context.Context, metadata *C.Metadata) (c C.Conn, err error) {
proxy := u.fast(true)
c, err = proxy.DialContext(ctx, metadata, u.Base.DialOptions(opts...)...)
c, err = proxy.DialContext(ctx, metadata)
if err == nil {
c.AppendToChains(u)
} else {
u.onDialFailed(proxy.Type(), err)
u.onDialFailed(proxy.Type(), err, u.healthCheck)
}
if N.NeedHandshake(c) {
@@ -72,7 +74,7 @@ func (u *URLTest) DialContext(ctx context.Context, metadata *C.Metadata, opts ..
if err == nil {
u.onDialSuccess()
} else {
u.onDialFailed(proxy.Type(), err)
u.onDialFailed(proxy.Type(), err, u.healthCheck)
}
})
}
@@ -81,10 +83,13 @@ func (u *URLTest) DialContext(ctx context.Context, metadata *C.Metadata, opts ..
}
// ListenPacketContext implements C.ProxyAdapter
func (u *URLTest) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) {
pc, err := u.fast(true).ListenPacketContext(ctx, metadata, u.Base.DialOptions(opts...)...)
func (u *URLTest) ListenPacketContext(ctx context.Context, metadata *C.Metadata) (C.PacketConn, error) {
proxy := u.fast(true)
pc, err := proxy.ListenPacketContext(ctx, metadata)
if err == nil {
pc.AppendToChains(u)
} else {
u.onDialFailed(proxy.Type(), err, u.healthCheck)
}
return pc, err
@@ -95,45 +100,51 @@ func (u *URLTest) Unwrap(metadata *C.Metadata, touch bool) C.Proxy {
return u.fast(touch)
}
func (u *URLTest) healthCheck() {
u.fastSingle.Reset()
u.GroupBase.healthCheck()
u.fastSingle.Reset()
}
func (u *URLTest) fast(touch bool) C.Proxy {
elm, _, shared := u.fastSingle.Do(func() (C.Proxy, error) {
var s C.Proxy
proxies := u.GetProxies(touch)
fast := proxies[0]
if fast.Name() == u.selected {
s = fast
if u.selected != "" {
for _, proxy := range proxies {
if !proxy.AliveForTestUrl(u.testUrl) {
continue
}
if proxy.Name() == u.selected {
u.fastNode = proxy
return proxy, nil
}
}
}
min := fast.LastDelay()
fast := proxies[0]
minDelay := fast.LastDelayForTestUrl(u.testUrl)
fastNotExist := true
for _, proxy := range proxies[1:] {
if u.fastNode != nil && proxy.Name() == u.fastNode.Name() {
fastNotExist = false
}
if proxy.Name() == u.selected {
s = proxy
}
if !proxy.Alive() {
if !proxy.AliveForTestUrl(u.testUrl) {
continue
}
delay := proxy.LastDelay()
if delay < min {
delay := proxy.LastDelayForTestUrl(u.testUrl)
if delay < minDelay {
fast = proxy
min = delay
minDelay = delay
}
}
// 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 {
u.fastNode = fast
}
if s != nil {
if s.Alive() && s.LastDelay() < fast.LastDelay()+u.tolerance {
u.fastNode = s
}
}
return u.fastNode, nil
})
if shared && touch { // a shared fastSingle.Do() may cause providers untouched, so we touch them again
@@ -163,12 +174,21 @@ func (u *URLTest) MarshalJSON() ([]byte, error) {
all = append(all, proxy.Name())
}
return json.Marshal(map[string]any{
"type": u.Type().String(),
"now": u.Now(),
"all": all,
"type": u.Type().String(),
"now": u.Now(),
"all": all,
"testUrl": u.testUrl,
"expectedStatus": u.expectedStatus,
"fixed": u.selected,
"hidden": u.Hidden,
"icon": u.Icon,
})
}
func (u *URLTest) URLTest(ctx context.Context, url string, expectedStatus utils.IntRanges[uint16]) (map[string]uint16, error) {
return u.GroupBase.URLTest(ctx, u.testUrl, expectedStatus)
}
func parseURLTestOption(config map[string]any) []urlTestOption {
opts := []urlTestOption{}
@@ -182,24 +202,24 @@ func parseURLTestOption(config map[string]any) []urlTestOption {
return opts
}
func NewURLTest(option *GroupCommonOption, providers []provider.ProxyProvider, options ...urlTestOption) *URLTest {
func NewURLTest(option *GroupCommonOption, providers []P.ProxyProvider, options ...urlTestOption) *URLTest {
urlTest := &URLTest{
GroupBase: NewGroupBase(GroupBaseOption{
outbound.BaseOption{
Name: option.Name,
Type: C.URLTest,
Interface: option.Interface,
RoutingMark: option.RoutingMark,
},
option.Filter,
option.ExcludeFilter,
option.ExcludeType,
providers,
Name: option.Name,
Type: C.URLTest,
Filter: option.Filter,
ExcludeFilter: option.ExcludeFilter,
ExcludeType: option.ExcludeType,
TestTimeout: option.TestTimeout,
MaxFailedTimes: option.MaxFailedTimes,
Providers: providers,
}),
fastSingle: singledo.NewSingle[C.Proxy](time.Second * 10),
disableUDP: option.DisableUDP,
testUrl: option.URL,
fastSingle: singledo.NewSingle[C.Proxy](time.Second * 10),
disableUDP: option.DisableUDP,
testUrl: option.URL,
expectedStatus: option.ExpectedStatus,
Hidden: option.Hidden,
Icon: option.Icon,
}
for _, option := range options {

View File

@@ -1,18 +1,10 @@
package outboundgroup
import (
"net"
"time"
)
func tcpKeepAlive(c net.Conn) {
if tcp, ok := c.(*net.TCPConn); ok {
_ = tcp.SetKeepAlive(true)
_ = tcp.SetKeepAlivePeriod(30 * time.Second)
}
}
type SelectAble interface {
Set(string) error
ForceSet(name string)
}
var _ SelectAble = (*Fallback)(nil)
var _ SelectAble = (*URLTest)(nil)
var _ SelectAble = (*Selector)(nil)

View File

@@ -3,11 +3,9 @@ package adapter
import (
"fmt"
tlsC "github.com/Dreamacro/clash/component/tls"
"github.com/Dreamacro/clash/adapter/outbound"
"github.com/Dreamacro/clash/common/structure"
C "github.com/Dreamacro/clash/constant"
"github.com/metacubex/mihomo/adapter/outbound"
"github.com/metacubex/mihomo/common/structure"
C "github.com/metacubex/mihomo/constant"
)
func ParseProxy(mapping map[string]any) (C.Proxy, error) {
@@ -18,12 +16,12 @@ func ParseProxy(mapping map[string]any) (C.Proxy, error) {
}
var (
proxy C.ProxyAdapter
proxy outbound.ProxyAdapter
err error
)
switch proxyType {
case "ss":
ssOption := &outbound.ShadowSocksOption{ClientFingerprint: tlsC.GetGlobalFingerprint()}
ssOption := &outbound.ShadowSocksOption{}
err = decoder.Decode(mapping, ssOption)
if err != nil {
break
@@ -51,21 +49,14 @@ func ParseProxy(mapping map[string]any) (C.Proxy, error) {
}
proxy, err = outbound.NewHttp(*httpOption)
case "vmess":
vmessOption := &outbound.VmessOption{
HTTPOpts: outbound.HTTPOptions{
Method: "GET",
Path: []string{"/"},
},
ClientFingerprint: tlsC.GetGlobalFingerprint(),
}
vmessOption := &outbound.VmessOption{}
err = decoder.Decode(mapping, vmessOption)
if err != nil {
break
}
proxy, err = outbound.NewVmess(*vmessOption)
case "vless":
vlessOption := &outbound.VlessOption{ClientFingerprint: tlsC.GetGlobalFingerprint()}
vlessOption := &outbound.VlessOption{}
err = decoder.Decode(mapping, vlessOption)
if err != nil {
break
@@ -79,7 +70,7 @@ func ParseProxy(mapping map[string]any) (C.Proxy, error) {
}
proxy, err = outbound.NewSnell(*snellOption)
case "trojan":
trojanOption := &outbound.TrojanOption{ClientFingerprint: tlsC.GetGlobalFingerprint()}
trojanOption := &outbound.TrojanOption{}
err = decoder.Decode(mapping, trojanOption)
if err != nil {
break
@@ -92,6 +83,13 @@ func ParseProxy(mapping map[string]any) (C.Proxy, error) {
break
}
proxy, err = outbound.NewHysteria(*hyOption)
case "hysteria2":
hyOption := &outbound.Hysteria2Option{}
err = decoder.Decode(mapping, hyOption)
if err != nil {
break
}
proxy, err = outbound.NewHysteria2(*hyOption)
case "wireguard":
wgOption := &outbound.WireGuardOption{}
err = decoder.Decode(mapping, wgOption)
@@ -106,6 +104,55 @@ func ParseProxy(mapping map[string]any) (C.Proxy, error) {
break
}
proxy, err = outbound.NewTuic(*tuicOption)
case "direct":
directOption := &outbound.DirectOption{}
err = decoder.Decode(mapping, directOption)
if err != nil {
break
}
proxy = outbound.NewDirectWithOption(*directOption)
case "dns":
dnsOptions := &outbound.DnsOption{}
err = decoder.Decode(mapping, dnsOptions)
if err != nil {
break
}
proxy = outbound.NewDnsWithOption(*dnsOptions)
case "reject":
rejectOption := &outbound.RejectOption{}
err = decoder.Decode(mapping, rejectOption)
if err != nil {
break
}
proxy = outbound.NewRejectWithOption(*rejectOption)
case "ssh":
sshOption := &outbound.SshOption{}
err = decoder.Decode(mapping, sshOption)
if err != nil {
break
}
proxy, err = outbound.NewSsh(*sshOption)
case "mieru":
mieruOption := &outbound.MieruOption{}
err = decoder.Decode(mapping, mieruOption)
if err != nil {
break
}
proxy, err = outbound.NewMieru(*mieruOption)
case "anytls":
anytlsOption := &outbound.AnyTLSOption{}
err = decoder.Decode(mapping, anytlsOption)
if err != nil {
break
}
proxy, err = outbound.NewAnyTLS(*anytlsOption)
case "sudoku":
sudokuOption := &outbound.SudokuOption{}
err = decoder.Decode(mapping, sudokuOption)
if err != nil {
break
}
proxy, err = outbound.NewSudoku(*sudokuOption)
default:
return nil, fmt.Errorf("unsupport proxy type: %s", proxyType)
}
@@ -121,12 +168,13 @@ func ParseProxy(mapping map[string]any) (C.Proxy, error) {
return nil, err
}
if muxOption.Enabled {
proxy, err = outbound.NewSingMux(*muxOption, proxy, proxy.(outbound.ProxyBase))
proxy, err = outbound.NewSingMux(*muxOption, proxy)
if err != nil {
return nil, err
}
}
}
proxy = outbound.NewAutoCloseProxyAdapter(proxy)
return NewProxy(proxy), nil
}

View File

@@ -2,18 +2,18 @@ package provider
import (
"context"
"strings"
"sync"
"time"
"github.com/Dreamacro/clash/common/atomic"
"github.com/Dreamacro/clash/common/batch"
"github.com/Dreamacro/clash/common/singledo"
"github.com/Dreamacro/clash/common/utils"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/log"
)
"github.com/metacubex/mihomo/common/atomic"
"github.com/metacubex/mihomo/common/singledo"
"github.com/metacubex/mihomo/common/utils"
C "github.com/metacubex/mihomo/constant"
"github.com/metacubex/mihomo/log"
const (
defaultURLTestTimeout = time.Second * 5
"github.com/dlclark/regexp2"
"golang.org/x/sync/errgroup"
)
type HealthCheckOption struct {
@@ -21,81 +21,197 @@ type HealthCheckOption struct {
Interval uint
}
type extraOption struct {
expectedStatus utils.IntRanges[uint16]
filters map[string]struct{}
}
type HealthCheck struct {
url string
proxies []C.Proxy
interval uint
lazy bool
lastTouch *atomic.Int64
done chan struct{}
singleDo *singledo.Single[struct{}]
ctx context.Context
ctxCancel context.CancelFunc
url string
extra map[string]*extraOption
mu sync.Mutex
proxies []C.Proxy
interval time.Duration
lazy bool
expectedStatus utils.IntRanges[uint16]
lastTouch atomic.TypedValue[time.Time]
singleDo *singledo.Single[struct{}]
timeout time.Duration
}
func (hc *HealthCheck) process() {
ticker := time.NewTicker(time.Duration(hc.interval) * time.Second)
ticker := time.NewTicker(hc.interval)
go hc.check()
for {
select {
case <-ticker.C:
now := time.Now().Unix()
if !hc.lazy || now-hc.lastTouch.Load() < int64(hc.interval) {
lastTouch := hc.lastTouch.Load()
since := time.Since(lastTouch)
if !hc.lazy || since < hc.interval {
hc.check()
} else {
log.Debugln("Skip once health check because we are lazy")
}
case <-hc.done:
case <-hc.ctx.Done():
ticker.Stop()
return
}
}
}
func (hc *HealthCheck) setProxy(proxies []C.Proxy) {
func (hc *HealthCheck) setProxies(proxies []C.Proxy) {
hc.proxies = proxies
}
func (hc *HealthCheck) registerHealthCheckTask(url string, expectedStatus utils.IntRanges[uint16], filter string, interval uint) {
url = strings.TrimSpace(url)
if len(url) == 0 || url == hc.url {
log.Debugln("ignore invalid health check url: %s", url)
return
}
hc.mu.Lock()
defer hc.mu.Unlock()
// 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 {
hc.interval = time.Duration(interval) * time.Second
}
if hc.extra == nil {
hc.extra = make(map[string]*extraOption)
}
// prioritize the use of previously registered configurations, especially those from provider
if _, ok := hc.extra[url]; ok {
// provider default health check does not set filter
if url != hc.url && len(filter) != 0 {
splitAndAddFiltersToExtra(filter, hc.extra[url])
}
log.Debugln("health check url: %s exists", url)
return
}
option := &extraOption{filters: map[string]struct{}{}, expectedStatus: expectedStatus}
splitAndAddFiltersToExtra(filter, option)
hc.extra[url] = option
}
func splitAndAddFiltersToExtra(filter string, option *extraOption) {
filter = strings.TrimSpace(filter)
if len(filter) != 0 {
for _, regex := range strings.Split(filter, "`") {
regex = strings.TrimSpace(regex)
if len(regex) != 0 {
option.filters[regex] = struct{}{}
}
}
}
}
func (hc *HealthCheck) auto() bool {
return hc.interval != 0
}
func (hc *HealthCheck) touch() {
hc.lastTouch.Store(time.Now().Unix())
hc.lastTouch.Store(time.Now())
}
func (hc *HealthCheck) check() {
if len(hc.proxies) == 0 {
return
}
_, _, _ = hc.singleDo.Do(func() (struct{}, error) {
id := utils.NewUUIDV4().String()
log.Debugln("Start New Health Checking {%s}", id)
b, _ := batch.New[bool](context.Background(), batch.WithConcurrencyNum[bool](10))
for _, proxy := range hc.proxies {
p := proxy
b.Go(p.Name(), func() (bool, error) {
ctx, cancel := context.WithTimeout(context.Background(), defaultURLTestTimeout)
defer cancel()
log.Debugln("Health Checking %s {%s}", p.Name(), id)
_, _ = p.URLTest(ctx, hc.url)
log.Debugln("Health Checked %s : %t %d ms {%s}", p.Name(), p.Alive(), p.LastDelay(), id)
return false, nil
})
}
b := new(errgroup.Group)
b.SetLimit(10)
b.Wait()
// execute default health check
option := &extraOption{filters: nil, expectedStatus: hc.expectedStatus}
hc.execute(b, hc.url, id, option)
// execute extra health check
if len(hc.extra) != 0 {
for url, option := range hc.extra {
hc.execute(b, url, id, option)
}
}
_ = b.Wait()
log.Debugln("Finish A Health Checking {%s}", id)
return struct{}{}, nil
})
}
func (hc *HealthCheck) close() {
hc.done <- struct{}{}
}
func (hc *HealthCheck) execute(b *errgroup.Group, url, uid string, option *extraOption) {
url = strings.TrimSpace(url)
if len(url) == 0 {
log.Debugln("Health Check has been skipped due to testUrl is empty, {%s}", uid)
return
}
func NewHealthCheck(proxies []C.Proxy, url string, interval uint, lazy bool) *HealthCheck {
return &HealthCheck{
proxies: proxies,
url: url,
interval: interval,
lazy: lazy,
lastTouch: atomic.NewInt64(0),
done: make(chan struct{}, 1),
singleDo: singledo.NewSingle[struct{}](time.Second),
var filterReg *regexp2.Regexp
var expectedStatus utils.IntRanges[uint16]
if option != nil {
expectedStatus = option.expectedStatus
if len(option.filters) != 0 {
filters := make([]string, 0, len(option.filters))
for filter := range option.filters {
filters = append(filters, filter)
}
filterReg = regexp2.MustCompile(strings.Join(filters, "|"), regexp2.None)
}
}
for _, proxy := range hc.proxies {
// skip proxies that do not require health check
if filterReg != nil {
if match, _ := filterReg.MatchString(proxy.Name()); !match {
continue
}
}
p := proxy
b.Go(func() error {
ctx, cancel := context.WithTimeout(hc.ctx, hc.timeout)
defer cancel()
log.Debugln("Health Checking, proxy: %s, url: %s, id: {%s}", p.Name(), url, uid)
_, _ = 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)
return nil
})
}
}
func (hc *HealthCheck) close() {
hc.ctxCancel()
}
func NewHealthCheck(proxies []C.Proxy, url string, timeout uint, interval uint, lazy bool, expectedStatus utils.IntRanges[uint16]) *HealthCheck {
if url == "" {
expectedStatus = nil
interval = 0
}
if timeout == 0 {
timeout = 5000
}
ctx, cancel := context.WithCancel(context.Background())
return &HealthCheck{
ctx: ctx,
ctxCancel: cancel,
proxies: proxies,
url: url,
timeout: time.Duration(timeout) * time.Millisecond,
extra: map[string]*extraOption{},
interval: time.Duration(interval) * time.Second,
lazy: lazy,
expectedStatus: expectedStatus,
singleDo: singledo.NewSingle[struct{}](time.Second),
}
}

View File

@@ -1,38 +1,79 @@
package provider
import (
"encoding"
"errors"
"fmt"
"time"
"github.com/Dreamacro/clash/common/structure"
"github.com/Dreamacro/clash/component/resource"
C "github.com/Dreamacro/clash/constant"
types "github.com/Dreamacro/clash/constant/provider"
"github.com/metacubex/mihomo/common/structure"
"github.com/metacubex/mihomo/common/utils"
"github.com/metacubex/mihomo/component/resource"
C "github.com/metacubex/mihomo/constant"
P "github.com/metacubex/mihomo/constant/provider"
"github.com/dlclark/regexp2"
)
var errVehicleType = errors.New("unsupport vehicle type")
var (
errVehicleType = errors.New("unsupport vehicle type")
)
type healthCheckSchema struct {
Enable bool `provider:"enable"`
URL string `provider:"url"`
Interval int `provider:"interval"`
Lazy bool `provider:"lazy,omitempty"`
Enable bool `provider:"enable"`
URL string `provider:"url"`
Interval int `provider:"interval"`
TestTimeout int `provider:"timeout,omitempty"`
Lazy bool `provider:"lazy,omitempty"`
ExpectedStatus string `provider:"expected-status,omitempty"`
}
type OverrideProxyNameSchema struct {
// matching expression for regex replacement
Pattern *regexp2.Regexp `provider:"pattern"`
// the new content after regex matching
Target string `provider:"target"`
}
var _ encoding.TextUnmarshaler = (*regexp2.Regexp)(nil) // ensure *regexp2.Regexp can decode direct by structure package
type OverrideSchema struct {
TFO *bool `provider:"tfo,omitempty"`
MPTcp *bool `provider:"mptcp,omitempty"`
UDP *bool `provider:"udp,omitempty"`
UDPOverTCP *bool `provider:"udp-over-tcp,omitempty"`
Up *string `provider:"up,omitempty"`
Down *string `provider:"down,omitempty"`
DialerProxy *string `provider:"dialer-proxy,omitempty"`
SkipCertVerify *bool `provider:"skip-cert-verify,omitempty"`
Interface *string `provider:"interface-name,omitempty"`
RoutingMark *int `provider:"routing-mark,omitempty"`
IPVersion *string `provider:"ip-version,omitempty"`
AdditionalPrefix *string `provider:"additional-prefix,omitempty"`
AdditionalSuffix *string `provider:"additional-suffix,omitempty"`
ProxyName []OverrideProxyNameSchema `provider:"proxy-name,omitempty"`
}
type proxyProviderSchema struct {
Type string `provider:"type"`
Path string `provider:"path"`
URL string `provider:"url,omitempty"`
Interval int `provider:"interval,omitempty"`
Filter string `provider:"filter,omitempty"`
ExcludeFilter string `provider:"exclude-filter,omitempty"`
ExcludeType string `provider:"exclude-type,omitempty"`
DialerProxy string `provider:"dialer-proxy,omitempty"`
HealthCheck healthCheckSchema `provider:"health-check,omitempty"`
Type string `provider:"type"`
Path string `provider:"path,omitempty"`
URL string `provider:"url,omitempty"`
Proxy string `provider:"proxy,omitempty"`
Interval int `provider:"interval,omitempty"`
Filter string `provider:"filter,omitempty"`
ExcludeFilter string `provider:"exclude-filter,omitempty"`
ExcludeType string `provider:"exclude-type,omitempty"`
DialerProxy string `provider:"dialer-proxy,omitempty"`
SizeLimit int64 `provider:"size-limit,omitempty"`
Payload []map[string]any `provider:"payload,omitempty"`
HealthCheck healthCheckSchema `provider:"health-check,omitempty"`
Override OverrideSchema `provider:"override,omitempty"`
Header map[string][]string `provider:"header,omitempty"`
}
func ParseProxyProvider(name string, mapping map[string]any) (types.ProxyProvider, error) {
func ParseProxyProvider(name string, mapping map[string]any) (P.ProxyProvider, error) {
decoder := structure.NewDecoder(structure.Option{TagName: "provider", WeaklyTypedInput: true})
schema := &proxyProviderSchema{
@@ -44,29 +85,49 @@ func ParseProxyProvider(name string, mapping map[string]any) (types.ProxyProvide
return nil, err
}
expectedStatus, err := utils.NewUnsignedRanges[uint16](schema.HealthCheck.ExpectedStatus)
if err != nil {
return nil, err
}
var hcInterval uint
if schema.HealthCheck.Enable {
if schema.HealthCheck.Interval == 0 {
schema.HealthCheck.Interval = 300
}
hcInterval = uint(schema.HealthCheck.Interval)
}
hc := NewHealthCheck([]C.Proxy{}, schema.HealthCheck.URL, hcInterval, schema.HealthCheck.Lazy)
hc := NewHealthCheck([]C.Proxy{}, schema.HealthCheck.URL, uint(schema.HealthCheck.TestTimeout), hcInterval, schema.HealthCheck.Lazy, expectedStatus)
path := C.Path.Resolve(schema.Path)
parser, err := NewProxiesParser(schema.Filter, schema.ExcludeFilter, schema.ExcludeType, schema.DialerProxy, schema.Override)
if err != nil {
return nil, err
}
var vehicle types.Vehicle
var vehicle P.Vehicle
switch schema.Type {
case "file":
path := C.Path.Resolve(schema.Path)
if !C.Path.IsSafePath(path) {
return nil, C.Path.ErrNotSafePath(path)
}
vehicle = resource.NewFileVehicle(path)
case "http":
vehicle = resource.NewHTTPVehicle(schema.URL, path)
path := C.Path.GetPathByHash("proxies", schema.URL)
if schema.Path != "" {
path = C.Path.Resolve(schema.Path)
if !C.Path.IsSafePath(path) {
return nil, C.Path.ErrNotSafePath(path)
}
}
vehicle = resource.NewHTTPVehicle(schema.URL, path, schema.Proxy, schema.Header, resource.DefaultHttpTimeout, schema.SizeLimit)
case "inline":
return NewInlineProvider(name, schema.Payload, parser, hc)
default:
return nil, fmt.Errorf("%w: %s", errVehicleType, schema.Type)
}
interval := time.Duration(uint(schema.Interval)) * time.Second
filter := schema.Filter
excludeFilter := schema.ExcludeFilter
excludeType := schema.ExcludeType
dialerProxy := schema.DialerProxy
return NewProxySetProvider(name, interval, filter, excludeFilter, excludeType, dialerProxy, vehicle, hc)
return NewProxySetProvider(name, interval, schema.Payload, parser, vehicle, hc)
}

View File

@@ -1,23 +1,23 @@
package provider
import (
"context"
"encoding/json"
"errors"
"fmt"
"net/http"
"reflect"
"runtime"
"strings"
"time"
"github.com/Dreamacro/clash/adapter"
"github.com/Dreamacro/clash/common/convert"
clashHttp "github.com/Dreamacro/clash/component/http"
"github.com/Dreamacro/clash/component/resource"
C "github.com/Dreamacro/clash/constant"
types "github.com/Dreamacro/clash/constant/provider"
"github.com/Dreamacro/clash/log"
"github.com/Dreamacro/clash/tunnel/statistic"
"github.com/metacubex/mihomo/adapter"
"github.com/metacubex/mihomo/common/convert"
"github.com/metacubex/mihomo/common/utils"
"github.com/metacubex/mihomo/component/profile/cachefile"
"github.com/metacubex/mihomo/component/resource"
C "github.com/metacubex/mihomo/constant"
P "github.com/metacubex/mihomo/constant/provider"
"github.com/metacubex/mihomo/tunnel/statistic"
"github.com/dlclark/regexp2"
"gopkg.in/yaml.v3"
@@ -31,226 +31,280 @@ type ProxySchema struct {
Proxies []map[string]any `yaml:"proxies"`
}
type providerForApi struct {
Name string `json:"name"`
Type string `json:"type"`
VehicleType string `json:"vehicleType"`
Proxies []C.Proxy `json:"proxies"`
TestUrl string `json:"testUrl"`
ExpectedStatus string `json:"expectedStatus"`
UpdatedAt time.Time `json:"updatedAt,omitempty"`
SubscriptionInfo *SubscriptionInfo `json:"subscriptionInfo,omitempty"`
}
type baseProvider struct {
name string
proxies []C.Proxy
healthCheck *HealthCheck
version uint32
}
func (bp *baseProvider) Name() string {
return bp.name
}
func (bp *baseProvider) Version() uint32 {
return bp.version
}
func (bp *baseProvider) Initial() error {
if bp.healthCheck.auto() {
go bp.healthCheck.process()
}
return nil
}
func (bp *baseProvider) HealthCheck() {
bp.healthCheck.check()
}
func (bp *baseProvider) Type() P.ProviderType {
return P.Proxy
}
func (bp *baseProvider) Proxies() []C.Proxy {
return bp.proxies
}
func (bp *baseProvider) Count() int {
return len(bp.proxies)
}
func (bp *baseProvider) Touch() {
bp.healthCheck.touch()
}
func (bp *baseProvider) HealthCheckURL() string {
return bp.healthCheck.url
}
func (bp *baseProvider) RegisterHealthCheckTask(url string, expectedStatus utils.IntRanges[uint16], filter string, interval uint) {
bp.healthCheck.registerHealthCheckTask(url, expectedStatus, filter, interval)
}
func (bp *baseProvider) setProxies(proxies []C.Proxy) {
bp.proxies = proxies
bp.version += 1
bp.healthCheck.setProxies(proxies)
if bp.healthCheck.auto() {
go bp.healthCheck.check()
}
}
func (bp *baseProvider) Close() error {
bp.healthCheck.close()
return nil
}
// ProxySetProvider for auto gc
type ProxySetProvider struct {
*proxySetProvider
}
type proxySetProvider struct {
baseProvider
*resource.Fetcher[[]C.Proxy]
proxies []C.Proxy
healthCheck *HealthCheck
version uint32
subscriptionInfo *SubscriptionInfo
}
func (pp *proxySetProvider) MarshalJSON() ([]byte, error) {
return json.Marshal(map[string]any{
"name": pp.Name(),
"type": pp.Type().String(),
"vehicleType": pp.VehicleType().String(),
"proxies": pp.Proxies(),
"updatedAt": pp.UpdatedAt,
"subscriptionInfo": pp.subscriptionInfo,
return json.Marshal(providerForApi{
Name: pp.Name(),
Type: pp.Type().String(),
VehicleType: pp.VehicleType().String(),
Proxies: pp.Proxies(),
TestUrl: pp.healthCheck.url,
ExpectedStatus: pp.healthCheck.expectedStatus.String(),
UpdatedAt: pp.UpdatedAt(),
SubscriptionInfo: pp.subscriptionInfo,
})
}
func (pp *proxySetProvider) Version() uint32 {
return pp.version
}
func (pp *proxySetProvider) Name() string {
return pp.Fetcher.Name()
}
func (pp *proxySetProvider) HealthCheck() {
pp.healthCheck.check()
}
func (pp *proxySetProvider) Update() error {
elm, same, err := pp.Fetcher.Update()
if err == nil && !same {
pp.OnUpdate(elm)
}
_, _, err := pp.Fetcher.Update()
return err
}
func (pp *proxySetProvider) Initial() error {
elm, err := pp.Fetcher.Initial()
if err := pp.baseProvider.Initial(); err != nil {
return err
}
_, err := pp.Fetcher.Initial()
if err != nil {
return err
}
pp.OnUpdate(elm)
pp.getSubscriptionInfo()
if subscriptionInfo := cachefile.Cache().GetSubscriptionInfo(pp.Name()); subscriptionInfo != "" {
pp.subscriptionInfo = NewSubscriptionInfo(subscriptionInfo)
}
pp.closeAllConnections()
return nil
}
func (pp *proxySetProvider) Type() types.ProviderType {
return types.Proxy
}
func (pp *proxySetProvider) Proxies() []C.Proxy {
return pp.proxies
}
func (pp *proxySetProvider) Touch() {
pp.healthCheck.touch()
}
func (pp *proxySetProvider) setProxies(proxies []C.Proxy) {
pp.proxies = proxies
pp.healthCheck.setProxy(proxies)
if pp.healthCheck.auto() {
go pp.healthCheck.check()
}
}
func (pp *proxySetProvider) getSubscriptionInfo() {
if pp.VehicleType() != types.HTTP {
return
}
go func() {
ctx, cancel := context.WithTimeout(context.Background(), time.Second*90)
defer cancel()
resp, err := clashHttp.HttpRequest(ctx, pp.Vehicle().(*resource.HTTPVehicle).Url(),
http.MethodGet, http.Header{"User-Agent": {"clash"}}, nil)
if err != nil {
return
}
defer resp.Body.Close()
userInfoStr := strings.TrimSpace(resp.Header.Get("subscription-userinfo"))
if userInfoStr == "" {
resp2, err := clashHttp.HttpRequest(ctx, pp.Vehicle().(*resource.HTTPVehicle).Url(),
http.MethodGet, http.Header{"User-Agent": {"Quantumultx"}}, nil)
if err != nil {
return
}
defer resp2.Body.Close()
userInfoStr = strings.TrimSpace(resp2.Header.Get("subscription-userinfo"))
if userInfoStr == "" {
return
}
}
pp.subscriptionInfo, err = NewSubscriptionInfo(userInfoStr)
if err != nil {
log.Warnln("[Provider] get subscription-userinfo: %e", err)
}
}()
}
func (pp *proxySetProvider) closeAllConnections() {
snapshot := statistic.DefaultManager.Snapshot()
for _, c := range snapshot.Connections {
statistic.DefaultManager.Range(func(c statistic.Tracker) bool {
for _, chain := range c.Chains() {
if chain == pp.Name() {
_ = c.Close()
break
}
}
}
return true
})
}
func stopProxyProvider(pd *ProxySetProvider) {
pd.healthCheck.close()
_ = pd.Fetcher.Destroy()
func (pp *proxySetProvider) Close() error {
_ = pp.baseProvider.Close()
return pp.Fetcher.Close()
}
func NewProxySetProvider(name string, interval time.Duration, filter string, excludeFilter string, excludeType string, dialerProxy string, vehicle types.Vehicle, hc *HealthCheck) (*ProxySetProvider, error) {
excludeFilterReg, err := regexp2.Compile(excludeFilter, 0)
if err != nil {
return nil, fmt.Errorf("invalid excludeFilter regex: %w", err)
}
var excludeTypeArray []string
if excludeType != "" {
excludeTypeArray = strings.Split(excludeType, "|")
}
var filterRegs []*regexp2.Regexp
for _, filter := range strings.Split(filter, "`") {
filterReg, err := regexp2.Compile(filter, 0)
if err != nil {
return nil, fmt.Errorf("invalid filter regex: %w", err)
}
filterRegs = append(filterRegs, filterReg)
}
if hc.auto() {
go hc.process()
}
func NewProxySetProvider(name string, interval time.Duration, payload []map[string]any, parser resource.Parser[[]C.Proxy], vehicle P.Vehicle, hc *HealthCheck) (*ProxySetProvider, error) {
pd := &proxySetProvider{
proxies: []C.Proxy{},
healthCheck: hc,
baseProvider: baseProvider{
name: name,
proxies: []C.Proxy{},
healthCheck: hc,
},
}
fetcher := resource.NewFetcher[[]C.Proxy](name, interval, vehicle, proxiesParseAndFilter(filter, excludeFilter, excludeTypeArray, filterRegs, excludeFilterReg, dialerProxy), proxiesOnUpdate(pd))
if len(payload) > 0 { // using as fallback proxies
ps := ProxySchema{Proxies: payload}
buf, err := yaml.Marshal(ps)
if err != nil {
return nil, err
}
proxies, err := parser(buf)
if err != nil {
return nil, err
}
pd.proxies = proxies
// direct call setProxies on hc to avoid starting a health check process immediately, it should be done by Initial()
hc.setProxies(proxies)
}
fetcher := resource.NewFetcher[[]C.Proxy](name, interval, vehicle, parser, pd.setProxies)
pd.Fetcher = fetcher
if httpVehicle, ok := vehicle.(*resource.HTTPVehicle); ok {
httpVehicle.SetInRead(func(resp *http.Response) {
if subscriptionInfo := resp.Header.Get("subscription-userinfo"); subscriptionInfo != "" {
cachefile.Cache().SetSubscriptionInfo(name, subscriptionInfo)
pd.subscriptionInfo = NewSubscriptionInfo(subscriptionInfo)
}
})
}
wrapper := &ProxySetProvider{pd}
runtime.SetFinalizer(wrapper, stopProxyProvider)
runtime.SetFinalizer(wrapper, (*ProxySetProvider).Close)
return wrapper, nil
}
func (pp *ProxySetProvider) Close() error {
runtime.SetFinalizer(pp, nil)
return pp.proxySetProvider.Close()
}
// InlineProvider for auto gc
type InlineProvider struct {
*inlineProvider
}
type inlineProvider struct {
baseProvider
updateAt time.Time
}
func (ip *inlineProvider) MarshalJSON() ([]byte, error) {
return json.Marshal(providerForApi{
Name: ip.Name(),
Type: ip.Type().String(),
VehicleType: ip.VehicleType().String(),
Proxies: ip.Proxies(),
TestUrl: ip.healthCheck.url,
ExpectedStatus: ip.healthCheck.expectedStatus.String(),
UpdatedAt: ip.updateAt,
})
}
func (ip *inlineProvider) VehicleType() P.VehicleType {
return P.Inline
}
func (ip *inlineProvider) Update() error {
// make api update happy
ip.updateAt = time.Now()
return nil
}
func NewInlineProvider(name string, payload []map[string]any, parser resource.Parser[[]C.Proxy], hc *HealthCheck) (*InlineProvider, error) {
ps := ProxySchema{Proxies: payload}
buf, err := yaml.Marshal(ps)
if err != nil {
return nil, err
}
proxies, err := parser(buf)
if err != nil {
return nil, err
}
// direct call setProxies on hc to avoid starting a health check process immediately, it should be done by Initial()
hc.setProxies(proxies)
ip := &inlineProvider{
baseProvider: baseProvider{
name: name,
proxies: proxies,
healthCheck: hc,
},
updateAt: time.Now(),
}
wrapper := &InlineProvider{ip}
runtime.SetFinalizer(wrapper, (*InlineProvider).Close)
return wrapper, nil
}
func (ip *InlineProvider) Close() error {
runtime.SetFinalizer(ip, nil)
return ip.baseProvider.Close()
}
// CompatibleProvider for auto gc
type CompatibleProvider struct {
*compatibleProvider
}
type compatibleProvider struct {
name string
healthCheck *HealthCheck
proxies []C.Proxy
version uint32
baseProvider
}
func (cp *compatibleProvider) MarshalJSON() ([]byte, error) {
return json.Marshal(map[string]any{
"name": cp.Name(),
"type": cp.Type().String(),
"vehicleType": cp.VehicleType().String(),
"proxies": cp.Proxies(),
return json.Marshal(providerForApi{
Name: cp.Name(),
Type: cp.Type().String(),
VehicleType: cp.VehicleType().String(),
Proxies: cp.Proxies(),
TestUrl: cp.healthCheck.url,
ExpectedStatus: cp.healthCheck.expectedStatus.String(),
})
}
func (cp *compatibleProvider) Version() uint32 {
return cp.version
}
func (cp *compatibleProvider) Name() string {
return cp.name
}
func (cp *compatibleProvider) HealthCheck() {
cp.healthCheck.check()
}
func (cp *compatibleProvider) Update() error {
return nil
}
func (cp *compatibleProvider) Initial() error {
return nil
}
func (cp *compatibleProvider) VehicleType() types.VehicleType {
return types.Compatible
}
func (cp *compatibleProvider) Type() types.ProviderType {
return types.Proxy
}
func (cp *compatibleProvider) Proxies() []C.Proxy {
return cp.proxies
}
func (cp *compatibleProvider) Touch() {
cp.healthCheck.touch()
}
func stopCompatibleProvider(pd *CompatibleProvider) {
pd.healthCheck.close()
func (cp *compatibleProvider) VehicleType() P.VehicleType {
return P.Compatible
}
func NewCompatibleProvider(name string, proxies []C.Proxy, hc *HealthCheck) (*CompatibleProvider, error) {
@@ -258,37 +312,57 @@ func NewCompatibleProvider(name string, proxies []C.Proxy, hc *HealthCheck) (*Co
return nil, errors.New("provider need one proxy at least")
}
if hc.auto() {
go hc.process()
}
pd := &compatibleProvider{
name: name,
proxies: proxies,
healthCheck: hc,
baseProvider: baseProvider{
name: name,
proxies: proxies,
healthCheck: hc,
},
}
wrapper := &CompatibleProvider{pd}
runtime.SetFinalizer(wrapper, stopCompatibleProvider)
runtime.SetFinalizer(wrapper, (*CompatibleProvider).Close)
return wrapper, nil
}
func proxiesOnUpdate(pd *proxySetProvider) func([]C.Proxy) {
return func(elm []C.Proxy) {
pd.setProxies(elm)
pd.version += 1
pd.getSubscriptionInfo()
}
func (cp *CompatibleProvider) Close() error {
runtime.SetFinalizer(cp, nil)
return cp.compatibleProvider.Close()
}
func proxiesParseAndFilter(filter string, excludeFilter string, excludeTypeArray []string, filterRegs []*regexp2.Regexp, excludeFilterReg *regexp2.Regexp, dialerProxy string) resource.Parser[[]C.Proxy] {
func NewProxiesParser(filter string, excludeFilter string, excludeType string, dialerProxy string, override OverrideSchema) (resource.Parser[[]C.Proxy], error) {
var excludeTypeArray []string
if excludeType != "" {
excludeTypeArray = strings.Split(excludeType, "|")
}
var excludeFilterRegs []*regexp2.Regexp
if excludeFilter != "" {
for _, excludeFilter := range strings.Split(excludeFilter, "`") {
excludeFilterReg, err := regexp2.Compile(excludeFilter, regexp2.None)
if err != nil {
return nil, fmt.Errorf("invalid excludeFilter regex: %w", err)
}
excludeFilterRegs = append(excludeFilterRegs, excludeFilterReg)
}
}
var filterRegs []*regexp2.Regexp
for _, filter := range strings.Split(filter, "`") {
filterReg, err := regexp2.Compile(filter, regexp2.None)
if err != nil {
return nil, fmt.Errorf("invalid filter regex: %w", err)
}
filterRegs = append(filterRegs, filterReg)
}
return func(buf []byte) ([]C.Proxy, error) {
schema := &ProxySchema{}
if err := yaml.Unmarshal(buf, schema); err != nil {
proxies, err1 := convert.ConvertsV2Ray(buf)
if err1 != nil {
return nil, fmt.Errorf("%s, %w", err.Error(), err1)
return nil, fmt.Errorf("%w, %w", err, err1)
}
schema.Proxies = proxies
}
@@ -300,8 +374,9 @@ func proxiesParseAndFilter(filter string, excludeFilter string, excludeTypeArray
proxies := []C.Proxy{}
proxiesSet := map[string]struct{}{}
for _, filterReg := range filterRegs {
LOOP1:
for idx, mapping := range schema.Proxies {
if nil != excludeTypeArray && len(excludeTypeArray) > 0 {
if len(excludeTypeArray) > 0 {
mType, ok := mapping["type"]
if !ok {
continue
@@ -310,18 +385,11 @@ func proxiesParseAndFilter(filter string, excludeFilter string, excludeTypeArray
if !ok {
continue
}
flag := false
for i := range excludeTypeArray {
if strings.EqualFold(pType, excludeTypeArray[i]) {
flag = true
break
for _, excludeType := range excludeTypeArray {
if strings.EqualFold(pType, excludeType) {
continue LOOP1
}
}
if flag {
continue
}
}
mName, ok := mapping["name"]
if !ok {
@@ -331,26 +399,60 @@ func proxiesParseAndFilter(filter string, excludeFilter string, excludeTypeArray
if !ok {
continue
}
if len(excludeFilter) > 0 {
if mat, _ := excludeFilterReg.FindStringMatch(name); mat != nil {
continue
if len(excludeFilterRegs) > 0 {
for _, excludeFilterReg := range excludeFilterRegs {
if mat, _ := excludeFilterReg.MatchString(name); mat {
continue LOOP1
}
}
}
if len(filter) > 0 {
if mat, _ := filterReg.FindStringMatch(name); mat == nil {
if mat, _ := filterReg.MatchString(name); !mat {
continue
}
}
if _, ok := proxiesSet[name]; ok {
continue
}
if len(dialerProxy) > 0 {
mapping["dialer-proxy"] = dialerProxy
}
val := reflect.ValueOf(override)
for i := 0; i < val.NumField(); i++ {
field := val.Field(i)
if field.IsNil() {
continue
}
fieldName := strings.Split(val.Type().Field(i).Tag.Get("provider"), ",")[0]
switch fieldName {
case "additional-prefix":
name := mapping["name"].(string)
mapping["name"] = *field.Interface().(*string) + name
case "additional-suffix":
name := mapping["name"].(string)
mapping["name"] = name + *field.Interface().(*string)
case "proxy-name":
// Iterate through all naming replacement rules and perform the replacements.
for _, expr := range override.ProxyName {
name := mapping["name"].(string)
newName, err := expr.Pattern.Replace(name, expr.Target, 0, -1)
if err != nil {
return nil, fmt.Errorf("proxy name replace error: %w", err)
}
mapping["name"] = newName
}
default:
mapping[fieldName] = field.Elem().Interface()
}
}
proxy, err := adapter.ParseProxy(mapping)
if err != nil {
return nil, fmt.Errorf("proxy %d error: %w", idx, err)
}
proxiesSet[name] = struct{}{}
proxies = append(proxies, proxy)
}
@@ -364,5 +466,5 @@ func proxiesParseAndFilter(filter string, excludeFilter string, excludeTypeArray
}
return proxies, nil
}
}, nil
}

View File

@@ -1,9 +1,11 @@
package provider
import (
"github.com/dlclark/regexp2"
"fmt"
"strconv"
"strings"
"github.com/metacubex/mihomo/log"
)
type SubscriptionInfo struct {
@@ -13,45 +15,44 @@ type SubscriptionInfo struct {
Expire int64
}
func NewSubscriptionInfo(str string) (si *SubscriptionInfo, err error) {
si = &SubscriptionInfo{}
str = strings.ToLower(str)
reTraffic := regexp2.MustCompile("upload=(\\d+); download=(\\d+); total=(\\d+)", 0)
reExpire := regexp2.MustCompile("expire=(\\d+)", 0)
func NewSubscriptionInfo(userinfo string) (si *SubscriptionInfo) {
userinfo = strings.ReplaceAll(strings.ToLower(userinfo), " ", "")
si = new(SubscriptionInfo)
match, err := reTraffic.FindStringMatch(str)
if err != nil || match == nil {
return nil, err
}
group := match.Groups()
si.Upload, err = str2uint64(group[1].String())
if err != nil {
return nil, err
}
for _, field := range strings.Split(userinfo, ";") {
name, value, ok := strings.Cut(field, "=")
if !ok {
continue
}
si.Download, err = str2uint64(group[2].String())
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())
intValue, err := parseValue(value)
if err != nil {
return nil, err
log.Warnln("[Provider] get subscription-userinfo: %e", err)
continue
}
switch name {
case "upload":
si.Upload = intValue
case "download":
si.Download = intValue
case "total":
si.Total = intValue
case "expire":
si.Expire = intValue
}
}
return
return si
}
func str2uint64(str string) (int64, error) {
i, err := strconv.ParseInt(str, 10, 64)
return i, err
func parseValue(value string) (int64, error) {
if intValue, err := strconv.ParseInt(value, 10, 64); err == nil {
return intValue, nil
}
if floatValue, err := strconv.ParseFloat(value, 64); err == nil {
return int64(floatValue), nil
}
return 0, fmt.Errorf("failed to parse value '%s'", value)
}

21
android_tz.go Normal file
View File

@@ -0,0 +1,21 @@
// Copyright 2014 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.
// kanged from https://github.com/golang/mobile/blob/c713f31d574bb632a93f169b2cc99c9e753fef0e/app/android.go#L89
package main
// #include <time.h>
import "C"
import "time"
func init() {
var currentT C.time_t
var currentTM C.struct_tm
C.time(&currentT)
C.localtime_r(&currentT, &currentTM)
tzOffset := int(currentTM.tm_gmtoff)
tz := C.GoString(currentTM.tm_zone)
time.Local = time.FixedZone(tz, tzOffset)
}

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

@@ -0,0 +1,241 @@
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]
}
// 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]{}
arc.Clear()
for _, option := range options {
option(arc)
}
return arc
}
func (a *ARC[K, V]) Clear() {
a.mutex.Lock()
defer a.mutex.Unlock()
a.p = 0
a.t1 = list.New[*entry[K, V]]()
a.b1 = list.New[*entry[K, V]]()
a.t2 = list.New[*entry[K, V]]()
a.b2 = list.New[*entry[K, V]]()
a.len = 0
a.cache = make(map[K]*entry[K, V])
}
// 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 {
a.len++
ent := &entry[K, V]{key: key, value: value, ghost: false, expires: expires.Unix()}
a.req(ent)
a.cache[key] = ent
return
}
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 lo.Empty[V](), false
}
return ent.value, true
}
func (a *ARC[K, V]) get(key K) (e *entry[K, V], ok bool) {
ent, ok := a.cache[key]
if !ok {
return ent, false
}
a.req(ent)
return ent, !ent.ghost
}
// 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 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]) {
switch {
case ent.ll == a.t1 || ent.ll == a.t2:
// Case I
ent.setMRU(a.t2)
case 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.replace(ent)
ent.setMRU(a.t2)
case 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.replace(ent)
ent.setMRU(a.t2)
case ent.ll == nil && a.t1.Len()+a.b1.Len() == a.c:
// Case IV A
if a.t1.Len() < a.c {
a.delLRU(a.b1)
a.replace(ent)
} else {
a.delLRU(a.t1)
}
ent.setMRU(a.t1)
case ent.ll == nil && a.t1.Len()+a.b1.Len() < a.c:
// Case IV 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)
case ent.ll == nil:
// Case IV, not A nor B
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)
}
}
func min(a, b int) int {
if a < b {
return a
}
return b
}
func max(a int, b int) int {
if a < b {
return b
}
return a
}

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

@@ -0,0 +1,105 @@
package arc
import (
"testing"
)
func TestInsertion(t *testing.T) {
cache := New[string, string](WithSize[string, string](3))
if got, want := cache.Len(), 0; got != want {
t.Errorf("empty cache.Len(): got %d want %d", cache.Len(), want)
}
const (
k1 = "Hello"
k2 = "Hallo"
k3 = "Ciao"
k4 = "Salut"
v1 = "World"
v2 = "Worlds"
v3 = "Welt"
)
// Insert the first value
cache.Set(k1, v1)
if got, want := cache.Len(), 1; got != want {
t.Errorf("insertion of key #%d: cache.Len(): got %d want %d", want, cache.Len(), want)
}
if got, ok := cache.Get(k1); !ok || got != v1 {
t.Errorf("cache.Get(%v): got (%v,%t) want (%v,true)", k1, got, ok, v1)
}
// Replace existing value for a given key
cache.Set(k1, v2)
if got, want := cache.Len(), 1; got != want {
t.Errorf("re-insertion: cache.Len(): got %d want %d", cache.Len(), want)
}
if got, ok := cache.Get(k1); !ok || got != v2 {
t.Errorf("re-insertion: cache.Get(%v): got (%v,%t) want (%v,true)", k1, got, ok, v2)
}
// Add a second different key
cache.Set(k2, v3)
if got, want := cache.Len(), 2; got != want {
t.Errorf("insertion of key #%d: cache.Len(): got %d want %d", want, cache.Len(), want)
}
if got, ok := cache.Get(k1); !ok || got != v2 {
t.Errorf("cache.Get(%v): got (%v,%t) want (%v,true)", k1, got, ok, v2)
}
if got, ok := cache.Get(k2); !ok || got != v3 {
t.Errorf("cache.Get(%v): got (%v,%t) want (%v,true)", k2, got, ok, v3)
}
// Fill cache
cache.Set(k3, v1)
if got, want := cache.Len(), 3; got != want {
t.Errorf("insertion of key #%d: cache.Len(): got %d want %d", want, cache.Len(), want)
}
// Exceed size, this should not exceed size:
cache.Set(k4, v1)
if got, want := cache.Len(), 3; got != want {
t.Errorf("insertion of key out of size: cache.Len(): got %d want %d", cache.Len(), want)
}
}
func TestEviction(t *testing.T) {
size := 3
cache := New[string, string](WithSize[string, string](size))
if got, want := cache.Len(), 0; got != want {
t.Errorf("empty cache.Len(): got %d want %d", cache.Len(), want)
}
tests := []struct {
k, v string
}{
{"k1", "v1"},
{"k2", "v2"},
{"k3", "v3"},
{"k4", "v4"},
}
for i, tt := range tests[:size] {
cache.Set(tt.k, tt.v)
if got, want := cache.Len(), i+1; got != want {
t.Errorf("insertion of key #%d: cache.Len(): got %d want %d", want, cache.Len(), want)
}
}
// Exceed size and check we don't outgrow it:
cache.Set(tests[size].k, tests[size].v)
if got := cache.Len(); got != size {
t.Errorf("insertion of overflow key #%d: cache.Len(): got %d want %d", 4, cache.Len(), size)
}
// Check that LRU got evicted:
if got, ok := cache.Get(tests[0].k); ok || got != "" {
t.Errorf("cache.Get(%v): got (%v,%t) want (<nil>,true)", tests[0].k, got, ok)
}
for _, tt := range tests[1:] {
if got, ok := cache.Get(tt.k); !ok || got != tt.v {
t.Errorf("cache.Get(%v): got (%v,%t) want (%v,true)", tt.k, got, ok, tt.v)
}
}
}

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

63
common/atomic/enum.go Normal file
View File

@@ -0,0 +1,63 @@
package atomic
import (
"encoding/json"
"fmt"
"sync/atomic"
)
type Int32Enum[T ~int32] struct {
value atomic.Int32
}
func (i *Int32Enum[T]) MarshalJSON() ([]byte, error) {
return json.Marshal(i.Load())
}
func (i *Int32Enum[T]) UnmarshalJSON(b []byte) error {
var v T
if err := json.Unmarshal(b, &v); err != nil {
return err
}
i.Store(v)
return nil
}
func (i *Int32Enum[T]) MarshalYAML() (any, error) {
return i.Load(), nil
}
func (i *Int32Enum[T]) UnmarshalYAML(unmarshal func(any) error) error {
var v T
if err := unmarshal(&v); err != nil {
return err
}
i.Store(v)
return nil
}
func (i *Int32Enum[T]) String() string {
return fmt.Sprint(i.Load())
}
func (i *Int32Enum[T]) Store(v T) {
i.value.Store(int32(v))
}
func (i *Int32Enum[T]) Load() T {
return T(i.value.Load())
}
func (i *Int32Enum[T]) Swap(new T) T {
return T(i.value.Swap(int32(new)))
}
func (i *Int32Enum[T]) CompareAndSwap(old, new T) bool {
return i.value.CompareAndSwap(int32(old), int32(new))
}
func NewInt32Enum[T ~int32](v T) *Int32Enum[T] {
a := &Int32Enum[T]{}
a.Store(v)
return a
}

View File

@@ -11,10 +11,9 @@ type Bool struct {
atomic.Bool
}
func NewBool(val bool) *Bool {
i := &Bool{}
func NewBool(val bool) (i Bool) {
i.Store(val)
return i
return
}
func (i *Bool) MarshalJSON() ([]byte, error) {
@@ -30,6 +29,19 @@ func (i *Bool) UnmarshalJSON(b []byte) error {
return nil
}
func (i *Bool) MarshalYAML() (any, error) {
return i.Load(), nil
}
func (i *Bool) UnmarshalYAML(unmarshal func(any) error) error {
var v bool
if err := unmarshal(&v); err != nil {
return err
}
i.Store(v)
return nil
}
func (i *Bool) String() string {
v := i.Load()
return strconv.FormatBool(v)
@@ -39,12 +51,11 @@ type Pointer[T any] struct {
atomic.Pointer[T]
}
func NewPointer[T any](v *T) *Pointer[T] {
var p Pointer[T]
func NewPointer[T any](v *T) (p Pointer[T]) {
if v != nil {
p.Store(v)
}
return &p
return
}
func (p *Pointer[T]) MarshalJSON() ([]byte, error) {
@@ -60,6 +71,19 @@ func (p *Pointer[T]) UnmarshalJSON(b []byte) error {
return nil
}
func (p *Pointer[T]) MarshalYAML() (any, error) {
return p.Load(), nil
}
func (p *Pointer[T]) UnmarshalYAML(unmarshal func(any) error) error {
var v *T
if err := unmarshal(&v); err != nil {
return err
}
p.Store(v)
return nil
}
func (p *Pointer[T]) String() string {
return fmt.Sprint(p.Load())
}
@@ -68,10 +92,9 @@ type Int32 struct {
atomic.Int32
}
func NewInt32(val int32) *Int32 {
i := &Int32{}
func NewInt32(val int32) (i Int32) {
i.Store(val)
return i
return
}
func (i *Int32) MarshalJSON() ([]byte, error) {
@@ -87,6 +110,19 @@ func (i *Int32) UnmarshalJSON(b []byte) error {
return nil
}
func (i *Int32) MarshalYAML() (any, error) {
return i.Load(), nil
}
func (i *Int32) UnmarshalYAML(unmarshal func(any) error) error {
var v int32
if err := unmarshal(&v); err != nil {
return err
}
i.Store(v)
return nil
}
func (i *Int32) String() string {
v := i.Load()
return strconv.FormatInt(int64(v), 10)
@@ -96,10 +132,9 @@ type Int64 struct {
atomic.Int64
}
func NewInt64(val int64) *Int64 {
i := &Int64{}
func NewInt64(val int64) (i Int64) {
i.Store(val)
return i
return
}
func (i *Int64) MarshalJSON() ([]byte, error) {
@@ -115,6 +150,19 @@ func (i *Int64) UnmarshalJSON(b []byte) error {
return nil
}
func (i *Int64) MarshalYAML() (any, error) {
return i.Load(), nil
}
func (i *Int64) UnmarshalYAML(unmarshal func(any) error) error {
var v int64
if err := unmarshal(&v); err != nil {
return err
}
i.Store(v)
return nil
}
func (i *Int64) String() string {
v := i.Load()
return strconv.FormatInt(int64(v), 10)
@@ -124,10 +172,9 @@ type Uint32 struct {
atomic.Uint32
}
func NewUint32(val uint32) *Uint32 {
i := &Uint32{}
func NewUint32(val uint32) (i Uint32) {
i.Store(val)
return i
return
}
func (i *Uint32) MarshalJSON() ([]byte, error) {
@@ -143,6 +190,19 @@ func (i *Uint32) UnmarshalJSON(b []byte) error {
return nil
}
func (i *Uint32) MarshalYAML() (any, error) {
return i.Load(), nil
}
func (i *Uint32) UnmarshalYAML(unmarshal func(any) error) error {
var v uint32
if err := unmarshal(&v); err != nil {
return err
}
i.Store(v)
return nil
}
func (i *Uint32) String() string {
v := i.Load()
return strconv.FormatUint(uint64(v), 10)
@@ -152,10 +212,9 @@ type Uint64 struct {
atomic.Uint64
}
func NewUint64(val uint64) *Uint64 {
i := &Uint64{}
func NewUint64(val uint64) (i Uint64) {
i.Store(val)
return i
return
}
func (i *Uint64) MarshalJSON() ([]byte, error) {
@@ -171,6 +230,19 @@ func (i *Uint64) UnmarshalJSON(b []byte) error {
return nil
}
func (i *Uint64) MarshalYAML() (any, error) {
return i.Load(), nil
}
func (i *Uint64) UnmarshalYAML(unmarshal func(any) error) error {
var v uint64
if err := unmarshal(&v); err != nil {
return err
}
i.Store(v)
return nil
}
func (i *Uint64) String() string {
v := i.Load()
return strconv.FormatUint(uint64(v), 10)
@@ -180,10 +252,9 @@ type Uintptr struct {
atomic.Uintptr
}
func NewUintptr(val uintptr) *Uintptr {
i := &Uintptr{}
func NewUintptr(val uintptr) (i Uintptr) {
i.Store(val)
return i
return
}
func (i *Uintptr) MarshalJSON() ([]byte, error) {
@@ -199,6 +270,19 @@ func (i *Uintptr) UnmarshalJSON(b []byte) error {
return nil
}
func (i *Uintptr) MarshalYAML() (any, error) {
return i.Load(), nil
}
func (i *Uintptr) UnmarshalYAML(unmarshal func(any) error) error {
var v uintptr
if err := unmarshal(&v); err != nil {
return err
}
i.Store(v)
return nil
}
func (i *Uintptr) String() string {
v := i.Load()
return strconv.FormatUint(uint64(v), 10)

View File

@@ -5,37 +5,50 @@ import (
"sync/atomic"
)
func DefaultValue[T any]() T {
var defaultValue T
return defaultValue
}
type TypedValue[T any] struct {
value atomic.Value
value atomic.Pointer[T]
}
func (t *TypedValue[T]) Load() T {
func (t *TypedValue[T]) Load() (v T) {
v, _ = t.LoadOk()
return
}
func (t *TypedValue[T]) LoadOk() (v T, ok bool) {
value := t.value.Load()
if value == nil {
return DefaultValue[T]()
return
}
return value.(T)
return *value, true
}
func (t *TypedValue[T]) Store(value T) {
t.value.Store(value)
t.value.Store(&value)
}
func (t *TypedValue[T]) Swap(new T) T {
old := t.value.Swap(new)
func (t *TypedValue[T]) Swap(new T) (v T) {
old := t.value.Swap(&new)
if old == nil {
return DefaultValue[T]()
return
}
return old.(T)
return *old
}
func (t *TypedValue[T]) CompareAndSwap(old, new T) bool {
return t.value.CompareAndSwap(old, new)
for {
currentP := t.value.Load()
var currentValue T
if currentP != nil {
currentValue = *currentP
}
// Compare old and current via runtime equality check.
if any(currentValue) != any(old) {
return false
}
if t.value.CompareAndSwap(currentP, &new) {
return true
}
}
}
func (t *TypedValue[T]) MarshalJSON() ([]byte, error) {
@@ -51,8 +64,20 @@ func (t *TypedValue[T]) UnmarshalJSON(b []byte) error {
return nil
}
func NewTypedValue[T any](t T) *TypedValue[T] {
v := &TypedValue[T]{}
v.Store(t)
return v
func (t *TypedValue[T]) MarshalYAML() (any, error) {
return t.Load(), nil
}
func (t *TypedValue[T]) UnmarshalYAML(unmarshal func(any) error) error {
var v T
if err := unmarshal(&v); err != nil {
return err
}
t.Store(v)
return nil
}
func NewTypedValue[T any](t T) (v TypedValue[T]) {
v.Store(t)
return
}

169
common/atomic/value_test.go Normal file
View File

@@ -0,0 +1,169 @@
package atomic
import (
"io"
"os"
"testing"
)
func TestTypedValue(t *testing.T) {
{
var v TypedValue[int]
got, gotOk := v.LoadOk()
if got != 0 || gotOk {
t.Fatalf("LoadOk = (%v, %v), want (0, false)", got, gotOk)
}
v.Store(1)
got, gotOk = v.LoadOk()
if got != 1 || !gotOk {
t.Fatalf("LoadOk = (%v, %v), want (1, true)", got, gotOk)
}
}
{
var v TypedValue[error]
got, gotOk := v.LoadOk()
if got != nil || gotOk {
t.Fatalf("LoadOk = (%v, %v), want (nil, false)", got, gotOk)
}
v.Store(io.EOF)
got, gotOk = v.LoadOk()
if got != io.EOF || !gotOk {
t.Fatalf("LoadOk = (%v, %v), want (EOF, true)", got, gotOk)
}
err := &os.PathError{}
v.Store(err)
got, gotOk = v.LoadOk()
if got != err || !gotOk {
t.Fatalf("LoadOk = (%v, %v), want (%v, true)", got, gotOk, err)
}
v.Store(nil)
got, gotOk = v.LoadOk()
if got != nil || !gotOk {
t.Fatalf("LoadOk = (%v, %v), want (nil, true)", got, gotOk)
}
}
{
e1, e2, e3 := io.EOF, &os.PathError{}, &os.PathError{}
var v TypedValue[error]
if v.CompareAndSwap(e1, e2) != false {
t.Fatalf("CompareAndSwap = true, want false")
}
if value := v.Load(); value != nil {
t.Fatalf("Load = (%v), want (%v)", value, nil)
}
if v.CompareAndSwap(nil, e1) != true {
t.Fatalf("CompareAndSwap = false, want true")
}
if value := v.Load(); value != e1 {
t.Fatalf("Load = (%v), want (%v)", value, e1)
}
if v.CompareAndSwap(e2, e3) != false {
t.Fatalf("CompareAndSwap = true, want false")
}
if value := v.Load(); value != e1 {
t.Fatalf("Load = (%v), want (%v)", value, e1)
}
if v.CompareAndSwap(e1, e2) != true {
t.Fatalf("CompareAndSwap = false, want true")
}
if value := v.Load(); value != e2 {
t.Fatalf("Load = (%v), want (%v)", value, e2)
}
if v.CompareAndSwap(e3, e2) != false {
t.Fatalf("CompareAndSwap = true, want false")
}
if value := v.Load(); value != e2 {
t.Fatalf("Load = (%v), want (%v)", value, e2)
}
if v.CompareAndSwap(nil, e3) != false {
t.Fatalf("CompareAndSwap = true, want false")
}
if value := v.Load(); value != e2 {
t.Fatalf("Load = (%v), want (%v)", value, e2)
}
}
{
c1, c2, c3 := make(chan struct{}), make(chan struct{}), make(chan struct{})
var v TypedValue[chan struct{}]
if v.CompareAndSwap(c1, c2) != false {
t.Fatalf("CompareAndSwap = true, want false")
}
if value := v.Load(); value != nil {
t.Fatalf("Load = (%v), want (%v)", value, nil)
}
if v.CompareAndSwap(nil, c1) != true {
t.Fatalf("CompareAndSwap = false, want true")
}
if value := v.Load(); value != c1 {
t.Fatalf("Load = (%v), want (%v)", value, c1)
}
if v.CompareAndSwap(c2, c3) != false {
t.Fatalf("CompareAndSwap = true, want false")
}
if value := v.Load(); value != c1 {
t.Fatalf("Load = (%v), want (%v)", value, c1)
}
if v.CompareAndSwap(c1, c2) != true {
t.Fatalf("CompareAndSwap = false, want true")
}
if value := v.Load(); value != c2 {
t.Fatalf("Load = (%v), want (%v)", value, c2)
}
if v.CompareAndSwap(c3, c2) != false {
t.Fatalf("CompareAndSwap = true, want false")
}
if value := v.Load(); value != c2 {
t.Fatalf("Load = (%v), want (%v)", value, c2)
}
if v.CompareAndSwap(nil, c3) != false {
t.Fatalf("CompareAndSwap = true, want false")
}
if value := v.Load(); value != c2 {
t.Fatalf("Load = (%v), want (%v)", value, c2)
}
}
{
c1, c2, c3 := &io.LimitedReader{}, &io.SectionReader{}, &io.SectionReader{}
var v TypedValue[io.Reader]
if v.CompareAndSwap(c1, c2) != false {
t.Fatalf("CompareAndSwap = true, want false")
}
if value := v.Load(); value != nil {
t.Fatalf("Load = (%v), want (%v)", value, nil)
}
if v.CompareAndSwap(nil, c1) != true {
t.Fatalf("CompareAndSwap = false, want true")
}
if value := v.Load(); value != c1 {
t.Fatalf("Load = (%v), want (%v)", value, c1)
}
if v.CompareAndSwap(c2, c3) != false {
t.Fatalf("CompareAndSwap = true, want false")
}
if value := v.Load(); value != c1 {
t.Fatalf("Load = (%v), want (%v)", value, c1)
}
if v.CompareAndSwap(c1, c2) != true {
t.Fatalf("CompareAndSwap = false, want true")
}
if value := v.Load(); value != c2 {
t.Fatalf("Load = (%v), want (%v)", value, c2)
}
if v.CompareAndSwap(c3, c2) != false {
t.Fatalf("CompareAndSwap = true, want false")
}
if value := v.Load(); value != c2 {
t.Fatalf("Load = (%v), want (%v)", value, c2)
}
if v.CompareAndSwap(nil, c3) != false {
t.Fatalf("CompareAndSwap = true, want false")
}
if value := v.Load(); value != c2 {
t.Fatalf("Load = (%v), want (%v)", value, c2)
}
}
}

View File

@@ -1,8 +1,8 @@
package buf
import (
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/buf"
"github.com/metacubex/sing/common"
"github.com/metacubex/sing/common/buf"
)
const BufferSize = buf.BufferSize
@@ -10,18 +10,11 @@ const BufferSize = buf.BufferSize
type Buffer = buf.Buffer
var New = buf.New
var NewPacket = buf.NewPacket
var NewSize = buf.NewSize
var StackNew = buf.StackNew
var StackNewSize = buf.StackNewSize
var With = buf.With
var As = buf.As
var KeepAlive = common.KeepAlive
//go:norace
func Dup[T any](obj T) T {
return common.Dup(obj)
}
var ReleaseMulti = buf.ReleaseMulti
var (
Must = common.Must

View File

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

View File

@@ -0,0 +1,61 @@
package callback
import (
"sync"
C "github.com/metacubex/mihomo/constant"
)
type closeCallbackConn struct {
C.Conn
closeFunc func()
closeOnce sync.Once
}
func (w *closeCallbackConn) Close() error {
w.closeOnce.Do(w.closeFunc)
return w.Conn.Close()
}
func (w *closeCallbackConn) ReaderReplaceable() bool {
return true
}
func (w *closeCallbackConn) WriterReplaceable() bool {
return true
}
func (w *closeCallbackConn) Upstream() any {
return w.Conn
}
func NewCloseCallbackConn(conn C.Conn, callback func()) C.Conn {
return &closeCallbackConn{Conn: conn, closeFunc: callback}
}
type closeCallbackPacketConn struct {
C.PacketConn
closeFunc func()
closeOnce sync.Once
}
func (w *closeCallbackPacketConn) Close() error {
w.closeOnce.Do(w.closeFunc)
return w.PacketConn.Close()
}
func (w *closeCallbackPacketConn) ReaderReplaceable() bool {
return true
}
func (w *closeCallbackPacketConn) WriterReplaceable() bool {
return true
}
func (w *closeCallbackPacketConn) Upstream() any {
return w.PacketConn
}
func NewCloseCallbackPacketConn(conn C.PacketConn, callback func()) C.PacketConn {
return &closeCallbackPacketConn{PacketConn: conn, closeFunc: callback}
}

View File

@@ -21,7 +21,7 @@ func TestSplitArgs(t *testing.T) {
func TestExecCmd(t *testing.T) {
if runtime.GOOS == "windows" {
_, err := ExecCmd("dir")
_, err := ExecCmd("cmd -c 'dir'")
assert.Nil(t, err)
return
}

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

@@ -0,0 +1,31 @@
package contextutils
import (
"context"
"sync"
)
func afterFunc(ctx context.Context, f func()) (stop func() bool) {
stopc := make(chan struct{})
once := sync.Once{} // either starts running f or stops f from running
if ctx.Done() != nil {
go func() {
select {
case <-ctx.Done():
once.Do(func() {
go f()
})
case <-stopc:
}
}()
}
return func() bool {
stopped := false
once.Do(func() {
stopped = true
close(stopc)
})
return stopped
}
}

View File

@@ -0,0 +1,11 @@
//go:build !go1.21
package contextutils
import (
"context"
)
func AfterFunc(ctx context.Context, f func()) (stop func() bool) {
return afterFunc(ctx, f)
}

View File

@@ -0,0 +1,9 @@
//go:build go1.21
package contextutils
import "context"
func AfterFunc(ctx context.Context, f func()) (stop func() bool) {
return context.AfterFunc(ctx, f)
}

View File

@@ -0,0 +1,100 @@
package contextutils
import (
"context"
"testing"
"time"
)
const (
shortDuration = 1 * time.Millisecond // a reasonable duration to block in a test
veryLongDuration = 1000 * time.Hour // an arbitrary upper bound on the test's running time
)
func TestAfterFuncCalledAfterCancel(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
donec := make(chan struct{})
stop := afterFunc(ctx, func() {
close(donec)
})
select {
case <-donec:
t.Fatalf("AfterFunc called before context is done")
case <-time.After(shortDuration):
}
cancel()
select {
case <-donec:
case <-time.After(veryLongDuration):
t.Fatalf("AfterFunc not called after context is canceled")
}
if stop() {
t.Fatalf("stop() = true, want false")
}
}
func TestAfterFuncCalledAfterTimeout(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), shortDuration)
defer cancel()
donec := make(chan struct{})
afterFunc(ctx, func() {
close(donec)
})
select {
case <-donec:
case <-time.After(veryLongDuration):
t.Fatalf("AfterFunc not called after context is canceled")
}
}
func TestAfterFuncCalledImmediately(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
cancel()
donec := make(chan struct{})
afterFunc(ctx, func() {
close(donec)
})
select {
case <-donec:
case <-time.After(veryLongDuration):
t.Fatalf("AfterFunc not called for already-canceled context")
}
}
func TestAfterFuncNotCalledAfterStop(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
donec := make(chan struct{})
stop := afterFunc(ctx, func() {
close(donec)
})
if !stop() {
t.Fatalf("stop() = false, want true")
}
cancel()
select {
case <-donec:
t.Fatalf("AfterFunc called for already-canceled context")
case <-time.After(shortDuration):
}
if stop() {
t.Fatalf("stop() = true, want false")
}
}
// This test verifies that canceling a context does not block waiting for AfterFuncs to finish.
func TestAfterFuncCalledAsynchronously(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
donec := make(chan struct{})
stop := afterFunc(ctx, func() {
// The channel send blocks until donec is read from.
donec <- struct{}{}
})
defer stop()
cancel()
// After cancel returns, read from donec and unblock the AfterFunc.
select {
case <-donec:
case <-time.After(veryLongDuration):
t.Fatalf("AfterFunc not called after context is canceled")
}
}

View File

@@ -2,6 +2,7 @@ package convert
import (
"encoding/base64"
"fmt"
"strings"
)
@@ -43,3 +44,22 @@ func decodeUrlSafe(data string) string {
}
return string(dcBuf)
}
func TryDecodeBase64(s string) (decoded []byte, err error) {
if len(s)%4 == 0 {
if decoded, err = base64.StdEncoding.DecodeString(s); err == nil {
return
}
if decoded, err = base64.URLEncoding.DecodeString(s); err == nil {
return
}
} else {
if decoded, err = base64.RawStdEncoding.DecodeString(s); err == nil {
return
}
if decoded, err = base64.RawURLEncoding.DecodeString(s); err == nil {
return
}
}
return nil, fmt.Errorf("invalid base64-encoded string")
}

View File

@@ -9,10 +9,10 @@ import (
"strconv"
"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) {
data := DecodeBase64(buf)
@@ -50,7 +50,9 @@ func ConvertsV2Ray(buf []byte) ([]map[string]any, error) {
hysteria["port"] = urlHysteria.Port()
hysteria["sni"] = query.Get("peer")
hysteria["obfs"] = query.Get("obfs")
hysteria["alpn"] = []string{query.Get("alpn")}
if alpn := query.Get("alpn"); alpn != "" {
hysteria["alpn"] = strings.Split(alpn, ",")
}
hysteria["auth_str"] = query.Get("auth")
hysteria["protocol"] = query.Get("protocol")
up := query.Get("up")
@@ -67,6 +69,83 @@ func ConvertsV2Ray(buf []byte) ([]map[string]any, error) {
proxies = append(proxies, hysteria)
case "hysteria2", "hy2":
urlHysteria2, err := url.Parse(line)
if err != nil {
continue
}
query := urlHysteria2.Query()
name := uniqueName(names, urlHysteria2.Fragment)
hysteria2 := make(map[string]any, 20)
hysteria2["name"] = name
hysteria2["type"] = "hysteria2"
hysteria2["server"] = urlHysteria2.Hostname()
if port := urlHysteria2.Port(); port != "" {
hysteria2["port"] = port
} else {
hysteria2["port"] = "443"
}
hysteria2["obfs"] = query.Get("obfs")
hysteria2["obfs-password"] = query.Get("obfs-password")
hysteria2["sni"] = query.Get("sni")
hysteria2["skip-cert-verify"], _ = strconv.ParseBool(query.Get("insecure"))
if alpn := query.Get("alpn"); alpn != "" {
hysteria2["alpn"] = strings.Split(alpn, ",")
}
if auth := urlHysteria2.User.String(); auth != "" {
hysteria2["password"] = auth
}
hysteria2["fingerprint"] = query.Get("pinSHA256")
hysteria2["down"] = query.Get("down")
hysteria2["up"] = query.Get("up")
proxies = append(proxies, hysteria2)
case "tuic":
// A temporary unofficial TUIC share link standard
// Modified from https://github.com/daeuniverse/dae/discussions/182
// Changes:
// 1. Support TUICv4, just replace uuid:password with token
// 2. Remove `allow_insecure` field
urlTUIC, err := url.Parse(line)
if err != nil {
continue
}
query := urlTUIC.Query()
tuic := make(map[string]any, 20)
tuic["name"] = uniqueName(names, urlTUIC.Fragment)
tuic["type"] = scheme
tuic["server"] = urlTUIC.Hostname()
tuic["port"] = urlTUIC.Port()
tuic["udp"] = true
password, v5 := urlTUIC.User.Password()
if v5 {
tuic["uuid"] = urlTUIC.User.Username()
tuic["password"] = password
} else {
tuic["token"] = urlTUIC.User.Username()
}
if cc := query.Get("congestion_control"); cc != "" {
tuic["congestion-controller"] = cc
}
if alpn := query.Get("alpn"); alpn != "" {
tuic["alpn"] = strings.Split(alpn, ",")
}
if sni := query.Get("sni"); sni != "" {
tuic["sni"] = sni
}
if query.Get("disable_sni") == "1" {
tuic["disable-sni"] = true
}
if udpRelayMode := query.Get("udp_relay_mode"); udpRelayMode != "" {
tuic["udp-relay-mode"] = udpRelayMode
}
proxies = append(proxies, tuic)
case "trojan":
urlTrojan, err := url.Parse(line)
if err != nil {
@@ -86,10 +165,12 @@ func ConvertsV2Ray(buf []byte) ([]map[string]any, error) {
trojan["udp"] = true
trojan["skip-cert-verify"], _ = strconv.ParseBool(query.Get("allowInsecure"))
sni := query.Get("sni")
if sni != "" {
if sni := query.Get("sni"); sni != "" {
trojan["sni"] = sni
}
if alpn := query.Get("alpn"); alpn != "" {
trojan["alpn"] = strings.Split(alpn, ",")
}
network := strings.ToLower(query.Get("type"))
if network != "" {
@@ -127,6 +208,9 @@ func ConvertsV2Ray(buf []byte) ([]map[string]any, error) {
if err != nil {
continue
}
if decodedHost, err := tryDecodeBase64([]byte(urlVLess.Host)); err == nil {
urlVLess.Host = string(decodedHost)
}
query := urlVLess.Query()
vless := make(map[string]any, 20)
err = handleVShareLink(names, urlVLess, scheme, vless)
@@ -137,6 +221,9 @@ func ConvertsV2Ray(buf []byte) ([]map[string]any, error) {
if flow := query.Get("flow"); flow != "" {
vless["flow"] = strings.ToLower(flow)
}
if encryption := query.Get("encryption"); encryption != "" {
vless["encryption"] = encryption
}
proxies = append(proxies, vless)
case "vmess":
@@ -194,22 +281,24 @@ func ConvertsV2Ray(buf []byte) ([]map[string]any, error) {
vmess["skip-cert-verify"] = false
vmess["cipher"] = "auto"
if cipher, ok := values["scy"]; ok && cipher != "" {
if cipher, ok := values["scy"].(string); ok && cipher != "" {
vmess["cipher"] = cipher
}
if sni, ok := values["sni"]; ok && sni != "" {
if sni, ok := values["sni"].(string); ok && sni != "" {
vmess["servername"] = sni
}
network, _ := values["net"].(string)
network = strings.ToLower(network)
if values["type"] == "http" {
network = "http"
} else if network == "http" {
network = "h2"
network, ok := values["net"].(string)
if ok {
network = strings.ToLower(network)
if values["type"] == "http" {
network = "http"
} else if network == "http" {
network = "h2"
}
vmess["network"] = network
}
vmess["network"] = network
tls, ok := values["tls"].(string)
if ok {
@@ -217,18 +306,21 @@ func ConvertsV2Ray(buf []byte) ([]map[string]any, error) {
if strings.HasSuffix(tls, "tls") {
vmess["tls"] = true
}
if alpn, ok := values["alpn"].(string); ok {
vmess["alpn"] = strings.Split(alpn, ",")
}
}
switch network {
case "http":
headers := make(map[string]any)
httpOpts := make(map[string]any)
if host, ok := values["host"]; ok && host != "" {
headers["Host"] = []string{host.(string)}
if host, ok := values["host"].(string); ok && host != "" {
headers["Host"] = []string{host}
}
httpOpts["path"] = []string{"/"}
if path, ok := values["path"]; ok && path != "" {
httpOpts["path"] = []string{path.(string)}
if path, ok := values["path"].(string); ok && path != "" {
httpOpts["path"] = []string{path}
}
httpOpts["headers"] = headers
@@ -237,8 +329,8 @@ func ConvertsV2Ray(buf []byte) ([]map[string]any, error) {
case "h2":
headers := make(map[string]any)
h2Opts := make(map[string]any)
if host, ok := values["host"]; ok && host != "" {
headers["Host"] = []string{host.(string)}
if host, ok := values["host"].(string); ok && host != "" {
headers["Host"] = []string{host}
}
h2Opts["path"] = values["path"]
@@ -246,15 +338,38 @@ func ConvertsV2Ray(buf []byte) ([]map[string]any, error) {
vmess["h2-opts"] = h2Opts
case "ws":
case "ws", "httpupgrade":
headers := make(map[string]any)
wsOpts := make(map[string]any)
wsOpts["path"] = []string{"/"}
if host, ok := values["host"]; ok && host != "" {
headers["Host"] = host.(string)
wsOpts["path"] = "/"
if host, ok := values["host"].(string); ok && host != "" {
headers["Host"] = host
}
if path, ok := values["path"]; ok && path != "" {
wsOpts["path"] = path.(string)
if path, ok := values["path"].(string); ok && path != "" {
path := path
pathURL, err := url.Parse(path)
if err == nil {
query := pathURL.Query()
if earlyData := query.Get("ed"); earlyData != "" {
med, err := strconv.Atoi(earlyData)
if err == nil {
switch network {
case "ws":
wsOpts["max-early-data"] = med
wsOpts["early-data-header-name"] = "Sec-WebSocket-Protocol"
case "httpupgrade":
wsOpts["v2ray-http-upgrade-fast-open"] = true
}
query.Del("ed")
pathURL.RawQuery = query.Encode()
path = pathURL.String()
}
}
if earlyDataHeader := query.Get("eh"); earlyDataHeader != "" {
wsOpts["early-data-header-name"] = earlyDataHeader
}
}
wsOpts["path"] = path
}
wsOpts["headers"] = headers
vmess["ws-opts"] = wsOpts
@@ -323,22 +438,36 @@ func ConvertsV2Ray(buf []byte) ([]map[string]any, error) {
if query.Get("udp-over-tcp") == "true" || query.Get("uot") == "1" {
ss["udp-over-tcp"] = true
}
if strings.Contains(query.Get("plugin"), "obfs") {
obfsParams := strings.Split(query.Get("plugin"), ";")
ss["plugin"] = "obfs"
ss["plugin-opts"] = map[string]any{
"host": obfsParams[2][10:],
"mode": obfsParams[1][5:],
plugin := query.Get("plugin")
if strings.Contains(plugin, ";") {
pluginInfo, _ := url.ParseQuery("pluginName=" + strings.ReplaceAll(plugin, ";", "&"))
pluginName := pluginInfo.Get("pluginName")
if strings.Contains(pluginName, "obfs") {
ss["plugin"] = "obfs"
ss["plugin-opts"] = map[string]any{
"mode": pluginInfo.Get("obfs"),
"host": pluginInfo.Get("obfs-host"),
}
} else if strings.Contains(pluginName, "v2ray-plugin") {
ss["plugin"] = "v2ray-plugin"
ss["plugin-opts"] = map[string]any{
"mode": pluginInfo.Get("mode"),
"host": pluginInfo.Get("host"),
"path": pluginInfo.Get("path"),
"tls": strings.Contains(plugin, "tls"),
}
}
}
proxies = append(proxies, ss)
case "ssr":
dcBuf, err := encRaw.DecodeString(body)
dcBuf, err := TryDecodeBase64(body)
if err != nil {
continue
}
// ssr://host:port:protocol:method:obfs:urlsafebase64pass/?obfsparam=urlsafebase64&protoparam=&remarks=urlsafebase64&group=urlsafebase64&udpport=0&uot=1
// ssr://host:port:protocol:method:obfs:urlsafebase64pass/?obfsparam=urlsafebase64param&protoparam=urlsafebase64param&remarks=urlsafebase64remarks&group=urlsafebase64group&udpport=0&uot=1
before, after, ok := strings.Cut(string(dcBuf), "/?")
if !ok {
@@ -367,7 +496,7 @@ func ConvertsV2Ray(buf []byte) ([]map[string]any, error) {
name := uniqueName(names, remarks)
obfsParam := decodeUrlSafe(query.Get("obfsparam"))
protocolParam := query.Get("protoparam")
protocolParam := decodeUrlSafe(query.Get("protoparam"))
ssr := make(map[string]any, 20)
@@ -390,6 +519,101 @@ func ConvertsV2Ray(buf []byte) ([]map[string]any, error) {
}
proxies = append(proxies, ssr)
case "socks", "socks5", "socks5h", "http", "https":
link, err := url.Parse(line)
if err != nil {
continue
}
server := link.Hostname()
if server == "" {
continue
}
portStr := link.Port()
if portStr == "" {
continue
}
remarks := link.Fragment
if remarks == "" {
remarks = fmt.Sprintf("%s:%s", server, portStr)
}
name := uniqueName(names, remarks)
encodeStr := link.User.String()
var username, password string
if encodeStr != "" {
decodeStr := string(DecodeBase64([]byte(encodeStr)))
splitStr := strings.Split(decodeStr, ":")
// todo: should use url.QueryUnescape ?
username = splitStr[0]
if len(splitStr) == 2 {
password = splitStr[1]
}
}
socks := make(map[string]any, 10)
socks["name"] = name
socks["type"] = func() string {
switch scheme {
case "socks", "socks5", "socks5h":
return "socks5"
case "http", "https":
return "http"
}
return scheme
}()
socks["server"] = server
socks["port"] = portStr
socks["username"] = username
socks["password"] = password
socks["skip-cert-verify"] = true
if scheme == "https" {
socks["tls"] = true
}
proxies = append(proxies, socks)
case "anytls":
// https://github.com/anytls/anytls-go/blob/main/docs/uri_scheme.md
link, err := url.Parse(line)
if err != nil {
continue
}
username := link.User.Username()
password, exist := link.User.Password()
if !exist {
password = username
}
query := link.Query()
server := link.Hostname()
if server == "" {
continue
}
portStr := link.Port()
if portStr == "" {
continue
}
insecure, sni := query.Get("insecure"), query.Get("sni")
insecureBool := insecure == "1"
fingerprint := query.Get("hpkp")
remarks := link.Fragment
if remarks == "" {
remarks = fmt.Sprintf("%s:%s", server, portStr)
}
name := uniqueName(names, remarks)
anytls := make(map[string]any, 10)
anytls["name"] = name
anytls["type"] = "anytls"
anytls["server"] = server
anytls["port"] = portStr
anytls["username"] = username
anytls["password"] = password
anytls["sni"] = sni
anytls["fingerprint"] = fingerprint
anytls["skip-cert-verify"] = insecureBool
anytls["udp"] = true
proxies = append(proxies, anytls)
}
}

View File

@@ -0,0 +1,35 @@
package convert
import (
"testing"
"github.com/stretchr/testify/assert"
)
// https://v2.hysteria.network/zh/docs/developers/URI-Scheme/
func TestConvertsV2Ray_normal(t *testing.T) {
hy2test := "hysteria2://letmein@example.com:8443/?insecure=1&obfs=salamander&obfs-password=gawrgura&pinSHA256=deadbeef&sni=real.example.com&up=114&down=514&alpn=h3,h4#hy2test"
expected := []map[string]interface{}{
{
"name": "hy2test",
"type": "hysteria2",
"server": "example.com",
"port": "8443",
"sni": "real.example.com",
"obfs": "salamander",
"obfs-password": "gawrgura",
"alpn": []string{"h3", "h4"},
"password": "letmein",
"up": "114",
"down": "514",
"skip-cert-verify": true,
"fingerprint": "deadbeef",
},
}
proxies, err := ConvertsV2Ray([]byte(hy2test))
assert.Nil(t, err)
assert.Equal(t, expected, proxies)
}

View File

@@ -6,10 +6,10 @@ import (
"strings"
"time"
"github.com/Dreamacro/clash/common/utils"
"github.com/metacubex/mihomo/common/utils"
"github.com/metacubex/randv2"
"github.com/metacubex/sing-shadowsocks/shadowimpl"
"github.com/zhangyunhao116/fastrand"
)
var hostsSuffix = []string{
@@ -302,11 +302,11 @@ func RandHost() string {
prefix += string(buf[6:8]) + "-"
prefix += string(buf[len(buf)-8:])
return prefix + hostsSuffix[fastrand.Intn(hostsLen)]
return prefix + hostsSuffix[randv2.IntN(hostsLen)]
}
func RandUserAgent() string {
return userAgents[fastrand.Intn(uaLen)]
return userAgents[randv2.IntN(uaLen)]
}
func SetUserAgent(header http.Header) {

View File

@@ -24,8 +24,6 @@ func handleVShareLink(names map[string]int, url *url.URL, scheme string, proxy m
proxy["port"] = url.Port()
proxy["uuid"] = url.User.Username()
proxy["udp"] = true
proxy["skip-cert-verify"] = false
proxy["tls"] = false
tls := strings.ToLower(query.Get("security"))
if strings.HasSuffix(tls, "tls") || tls == "reality" {
proxy["tls"] = true
@@ -34,6 +32,9 @@ func handleVShareLink(names map[string]int, url *url.URL, scheme string, proxy m
} else {
proxy["client-fingerprint"] = fingerprint
}
if alpn := query.Get("alpn"); alpn != "" {
proxy["alpn"] = strings.Split(alpn, ",")
}
}
if sni := query.Get("sni"); sni != "" {
proxy["servername"] = sni
@@ -99,7 +100,7 @@ func handleVShareLink(names map[string]int, url *url.URL, scheme string, proxy m
h2Opts["headers"] = headers
proxy["h2-opts"] = h2Opts
case "ws":
case "ws", "httpupgrade":
headers := make(map[string]any)
wsOpts := make(map[string]any)
headers["User-Agent"] = RandUserAgent()
@@ -112,7 +113,13 @@ func handleVShareLink(names map[string]int, url *url.URL, scheme string, proxy m
if err != nil {
return fmt.Errorf("bad WebSocket max early data size: %v", err)
}
wsOpts["max-early-data"] = med
switch network {
case "ws":
wsOpts["max-early-data"] = med
wsOpts["early-data-header-name"] = "Sec-WebSocket-Protocol"
case "httpupgrade":
wsOpts["v2ray-http-upgrade-fast-open"] = true
}
}
if earlyDataHeader := query.Get("eh"); earlyDataHeader != "" {
wsOpts["early-data-header-name"] = earlyDataHeader

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
@@ -6,7 +6,8 @@ import (
"sync"
"time"
"github.com/Dreamacro/clash/common/generics/list"
list "github.com/bahlo/generic-list-go"
"github.com/samber/lo"
)
// Option is part of Functional Options Pattern
@@ -67,10 +68,8 @@ type LruCache[K comparable, V any] struct {
// New creates an LruCache
func New[K comparable, V any](options ...Option[K, V]) *LruCache[K, V] {
lc := &LruCache[K, V]{
lru: list.New[*entry[K, V]](),
cache: make(map[K]*list.Element[*entry[K, V]]),
}
lc := &LruCache[K, V]{}
lc.Clear()
for _, option := range options {
option(lc)
@@ -79,26 +78,55 @@ func New[K comparable, V any](options ...Option[K, V]) *LruCache[K, V] {
return lc
}
// Get returns the any representation of a cached response and a bool
func (c *LruCache[K, V]) Clear() {
c.mu.Lock()
defer c.mu.Unlock()
c.lru = list.New[*entry[K, V]]()
c.cache = make(map[K]*list.Element[*entry[K, V]])
}
// Get returns any representation of a cached response and a bool
// set to true if the key was found.
func (c *LruCache[K, V]) Get(key K) (V, bool) {
c.mu.Lock()
defer c.mu.Unlock()
el := c.get(key)
if el == nil {
return getZero[V](), false
return lo.Empty[V](), false
}
value := el.value
return value, true
}
// GetWithExpire returns the any representation of a cached response,
func (c *LruCache[K, V]) GetOrStore(key K, constructor func() V) (V, bool) {
c.mu.Lock()
defer c.mu.Unlock()
el := c.get(key)
if el == nil {
value := constructor()
c.set(key, value)
return value, false
}
value := el.value
return value, true
}
// 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 (c *LruCache[K, V]) GetWithExpire(key K) (V, time.Time, bool) {
c.mu.Lock()
defer c.mu.Unlock()
el := c.get(key)
if el == nil {
return getZero[V](), time.Time{}, false
return lo.Empty[V](), time.Time{}, false
}
return el.value, time.Unix(el.expires, 0), true
@@ -113,21 +141,32 @@ func (c *LruCache[K, V]) Exist(key K) bool {
return ok
}
// Set stores the any representation of a response for a given key.
// Set stores any representation of a response for a given key.
func (c *LruCache[K, V]) Set(key K, value V) {
c.mu.Lock()
defer c.mu.Unlock()
c.set(key, value)
}
func (c *LruCache[K, V]) set(key K, value V) {
expires := int64(0)
if c.maxAge > 0 {
expires = time.Now().Unix() + c.maxAge
}
c.SetWithExpire(key, value, time.Unix(expires, 0))
c.setWithExpire(key, value, time.Unix(expires, 0))
}
// SetWithExpire stores the any representation of a response for a given key and given expires.
// SetWithExpire stores any representation of a response for a given key and given expires.
// The expires time will round to second.
func (c *LruCache[K, V]) SetWithExpire(key K, value V, expires time.Time) {
c.mu.Lock()
defer c.mu.Unlock()
c.setWithExpire(key, value, expires)
}
func (c *LruCache[K, V]) setWithExpire(key K, value V, expires time.Time) {
if le, ok := c.cache[key]; ok {
c.lru.MoveToBack(le)
e := le.Value
@@ -165,9 +204,6 @@ func (c *LruCache[K, V]) CloneTo(n *LruCache[K, V]) {
}
func (c *LruCache[K, V]) get(key K) *entry[K, V] {
c.mu.Lock()
defer c.mu.Unlock()
le, ok := c.cache[key]
if !ok {
return nil
@@ -191,12 +227,15 @@ func (c *LruCache[K, V]) get(key K) *entry[K, V] {
// Delete removes the value associated with a key.
func (c *LruCache[K, V]) Delete(key K) {
c.mu.Lock()
defer c.mu.Unlock()
c.delete(key)
}
func (c *LruCache[K, V]) delete(key K) {
if le, ok := c.cache[key]; ok {
c.deleteElement(le)
}
c.mu.Unlock()
}
func (c *LruCache[K, V]) maybeDeleteOldest() {
@@ -217,13 +256,32 @@ func (c *LruCache[K, V]) deleteElement(le *list.Element[*entry[K, V]]) {
}
}
func (c *LruCache[K, V]) Clear() error {
// Compute either sets the computed new value for the key or deletes
// the value for the key. When the delete result of the valueFn function
// is set to true, the value will be deleted, if it exists. When delete
// is set to false, the value is updated to the newValue.
// The ok result indicates whether value was computed and stored, thus, is
// present in the map. The actual result contains the new value in cases where
// the value was computed and stored.
func (c *LruCache[K, V]) Compute(
key K,
valueFn func(oldValue V, loaded bool) (newValue V, delete bool),
) (actual V, ok bool) {
c.mu.Lock()
defer c.mu.Unlock()
c.cache = make(map[K]*list.Element[*entry[K, V]])
c.mu.Unlock()
return nil
if el := c.get(key); el != nil {
actual, ok = el.value, true
}
if newValue, del := valueFn(actual, ok); del {
if ok { // data not in cache, so needn't delete
c.delete(key)
}
return lo.Empty[V](), false
} else {
c.set(key, newValue)
return newValue, true
}
}
type entry[K comparable, V any] struct {
@@ -231,8 +289,3 @@ type entry[K comparable, V any] struct {
value V
expires int64
}
func getZero[T any]() T {
var result T
return result
}

View File

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

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