mirror of
https://github.com/MetaCubeX/mihomo.git
synced 2026-03-01 18:30:09 +00:00
Compare commits
456 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
11000dccd7 | ||
|
|
0818aa54aa | ||
|
|
edbfebeacd | ||
|
|
06f5fbac06 | ||
|
|
f38fc2020f | ||
|
|
97bce45eba | ||
|
|
bc28cd486a | ||
|
|
cdabd1e8b1 | ||
|
|
c5b0f00bb2 | ||
|
|
c128d23dec | ||
|
|
ee37a353d0 | ||
|
|
0cf37de1a8 | ||
|
|
ae6069c178 | ||
|
|
c8e33a4347 | ||
|
|
19a6b5d6f7 | ||
|
|
efb800866e | ||
|
|
94c8d60f72 | ||
|
|
0f2baca2de | ||
|
|
b18a33552c | ||
|
|
487de9b548 | ||
|
|
1a6230ec03 | ||
|
|
e6bf56b9af | ||
|
|
0ad9ac325a | ||
|
|
d6b1263236 | ||
|
|
4d7670339b | ||
|
|
0cffc8d76d | ||
|
|
1f8bee9710 | ||
|
|
eb30d3f331 | ||
|
|
10f4bebdfa | ||
|
|
06387d5045 | ||
|
|
c393e917eb | ||
|
|
4f0a6fa117 | ||
|
|
4f9bfd216f | ||
|
|
498f81aad3 | ||
|
|
9168bee6b7 | ||
|
|
e6c0e3b19c | ||
|
|
287f9e5185 | ||
|
|
c456370f4f | ||
|
|
10ef29f5cd | ||
|
|
85ba7f6a0a | ||
|
|
7daf37bc15 | ||
|
|
64015b7634 | ||
|
|
5585304d68 | ||
|
|
911211578c | ||
|
|
abb55199f2 | ||
|
|
87c3f700e5 | ||
|
|
4a723e8d3f | ||
|
|
93cf46e430 | ||
|
|
35a1130c92 | ||
|
|
1ebcb25e4a | ||
|
|
cbcacdbb8c | ||
|
|
17966b5418 | ||
|
|
bc8f0dcf77 | ||
|
|
827cd616e8 | ||
|
|
e1384e86ab | ||
|
|
b92b38701c | ||
|
|
1cab34d257 | ||
|
|
a06097c2c4 | ||
|
|
bc9db11cb4 | ||
|
|
69e301820c | ||
|
|
e7a04e0762 | ||
|
|
7e8c2876fb | ||
|
|
936ebc7718 | ||
|
|
b753a57e6a | ||
|
|
dd99bfc892 | ||
|
|
2a1b3b2aed | ||
|
|
2211789a7c | ||
|
|
e652e277a7 | ||
|
|
40863d248d | ||
|
|
17b8eb8772 | ||
|
|
6b40072bc5 | ||
|
|
f44aa22d50 | ||
|
|
c33d9ad857 | ||
|
|
25041b599e | ||
|
|
6539b509cb | ||
|
|
d2007fdc22 | ||
|
|
b5fa3ee99a | ||
|
|
91f5593f4e | ||
|
|
90470ac304 | ||
|
|
b509affe5b | ||
|
|
32ce513977 | ||
|
|
30891f8781 | ||
|
|
e4cdb9b600 | ||
|
|
d33dbbe2f9 | ||
|
|
d8dcaa7500 | ||
|
|
9df8392c65 | ||
|
|
fdb7cb1f58 | ||
|
|
7cd58fbdf6 | ||
|
|
bc719eb96d | ||
|
|
ac90543548 | ||
|
|
9a5e506f66 | ||
|
|
a001b1b110 | ||
|
|
d1f89fa05e | ||
|
|
e2796e2d5c | ||
|
|
93de49d20c | ||
|
|
4d3167ff2f | ||
|
|
5998956a72 | ||
|
|
6cf1743961 | ||
|
|
8b6ba22b90 | ||
|
|
7571c87afb | ||
|
|
d4d2c062a3 | ||
|
|
438d4138d6 | ||
|
|
140d892ccf | ||
|
|
5aa140c493 | ||
|
|
c107c6a824 | ||
|
|
f6e494e73f | ||
|
|
0b3159bf9b | ||
|
|
45fd628788 | ||
|
|
2f545ef634 | ||
|
|
054e63cb3f | ||
|
|
d48bcf1e1e | ||
|
|
0df2f79ece | ||
|
|
644c04fdc9 | ||
|
|
926aaec717 | ||
|
|
a4b76809ac | ||
|
|
ff76576cbe | ||
|
|
1d5890abc1 | ||
|
|
a3c023ae3e | ||
|
|
8b32c4371e | ||
|
|
5a285acd32 | ||
|
|
c2209d68f7 | ||
|
|
fd39c2a7fc | ||
|
|
421dc79aea | ||
|
|
27b47f976c | ||
|
|
c25a38898f | ||
|
|
f3edbc2b45 | ||
|
|
6fb1f796a5 | ||
|
|
99e68e9983 | ||
|
|
6bffbdd9d3 | ||
|
|
cfdaebe952 | ||
|
|
c8af92a01f | ||
|
|
ff62386f6b | ||
|
|
85c56e7446 | ||
|
|
9ed9c3d1c3 | ||
|
|
dcfe664a7d | ||
|
|
fb1ae21fb7 | ||
|
|
90f47a6d0c | ||
|
|
f2bf4a077e | ||
|
|
8701639347 | ||
|
|
5bc0ac7281 | ||
|
|
c5fe3670ef | ||
|
|
de2ff37f4f | ||
|
|
da69b192f2 | ||
|
|
c13549f564 | ||
|
|
ce168c0e67 | ||
|
|
d225625378 | ||
|
|
40e0813869 | ||
|
|
94b591ed44 | ||
|
|
f7bd8b83e5 | ||
|
|
f45c6f5e91 | ||
|
|
3b15cbd9eb | ||
|
|
6f4da5f1fb | ||
|
|
fdc46f0471 | ||
|
|
57b527d54a | ||
|
|
29eaa4d699 | ||
|
|
92ecdfcc00 | ||
|
|
0992ee8adf | ||
|
|
0c25831726 | ||
|
|
9cc208bd47 | ||
|
|
b57f3052a8 | ||
|
|
3a1caf1316 | ||
|
|
1b1f95aa9c | ||
|
|
8a9300d44e | ||
|
|
abe6c3bb35 | ||
|
|
e28c8e6a51 | ||
|
|
74a86f147b | ||
|
|
7917f24f42 | ||
|
|
0dc5e3051d | ||
|
|
40b2cde2b2 | ||
|
|
6786705212 | ||
|
|
00638f30a7 | ||
|
|
2222d0e3fa | ||
|
|
f3ebd5c489 | ||
|
|
74e64d31e3 | ||
|
|
0c556bcaf3 | ||
|
|
7e71d21ab4 | ||
|
|
30bead4e2e | ||
|
|
16836ea465 | ||
|
|
d6f1af5372 | ||
|
|
26335f8ecc | ||
|
|
c4449a9f62 | ||
|
|
5e17d6fe01 | ||
|
|
8cdfd87d1e | ||
|
|
f02766a765 | ||
|
|
cea29e2615 | ||
|
|
a0f1ac4ef5 | ||
|
|
57e14e5b62 | ||
|
|
08fc100c85 | ||
|
|
571be856ea | ||
|
|
ad69ee84a9 | ||
|
|
dd7b3c28ad | ||
|
|
1b99759eaf | ||
|
|
a8f7e25851 | ||
|
|
909729ca8f | ||
|
|
6c527f8d20 | ||
|
|
7061c5a8ea | ||
|
|
23448ec119 | ||
|
|
318b3524c3 | ||
|
|
1d09ed82f1 | ||
|
|
b27325eed0 | ||
|
|
0d3d31dc5f | ||
|
|
108bf645fa | ||
|
|
50e1afd963 | ||
|
|
0336d64e52 | ||
|
|
02d954bfa8 | ||
|
|
9a124a390f | ||
|
|
fed4b369a3 | ||
|
|
f8ee5c1e15 | ||
|
|
8eba1c8afd | ||
|
|
65d3920f02 | ||
|
|
7e9e12cc4c | ||
|
|
80a90f05f3 | ||
|
|
3b63fef2eb | ||
|
|
29872007b3 | ||
|
|
33cde6592e | ||
|
|
c98f5f44b7 | ||
|
|
d094075c88 | ||
|
|
4188277b61 | ||
|
|
545d9b844d | ||
|
|
472cefb6d7 | ||
|
|
ccff0035cb | ||
|
|
455f2136f1 | ||
|
|
63781b3a6a | ||
|
|
c6e596f33f | ||
|
|
45cb45accb | ||
|
|
5c73025b53 | ||
|
|
cdd02a90c3 | ||
|
|
0ced98da4d | ||
|
|
84086a6e6c | ||
|
|
443200a51e | ||
|
|
aca0d97beb | ||
|
|
2605bf78f9 | ||
|
|
d2395fb43a | ||
|
|
e3d9a8e2fd | ||
|
|
1ae050ca3b | ||
|
|
7f38763e22 | ||
|
|
2a8831b0d0 | ||
|
|
cdf5e0c73e | ||
|
|
48f3ea8bc9 | ||
|
|
375e160368 | ||
|
|
b31664beeb | ||
|
|
7960bcae15 | ||
|
|
664ddb8d55 | ||
|
|
e4dfe09744 | ||
|
|
b56068ee1c | ||
|
|
99e888c829 | ||
|
|
873d0deeaa | ||
|
|
7e0a77c99c | ||
|
|
5f09db2655 | ||
|
|
10174d281c | ||
|
|
12c30acdda | ||
|
|
2790481709 | ||
|
|
182f60d424 | ||
|
|
930c70f065 | ||
|
|
fc61715e4e | ||
|
|
438be2d379 | ||
|
|
4e20ed65f2 | ||
|
|
ce760fcf19 | ||
|
|
0f76fdf4c5 | ||
|
|
03f4513f61 | ||
|
|
26f603057f | ||
|
|
b481eca4a4 | ||
|
|
eb028b65fc | ||
|
|
48c1b1cdb2 | ||
|
|
76e40baebc | ||
|
|
946b4025df | ||
|
|
089766b285 | ||
|
|
b643388539 | ||
|
|
0836ec6ee3 | ||
|
|
eeb2ad8dae | ||
|
|
71290b057f | ||
|
|
41b321dfe1 | ||
|
|
a18e99f966 | ||
|
|
f90d0b954c | ||
|
|
0408da2aee | ||
|
|
335d54e488 | ||
|
|
d11f9c895c | ||
|
|
e54ca7ceca | ||
|
|
ce82d49c25 | ||
|
|
8e6be1992b | ||
|
|
0e9102daae | ||
|
|
46dccf26d1 | ||
|
|
854c6a13c3 | ||
|
|
b4c3bbf660 | ||
|
|
6c726d6436 | ||
|
|
a0bdb861a9 | ||
|
|
eca5a27774 | ||
|
|
16d95df100 | ||
|
|
9b90719ddd | ||
|
|
7392529677 | ||
|
|
dc52c38179 | ||
|
|
d7999a32d3 | ||
|
|
b41ea05481 | ||
|
|
e6fe895190 | ||
|
|
adf553a958 | ||
|
|
2a915a5c94 | ||
|
|
1b0c72bfab | ||
|
|
e89af723cd | ||
|
|
e8fddd85af | ||
|
|
f04af734e3 | ||
|
|
00035302a1 | ||
|
|
578e659bb9 | ||
|
|
0f1baeb935 | ||
|
|
16ff9e815b | ||
|
|
5f1f296213 | ||
|
|
66fd5c9f0c | ||
|
|
c3a3009a8c | ||
|
|
01cd7e2c0e | ||
|
|
deec7aafe5 | ||
|
|
a9b7e705f0 | ||
|
|
fb043df1b6 | ||
|
|
748b5df902 | ||
|
|
8cbae59d55 | ||
|
|
a37440c81b | ||
|
|
dbb002a5ba | ||
|
|
1a84153213 | ||
|
|
dfe6e0509b | ||
|
|
b6dde7ded7 | ||
|
|
9f1da11792 | ||
|
|
63ad95e10f | ||
|
|
b06ec5bef8 | ||
|
|
d4fbffd8e8 | ||
|
|
305020175d | ||
|
|
79decdc253 | ||
|
|
407c13b8a4 | ||
|
|
d84b182be3 | ||
|
|
8f18d3f6db | ||
|
|
b9260e06b8 | ||
|
|
6337151207 | ||
|
|
aa555ced5f | ||
|
|
349b773b40 | ||
|
|
300eb8b12a | ||
|
|
2b84dd3618 | ||
|
|
6a620ba287 | ||
|
|
56c3462b76 | ||
|
|
6f4fe71e41 | ||
|
|
ba3e7187a6 | ||
|
|
0d92b6724b | ||
|
|
241ae92bce | ||
|
|
91985c1ef8 | ||
|
|
6a9d428991 | ||
|
|
765cbbcc01 | ||
|
|
5b975275f5 | ||
|
|
166392fe17 | ||
|
|
5c6aa433ca | ||
|
|
2c55dc2557 | ||
|
|
56c0b088e8 | ||
|
|
5344e869a8 | ||
|
|
6cfaf15cbf | ||
|
|
31f0060b30 | ||
|
|
c60750d549 | ||
|
|
ebf5918e94 | ||
|
|
93ca18517c | ||
|
|
32d447ce99 | ||
|
|
617fef84ae | ||
|
|
d19199322d | ||
|
|
87795e3a07 | ||
|
|
85bb40aaf8 | ||
|
|
082bcec281 | ||
|
|
9283cb0f5f | ||
|
|
ae7967f662 | ||
|
|
01f8f2db2f | ||
|
|
255ff5e977 | ||
|
|
939e4109d7 | ||
|
|
40587b62b8 | ||
|
|
85e6d25de5 | ||
|
|
29a37f4f4b | ||
|
|
2f9a3b3469 | ||
|
|
40ea0ba098 | ||
|
|
8d7f947a80 | ||
|
|
71a8705636 | ||
|
|
c0f452b540 | ||
|
|
6c9abe16cc | ||
|
|
213d80c1e2 | ||
|
|
1db89da122 | ||
|
|
689c58f661 | ||
|
|
33590c4066 | ||
|
|
60ae9dce56 | ||
|
|
4741ac6702 | ||
|
|
ef3d7e4dd7 | ||
|
|
a1c7881229 | ||
|
|
12e3952b74 | ||
|
|
88419cbd12 | ||
|
|
4ed830330e | ||
|
|
3ed6ff9402 | ||
|
|
34de62d21d | ||
|
|
d2e255f257 | ||
|
|
a0c46bb4b7 | ||
|
|
9e3bf14b1a | ||
|
|
28c387a9b6 | ||
|
|
15eda703b4 | ||
|
|
b1d12a15db | ||
|
|
5a21bf3642 | ||
|
|
199fb8fd5d | ||
|
|
fd959feff2 | ||
|
|
d5a03901d2 | ||
|
|
257fead538 | ||
|
|
c489c5260b | ||
|
|
8f92b1de13 | ||
|
|
9f7a2a36c1 | ||
|
|
a93479124c | ||
|
|
ed42c4feb8 | ||
|
|
608ddb1b44 | ||
|
|
d036d98128 | ||
|
|
d900c71214 | ||
|
|
1672750c47 | ||
|
|
41b57afb3f | ||
|
|
188372cb04 | ||
|
|
a1350d4985 | ||
|
|
dc958e6a39 | ||
|
|
8a5f3b8909 | ||
|
|
c6d7ef8cb8 | ||
|
|
bb8c47d83d | ||
|
|
5cf0f18c29 | ||
|
|
83213d493e | ||
|
|
90ed01ed53 | ||
|
|
f91a586da8 | ||
|
|
266fb03838 | ||
|
|
76e9607fd7 | ||
|
|
23e2d3a132 | ||
|
|
6e35cf9399 | ||
|
|
2116640886 | ||
|
|
a4fcd3af07 | ||
|
|
d22a893060 | ||
|
|
00cceba890 | ||
|
|
2b4726b9ad | ||
|
|
26e6d83f8b | ||
|
|
50d7834e09 | ||
|
|
86c127db8b | ||
|
|
febb6021aa | ||
|
|
9e57b298bf | ||
|
|
791ea5e568 | ||
|
|
7e7016b567 | ||
|
|
b4fe669848 | ||
|
|
cad26ac6a8 | ||
|
|
f328203bc1 | ||
|
|
5c40a6340c | ||
|
|
61d6a9abd6 | ||
|
|
a013ac32a3 | ||
|
|
ee5d77cfd1 | ||
|
|
936df90ace | ||
|
|
f774276896 | ||
|
|
aa51b9faba | ||
|
|
d55b047125 | ||
|
|
efc7abc6e0 | ||
|
|
c2301f66a4 | ||
|
|
468cfc3cc4 | ||
|
|
5dce957755 | ||
|
|
4ecb49b3b9 | ||
|
|
7de4af28d2 | ||
|
|
48d8efb3e9 | ||
|
|
e6e7aa5ae2 | ||
|
|
99aa1b0de1 | ||
|
|
52ad793d11 | ||
|
|
2fb9331211 | ||
|
|
793ce45db0 |
182
.github/patch/go1.21.patch
vendored
Normal file
182
.github/patch/go1.21.patch
vendored
Normal 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
645
.github/patch/go1.22.patch
vendored
Normal 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
643
.github/patch/go1.23.patch
vendored
Normal 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
657
.github/patch/go1.24.patch
vendored
Normal 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)
|
||||
}
|
||||
884
.github/patch/go1.25.patch
vendored
Normal file
884
.github/patch/go1.25.patch
vendored
Normal file
@@ -0,0 +1,884 @@
|
||||
Subject: [PATCH] Revert "os: remove 5ms sleep on Windows in (*Process).Wait"
|
||||
Fix os.RemoveAll not working on Windows7
|
||||
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)
|
||||
}
|
||||
Index: src/os/removeall_at.go
|
||||
IDEA additional info:
|
||||
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
|
||||
<+>UTF-8
|
||||
===================================================================
|
||||
diff --git a/src/os/removeall_at.go b/src/os/removeall_at.go
|
||||
--- a/src/os/removeall_at.go (revision f56f1e23507e646c85243a71bde7b9629b2f970c)
|
||||
+++ b/src/os/removeall_at.go (revision 0a52622d2331ff975fb0442617ec19bc352bb2ed)
|
||||
@@ -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 unix || wasip1 || windows
|
||||
+//go:build unix || wasip1
|
||||
|
||||
package os
|
||||
|
||||
@@ -175,3 +175,25 @@
|
||||
}
|
||||
return newDirFile(fd, name)
|
||||
}
|
||||
+
|
||||
+func rootRemoveAll(r *Root, name string) error {
|
||||
+ // Consistency with os.RemoveAll: Strip trailing /s from the name,
|
||||
+ // so RemoveAll("not_a_directory/") succeeds.
|
||||
+ for len(name) > 0 && IsPathSeparator(name[len(name)-1]) {
|
||||
+ name = name[:len(name)-1]
|
||||
+ }
|
||||
+ if endsWithDot(name) {
|
||||
+ // Consistency with os.RemoveAll: Return EINVAL when trying to remove .
|
||||
+ return &PathError{Op: "RemoveAll", Path: name, Err: syscall.EINVAL}
|
||||
+ }
|
||||
+ _, err := doInRoot(r, name, nil, func(parent sysfdType, name string) (struct{}, error) {
|
||||
+ return struct{}{}, removeAllFrom(parent, name)
|
||||
+ })
|
||||
+ if IsNotExist(err) {
|
||||
+ return nil
|
||||
+ }
|
||||
+ if err != nil {
|
||||
+ return &PathError{Op: "RemoveAll", Path: name, Err: underlyingError(err)}
|
||||
+ }
|
||||
+ return err
|
||||
+}
|
||||
Index: src/os/removeall_noat.go
|
||||
IDEA additional info:
|
||||
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
|
||||
<+>UTF-8
|
||||
===================================================================
|
||||
diff --git a/src/os/removeall_noat.go b/src/os/removeall_noat.go
|
||||
--- a/src/os/removeall_noat.go (revision f56f1e23507e646c85243a71bde7b9629b2f970c)
|
||||
+++ b/src/os/removeall_noat.go (revision 0a52622d2331ff975fb0442617ec19bc352bb2ed)
|
||||
@@ -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 && wasm) || plan9
|
||||
+//go:build (js && wasm) || plan9 || windows
|
||||
|
||||
package os
|
||||
|
||||
@@ -140,3 +140,22 @@
|
||||
}
|
||||
return err
|
||||
}
|
||||
+
|
||||
+func rootRemoveAll(r *Root, name string) error {
|
||||
+ if endsWithDot(name) {
|
||||
+ // Consistency with os.RemoveAll: Return EINVAL when trying to remove .
|
||||
+ return &PathError{Op: "RemoveAll", Path: name, Err: syscall.EINVAL}
|
||||
+ }
|
||||
+ if err := checkPathEscapesLstat(r, name); err != nil {
|
||||
+ if err == syscall.ENOTDIR {
|
||||
+ // Some intermediate path component is not a directory.
|
||||
+ // RemoveAll treats this as success (since the target doesn't exist).
|
||||
+ return nil
|
||||
+ }
|
||||
+ return &PathError{Op: "RemoveAll", Path: name, Err: err}
|
||||
+ }
|
||||
+ if err := RemoveAll(joinPath(r.root.name, name)); err != nil {
|
||||
+ return &PathError{Op: "RemoveAll", Path: name, Err: underlyingError(err)}
|
||||
+ }
|
||||
+ return nil
|
||||
+}
|
||||
Index: src/os/root_noopenat.go
|
||||
IDEA additional info:
|
||||
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
|
||||
<+>UTF-8
|
||||
===================================================================
|
||||
diff --git a/src/os/root_noopenat.go b/src/os/root_noopenat.go
|
||||
--- a/src/os/root_noopenat.go (revision f56f1e23507e646c85243a71bde7b9629b2f970c)
|
||||
+++ b/src/os/root_noopenat.go (revision 0a52622d2331ff975fb0442617ec19bc352bb2ed)
|
||||
@@ -11,7 +11,6 @@
|
||||
"internal/filepathlite"
|
||||
"internal/stringslite"
|
||||
"sync/atomic"
|
||||
- "syscall"
|
||||
"time"
|
||||
)
|
||||
|
||||
@@ -185,25 +184,6 @@
|
||||
}
|
||||
return nil
|
||||
}
|
||||
-
|
||||
-func rootRemoveAll(r *Root, name string) error {
|
||||
- if endsWithDot(name) {
|
||||
- // Consistency with os.RemoveAll: Return EINVAL when trying to remove .
|
||||
- return &PathError{Op: "RemoveAll", Path: name, Err: syscall.EINVAL}
|
||||
- }
|
||||
- if err := checkPathEscapesLstat(r, name); err != nil {
|
||||
- if err == syscall.ENOTDIR {
|
||||
- // Some intermediate path component is not a directory.
|
||||
- // RemoveAll treats this as success (since the target doesn't exist).
|
||||
- return nil
|
||||
- }
|
||||
- return &PathError{Op: "RemoveAll", Path: name, Err: err}
|
||||
- }
|
||||
- if err := RemoveAll(joinPath(r.root.name, name)); err != nil {
|
||||
- return &PathError{Op: "RemoveAll", Path: name, Err: underlyingError(err)}
|
||||
- }
|
||||
- return nil
|
||||
-}
|
||||
|
||||
func rootReadlink(r *Root, name string) (string, error) {
|
||||
if err := checkPathEscapesLstat(r, name); err != nil {
|
||||
Index: src/os/root_openat.go
|
||||
IDEA additional info:
|
||||
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
|
||||
<+>UTF-8
|
||||
===================================================================
|
||||
diff --git a/src/os/root_openat.go b/src/os/root_openat.go
|
||||
--- a/src/os/root_openat.go (revision f56f1e23507e646c85243a71bde7b9629b2f970c)
|
||||
+++ b/src/os/root_openat.go (revision 0a52622d2331ff975fb0442617ec19bc352bb2ed)
|
||||
@@ -194,28 +194,6 @@
|
||||
return nil
|
||||
}
|
||||
|
||||
-func rootRemoveAll(r *Root, name string) error {
|
||||
- // Consistency with os.RemoveAll: Strip trailing /s from the name,
|
||||
- // so RemoveAll("not_a_directory/") succeeds.
|
||||
- for len(name) > 0 && IsPathSeparator(name[len(name)-1]) {
|
||||
- name = name[:len(name)-1]
|
||||
- }
|
||||
- if endsWithDot(name) {
|
||||
- // Consistency with os.RemoveAll: Return EINVAL when trying to remove .
|
||||
- return &PathError{Op: "RemoveAll", Path: name, Err: syscall.EINVAL}
|
||||
- }
|
||||
- _, err := doInRoot(r, name, nil, func(parent sysfdType, name string) (struct{}, error) {
|
||||
- return struct{}{}, removeAllFrom(parent, name)
|
||||
- })
|
||||
- if IsNotExist(err) {
|
||||
- return nil
|
||||
- }
|
||||
- if err != nil {
|
||||
- return &PathError{Op: "RemoveAll", Path: name, Err: underlyingError(err)}
|
||||
- }
|
||||
- return err
|
||||
-}
|
||||
-
|
||||
func rootRename(r *Root, oldname, newname string) error {
|
||||
_, err := doInRoot(r, oldname, nil, func(oldparent sysfdType, oldname string) (struct{}, error) {
|
||||
_, err := doInRoot(r, newname, nil, func(newparent sysfdType, newname string) (struct{}, error) {
|
||||
Index: src/os/root_windows.go
|
||||
IDEA additional info:
|
||||
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
|
||||
<+>UTF-8
|
||||
===================================================================
|
||||
diff --git a/src/os/root_windows.go b/src/os/root_windows.go
|
||||
--- a/src/os/root_windows.go (revision f56f1e23507e646c85243a71bde7b9629b2f970c)
|
||||
+++ b/src/os/root_windows.go (revision 0a52622d2331ff975fb0442617ec19bc352bb2ed)
|
||||
@@ -402,3 +402,14 @@
|
||||
}
|
||||
return fi.Mode(), nil
|
||||
}
|
||||
+
|
||||
+func checkPathEscapes(r *Root, name string) error {
|
||||
+ if !filepathlite.IsLocal(name) {
|
||||
+ return errPathEscapes
|
||||
+ }
|
||||
+ return nil
|
||||
+}
|
||||
+
|
||||
+func checkPathEscapesLstat(r *Root, name string) error {
|
||||
+ return checkPathEscapes(r, name)
|
||||
+}
|
||||
Index: src/os/exec_windows.go
|
||||
IDEA additional info:
|
||||
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
|
||||
<+>UTF-8
|
||||
===================================================================
|
||||
diff --git a/src/os/exec_windows.go b/src/os/exec_windows.go
|
||||
--- a/src/os/exec_windows.go (revision 0a52622d2331ff975fb0442617ec19bc352bb2ed)
|
||||
+++ b/src/os/exec_windows.go (revision fb3d09a67fe97008ad76fea97ae88170072cbdbb)
|
||||
@@ -10,6 +10,7 @@
|
||||
"runtime"
|
||||
"syscall"
|
||||
"time"
|
||||
+ _ "unsafe"
|
||||
)
|
||||
|
||||
// Note that Process.handle is never nil because Windows always requires
|
||||
@@ -49,9 +50,23 @@
|
||||
// than statusDone.
|
||||
p.doRelease(statusReleased)
|
||||
|
||||
+ var maj, min, build uint32
|
||||
+ rtlGetNtVersionNumbers(&maj, &min, &build)
|
||||
+ if maj < 10 {
|
||||
+ // NOTE(brainman): It seems that sometimes process is not dead
|
||||
+ // when WaitForSingleObject returns. But we do not know any
|
||||
+ // other way to wait for it. Sleeping for a while seems to do
|
||||
+ // the trick sometimes.
|
||||
+ // See https://golang.org/issue/25965 for details.
|
||||
+ time.Sleep(5 * time.Millisecond)
|
||||
+ }
|
||||
+
|
||||
return &ProcessState{p.Pid, syscall.WaitStatus{ExitCode: ec}, &u}, nil
|
||||
}
|
||||
|
||||
+//go:linkname rtlGetNtVersionNumbers syscall.rtlGetNtVersionNumbers
|
||||
+func rtlGetNtVersionNumbers(majorVersion *uint32, minorVersion *uint32, buildNumber *uint32)
|
||||
+
|
||||
func (p *Process) signal(sig Signal) error {
|
||||
handle, status := p.handleTransientAcquire()
|
||||
switch status {
|
||||
883
.github/patch/go1.26.patch
vendored
Normal file
883
.github/patch/go1.26.patch
vendored
Normal file
@@ -0,0 +1,883 @@
|
||||
Subject: [PATCH] Revert "os: remove 5ms sleep on Windows in (*Process).Wait"
|
||||
Fix os.RemoveAll not working on Windows7
|
||||
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 c599a8f2385849a225d02843b3c6389dbfc5aa69)
|
||||
+++ b/src/crypto/internal/sysrand/rand_windows.go (revision b0d48afabb9fd14976c27221cb525c5d2ebbfe79)
|
||||
@@ -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 c599a8f2385849a225d02843b3c6389dbfc5aa69)
|
||||
+++ b/src/crypto/rand/rand.go (revision b0d48afabb9fd14976c27221cb525c5d2ebbfe79)
|
||||
@@ -25,7 +25,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 c599a8f2385849a225d02843b3c6389dbfc5aa69)
|
||||
+++ b/src/internal/syscall/windows/syscall_windows.go (revision b0d48afabb9fd14976c27221cb525c5d2ebbfe79)
|
||||
@@ -421,7 +421,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 c599a8f2385849a225d02843b3c6389dbfc5aa69)
|
||||
+++ b/src/internal/syscall/windows/zsyscall_windows.go (revision b0d48afabb9fd14976c27221cb525c5d2ebbfe79)
|
||||
@@ -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")
|
||||
@@ -244,12 +243,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.SyscallN(procProcessPrng.Addr(), uintptr(unsafe.Pointer(_p0)), uintptr(len(buf)))
|
||||
+ r1, _, e1 := syscall.SyscallN(procSystemFunction036.Addr(), 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 c599a8f2385849a225d02843b3c6389dbfc5aa69)
|
||||
+++ b/src/runtime/os_windows.go (revision ea2726a6fa25fbfa1092e696e522eafca544d24c)
|
||||
@@ -40,7 +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"
|
||||
@@ -74,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,
|
||||
@@ -97,7 +97,8 @@
|
||||
_GetSystemInfo,
|
||||
_GetThreadContext,
|
||||
_SetThreadContext,
|
||||
- _LoadLibraryExW,
|
||||
+ _LoadLibraryW,
|
||||
+ _LoadLibraryA,
|
||||
_PostQueuedCompletionStatus,
|
||||
_QueryPerformanceCounter,
|
||||
_QueryPerformanceFrequency,
|
||||
@@ -126,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
|
||||
@@ -144,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)
|
||||
@@ -242,9 +251,40 @@
|
||||
return unsafe.String(&sysDirectory[0], sysDirectoryLen)
|
||||
}
|
||||
|
||||
-func windowsLoadSystemLib(name []uint16) uintptr {
|
||||
- const _LOAD_LIBRARY_SEARCH_SYSTEM32 = 0x00000800
|
||||
- return stdcall(_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 stdcall(_LoadLibraryExA, uintptr(unsafe.Pointer(&name[0])), 0, _LOAD_LIBRARY_SEARCH_SYSTEM32)
|
||||
+ } else {
|
||||
+ absName := append(sysDirectory[:sysDirectoryLen], name...)
|
||||
+ return stdcall(_LoadLibraryA, uintptr(unsafe.Pointer(&absName[0])))
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
+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, absoluteFilepath *uint16) (handle, err uintptr) {
|
||||
+ if useLoadLibraryEx {
|
||||
+ handle, _, err = syscall_syscalln(uintptr(unsafe.Pointer(_LoadLibraryExW)), 3, uintptr(unsafe.Pointer(filename)), 0, _LOAD_LIBRARY_SEARCH_SYSTEM32)
|
||||
+ } else {
|
||||
+ handle, _, err = syscall_syscalln(uintptr(unsafe.Pointer(_LoadLibraryW)), 1, uintptr(unsafe.Pointer(absoluteFilepath)))
|
||||
+ }
|
||||
+ KeepAlive(filename)
|
||||
+ KeepAlive(absoluteFilepath)
|
||||
+ if handle != 0 {
|
||||
+ err = 0
|
||||
+ }
|
||||
+ return
|
||||
}
|
||||
|
||||
//go:linkname windows_QueryPerformanceCounter internal/syscall/windows.QueryPerformanceCounter
|
||||
@@ -262,13 +302,28 @@
|
||||
}
|
||||
|
||||
func loadOptionalSyscalls() {
|
||||
- bcryptPrimitives := windowsLoadSystemLib(bcryptprimitivesdll[:])
|
||||
- if bcryptPrimitives == 0 {
|
||||
- throw("bcryptprimitives.dll not found")
|
||||
+ var kernel32dll = []byte("kernel32.dll\000")
|
||||
+ k32 := stdcall(_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")
|
||||
}
|
||||
@@ -297,7 +352,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.
|
||||
}
|
||||
@@ -351,6 +406,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
|
||||
@@ -417,7 +488,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")
|
||||
@@ -458,6 +530,28 @@
|
||||
canUseLongPaths = true
|
||||
}
|
||||
|
||||
+var osVersionInfo struct {
|
||||
+ majorVersion uint32
|
||||
+ minorVersion uint32
|
||||
+ buildNumber uint32
|
||||
+}
|
||||
+
|
||||
+func initOsVersionInfo() {
|
||||
+ info := windows.OSVERSIONINFOW{}
|
||||
+ info.OSVersionInfoSize = uint32(unsafe.Sizeof(info))
|
||||
+ stdcall(_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(windows.AsmStdCallAddr())
|
||||
|
||||
@@ -470,8 +564,8 @@
|
||||
initHighResTimer()
|
||||
timeBeginPeriodRetValue = osRelax(false)
|
||||
|
||||
- initSysDirectory()
|
||||
initLongPathSupport()
|
||||
+ initOsVersionInfo()
|
||||
|
||||
numCPUStartup = getCPUCount()
|
||||
|
||||
@@ -487,7 +581,7 @@
|
||||
//go:nosplit
|
||||
func readRandom(r []byte) int {
|
||||
n := 0
|
||||
- if stdcall(_ProcessPrng, uintptr(unsafe.Pointer(&r[0])), uintptr(len(r)))&0xff != 0 {
|
||||
+ if stdcall(_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 b0d48afabb9fd14976c27221cb525c5d2ebbfe79)
|
||||
+++ b/src/net/hook_windows.go (revision 44e76f7cf1bc6e04b5da724e0b2e48f393713506)
|
||||
@@ -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 b0d48afabb9fd14976c27221cb525c5d2ebbfe79)
|
||||
+++ b/src/net/internal/socktest/main_test.go (revision 44e76f7cf1bc6e04b5da724e0b2e48f393713506)
|
||||
@@ -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 44e76f7cf1bc6e04b5da724e0b2e48f393713506)
|
||||
+++ b/src/net/internal/socktest/main_windows_test.go (revision 44e76f7cf1bc6e04b5da724e0b2e48f393713506)
|
||||
@@ -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 b0d48afabb9fd14976c27221cb525c5d2ebbfe79)
|
||||
+++ b/src/net/internal/socktest/sys_windows.go (revision 44e76f7cf1bc6e04b5da724e0b2e48f393713506)
|
||||
@@ -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 b0d48afabb9fd14976c27221cb525c5d2ebbfe79)
|
||||
+++ b/src/net/main_windows_test.go (revision 44e76f7cf1bc6e04b5da724e0b2e48f393713506)
|
||||
@@ -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 b0d48afabb9fd14976c27221cb525c5d2ebbfe79)
|
||||
+++ b/src/net/sock_windows.go (revision 44e76f7cf1bc6e04b5da724e0b2e48f393713506)
|
||||
@@ -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 b0d48afabb9fd14976c27221cb525c5d2ebbfe79)
|
||||
+++ b/src/syscall/exec_windows.go (revision b4aece36e51ecce81c3ee9fe03e31db552e90018)
|
||||
@@ -15,7 +15,6 @@
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
-// ForkLock is not used on Windows.
|
||||
var ForkLock sync.RWMutex
|
||||
|
||||
// EscapeArg rewrites command line argument s as prescribed
|
||||
@@ -304,6 +303,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
|
||||
@@ -367,6 +369,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 {
|
||||
@@ -375,7 +388,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
|
||||
}
|
||||
@@ -406,6 +427,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/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 b4aece36e51ecce81c3ee9fe03e31db552e90018)
|
||||
+++ b/src/syscall/dll_windows.go (revision ea2726a6fa25fbfa1092e696e522eafca544d24c)
|
||||
@@ -119,14 +119,7 @@
|
||||
}
|
||||
|
||||
//go:linkname loadsystemlibrary
|
||||
-func loadsystemlibrary(filename *uint16) (uintptr, Errno) {
|
||||
- const _LOAD_LIBRARY_SEARCH_SYSTEM32 = 0x00000800
|
||||
- handle, _, err := SyscallN(uintptr(__LoadLibraryExW), uintptr(unsafe.Pointer(filename)), 0, _LOAD_LIBRARY_SEARCH_SYSTEM32)
|
||||
- if handle != 0 {
|
||||
- err = 0
|
||||
- }
|
||||
- return handle, err
|
||||
-}
|
||||
+func loadsystemlibrary(filename *uint16, absoluteFilepath *uint16) (handle uintptr, err Errno)
|
||||
|
||||
//go:linkname getprocaddress
|
||||
func getprocaddress(handle uintptr, procname *uint8) (uintptr, Errno) {
|
||||
@@ -143,6 +136,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
|
||||
@@ -159,7 +155,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)
|
||||
}
|
||||
Index: src/os/removeall_at.go
|
||||
IDEA additional info:
|
||||
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
|
||||
<+>UTF-8
|
||||
===================================================================
|
||||
diff --git a/src/os/removeall_at.go b/src/os/removeall_at.go
|
||||
--- a/src/os/removeall_at.go (revision ea2726a6fa25fbfa1092e696e522eafca544d24c)
|
||||
+++ b/src/os/removeall_at.go (revision d47e0d22130d597dcf9daa6b41fd9501274f0cb2)
|
||||
@@ -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 unix || wasip1 || windows
|
||||
+//go:build unix || wasip1
|
||||
|
||||
package os
|
||||
|
||||
@@ -175,3 +175,25 @@
|
||||
}
|
||||
return newDirFile(fd, name)
|
||||
}
|
||||
+
|
||||
+func rootRemoveAll(r *Root, name string) error {
|
||||
+ // Consistency with os.RemoveAll: Strip trailing /s from the name,
|
||||
+ // so RemoveAll("not_a_directory/") succeeds.
|
||||
+ for len(name) > 0 && IsPathSeparator(name[len(name)-1]) {
|
||||
+ name = name[:len(name)-1]
|
||||
+ }
|
||||
+ if endsWithDot(name) {
|
||||
+ // Consistency with os.RemoveAll: Return EINVAL when trying to remove .
|
||||
+ return &PathError{Op: "RemoveAll", Path: name, Err: syscall.EINVAL}
|
||||
+ }
|
||||
+ _, err := doInRoot(r, name, nil, func(parent sysfdType, name string) (struct{}, error) {
|
||||
+ return struct{}{}, removeAllFrom(parent, name)
|
||||
+ })
|
||||
+ if IsNotExist(err) {
|
||||
+ return nil
|
||||
+ }
|
||||
+ if err != nil {
|
||||
+ return &PathError{Op: "RemoveAll", Path: name, Err: underlyingError(err)}
|
||||
+ }
|
||||
+ return err
|
||||
+}
|
||||
Index: src/os/removeall_noat.go
|
||||
IDEA additional info:
|
||||
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
|
||||
<+>UTF-8
|
||||
===================================================================
|
||||
diff --git a/src/os/removeall_noat.go b/src/os/removeall_noat.go
|
||||
--- a/src/os/removeall_noat.go (revision ea2726a6fa25fbfa1092e696e522eafca544d24c)
|
||||
+++ b/src/os/removeall_noat.go (revision d47e0d22130d597dcf9daa6b41fd9501274f0cb2)
|
||||
@@ -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 && wasm) || plan9
|
||||
+//go:build (js && wasm) || plan9 || windows
|
||||
|
||||
package os
|
||||
|
||||
@@ -140,3 +140,22 @@
|
||||
}
|
||||
return err
|
||||
}
|
||||
+
|
||||
+func rootRemoveAll(r *Root, name string) error {
|
||||
+ if endsWithDot(name) {
|
||||
+ // Consistency with os.RemoveAll: Return EINVAL when trying to remove .
|
||||
+ return &PathError{Op: "RemoveAll", Path: name, Err: syscall.EINVAL}
|
||||
+ }
|
||||
+ if err := checkPathEscapesLstat(r, name); err != nil {
|
||||
+ if err == syscall.ENOTDIR {
|
||||
+ // Some intermediate path component is not a directory.
|
||||
+ // RemoveAll treats this as success (since the target doesn't exist).
|
||||
+ return nil
|
||||
+ }
|
||||
+ return &PathError{Op: "RemoveAll", Path: name, Err: err}
|
||||
+ }
|
||||
+ if err := RemoveAll(joinPath(r.root.name, name)); err != nil {
|
||||
+ return &PathError{Op: "RemoveAll", Path: name, Err: underlyingError(err)}
|
||||
+ }
|
||||
+ return nil
|
||||
+}
|
||||
Index: src/os/root_noopenat.go
|
||||
IDEA additional info:
|
||||
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
|
||||
<+>UTF-8
|
||||
===================================================================
|
||||
diff --git a/src/os/root_noopenat.go b/src/os/root_noopenat.go
|
||||
--- a/src/os/root_noopenat.go (revision ea2726a6fa25fbfa1092e696e522eafca544d24c)
|
||||
+++ b/src/os/root_noopenat.go (revision d47e0d22130d597dcf9daa6b41fd9501274f0cb2)
|
||||
@@ -11,7 +11,6 @@
|
||||
"internal/filepathlite"
|
||||
"internal/stringslite"
|
||||
"sync/atomic"
|
||||
- "syscall"
|
||||
"time"
|
||||
)
|
||||
|
||||
@@ -185,25 +184,6 @@
|
||||
}
|
||||
return nil
|
||||
}
|
||||
-
|
||||
-func rootRemoveAll(r *Root, name string) error {
|
||||
- if endsWithDot(name) {
|
||||
- // Consistency with os.RemoveAll: Return EINVAL when trying to remove .
|
||||
- return &PathError{Op: "RemoveAll", Path: name, Err: syscall.EINVAL}
|
||||
- }
|
||||
- if err := checkPathEscapesLstat(r, name); err != nil {
|
||||
- if err == syscall.ENOTDIR {
|
||||
- // Some intermediate path component is not a directory.
|
||||
- // RemoveAll treats this as success (since the target doesn't exist).
|
||||
- return nil
|
||||
- }
|
||||
- return &PathError{Op: "RemoveAll", Path: name, Err: err}
|
||||
- }
|
||||
- if err := RemoveAll(joinPath(r.root.name, name)); err != nil {
|
||||
- return &PathError{Op: "RemoveAll", Path: name, Err: underlyingError(err)}
|
||||
- }
|
||||
- return nil
|
||||
-}
|
||||
|
||||
func rootReadlink(r *Root, name string) (string, error) {
|
||||
if err := checkPathEscapesLstat(r, name); err != nil {
|
||||
Index: src/os/root_openat.go
|
||||
IDEA additional info:
|
||||
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
|
||||
<+>UTF-8
|
||||
===================================================================
|
||||
diff --git a/src/os/root_openat.go b/src/os/root_openat.go
|
||||
--- a/src/os/root_openat.go (revision ea2726a6fa25fbfa1092e696e522eafca544d24c)
|
||||
+++ b/src/os/root_openat.go (revision d47e0d22130d597dcf9daa6b41fd9501274f0cb2)
|
||||
@@ -196,28 +196,6 @@
|
||||
return nil
|
||||
}
|
||||
|
||||
-func rootRemoveAll(r *Root, name string) error {
|
||||
- // Consistency with os.RemoveAll: Strip trailing /s from the name,
|
||||
- // so RemoveAll("not_a_directory/") succeeds.
|
||||
- for len(name) > 0 && IsPathSeparator(name[len(name)-1]) {
|
||||
- name = name[:len(name)-1]
|
||||
- }
|
||||
- if endsWithDot(name) {
|
||||
- // Consistency with os.RemoveAll: Return EINVAL when trying to remove .
|
||||
- return &PathError{Op: "RemoveAll", Path: name, Err: syscall.EINVAL}
|
||||
- }
|
||||
- _, err := doInRoot(r, name, nil, func(parent sysfdType, name string) (struct{}, error) {
|
||||
- return struct{}{}, removeAllFrom(parent, name)
|
||||
- })
|
||||
- if IsNotExist(err) {
|
||||
- return nil
|
||||
- }
|
||||
- if err != nil {
|
||||
- return &PathError{Op: "RemoveAll", Path: name, Err: underlyingError(err)}
|
||||
- }
|
||||
- return err
|
||||
-}
|
||||
-
|
||||
func rootRename(r *Root, oldname, newname string) error {
|
||||
_, err := doInRoot(r, oldname, nil, func(oldparent sysfdType, oldname string) (struct{}, error) {
|
||||
_, err := doInRoot(r, newname, nil, func(newparent sysfdType, newname string) (struct{}, error) {
|
||||
Index: src/os/root_windows.go
|
||||
IDEA additional info:
|
||||
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
|
||||
<+>UTF-8
|
||||
===================================================================
|
||||
diff --git a/src/os/root_windows.go b/src/os/root_windows.go
|
||||
--- a/src/os/root_windows.go (revision ea2726a6fa25fbfa1092e696e522eafca544d24c)
|
||||
+++ b/src/os/root_windows.go (revision d47e0d22130d597dcf9daa6b41fd9501274f0cb2)
|
||||
@@ -402,3 +402,14 @@
|
||||
}
|
||||
return fi.Mode(), nil
|
||||
}
|
||||
+
|
||||
+func checkPathEscapes(r *Root, name string) error {
|
||||
+ if !filepathlite.IsLocal(name) {
|
||||
+ return errPathEscapes
|
||||
+ }
|
||||
+ return nil
|
||||
+}
|
||||
+
|
||||
+func checkPathEscapesLstat(r *Root, name string) error {
|
||||
+ return checkPathEscapes(r, name)
|
||||
+}
|
||||
Index: src/os/exec_windows.go
|
||||
IDEA additional info:
|
||||
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
|
||||
<+>UTF-8
|
||||
===================================================================
|
||||
diff --git a/src/os/exec_windows.go b/src/os/exec_windows.go
|
||||
--- a/src/os/exec_windows.go (revision d47e0d22130d597dcf9daa6b41fd9501274f0cb2)
|
||||
+++ b/src/os/exec_windows.go (revision 00e8daec9a4d88f44a8dc55d3bdb71878e525b41)
|
||||
@@ -10,6 +10,7 @@
|
||||
"runtime"
|
||||
"syscall"
|
||||
"time"
|
||||
+ _ "unsafe"
|
||||
)
|
||||
|
||||
// Note that Process.handle is never nil because Windows always requires
|
||||
@@ -49,9 +50,23 @@
|
||||
// than statusDone.
|
||||
p.doRelease(statusReleased)
|
||||
|
||||
+ var maj, min, build uint32
|
||||
+ rtlGetNtVersionNumbers(&maj, &min, &build)
|
||||
+ if maj < 10 {
|
||||
+ // NOTE(brainman): It seems that sometimes process is not dead
|
||||
+ // when WaitForSingleObject returns. But we do not know any
|
||||
+ // other way to wait for it. Sleeping for a while seems to do
|
||||
+ // the trick sometimes.
|
||||
+ // See https://golang.org/issue/25965 for details.
|
||||
+ time.Sleep(5 * time.Millisecond)
|
||||
+ }
|
||||
+
|
||||
return &ProcessState{p.Pid, syscall.WaitStatus{ExitCode: ec}, &u}, nil
|
||||
}
|
||||
|
||||
+//go:linkname rtlGetNtVersionNumbers syscall.rtlGetNtVersionNumbers
|
||||
+func rtlGetNtVersionNumbers(majorVersion *uint32, minorVersion *uint32, buildNumber *uint32)
|
||||
+
|
||||
func (p *Process) signal(sig Signal) error {
|
||||
handle, status := p.handleTransientAcquire()
|
||||
switch status {
|
||||
18
.github/release/.fpm_systemd
vendored
Normal file
18
.github/release/.fpm_systemd
vendored
Normal 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
15
.github/release/config.yaml
vendored
Normal 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
|
||||
280
.github/workflows/build.yml
vendored
280
.github/workflows/build.yml
vendored
@@ -29,36 +29,52 @@ jobs:
|
||||
strategy:
|
||||
matrix:
|
||||
jobs:
|
||||
- { goos: darwin, goarch: arm64, output: arm64 }
|
||||
- { goos: darwin, goarch: amd64, goamd64: v1, output: amd64-compatible }
|
||||
- { 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', output: '386' }
|
||||
- { goos: linux, goarch: amd64, goamd64: v1, output: amd64-compatible, test: test }
|
||||
- { goos: linux, goarch: amd64, goamd64: v3, output: amd64 }
|
||||
- { goos: linux, 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 }
|
||||
- { goos: linux, goarch: arm, goarm: '7', output: armv7 }
|
||||
- { 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 }
|
||||
- { goos: linux, goarch: loong64, output: loong64-abi1, abi: '1' }
|
||||
- { goos: linux, goarch: loong64, output: loong64-abi2, abi: '2' }
|
||||
- { goos: linux, goarch: riscv64, output: riscv64 }
|
||||
- { goos: linux, goarch: s390x, output: s390x }
|
||||
- { 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 }
|
||||
|
||||
# Go 1.25 with special patch can work on Windows 7
|
||||
# https://github.com/MetaCubeX/go/commits/release-branch.go1.25/
|
||||
- { goos: windows, goarch: '386', output: '386' }
|
||||
- { goos: windows, goarch: amd64, goamd64: v1, output: amd64-compatible }
|
||||
- { 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 }
|
||||
- { 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' }
|
||||
@@ -66,72 +82,113 @@ jobs:
|
||||
- { 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-compatible-go123, goversion: '1.23' }
|
||||
- { goos: windows, goarch: amd64, goamd64: v3, output: amd64-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-compatible-go122, goversion: '1.22' }
|
||||
- { goos: windows, goarch: amd64, goamd64: v3, output: amd64-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-compatible-go121, goversion: '1.21' }
|
||||
- { goos: windows, goarch: amd64, goamd64: v3, output: amd64-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-compatible-go120, goversion: '1.20' }
|
||||
- { goos: windows, goarch: amd64, goamd64: v3, output: amd64-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-compatible-go122, goversion: '1.22' }
|
||||
- { goos: darwin, goarch: amd64, goamd64: v3, output: amd64-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-compatible-go120, goversion: '1.20' }
|
||||
- { goos: darwin, goarch: amd64, goamd64: v3, output: amd64-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-compatible-go123, goversion: '1.23', test: test }
|
||||
- { goos: linux, goarch: amd64, goamd64: v3, output: amd64-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-compatible-go120, goversion: '1.20', test: test }
|
||||
- { goos: linux, goarch: amd64, goamd64: v3, output: amd64-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:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
|
||||
- name: Set up Go
|
||||
if: ${{ matrix.jobs.goversion == '' && matrix.jobs.abi != '1' }}
|
||||
uses: actions/setup-go@v5
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: '1.24'
|
||||
go-version: '1.25'
|
||||
check-latest: true # Always check for the latest patch release
|
||||
|
||||
- name: Set up Go
|
||||
if: ${{ matrix.jobs.goversion != '' && matrix.jobs.abi != '1' }}
|
||||
uses: actions/setup-go@v5
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: ${{ matrix.jobs.goversion }}
|
||||
check-latest: true # Always check for the latest patch release
|
||||
|
||||
- name: Set up Go1.23 loongarch abi1
|
||||
- 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.23.0/go1.23.0.linux-amd64-abi1.tar.gz
|
||||
sudo tar zxf go1.23.0.linux-amd64-abi1.tar.gz -C /usr/local
|
||||
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
|
||||
|
||||
# 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"
|
||||
# f0894a00f4b756d4b9b4078af2e686b359493583: "os: remove 5ms sleep on Windows in (*Process).Wait"
|
||||
# sepical fix:
|
||||
# - os.RemoveAll not working on Windows7
|
||||
- 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
|
||||
|
||||
# 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
|
||||
@@ -142,13 +199,10 @@ jobs:
|
||||
# 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 == '' }}
|
||||
if: ${{ matrix.jobs.goos == 'windows' && matrix.jobs.goversion == '1.24' }}
|
||||
run: |
|
||||
cd $(go env GOROOT)
|
||||
curl https://github.com/MetaCubeX/go/commit/2a406dc9f1ea7323d6ca9fccb2fe9ddebb6b1cc8.diff | patch --verbose -p 1
|
||||
curl https://github.com/MetaCubeX/go/commit/7b1fd7d39c6be0185fbe1d929578ab372ac5c632.diff | patch --verbose -p 1
|
||||
curl https://github.com/MetaCubeX/go/commit/979d6d8bab3823ff572ace26767fd2ce3cf351ae.diff | patch --verbose -p 1
|
||||
curl https://github.com/MetaCubeX/go/commit/ac3e93c061779dfefc0dd13a5b6e6f764a25621e.diff | patch --verbose -p 1
|
||||
patch --verbose -p 1 < $GITHUB_WORKSPACE/.github/patch/go1.24.patch
|
||||
|
||||
# modify from https://github.com/restic/restic/issues/4636#issuecomment-1896455557
|
||||
# this patch file only works on golang1.23.x
|
||||
@@ -163,10 +217,7 @@ jobs:
|
||||
if: ${{ matrix.jobs.goos == 'windows' && matrix.jobs.goversion == '1.23' }}
|
||||
run: |
|
||||
cd $(go env GOROOT)
|
||||
curl https://github.com/MetaCubeX/go/commit/9ac42137ef6730e8b7daca016ece831297a1d75b.diff | patch --verbose -p 1
|
||||
curl https://github.com/MetaCubeX/go/commit/21290de8a4c91408de7c2b5b68757b1e90af49dd.diff | patch --verbose -p 1
|
||||
curl https://github.com/MetaCubeX/go/commit/6a31d3fa8e47ddabc10bd97bff10d9a85f4cfb76.diff | patch --verbose -p 1
|
||||
curl https://github.com/MetaCubeX/go/commit/69e2eed6dd0f6d815ebf15797761c13f31213dd6.diff | patch --verbose -p 1
|
||||
patch --verbose -p 1 < $GITHUB_WORKSPACE/.github/patch/go1.23.patch
|
||||
|
||||
# modify from https://github.com/restic/restic/issues/4636#issuecomment-1896455557
|
||||
# this patch file only works on golang1.22.x
|
||||
@@ -181,30 +232,27 @@ jobs:
|
||||
if: ${{ matrix.jobs.goos == 'windows' && matrix.jobs.goversion == '1.22' }}
|
||||
run: |
|
||||
cd $(go env GOROOT)
|
||||
curl https://github.com/MetaCubeX/go/commit/9779155f18b6556a034f7bb79fb7fb2aad1e26a9.diff | patch --verbose -p 1
|
||||
curl https://github.com/MetaCubeX/go/commit/ef0606261340e608017860b423ffae5c1ce78239.diff | patch --verbose -p 1
|
||||
curl https://github.com/MetaCubeX/go/commit/7f83badcb925a7e743188041cb6e561fc9b5b642.diff | patch --verbose -p 1
|
||||
curl https://github.com/MetaCubeX/go/commit/83ff9782e024cb328b690cbf0da4e7848a327f4f.diff | patch --verbose -p 1
|
||||
patch --verbose -p 1 < $GITHUB_WORKSPACE/.github/patch/go1.22.patch
|
||||
|
||||
# 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)
|
||||
curl https://github.com/golang/go/commit/9e43850a3298a9b8b1162ba0033d4c53f8637571.diff | patch --verbose -R -p 1
|
||||
patch --verbose -p 1 < $GITHUB_WORKSPACE/.github/patch/go1.21.patch
|
||||
|
||||
- name: Set variables
|
||||
if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.version != '' }}
|
||||
run: echo "VERSION=${{ github.event.inputs.version }}" >> $GITHUB_ENV
|
||||
shell: bash
|
||||
|
||||
- name: Set variables
|
||||
if: ${{ github.event_name != 'workflow_dispatch' && github.ref_name == 'Alpha' }}
|
||||
run: echo "VERSION=alpha-$(git rev-parse --short HEAD)" >> $GITHUB_ENV
|
||||
shell: bash
|
||||
|
||||
- name: Set Time Variable
|
||||
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
|
||||
@@ -215,7 +263,7 @@ jobs:
|
||||
uses: nttld/setup-ndk@v1
|
||||
id: setup-ndk
|
||||
with:
|
||||
ndk-version: r28-beta1
|
||||
ndk-version: r29-beta1
|
||||
|
||||
- name: Set NDK path
|
||||
if: ${{ matrix.jobs.goos == 'android' }}
|
||||
@@ -233,7 +281,7 @@ jobs:
|
||||
|
||||
- name: Update CA
|
||||
run: |
|
||||
sudo apt-get install ca-certificates
|
||||
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
|
||||
|
||||
@@ -242,6 +290,7 @@ jobs:
|
||||
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: |
|
||||
@@ -256,79 +305,51 @@ jobs:
|
||||
rm mihomo-${{matrix.jobs.goos}}-${{matrix.jobs.output}}
|
||||
fi
|
||||
|
||||
- name: Create DEB package
|
||||
if: ${{ matrix.jobs.goos == 'linux' && !contains(matrix.jobs.goarch, 'mips') }}
|
||||
- name: Package DEB
|
||||
if: matrix.jobs.debian != ''
|
||||
run: |
|
||||
sudo apt-get install dpkg
|
||||
if [ "${{matrix.jobs.abi}}" = "1" ]; then
|
||||
ARCH=loongarch64
|
||||
elif [ "${{matrix.jobs.goarm}}" = "7" ]; then
|
||||
ARCH=armhf
|
||||
elif [ "${{matrix.jobs.goarch}}" = "arm" ]; then
|
||||
ARCH=armel
|
||||
else
|
||||
ARCH=${{matrix.jobs.goarch}}
|
||||
fi
|
||||
PackageVersion=$(curl -s "https://api.github.com/repos/MetaCubeX/mihomo/releases/latest" | grep -o '"tag_name": "[^"]*' | grep -o '[^"]*$' | sed 's/v//g' )
|
||||
if [ $(git branch | awk -F ' ' '{print $2}') = "Alpha" ]; then
|
||||
PackageVersion="$(echo "${PackageVersion}" | awk -F '.' '{$NF = $NF + 1; print}' OFS='.')-${VERSION}"
|
||||
fi
|
||||
set -xeuo pipefail
|
||||
sudo gem install fpm
|
||||
cp .github/release/.fpm_systemd .fpm
|
||||
|
||||
mkdir -p mihomo-${{matrix.jobs.goos}}-${{matrix.jobs.output}}-${VERSION}/DEBIAN
|
||||
mkdir -p mihomo-${{matrix.jobs.goos}}-${{matrix.jobs.output}}-${VERSION}/usr/bin
|
||||
mkdir -p mihomo-${{matrix.jobs.goos}}-${{matrix.jobs.output}}-${VERSION}/usr/share/licenses/mihomo
|
||||
mkdir -p mihomo-${{matrix.jobs.goos}}-${{matrix.jobs.output}}-${VERSION}/etc/mihomo
|
||||
mkdir -p mihomo-${{matrix.jobs.goos}}-${{matrix.jobs.output}}-${VERSION}/lib/systemd/system
|
||||
fpm -t deb \
|
||||
-v "${PackageVersion}" \
|
||||
-p "mihomo-${{matrix.jobs.goos}}-${{matrix.jobs.output}}-${VERSION}.deb" \
|
||||
--architecture ${{ matrix.jobs.debian }} \
|
||||
mihomo=/usr/bin/mihomo
|
||||
|
||||
cp mihomo mihomo-${{matrix.jobs.goos}}-${{matrix.jobs.output}}-${VERSION}/usr/bin/
|
||||
cp LICENSE mihomo-${{matrix.jobs.goos}}-${{matrix.jobs.output}}-${VERSION}/usr/share/licenses/mihomo/
|
||||
cp .github/{mihomo.service,mihomo@.service} mihomo-${{matrix.jobs.goos}}-${{matrix.jobs.output}}-${VERSION}/lib/systemd/system/
|
||||
|
||||
cat > mihomo-${{matrix.jobs.goos}}-${{matrix.jobs.output}}-${VERSION}/etc/mihomo/config.yaml <<EOF
|
||||
mixed-port: 7890
|
||||
EOF
|
||||
|
||||
cat > mihomo-${{matrix.jobs.goos}}-${{matrix.jobs.output}}-${VERSION}/DEBIAN/conffiles <<EOF
|
||||
/etc/mihomo/config.yaml
|
||||
EOF
|
||||
|
||||
cat > mihomo-${{matrix.jobs.goos}}-${{matrix.jobs.output}}-${VERSION}/DEBIAN/control <<EOF
|
||||
Package: mihomo
|
||||
Version: ${PackageVersion}
|
||||
Section:
|
||||
Priority: extra
|
||||
Architecture: ${ARCH}
|
||||
Maintainer: MetaCubeX <none@example.com>
|
||||
Homepage: https://wiki.metacubex.one/
|
||||
Description: The universal proxy platform.
|
||||
EOF
|
||||
|
||||
dpkg-deb -Z gzip --build mihomo-${{matrix.jobs.goos}}-${{matrix.jobs.output}}-${VERSION}
|
||||
|
||||
- name: Convert DEB to RPM
|
||||
if: ${{ matrix.jobs.goos == 'linux' && !contains(matrix.jobs.goarch, 'mips') }}
|
||||
- name: Package RPM
|
||||
if: matrix.jobs.rpm != ''
|
||||
run: |
|
||||
sudo apt-get install -y alien
|
||||
alien --to-rpm --scripts mihomo-${{matrix.jobs.goos}}-${{matrix.jobs.output}}-${VERSION}.deb
|
||||
mv mihomo*.rpm mihomo-${{matrix.jobs.goos}}-${{matrix.jobs.output}}-${VERSION}.rpm
|
||||
set -xeuo pipefail
|
||||
sudo gem install fpm
|
||||
cp .github/release/.fpm_systemd .fpm
|
||||
|
||||
# - name: Convert DEB to PKG
|
||||
# if: ${{ matrix.jobs.goos == 'linux' && !contains(matrix.jobs.goarch, 'mips') && !contains(matrix.jobs.goarch, 'loong64') }}
|
||||
# run: |
|
||||
# docker pull archlinux
|
||||
# docker run --rm -v ./:/mnt archlinux bash -c "
|
||||
# pacman -Syu pkgfile base-devel --noconfirm
|
||||
# curl -L https://github.com/helixarch/debtap/raw/master/debtap > /usr/bin/debtap
|
||||
# chmod 755 /usr/bin/debtap
|
||||
# debtap -u
|
||||
# debtap -Q /mnt/mihomo-${{matrix.jobs.goos}}-${{matrix.jobs.output}}-${VERSION}.deb
|
||||
# "
|
||||
# mv mihomo*.pkg.tar.zst mihomo-${{matrix.jobs.goos}}-${{matrix.jobs.output}}-${VERSION}.pkg.tar.zst
|
||||
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:
|
||||
@@ -337,6 +358,7 @@ jobs:
|
||||
mihomo*.gz
|
||||
mihomo*.deb
|
||||
mihomo*.rpm
|
||||
mihomo*.pkg.tar.zst
|
||||
mihomo*.zip
|
||||
version.txt
|
||||
checksums.txt
|
||||
@@ -406,7 +428,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
ref: Meta
|
||||
fetch-depth: '0'
|
||||
@@ -465,7 +487,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
|
||||
71
.github/workflows/test.yml
vendored
71
.github/workflows/test.yml
vendored
@@ -22,8 +22,10 @@ jobs:
|
||||
- 'windows-latest' # amd64 windows
|
||||
- 'macos-latest' # arm64 macos
|
||||
- 'ubuntu-24.04-arm' # arm64 linux
|
||||
- 'macos-13' # amd64 macos
|
||||
- 'macos-15-intel' # amd64 macos
|
||||
go-version:
|
||||
- '1.26.0-rc.2'
|
||||
- '1.25'
|
||||
- '1.24'
|
||||
- '1.23'
|
||||
- '1.22'
|
||||
@@ -40,73 +42,30 @@ jobs:
|
||||
# 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@v4
|
||||
- uses: actions/checkout@v5
|
||||
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v5
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: ${{ matrix.go-version }}
|
||||
check-latest: true # Always check for the latest patch release
|
||||
|
||||
# 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: ${{ runner.os == 'Windows' && matrix.go-version == '1.24' }}
|
||||
- name: Revert Golang commit for Windows7/8
|
||||
if: ${{ runner.os == 'Windows' && matrix.go-version != '1.20' && matrix.go-version != '1.26.0-rc.2' }}
|
||||
run: |
|
||||
cd $(go env GOROOT)
|
||||
curl https://github.com/MetaCubeX/go/commit/2a406dc9f1ea7323d6ca9fccb2fe9ddebb6b1cc8.diff | patch --verbose -p 1
|
||||
curl https://github.com/MetaCubeX/go/commit/7b1fd7d39c6be0185fbe1d929578ab372ac5c632.diff | patch --verbose -p 1
|
||||
curl https://github.com/MetaCubeX/go/commit/979d6d8bab3823ff572ace26767fd2ce3cf351ae.diff | patch --verbose -p 1
|
||||
curl https://github.com/MetaCubeX/go/commit/ac3e93c061779dfefc0dd13a5b6e6f764a25621e.diff | patch --verbose -p 1
|
||||
patch --verbose -p 1 < $GITHUB_WORKSPACE/.github/patch/go${{matrix.go-version}}.patch
|
||||
|
||||
# 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: ${{ runner.os == 'Windows' && matrix.go-version == '1.23' }}
|
||||
- name: Revert Golang commit for Windows7/8
|
||||
if: ${{ runner.os == 'Windows' && matrix.go-version == '1.26.0-rc.2' }}
|
||||
run: |
|
||||
cd $(go env GOROOT)
|
||||
curl https://github.com/MetaCubeX/go/commit/9ac42137ef6730e8b7daca016ece831297a1d75b.diff | patch --verbose -p 1
|
||||
curl https://github.com/MetaCubeX/go/commit/21290de8a4c91408de7c2b5b68757b1e90af49dd.diff | patch --verbose -p 1
|
||||
curl https://github.com/MetaCubeX/go/commit/6a31d3fa8e47ddabc10bd97bff10d9a85f4cfb76.diff | patch --verbose -p 1
|
||||
curl https://github.com/MetaCubeX/go/commit/69e2eed6dd0f6d815ebf15797761c13f31213dd6.diff | patch --verbose -p 1
|
||||
patch --verbose -p 1 < $GITHUB_WORKSPACE/.github/patch/go1.26.patch
|
||||
|
||||
# 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: ${{ runner.os == 'Windows' && matrix.go-version == '1.22' }}
|
||||
- name: Remove inbound test for macOS
|
||||
if: ${{ runner.os == 'macOS' }}
|
||||
run: |
|
||||
cd $(go env GOROOT)
|
||||
curl https://github.com/MetaCubeX/go/commit/9779155f18b6556a034f7bb79fb7fb2aad1e26a9.diff | patch --verbose -p 1
|
||||
curl https://github.com/MetaCubeX/go/commit/ef0606261340e608017860b423ffae5c1ce78239.diff | patch --verbose -p 1
|
||||
curl https://github.com/MetaCubeX/go/commit/7f83badcb925a7e743188041cb6e561fc9b5b642.diff | patch --verbose -p 1
|
||||
curl https://github.com/MetaCubeX/go/commit/83ff9782e024cb328b690cbf0da4e7848a327f4f.diff | patch --verbose -p 1
|
||||
|
||||
# modify from https://github.com/restic/restic/issues/4636#issuecomment-1896455557
|
||||
- name: Revert Golang1.21 commit for Windows7/8
|
||||
if: ${{ runner.os == 'Windows' && matrix.go-version == '1.21' }}
|
||||
run: |
|
||||
cd $(go env GOROOT)
|
||||
curl https://github.com/golang/go/commit/9e43850a3298a9b8b1162ba0033d4c53f8637571.diff | patch --verbose -R -p 1
|
||||
rm -rf listener/inbound/*_test.go
|
||||
|
||||
- name: Test
|
||||
run: go test ./... -v -count=1
|
||||
|
||||
3
.github/workflows/trigger-cmfa-update.yml
vendored
3
.github/workflows/trigger-cmfa-update.yml
vendored
@@ -10,9 +10,6 @@ on:
|
||||
- Alpha
|
||||
tags:
|
||||
- "v*"
|
||||
pull_request_target:
|
||||
branches:
|
||||
- Alpha
|
||||
|
||||
jobs:
|
||||
# Send "core-updated" to MetaCubeX/ClashMetaForAndroid to trigger update-dependencies
|
||||
|
||||
@@ -4,9 +4,9 @@ RUN echo "I'm building for $TARGETPLATFORM"
|
||||
|
||||
RUN apk add --no-cache gzip && \
|
||||
mkdir /mihomo-config && \
|
||||
wget -O /mihomo-config/geoip.metadb https://fastly.jsdelivr.net/gh/MetaCubeX/meta-rules-dat@release/geoip.metadb && \
|
||||
wget -O /mihomo-config/geosite.dat https://fastly.jsdelivr.net/gh/MetaCubeX/meta-rules-dat@release/geosite.dat && \
|
||||
wget -O /mihomo-config/geoip.dat https://fastly.jsdelivr.net/gh/MetaCubeX/meta-rules-dat@release/geoip.dat
|
||||
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 /mihomo/file-name.sh
|
||||
WORKDIR /mihomo
|
||||
|
||||
57
Makefile
57
Makefile
@@ -17,11 +17,19 @@ GOBUILD=CGO_ENABLED=0 go build -tags with_gvisor -trimpath -ldflags '-X "github.
|
||||
-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 \
|
||||
@@ -43,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)-$@
|
||||
|
||||
@@ -125,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
|
||||
|
||||
|
||||
@@ -2,25 +2,22 @@ package adapter
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/netip"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"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"
|
||||
"github.com/metacubex/mihomo/component/dialer"
|
||||
C "github.com/metacubex/mihomo/constant"
|
||||
"github.com/metacubex/mihomo/log"
|
||||
"github.com/puzpuzpuz/xsync/v3"
|
||||
|
||||
"github.com/metacubex/http"
|
||||
)
|
||||
|
||||
var UnifiedDelay = atomic.NewBool(false)
|
||||
@@ -38,7 +35,7 @@ type Proxy struct {
|
||||
C.ProxyAdapter
|
||||
alive atomic.Bool
|
||||
history *queue.Queue[C.DelayHistory]
|
||||
extra *xsync.MapOf[string, *internalProxyState]
|
||||
extra xsync.Map[string, *internalProxyState]
|
||||
}
|
||||
|
||||
// Adapter implements C.Proxy
|
||||
@@ -55,29 +52,15 @@ func (p *Proxy) AliveForTestUrl(url string) bool {
|
||||
return p.alive.Load()
|
||||
}
|
||||
|
||||
// Dial implements C.Proxy
|
||||
func (p *Proxy) Dial(metadata *C.Metadata) (C.Conn, error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), C.DefaultTCPTimeout)
|
||||
defer cancel()
|
||||
return p.DialContext(ctx, metadata)
|
||||
}
|
||||
|
||||
// DialContext implements C.ProxyAdapter
|
||||
func (p *Proxy) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.Conn, error) {
|
||||
conn, err := p.ProxyAdapter.DialContext(ctx, metadata, opts...)
|
||||
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
|
||||
}
|
||||
|
||||
@@ -171,8 +154,9 @@ func (p *Proxy) MarshalJSON() ([]byte, error) {
|
||||
mapping["mptcp"] = proxyInfo.MPTCP
|
||||
mapping["smux"] = proxyInfo.SMUX
|
||||
mapping["interface"] = proxyInfo.Interface
|
||||
mapping["dialer-proxy"] = proxyInfo.DialerProxy
|
||||
mapping["routing-mark"] = proxyInfo.RoutingMark
|
||||
mapping["provider-name"] = proxyInfo.ProviderName
|
||||
mapping["dialer-proxy"] = proxyInfo.DialerProxy
|
||||
|
||||
return json.Marshal(mapping)
|
||||
}
|
||||
@@ -195,14 +179,12 @@ func (p *Proxy) URLTest(ctx context.Context, url string, expectedStatus utils.In
|
||||
p.history.Pop()
|
||||
}
|
||||
|
||||
state, ok := p.extra.Load(url)
|
||||
if !ok {
|
||||
state = &internalProxyState{
|
||||
state, _ := p.extra.LoadOrStoreFn(url, func() *internalProxyState {
|
||||
return &internalProxyState{
|
||||
history: queue.New[C.DelayHistory](defaultHistoriesNum),
|
||||
alive: atomic.NewBool(true),
|
||||
}
|
||||
p.extra.Store(url, state)
|
||||
}
|
||||
})
|
||||
|
||||
if !satisfied {
|
||||
record.Delay = 0
|
||||
@@ -239,6 +221,11 @@ func (p *Proxy) URLTest(ctx context.Context, url string, expectedStatus utils.In
|
||||
}
|
||||
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
|
||||
@@ -248,7 +235,7 @@ func (p *Proxy) URLTest(ctx context.Context, url string, expectedStatus utils.In
|
||||
IdleConnTimeout: 90 * time.Second,
|
||||
TLSHandshakeTimeout: 10 * time.Second,
|
||||
ExpectContinueTimeout: 1 * time.Second,
|
||||
TLSClientConfig: ca.GetGlobalTLSConfig(&tls.Config{}),
|
||||
TLSClientConfig: tlsConfig,
|
||||
}
|
||||
|
||||
client := http.Client{
|
||||
@@ -296,7 +283,7 @@ func NewProxy(adapter C.ProxyAdapter) *Proxy {
|
||||
ProxyAdapter: adapter,
|
||||
history: queue.New[C.DelayHistory](defaultHistoriesNum),
|
||||
alive: atomic.NewBool(true),
|
||||
extra: xsync.NewMapOf[string, *internalProxyState]()}
|
||||
}
|
||||
}
|
||||
|
||||
func urlToMetadata(rawURL string) (addr C.Metadata, err error) {
|
||||
@@ -317,15 +304,7 @@ func urlToMetadata(rawURL string) (addr C.Metadata, err error) {
|
||||
return
|
||||
}
|
||||
}
|
||||
uintPort, err := strconv.ParseUint(port, 10, 16)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
addr = C.Metadata{
|
||||
Host: u.Hostname(),
|
||||
DstIP: netip.Addr{},
|
||||
DstPort: uint16(uintPort),
|
||||
}
|
||||
err = addr.SetRemoteAddress(net.JoinHostPort(u.Hostname(), port))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -2,9 +2,10 @@ package inbound
|
||||
|
||||
import (
|
||||
"net"
|
||||
"net/http"
|
||||
|
||||
C "github.com/metacubex/mihomo/constant"
|
||||
|
||||
"github.com/metacubex/http"
|
||||
)
|
||||
|
||||
// NewHTTPS receive CONNECT request and return ConnContext
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"sync"
|
||||
|
||||
"github.com/metacubex/mihomo/component/keepalive"
|
||||
"github.com/metacubex/mihomo/component/mptcp"
|
||||
|
||||
"github.com/metacubex/tfo-go"
|
||||
)
|
||||
@@ -34,13 +35,13 @@ func Tfo() bool {
|
||||
func SetMPTCP(open bool) {
|
||||
mutex.Lock()
|
||||
defer mutex.Unlock()
|
||||
setMultiPathTCP(&lc.ListenConfig, open)
|
||||
mptcp.SetNetListenConfig(&lc.ListenConfig, open)
|
||||
}
|
||||
|
||||
func MPTCP() bool {
|
||||
mutex.RLock()
|
||||
defer mutex.RUnlock()
|
||||
return getMultiPathTCP(&lc.ListenConfig)
|
||||
return mptcp.GetNetListenConfig(&lc.ListenConfig)
|
||||
}
|
||||
|
||||
func preResolve(network, address string) (string, error) {
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
//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
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
//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()
|
||||
}
|
||||
@@ -2,13 +2,13 @@ package inbound
|
||||
|
||||
import (
|
||||
"net"
|
||||
"net/http"
|
||||
"net/netip"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
C "github.com/metacubex/mihomo/constant"
|
||||
"github.com/metacubex/mihomo/transport/socks5"
|
||||
|
||||
"github.com/metacubex/http"
|
||||
)
|
||||
|
||||
func parseSocksAddr(target socks5.Addr) *C.Metadata {
|
||||
@@ -41,23 +41,8 @@ func parseHTTPAddr(request *http.Request) *C.Metadata {
|
||||
// trim FQDN (#737)
|
||||
host = strings.TrimRight(host, ".")
|
||||
|
||||
var uint16Port uint16
|
||||
if port, err := strconv.ParseUint(port, 10, 16); err == nil {
|
||||
uint16Port = uint16(port)
|
||||
}
|
||||
|
||||
metadata := &C.Metadata{
|
||||
NetWork: C.TCP,
|
||||
Host: host,
|
||||
DstIP: netip.Addr{},
|
||||
DstPort: uint16Port,
|
||||
}
|
||||
|
||||
ip, err := netip.ParseAddr(host)
|
||||
if err == nil {
|
||||
metadata.DstIP = ip
|
||||
}
|
||||
|
||||
metadata := &C.Metadata{}
|
||||
_ = metadata.SetRemoteAddress(net.JoinHostPort(host, port))
|
||||
return metadata
|
||||
}
|
||||
|
||||
|
||||
@@ -2,50 +2,47 @@ package outbound
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
CN "github.com/metacubex/mihomo/common/net"
|
||||
"github.com/metacubex/mihomo/component/dialer"
|
||||
N "github.com/metacubex/mihomo/common/net"
|
||||
"github.com/metacubex/mihomo/component/proxydialer"
|
||||
"github.com/metacubex/mihomo/component/resolver"
|
||||
C "github.com/metacubex/mihomo/constant"
|
||||
"github.com/metacubex/mihomo/transport/anytls"
|
||||
"github.com/metacubex/mihomo/transport/vmess"
|
||||
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
"github.com/sagernet/sing/common/uot"
|
||||
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"`
|
||||
ClientFingerprint string `proxy:"client-fingerprint,omitempty"`
|
||||
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
|
||||
Fingerprint string `proxy:"fingerprint,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"`
|
||||
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, opts ...dialer.Option) (_ C.Conn, err error) {
|
||||
options := t.Base.DialOptions(opts...)
|
||||
t.dialer.SetDialer(dialer.NewDialer(options...))
|
||||
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
|
||||
@@ -53,25 +50,20 @@ func (t *AnyTLS) DialContext(ctx context.Context, metadata *C.Metadata, opts ...
|
||||
return NewConn(c, t), nil
|
||||
}
|
||||
|
||||
func (t *AnyTLS) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.PacketConn, err error) {
|
||||
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
|
||||
options := t.Base.DialOptions(opts...)
|
||||
t.dialer.SetDialer(dialer.NewDialer(options...))
|
||||
c, err := t.client.CreateProxy(ctx, uot.RequestDestination(2))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// create uot on tcp
|
||||
if !metadata.Resolved() {
|
||||
ip, err := resolver.ResolveIP(ctx, metadata.Host)
|
||||
if err != nil {
|
||||
return nil, errors.New("can't resolve ip")
|
||||
}
|
||||
metadata.DstIP = ip
|
||||
}
|
||||
destination := M.SocksaddrFromNet(metadata.UDPAddr())
|
||||
return newPacketConn(CN.NewThreadSafePacketConn(uot.NewLazyConn(c, uot.Request{Destination: destination})), t), nil
|
||||
return newPacketConn(N.NewThreadSafePacketConn(uot.NewLazyConn(c, uot.Request{Destination: destination})), t), nil
|
||||
}
|
||||
|
||||
// SupportUOT implements C.ProxyAdapter
|
||||
@@ -93,8 +85,23 @@ func (t *AnyTLS) Close() error {
|
||||
|
||||
func NewAnyTLS(option AnyTLSOption) (*AnyTLS, error) {
|
||||
addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port))
|
||||
|
||||
singDialer := proxydialer.NewByNameSingDialer(option.DialerProxy, dialer.NewDialer())
|
||||
outbound := &AnyTLS{
|
||||
Base: &Base{
|
||||
name: option.Name,
|
||||
addr: addr,
|
||||
tp: C.AnyTLS,
|
||||
pdName: option.ProviderName,
|
||||
udp: option.UDP,
|
||||
tfo: option.TFO,
|
||||
mpTcp: option.MPTCP,
|
||||
iface: option.Interface,
|
||||
rmark: option.RoutingMark,
|
||||
prefer: option.IPVersion,
|
||||
},
|
||||
option: &option,
|
||||
}
|
||||
outbound.dialer = option.NewDialer(outbound.DialOptions())
|
||||
singDialer := proxydialer.NewSingDialer(outbound.dialer)
|
||||
|
||||
tOption := anytls.ClientConfig{
|
||||
Password: option.Password,
|
||||
@@ -104,34 +111,27 @@ func NewAnyTLS(option AnyTLSOption) (*AnyTLS, error) {
|
||||
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
|
||||
|
||||
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),
|
||||
},
|
||||
client: anytls.NewClient(context.TODO(), tOption),
|
||||
option: &option,
|
||||
dialer: singDialer,
|
||||
}
|
||||
client := anytls.NewClient(context.TODO(), tOption)
|
||||
outbound.client = client
|
||||
|
||||
return outbound, nil
|
||||
}
|
||||
|
||||
@@ -3,36 +3,41 @@ package outbound
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall"
|
||||
|
||||
N "github.com/metacubex/mihomo/common/net"
|
||||
"github.com/metacubex/mihomo/common/utils"
|
||||
"github.com/metacubex/mihomo/component/dialer"
|
||||
"github.com/metacubex/mihomo/component/proxydialer"
|
||||
"github.com/metacubex/mihomo/component/resolver"
|
||||
C "github.com/metacubex/mihomo/constant"
|
||||
"github.com/metacubex/mihomo/log"
|
||||
)
|
||||
|
||||
type ProxyAdapter interface {
|
||||
C.ProxyAdapter
|
||||
DialOptions(opts ...dialer.Option) []dialer.Option
|
||||
DialOptions() []dialer.Option
|
||||
ResolveUDP(ctx context.Context, metadata *C.Metadata) error
|
||||
}
|
||||
|
||||
type Base struct {
|
||||
name string
|
||||
addr string
|
||||
iface string
|
||||
tp C.AdapterType
|
||||
pdName string
|
||||
udp bool
|
||||
xudp bool
|
||||
tfo bool
|
||||
mpTcp bool
|
||||
iface string
|
||||
rmark int
|
||||
id string
|
||||
prefer C.DNSPrefer
|
||||
dialer C.Dialer
|
||||
id string
|
||||
}
|
||||
|
||||
// Name implements C.ProxyAdapter
|
||||
@@ -54,35 +59,15 @@ func (b *Base) Type() C.AdapterType {
|
||||
return b.tp
|
||||
}
|
||||
|
||||
// StreamConnContext implements C.ProxyAdapter
|
||||
func (b *Base) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
||||
return c, C.ErrNotSupport
|
||||
}
|
||||
|
||||
func (b *Base) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.Conn, error) {
|
||||
return nil, C.ErrNotSupport
|
||||
}
|
||||
|
||||
// DialContextWithDialer implements C.ProxyAdapter
|
||||
func (b *Base) DialContextWithDialer(ctx context.Context, dialer C.Dialer, metadata *C.Metadata) (_ C.Conn, err error) {
|
||||
func (b *Base) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
|
||||
return nil, C.ErrNotSupport
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// ListenPacketWithDialer implements C.ProxyAdapter
|
||||
func (b *Base) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, metadata *C.Metadata) (_ C.PacketConn, err error) {
|
||||
return nil, C.ErrNotSupport
|
||||
}
|
||||
|
||||
// SupportWithDialer implements C.ProxyAdapter
|
||||
func (b *Base) SupportWithDialer() C.NetWork {
|
||||
return C.InvalidNet
|
||||
}
|
||||
|
||||
// SupportUOT implements C.ProxyAdapter
|
||||
func (b *Base) SupportUOT() bool {
|
||||
return false
|
||||
@@ -101,6 +86,7 @@ func (b *Base) ProxyInfo() (info C.ProxyInfo) {
|
||||
info.SMUX = false
|
||||
info.Interface = b.iface
|
||||
info.RoutingMark = b.rmark
|
||||
info.ProviderName = b.pdName
|
||||
return
|
||||
}
|
||||
|
||||
@@ -128,7 +114,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))
|
||||
}
|
||||
@@ -160,17 +146,46 @@ func (b *Base) DialOptions(opts ...dialer.Option) []dialer.Option {
|
||||
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"`
|
||||
MPTCP bool `proxy:"mptcp,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"`
|
||||
DialerProxy string `proxy:"dialer-proxy,omitempty"` // don't apply this option into groups, but can set a group name in a proxy
|
||||
TFO bool `proxy:"tfo,omitempty"`
|
||||
MPTCP bool `proxy:"mptcp,omitempty"`
|
||||
Interface string `proxy:"interface-name,omitempty"`
|
||||
RoutingMark int `proxy:"routing-mark,omitempty"`
|
||||
IPVersion C.DNSPrefer `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
|
||||
|
||||
//
|
||||
// The following parameters are used internally, assign value by the structure decoder are disallowed
|
||||
//
|
||||
DialerForAPI C.Dialer `proxy:"-"` // the dialer used for API usage has higher priority than all the above configurations.
|
||||
ProviderName string `proxy:"-"`
|
||||
}
|
||||
|
||||
func (b *BasicOption) NewDialer(opts []dialer.Option) C.Dialer {
|
||||
cDialer := b.DialerForAPI
|
||||
if cDialer == nil {
|
||||
if b.DialerProxy != "" {
|
||||
cDialer = proxydialer.NewByName(b.DialerProxy)
|
||||
} else {
|
||||
cDialer = dialer.NewDialer(opts...)
|
||||
}
|
||||
}
|
||||
return cDialer
|
||||
}
|
||||
|
||||
type BaseOption struct {
|
||||
@@ -203,12 +218,22 @@ func NewBase(opt BaseOption) *Base {
|
||||
|
||||
type conn struct {
|
||||
N.ExtendedConn
|
||||
chain C.Chain
|
||||
actualRemoteDestination string
|
||||
chain C.Chain
|
||||
pdChain 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
|
||||
@@ -216,9 +241,15 @@ func (c *conn) Chains() C.Chain {
|
||||
return c.chain
|
||||
}
|
||||
|
||||
// ProviderChains implements C.Connection
|
||||
func (c *conn) ProviderChains() C.Chain {
|
||||
return c.pdChain
|
||||
}
|
||||
|
||||
// AppendToChains implements C.Connection
|
||||
func (c *conn) AppendToChains(a C.ProxyAdapter) {
|
||||
c.chain = append(c.chain, a.Name())
|
||||
c.pdChain = append(c.pdChain, a.ProxyInfo().ProviderName)
|
||||
}
|
||||
|
||||
func (c *conn) Upstream() any {
|
||||
@@ -241,19 +272,28 @@ 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, 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
|
||||
pdChain 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
|
||||
@@ -261,9 +301,15 @@ func (c *packetConn) Chains() C.Chain {
|
||||
return c.chain
|
||||
}
|
||||
|
||||
// ProviderChains implements C.Connection
|
||||
func (c *packetConn) ProviderChains() C.Chain {
|
||||
return c.pdChain
|
||||
}
|
||||
|
||||
// AppendToChains implements C.Connection
|
||||
func (c *packetConn) AppendToChains(a C.ProxyAdapter) {
|
||||
c.chain = append(c.chain, a.Name())
|
||||
c.pdChain = append(c.pdChain, a.ProxyInfo().ProviderName)
|
||||
}
|
||||
|
||||
func (c *packetConn) LocalAddr() net.Addr {
|
||||
@@ -287,24 +333,14 @@ func (c *packetConn) AddRef(ref any) {
|
||||
c.EnhancePacketConn = N.NewRefPacketConn(c.EnhancePacketConn, ref) // add ref for autoCloseProxyAdapter
|
||||
}
|
||||
|
||||
func newPacketConn(pc net.PacketConn, a C.ProxyAdapter) C.PacketConn {
|
||||
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())}
|
||||
}
|
||||
|
||||
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 ""
|
||||
}
|
||||
}
|
||||
cpc := &packetConn{epc, nil, nil, a.Name(), utils.NewUUIDV4().String(), a.Addr(), a.ResolveUDP}
|
||||
cpc.AppendToChains(a)
|
||||
return cpc
|
||||
}
|
||||
|
||||
type AddRef interface {
|
||||
@@ -317,8 +353,8 @@ type autoCloseProxyAdapter struct {
|
||||
closeErr error
|
||||
}
|
||||
|
||||
func (p *autoCloseProxyAdapter) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) {
|
||||
c, err := p.ProxyAdapter.DialContext(ctx, metadata, opts...)
|
||||
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
|
||||
}
|
||||
@@ -328,30 +364,8 @@ func (p *autoCloseProxyAdapter) DialContext(ctx context.Context, metadata *C.Met
|
||||
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, opts ...dialer.Option) (_ C.PacketConn, err error) {
|
||||
pc, err := p.ProxyAdapter.ListenPacketContext(ctx, metadata, opts...)
|
||||
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)
|
||||
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
|
||||
}
|
||||
|
||||
@@ -2,7 +2,8 @@ package outbound
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/metacubex/mihomo/component/dialer"
|
||||
"github.com/metacubex/mihomo/component/loopback"
|
||||
"github.com/metacubex/mihomo/component/resolver"
|
||||
@@ -20,12 +21,13 @@ type DirectOption struct {
|
||||
}
|
||||
|
||||
// DialContext implements C.ProxyAdapter
|
||||
func (d *Direct) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.Conn, error) {
|
||||
func (d *Direct) DialContext(ctx context.Context, metadata *C.Metadata) (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(), d.Base.DialOptions(opts...)...)
|
||||
c, err := dialer.DialContext(ctx, "tcp", metadata.RemoteAddress(), opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -33,25 +35,31 @@ func (d *Direct) DialContext(ctx context.Context, metadata *C.Metadata, opts ...
|
||||
}
|
||||
|
||||
// ListenPacketContext implements C.ProxyAdapter
|
||||
func (d *Direct) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) {
|
||||
func (d *Direct) ListenPacketContext(ctx context.Context, metadata *C.Metadata) (C.PacketConn, error) {
|
||||
if err := d.loopBack.CheckPacketConn(metadata); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// 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.DirectHostResolver)
|
||||
if err != nil {
|
||||
return nil, errors.New("can't resolve ip")
|
||||
}
|
||||
metadata.DstIP = ip
|
||||
if err := d.ResolveUDP(ctx, metadata); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pc, err := dialer.NewDialer(d.Base.DialOptions(opts...)...).ListenPacket(ctx, "udp", "", metadata.AddrPort())
|
||||
pc, err := dialer.NewDialer(d.DialOptions()...).ListenPacket(ctx, "udp", "", metadata.AddrPort())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
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
|
||||
}
|
||||
@@ -61,12 +69,13 @@ func NewDirectWithOption(option DirectOption) *Direct {
|
||||
Base: &Base{
|
||||
name: option.Name,
|
||||
tp: C.Direct,
|
||||
pdName: option.ProviderName,
|
||||
udp: true,
|
||||
tfo: option.TFO,
|
||||
mpTcp: option.MPTCP,
|
||||
iface: option.Interface,
|
||||
rmark: option.RoutingMark,
|
||||
prefer: C.NewDNSPrefer(option.IPVersion),
|
||||
prefer: option.IPVersion,
|
||||
},
|
||||
loopBack: loopback.NewDetector(),
|
||||
}
|
||||
|
||||
@@ -3,11 +3,11 @@ 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/dialer"
|
||||
"github.com/metacubex/mihomo/component/resolver"
|
||||
C "github.com/metacubex/mihomo/constant"
|
||||
"github.com/metacubex/mihomo/log"
|
||||
@@ -23,15 +23,18 @@ type DnsOption struct {
|
||||
}
|
||||
|
||||
// DialContext implements C.ProxyAdapter
|
||||
func (d *Dns) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.Conn, error) {
|
||||
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, opts ...dialer.Option) (C.PacketConn, error) {
|
||||
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())
|
||||
|
||||
@@ -42,6 +45,13 @@ func (d *Dns) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opt
|
||||
}, 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()
|
||||
@@ -148,12 +158,13 @@ func NewDnsWithOption(option DnsOption) *Dns {
|
||||
Base: &Base{
|
||||
name: option.Name,
|
||||
tp: C.Dns,
|
||||
pdName: option.ProviderName,
|
||||
udp: true,
|
||||
tfo: option.TFO,
|
||||
mpTcp: option.MPTCP,
|
||||
iface: option.Interface,
|
||||
rmark: option.RoutingMark,
|
||||
prefer: C.NewDNSPrefer(option.IPVersion),
|
||||
prefer: option.IPVersion,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
36
adapter/outbound/ech.go
Normal file
36
adapter/outbound/ech.go
Normal 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
|
||||
}
|
||||
@@ -3,19 +3,18 @@ package outbound
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
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/http"
|
||||
"github.com/metacubex/tls"
|
||||
)
|
||||
|
||||
type Http struct {
|
||||
@@ -37,6 +36,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"`
|
||||
}
|
||||
|
||||
@@ -58,19 +59,8 @@ func (h *Http) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.Me
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
// DialContextWithDialer implements C.ProxyAdapter
|
||||
func (h *Http) DialContextWithDialer(ctx context.Context, dialer C.Dialer, metadata *C.Metadata) (_ C.Conn, err error) {
|
||||
if len(h.option.DialerProxy) > 0 {
|
||||
dialer, err = proxydialer.NewByName(h.option.DialerProxy, dialer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
c, err := dialer.DialContext(ctx, "tcp", h.addr)
|
||||
func (h *Http) DialContext(ctx context.Context, metadata *C.Metadata) (_ C.Conn, err error) {
|
||||
c, err := h.dialer.DialContext(ctx, "tcp", h.addr)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s connect error: %w", h.addr, err)
|
||||
}
|
||||
@@ -87,11 +77,6 @@ func (h *Http) DialContextWithDialer(ctx context.Context, dialer C.Dialer, metad
|
||||
return NewConn(c, h), nil
|
||||
}
|
||||
|
||||
// SupportWithDialer implements C.ProxyAdapter
|
||||
func (h *Http) SupportWithDialer() C.NetWork {
|
||||
return C.TCP
|
||||
}
|
||||
|
||||
// ProxyInfo implements C.ProxyAdapter
|
||||
func (h *Http) ProxyInfo() C.ProxyInfo {
|
||||
info := h.Base.ProxyInfo()
|
||||
@@ -167,29 +152,37 @@ func NewHttp(option HttpOption) (*Http, error) {
|
||||
sni = option.SNI
|
||||
}
|
||||
var err error
|
||||
tlsConfig, err = ca.GetSpecifiedFingerprintTLSConfig(&tls.Config{
|
||||
InsecureSkipVerify: option.SkipCertVerify,
|
||||
ServerName: sni,
|
||||
}, option.Fingerprint)
|
||||
tlsConfig, err = ca.GetTLSConfig(ca.Option{
|
||||
TLSConfig: &tls.Config{
|
||||
InsecureSkipVerify: option.SkipCertVerify,
|
||||
ServerName: sni,
|
||||
},
|
||||
Fingerprint: option.Fingerprint,
|
||||
Certificate: option.Certificate,
|
||||
PrivateKey: option.PrivateKey,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return &Http{
|
||||
outbound := &Http{
|
||||
Base: &Base{
|
||||
name: option.Name,
|
||||
addr: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)),
|
||||
tp: C.Http,
|
||||
pdName: option.ProviderName,
|
||||
tfo: option.TFO,
|
||||
mpTcp: option.MPTCP,
|
||||
iface: option.Interface,
|
||||
rmark: option.RoutingMark,
|
||||
prefer: C.NewDNSPrefer(option.IPVersion),
|
||||
prefer: option.IPVersion,
|
||||
},
|
||||
user: option.UserName,
|
||||
pass: option.Password,
|
||||
tlsConfig: tlsConfig,
|
||||
option: &option,
|
||||
}, nil
|
||||
}
|
||||
outbound.dialer = option.NewDialer(outbound.DialOptions())
|
||||
return outbound, nil
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ package outbound
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"net"
|
||||
@@ -10,13 +9,9 @@ import (
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/metacubex/quic-go"
|
||||
"github.com/metacubex/quic-go/congestion"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
|
||||
"github.com/metacubex/mihomo/component/ca"
|
||||
"github.com/metacubex/mihomo/component/dialer"
|
||||
"github.com/metacubex/mihomo/component/proxydialer"
|
||||
"github.com/metacubex/mihomo/component/ech"
|
||||
C "github.com/metacubex/mihomo/constant"
|
||||
"github.com/metacubex/mihomo/log"
|
||||
hyCongestion "github.com/metacubex/mihomo/transport/hysteria/congestion"
|
||||
@@ -25,6 +20,12 @@ import (
|
||||
"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/tls"
|
||||
|
||||
"github.com/metacubex/quic-go"
|
||||
"github.com/metacubex/quic-go/congestion"
|
||||
M "github.com/metacubex/sing/common/metadata"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -43,10 +44,13 @@ type Hysteria struct {
|
||||
|
||||
option *HysteriaOption
|
||||
client *core.Client
|
||||
|
||||
tlsConfig *tls.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.String(), metadata.DstPort, 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
|
||||
}
|
||||
@@ -54,31 +58,34 @@ 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, rAddr net.Addr) (net.PacketConn, error) {
|
||||
var err error
|
||||
var cDialer C.Dialer = dialer.NewDialer(h.Base.DialOptions(opts...)...)
|
||||
if len(h.option.DialerProxy) > 0 {
|
||||
cDialer, err = proxydialer.NewByName(h.option.DialerProxy, cDialer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
rAddrPort, _ := netip.ParseAddrPort(rAddr.String())
|
||||
return cDialer.ListenPacket(ctx, network, "", rAddrPort)
|
||||
return h.dialer.ListenPacket(ctx, network, "", rAddrPort)
|
||||
},
|
||||
remoteAddr: func(addr string) (net.Addr, error) {
|
||||
return resolveUDPAddr(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
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -92,30 +99,31 @@ func (h *Hysteria) ProxyInfo() C.ProxyInfo {
|
||||
|
||||
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) {
|
||||
@@ -143,23 +151,32 @@ func NewHysteria(option HysteriaOption) (*Hysteria, error) {
|
||||
serverName = option.SNI
|
||||
}
|
||||
|
||||
tlsConfig := &tls.Config{
|
||||
ServerName: serverName,
|
||||
InsecureSkipVerify: option.SkipCertVerify,
|
||||
MinVersion: tls.VersionTLS13,
|
||||
}
|
||||
|
||||
var err error
|
||||
tlsConfig, err = ca.GetTLSConfig(tlsConfig, option.Fingerprint, option.CustomCA, 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(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 := tlsConfig
|
||||
|
||||
quicConfig := &quic.Config{
|
||||
InitialStreamReceiveWindow: uint64(option.ReceiveWindowConn),
|
||||
MaxStreamReceiveWindow: uint64(option.ReceiveWindowConn),
|
||||
@@ -214,7 +231,7 @@ 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,
|
||||
)
|
||||
@@ -226,15 +243,19 @@ func NewHysteria(option HysteriaOption) (*Hysteria, error) {
|
||||
name: option.Name,
|
||||
addr: addr,
|
||||
tp: C.Hysteria,
|
||||
pdName: option.ProviderName,
|
||||
udp: true,
|
||||
tfo: option.FastOpen,
|
||||
iface: option.Interface,
|
||||
rmark: option.RoutingMark,
|
||||
prefer: C.NewDNSPrefer(option.IPVersion),
|
||||
prefer: option.IPVersion,
|
||||
},
|
||||
option: &option,
|
||||
client: client,
|
||||
option: &option,
|
||||
client: client,
|
||||
tlsConfig: tlsClientConfig,
|
||||
echConfig: echConfig,
|
||||
}
|
||||
outbound.dialer = option.NewDialer(outbound.DialOptions())
|
||||
|
||||
return outbound, nil
|
||||
}
|
||||
|
||||
@@ -2,27 +2,24 @@ package outbound
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
CN "github.com/metacubex/mihomo/common/net"
|
||||
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/proxydialer"
|
||||
C "github.com/metacubex/mihomo/constant"
|
||||
"github.com/metacubex/mihomo/log"
|
||||
tuicCommon "github.com/metacubex/mihomo/transport/tuic/common"
|
||||
|
||||
"github.com/metacubex/sing-quic/hysteria2"
|
||||
|
||||
"github.com/metacubex/quic-go"
|
||||
"github.com/metacubex/randv2"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
"github.com/metacubex/sing-quic/hysteria2"
|
||||
M "github.com/metacubex/sing/common/metadata"
|
||||
"github.com/metacubex/tls"
|
||||
)
|
||||
|
||||
func init() {
|
||||
@@ -37,29 +34,29 @@ type Hysteria2 struct {
|
||||
|
||||
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"`
|
||||
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"`
|
||||
CWND int `proxy:"cwnd,omitempty"`
|
||||
UdpMTU int `proxy:"udp-mtu,omitempty"`
|
||||
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"`
|
||||
@@ -68,9 +65,7 @@ type Hysteria2Option struct {
|
||||
MaxConnectionReceiveWindow uint64 `proxy:"max-connection-receive-window,omitempty"`
|
||||
}
|
||||
|
||||
func (h *Hysteria2) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) {
|
||||
options := h.Base.DialOptions(opts...)
|
||||
h.dialer.SetDialer(dialer.NewDialer(options...))
|
||||
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
|
||||
@@ -78,9 +73,10 @@ func (h *Hysteria2) DialContext(ctx context.Context, metadata *C.Metadata, opts
|
||||
return NewConn(c, h), nil
|
||||
}
|
||||
|
||||
func (h *Hysteria2) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.PacketConn, err error) {
|
||||
options := h.Base.DialOptions(opts...)
|
||||
h.dialer.SetDialer(dialer.NewDialer(options...))
|
||||
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
|
||||
@@ -88,7 +84,7 @@ func (h *Hysteria2) ListenPacketContext(ctx context.Context, metadata *C.Metadat
|
||||
if pc == nil {
|
||||
return nil, errors.New("packetConn is nil")
|
||||
}
|
||||
return newPacketConn(CN.NewThreadSafePacketConn(pc), h), nil
|
||||
return newPacketConn(N.NewThreadSafePacketConn(pc), h), nil
|
||||
}
|
||||
|
||||
// Close implements C.ProxyAdapter
|
||||
@@ -108,6 +104,22 @@ func (h *Hysteria2) ProxyInfo() C.ProxyInfo {
|
||||
|
||||
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,
|
||||
pdName: option.ProviderName,
|
||||
udp: true,
|
||||
iface: option.Interface,
|
||||
rmark: option.RoutingMark,
|
||||
prefer: option.IPVersion,
|
||||
},
|
||||
option: &option,
|
||||
}
|
||||
outbound.dialer = option.NewDialer(outbound.DialOptions())
|
||||
singDialer := proxydialer.NewSingDialer(outbound.dialer)
|
||||
|
||||
var salamanderPassword string
|
||||
if len(option.Obfs) > 0 {
|
||||
if option.ObfsPassword == "" {
|
||||
@@ -126,22 +138,30 @@ func NewHysteria2(option Hysteria2Option) (*Hysteria2, error) {
|
||||
serverName = option.SNI
|
||||
}
|
||||
|
||||
tlsConfig := &tls.Config{
|
||||
ServerName: serverName,
|
||||
InsecureSkipVerify: option.SkipCertVerify,
|
||||
MinVersion: tls.VersionTLS13,
|
||||
}
|
||||
|
||||
var err error
|
||||
tlsConfig, err = ca.GetTLSConfig(tlsConfig, option.Fingerprint, option.CustomCA, 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(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
|
||||
}
|
||||
|
||||
tlsClientConfig := 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
|
||||
@@ -155,8 +175,6 @@ func NewHysteria2(option Hysteria2Option) (*Hysteria2, error) {
|
||||
MaxConnectionReceiveWindow: option.MaxConnectionReceiveWindow,
|
||||
}
|
||||
|
||||
singDialer := proxydialer.NewByNameSingDialer(option.DialerProxy, dialer.NewDialer())
|
||||
|
||||
clientOptions := hysteria2.ClientOptions{
|
||||
Context: context.TODO(),
|
||||
Dialer: singDialer,
|
||||
@@ -165,41 +183,46 @@ func NewHysteria2(option Hysteria2Option) (*Hysteria2, error) {
|
||||
ReceiveBPS: StringToBps(option.Down),
|
||||
SalamanderPassword: salamanderPassword,
|
||||
Password: option.Password,
|
||||
TLSConfig: tlsConfig,
|
||||
TLSConfig: tlsClientConfig,
|
||||
QUICConfig: quicConfig,
|
||||
UDPDisabled: false,
|
||||
CWND: option.CWND,
|
||||
UdpMTU: option.UdpMTU,
|
||||
ServerAddress: func(ctx context.Context) (*net.UDPAddr, error) {
|
||||
return resolveUDPAddr(ctx, "udp", addr, C.NewDNSPrefer(option.IPVersion))
|
||||
udpAddr, err := resolveUDPAddr(ctx, "udp", addr, 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 serverAddress []string
|
||||
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 {
|
||||
serverAddress = append(serverAddress, net.JoinHostPort(option.Server, strconv.Itoa(int(port))))
|
||||
serverPorts = append(serverPorts, port)
|
||||
return true
|
||||
})
|
||||
if len(serverAddress) > 0 {
|
||||
clientOptions.ServerAddress = func(ctx context.Context) (*net.UDPAddr, error) {
|
||||
return resolveUDPAddr(ctx, "udp", serverAddress[randv2.IntN(len(serverAddress))], C.NewDNSPrefer(option.IPVersion))
|
||||
}
|
||||
|
||||
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(serverAddress) == 0 {
|
||||
if option.Port == 0 && len(serverPorts) == 0 {
|
||||
return nil, errors.New("invalid port")
|
||||
}
|
||||
|
||||
@@ -207,21 +230,7 @@ func NewHysteria2(option Hysteria2Option) (*Hysteria2, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
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,
|
||||
client: client,
|
||||
dialer: singDialer,
|
||||
}
|
||||
outbound.client = client
|
||||
|
||||
return outbound, nil
|
||||
}
|
||||
|
||||
@@ -4,12 +4,12 @@ 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"
|
||||
N "github.com/metacubex/mihomo/common/net"
|
||||
"github.com/metacubex/mihomo/component/resolver"
|
||||
C "github.com/metacubex/mihomo/constant"
|
||||
|
||||
mieruclient "github.com/enfein/mieru/v3/apis/client"
|
||||
@@ -28,20 +28,60 @@ type Mieru struct {
|
||||
|
||||
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"`
|
||||
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, opts ...dialer.Option) (C.Conn, error) {
|
||||
if err := m.ensureClientIsRunning(opts...); err != nil {
|
||||
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)
|
||||
@@ -53,15 +93,18 @@ func (m *Mieru) DialContext(ctx context.Context, metadata *C.Metadata, opts ...d
|
||||
}
|
||||
|
||||
// ListenPacketContext implements C.ProxyAdapter
|
||||
func (m *Mieru) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.PacketConn, err error) {
|
||||
if err := m.ensureClientIsRunning(opts...); err != nil {
|
||||
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
|
||||
return newPacketConn(N.NewThreadSafePacketConn(mierucommon.NewUDPAssociateWrapper(mierucommon.NewPacketOverStreamTunnel(c))), m), nil
|
||||
}
|
||||
|
||||
// SupportUOT implements C.ProxyAdapter
|
||||
@@ -76,7 +119,7 @@ func (m *Mieru) ProxyInfo() C.ProxyInfo {
|
||||
return info
|
||||
}
|
||||
|
||||
func (m *Mieru) ensureClientIsRunning(opts ...dialer.Option) error {
|
||||
func (m *Mieru) ensureClientIsRunning() error {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
|
||||
@@ -85,19 +128,13 @@ func (m *Mieru) ensureClientIsRunning(opts ...dialer.Option) error {
|
||||
}
|
||||
|
||||
// Create a dialer and add it to the client config, before starting the client.
|
||||
var dialer C.Dialer = dialer.NewDialer(m.Base.DialOptions(opts...)...)
|
||||
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.Dialer = m.dialer
|
||||
config.PacketDialer = mieruPacketDialer{Dialer: m.dialer}
|
||||
config.Resolver = mieruDNSResolver{prefer: m.prefer}
|
||||
if err := m.client.Store(config); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -130,16 +167,18 @@ func NewMieru(option MieruOption) (*Mieru, error) {
|
||||
Base: &Base{
|
||||
name: option.Name,
|
||||
addr: addr,
|
||||
iface: option.Interface,
|
||||
tp: C.Mieru,
|
||||
pdName: option.ProviderName,
|
||||
udp: option.UDP,
|
||||
xudp: false,
|
||||
iface: option.Interface,
|
||||
rmark: option.RoutingMark,
|
||||
prefer: C.NewDNSPrefer(option.IPVersion),
|
||||
prefer: option.IPVersion,
|
||||
},
|
||||
option: &option,
|
||||
client: c,
|
||||
}
|
||||
outbound.dialer = option.NewDialer(outbound.DialOptions())
|
||||
return outbound, nil
|
||||
}
|
||||
|
||||
@@ -154,23 +193,21 @@ func (m *Mieru) Close() error {
|
||||
}
|
||||
|
||||
func metadataToMieruNetAddrSpec(metadata *C.Metadata) mierumodel.NetAddrSpec {
|
||||
spec := mierumodel.NetAddrSpec{
|
||||
Net: metadata.NetWork.String(),
|
||||
}
|
||||
if metadata.Host != "" {
|
||||
return mierumodel.NetAddrSpec{
|
||||
AddrSpec: mierumodel.AddrSpec{
|
||||
FQDN: metadata.Host,
|
||||
Port: int(metadata.DstPort),
|
||||
},
|
||||
Net: "tcp",
|
||||
spec.AddrSpec = mierumodel.AddrSpec{
|
||||
FQDN: metadata.Host,
|
||||
Port: int(metadata.DstPort),
|
||||
}
|
||||
} else {
|
||||
return mierumodel.NetAddrSpec{
|
||||
AddrSpec: mierumodel.AddrSpec{
|
||||
IP: metadata.DstIP.AsSlice(),
|
||||
Port: int(metadata.DstPort),
|
||||
},
|
||||
Net: "tcp",
|
||||
spec.AddrSpec = mierumodel.AddrSpec{
|
||||
IP: metadata.DstIP.AsSlice(),
|
||||
Port: int(metadata.DstPort),
|
||||
}
|
||||
}
|
||||
return spec
|
||||
}
|
||||
|
||||
func buildMieruClientConfig(option MieruOption) (*mieruclient.ClientConfig, error) {
|
||||
@@ -178,7 +215,13 @@ func buildMieruClientConfig(option MieruOption) (*mieruclient.ClientConfig, erro
|
||||
return nil, fmt.Errorf("failed to validate mieru option: %w", err)
|
||||
}
|
||||
|
||||
transportProtocol := mierupb.TransportProtocol_TCP.Enum()
|
||||
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
|
||||
@@ -236,12 +279,18 @@ func buildMieruClientConfig(option MieruOption) (*mieruclient.ClientConfig, erro
|
||||
},
|
||||
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
|
||||
}
|
||||
|
||||
@@ -277,8 +326,8 @@ func validateMieruOption(option MieruOption) error {
|
||||
}
|
||||
}
|
||||
|
||||
if option.Transport != "TCP" {
|
||||
return fmt.Errorf("transport must be TCP")
|
||||
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")
|
||||
@@ -291,6 +340,11 @@ func validateMieruOption(option MieruOption) error {
|
||||
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
|
||||
}
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ func TestNewMieru(t *testing.T) {
|
||||
Name: "test",
|
||||
Server: "example.com",
|
||||
Port: 10003,
|
||||
Transport: "TCP",
|
||||
Transport: "UDP",
|
||||
UserName: "test",
|
||||
Password: "test",
|
||||
},
|
||||
|
||||
@@ -13,11 +13,14 @@ import (
|
||||
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
|
||||
|
||||
const x25519ScalarSize = 32
|
||||
publicKey, err := base64.RawURLEncoding.DecodeString(o.PublicKey)
|
||||
|
||||
@@ -4,10 +4,10 @@ import (
|
||||
"context"
|
||||
"io"
|
||||
"net"
|
||||
"net/netip"
|
||||
"time"
|
||||
|
||||
"github.com/metacubex/mihomo/common/buf"
|
||||
"github.com/metacubex/mihomo/component/dialer"
|
||||
C "github.com/metacubex/mihomo/constant"
|
||||
)
|
||||
|
||||
@@ -17,11 +17,12 @@ type Reject struct {
|
||||
}
|
||||
|
||||
type RejectOption struct {
|
||||
BasicOption
|
||||
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
|
||||
}
|
||||
@@ -29,10 +30,20 @@ func (r *Reject) DialContext(ctx context.Context, metadata *C.Metadata, opts ...
|
||||
}
|
||||
|
||||
// ListenPacketContext implements C.ProxyAdapter
|
||||
func (r *Reject) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) {
|
||||
func (r *Reject) ListenPacketContext(ctx context.Context, metadata *C.Metadata) (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{
|
||||
|
||||
@@ -2,27 +2,25 @@ package outbound
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"strconv"
|
||||
|
||||
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"
|
||||
"github.com/metacubex/mihomo/component/resolver"
|
||||
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"
|
||||
|
||||
shadowsocks "github.com/metacubex/sing-shadowsocks2"
|
||||
"github.com/sagernet/sing/common/bufio"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
"github.com/sagernet/sing/common/uot"
|
||||
"github.com/metacubex/sing/common/bufio"
|
||||
M "github.com/metacubex/sing/common/metadata"
|
||||
"github.com/metacubex/sing/common/uot"
|
||||
)
|
||||
|
||||
type ShadowSocks struct {
|
||||
@@ -37,6 +35,7 @@ type ShadowSocks struct {
|
||||
gostOption *gost.Option
|
||||
shadowTLSOption *shadowtls.ShadowTLSOption
|
||||
restlsConfig *restls.Config
|
||||
kcptunClient *kcptun.Client
|
||||
}
|
||||
|
||||
type ShadowSocksOption struct {
|
||||
@@ -64,7 +63,10 @@ type v2rayObfsOption struct {
|
||||
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"`
|
||||
@@ -77,18 +79,24 @@ type gostObfsOption struct {
|
||||
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 {
|
||||
@@ -98,6 +106,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"`
|
||||
RateLimit int `obfs:"ratelimit,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"`
|
||||
FrameSize int `obfs:"framesize,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, err error) {
|
||||
useEarly := false
|
||||
@@ -154,19 +190,28 @@ func (ss *ShadowSocks) StreamConnContext(ctx context.Context, c net.Conn, metada
|
||||
}
|
||||
|
||||
// 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) {
|
||||
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
|
||||
}
|
||||
|
||||
// DialContextWithDialer implements C.ProxyAdapter
|
||||
func (ss *ShadowSocks) DialContextWithDialer(ctx context.Context, dialer C.Dialer, metadata *C.Metadata) (_ C.Conn, err error) {
|
||||
if len(ss.option.DialerProxy) > 0 {
|
||||
dialer, err = proxydialer.NewByName(ss.option.DialerProxy, dialer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pc, err := ss.dialer.ListenPacket(ctx, "udp", "", addr.AddrPort())
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return pc, addr, nil
|
||||
})
|
||||
} else {
|
||||
c, err = ss.dialer.DialContext(ctx, "tcp", ss.addr)
|
||||
}
|
||||
c, err := dialer.DialContext(ctx, "tcp", ss.addr)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s connect error: %w", ss.addr, err)
|
||||
}
|
||||
@@ -180,31 +225,23 @@ 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)
|
||||
}
|
||||
|
||||
// ListenPacketWithDialer implements C.ProxyAdapter
|
||||
func (ss *ShadowSocks) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, metadata *C.Metadata) (_ C.PacketConn, err error) {
|
||||
func (ss *ShadowSocks) ListenPacketContext(ctx context.Context, metadata *C.Metadata) (C.PacketConn, error) {
|
||||
if ss.option.UDPOverTCP {
|
||||
tcpConn, err := ss.DialContextWithDialer(ctx, dialer, metadata)
|
||||
tcpConn, err := ss.DialContext(ctx, metadata)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ss.ListenPacketOnStreamConn(ctx, tcpConn, metadata)
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
pc, err := dialer.ListenPacket(ctx, "udp", "", addr.AddrPort())
|
||||
pc, err := ss.dialer.ListenPacket(ctx, "udp", "", addr.AddrPort())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -212,11 +249,6 @@ func (ss *ShadowSocks) ListenPacketWithDialer(ctx context.Context, dialer C.Dial
|
||||
return newPacketConn(pc, ss), nil
|
||||
}
|
||||
|
||||
// SupportWithDialer implements C.ProxyAdapter
|
||||
func (ss *ShadowSocks) SupportWithDialer() C.NetWork {
|
||||
return C.ALLNet
|
||||
}
|
||||
|
||||
// ProxyInfo implements C.ProxyAdapter
|
||||
func (ss *ShadowSocks) ProxyInfo() C.ProxyInfo {
|
||||
info := ss.Base.ProxyInfo()
|
||||
@@ -227,15 +259,9 @@ func (ss *ShadowSocks) ProxyInfo() C.ProxyInfo {
|
||||
// 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(N.NewThreadSafePacketConn(uot.NewConn(c, uot.Request{Destination: destination})), ss), nil
|
||||
@@ -251,10 +277,18 @@ 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 cipher: %s initialize error: %w", addr, option.Cipher, err)
|
||||
@@ -265,6 +299,7 @@ func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) {
|
||||
var obfsOption *simpleObfsOption
|
||||
var shadowTLSOpt *shadowtls.ShadowTLSOption
|
||||
var restlsConfig *restls.Config
|
||||
var kcptunClient *kcptun.Client
|
||||
obfsMode := ""
|
||||
|
||||
decoder := structure.NewDecoder(structure.Option{TagName: "obfs", WeaklyTypedInput: true})
|
||||
@@ -302,6 +337,14 @@ func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) {
|
||||
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}
|
||||
@@ -324,6 +367,14 @@ func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) {
|
||||
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
|
||||
@@ -338,10 +389,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{}
|
||||
@@ -354,6 +413,41 @@ func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) {
|
||||
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,
|
||||
RateLimit: kcptunOpt.RateLimit,
|
||||
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,
|
||||
FrameSize: kcptunOpt.FrameSize,
|
||||
StreamBuf: kcptunOpt.StreamBuf,
|
||||
KeepAlive: kcptunOpt.KeepAlive,
|
||||
})
|
||||
option.UDPOverTCP = true // must open uot
|
||||
}
|
||||
switch option.UDPOverTCPVersion {
|
||||
case uot.Version, uot.LegacyVersion:
|
||||
@@ -363,17 +457,18 @@ func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) {
|
||||
return nil, fmt.Errorf("ss %s unknown udp over tcp protocol version: %d", addr, option.UDPOverTCPVersion)
|
||||
}
|
||||
|
||||
return &ShadowSocks{
|
||||
outbound := &ShadowSocks{
|
||||
Base: &Base{
|
||||
name: option.Name,
|
||||
addr: addr,
|
||||
tp: C.Shadowsocks,
|
||||
pdName: option.ProviderName,
|
||||
udp: option.UDP,
|
||||
tfo: option.TFO,
|
||||
mpTcp: option.MPTCP,
|
||||
iface: option.Interface,
|
||||
rmark: option.RoutingMark,
|
||||
prefer: C.NewDNSPrefer(option.IPVersion),
|
||||
prefer: option.IPVersion,
|
||||
},
|
||||
method: method,
|
||||
|
||||
@@ -384,5 +479,8 @@ func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) {
|
||||
obfsOption: obfsOption,
|
||||
shadowTLSOption: shadowTLSOpt,
|
||||
restlsConfig: restlsConfig,
|
||||
}, nil
|
||||
kcptunClient: kcptunClient,
|
||||
}
|
||||
outbound.dialer = option.NewDialer(outbound.DialOptions())
|
||||
return outbound, nil
|
||||
}
|
||||
|
||||
@@ -8,8 +8,6 @@ import (
|
||||
"strconv"
|
||||
|
||||
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"
|
||||
@@ -67,19 +65,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)
|
||||
}
|
||||
|
||||
// DialContextWithDialer implements C.ProxyAdapter
|
||||
func (ssr *ShadowSocksR) DialContextWithDialer(ctx context.Context, dialer C.Dialer, metadata *C.Metadata) (_ C.Conn, err error) {
|
||||
if len(ssr.option.DialerProxy) > 0 {
|
||||
dialer, err = proxydialer.NewByName(ssr.option.DialerProxy, dialer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
c, err := dialer.DialContext(ctx, "tcp", ssr.addr)
|
||||
func (ssr *ShadowSocksR) DialContext(ctx context.Context, metadata *C.Metadata) (_ C.Conn, err error) {
|
||||
c, err := ssr.dialer.DialContext(ctx, "tcp", ssr.addr)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s connect error: %w", ssr.addr, err)
|
||||
}
|
||||
@@ -93,24 +80,16 @@ 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)
|
||||
}
|
||||
|
||||
// ListenPacketWithDialer implements C.ProxyAdapter
|
||||
func (ssr *ShadowSocksR) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, metadata *C.Metadata) (_ C.PacketConn, err error) {
|
||||
if len(ssr.option.DialerProxy) > 0 {
|
||||
dialer, err = proxydialer.NewByName(ssr.option.DialerProxy, dialer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
func (ssr *ShadowSocksR) ListenPacketContext(ctx context.Context, metadata *C.Metadata) (C.PacketConn, error) {
|
||||
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
|
||||
}
|
||||
|
||||
pc, err := dialer.ListenPacket(ctx, "udp", "", addr.AddrPort())
|
||||
pc, err := ssr.dialer.ListenPacket(ctx, "udp", "", addr.AddrPort())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -120,11 +99,6 @@ func (ssr *ShadowSocksR) ListenPacketWithDialer(ctx context.Context, dialer C.Di
|
||||
return newPacketConn(&ssrPacketConn{EnhancePacketConn: epc, rAddr: addr}, ssr), nil
|
||||
}
|
||||
|
||||
// SupportWithDialer implements C.ProxyAdapter
|
||||
func (ssr *ShadowSocksR) SupportWithDialer() C.NetWork {
|
||||
return C.ALLNet
|
||||
}
|
||||
|
||||
// ProxyInfo implements C.ProxyAdapter
|
||||
func (ssr *ShadowSocksR) ProxyInfo() C.ProxyInfo {
|
||||
info := ssr.Base.ProxyInfo()
|
||||
@@ -183,23 +157,26 @@ func NewShadowSocksR(option ShadowSocksROption) (*ShadowSocksR, error) {
|
||||
return nil, fmt.Errorf("ssr %s initialize protocol error: %w", addr, err)
|
||||
}
|
||||
|
||||
return &ShadowSocksR{
|
||||
outbound := &ShadowSocksR{
|
||||
Base: &Base{
|
||||
name: option.Name,
|
||||
addr: addr,
|
||||
tp: C.ShadowsocksR,
|
||||
pdName: option.ProviderName,
|
||||
udp: option.UDP,
|
||||
tfo: option.TFO,
|
||||
mpTcp: option.MPTCP,
|
||||
iface: option.Interface,
|
||||
rmark: option.RoutingMark,
|
||||
prefer: C.NewDNSPrefer(option.IPVersion),
|
||||
prefer: option.IPVersion,
|
||||
},
|
||||
option: &option,
|
||||
cipher: coreCiph,
|
||||
obfs: obfs,
|
||||
protocol: protocol,
|
||||
}, nil
|
||||
}
|
||||
outbound.dialer = option.NewDialer(outbound.DialOptions())
|
||||
return outbound, nil
|
||||
}
|
||||
|
||||
type ssrPacketConn struct {
|
||||
@@ -249,13 +226,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
|
||||
|
||||
@@ -2,24 +2,20 @@ package outbound
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
CN "github.com/metacubex/mihomo/common/net"
|
||||
"github.com/metacubex/mihomo/component/dialer"
|
||||
N "github.com/metacubex/mihomo/common/net"
|
||||
"github.com/metacubex/mihomo/component/proxydialer"
|
||||
"github.com/metacubex/mihomo/component/resolver"
|
||||
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"
|
||||
mux "github.com/metacubex/sing-mux"
|
||||
E "github.com/metacubex/sing/common/exceptions"
|
||||
M "github.com/metacubex/sing/common/metadata"
|
||||
)
|
||||
|
||||
type SingMux struct {
|
||||
ProxyAdapter
|
||||
client *mux.Client
|
||||
dialer proxydialer.SingDialer
|
||||
onlyTcp bool
|
||||
}
|
||||
|
||||
@@ -41,9 +37,7 @@ type BrutalOption struct {
|
||||
Down string `proxy:"down,omitempty"`
|
||||
}
|
||||
|
||||
func (s *SingMux) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) {
|
||||
options := s.ProxyAdapter.DialOptions(opts...)
|
||||
s.dialer.SetDialer(dialer.NewDialer(options...))
|
||||
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
|
||||
@@ -51,22 +45,13 @@ func (s *SingMux) DialContext(ctx context.Context, metadata *C.Metadata, opts ..
|
||||
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.ProxyAdapter.DialOptions(opts...)
|
||||
s.dialer.SetDialer(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
|
||||
@@ -74,7 +59,7 @@ func (s *SingMux) ListenPacketContext(ctx context.Context, metadata *C.Metadata,
|
||||
if pc == nil {
|
||||
return nil, E.New("packetConn is nil")
|
||||
}
|
||||
return newPacketConn(CN.NewThreadSafePacketConn(pc), s), nil
|
||||
return newPacketConn(N.NewThreadSafePacketConn(pc), s), nil
|
||||
}
|
||||
|
||||
func (s *SingMux) SupportUDP() bool {
|
||||
@@ -109,7 +94,7 @@ func NewSingMux(option SingMuxOption, proxy ProxyAdapter) (ProxyAdapter, error)
|
||||
// TODO
|
||||
// "TCP Brutal is only supported on Linux-based systems"
|
||||
|
||||
singDialer := proxydialer.NewSingDialer(proxy, dialer.NewDialer(), option.Statistic)
|
||||
singDialer := proxydialer.NewSingDialer(proxydialer.New(proxy, option.Statistic))
|
||||
client, err := mux.NewClient(mux.Options{
|
||||
Dialer: singDialer,
|
||||
Logger: log.SingLogger,
|
||||
@@ -130,7 +115,6 @@ func NewSingMux(option SingMuxOption, proxy ProxyAdapter) (ProxyAdapter, error)
|
||||
outbound := &SingMux{
|
||||
ProxyAdapter: proxy,
|
||||
client: client,
|
||||
dialer: singDialer,
|
||||
onlyTcp: option.OnlyTcp,
|
||||
}
|
||||
return outbound, nil
|
||||
|
||||
@@ -8,8 +8,6 @@ import (
|
||||
|
||||
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"
|
||||
@@ -75,8 +73,8 @@ func (s *Snell) writeHeaderContext(ctx context.Context, c net.Conn, metadata *C.
|
||||
}
|
||||
|
||||
// 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 && dialer.IsZeroOptions(opts) {
|
||||
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
|
||||
@@ -89,18 +87,7 @@ func (s *Snell) DialContext(ctx context.Context, metadata *C.Metadata, opts ...d
|
||||
return NewConn(c, s), err
|
||||
}
|
||||
|
||||
return s.DialContextWithDialer(ctx, dialer.NewDialer(s.Base.DialOptions(opts...)...), metadata)
|
||||
}
|
||||
|
||||
// DialContextWithDialer implements C.ProxyAdapter
|
||||
func (s *Snell) DialContextWithDialer(ctx context.Context, dialer C.Dialer, metadata *C.Metadata) (_ C.Conn, err error) {
|
||||
if len(s.option.DialerProxy) > 0 {
|
||||
dialer, err = proxydialer.NewByName(s.option.DialerProxy, dialer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
c, err := dialer.DialContext(ctx, "tcp", s.addr)
|
||||
c, err := s.dialer.DialContext(ctx, "tcp", s.addr)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s connect error: %w", s.addr, err)
|
||||
}
|
||||
@@ -114,20 +101,12 @@ 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)
|
||||
}
|
||||
|
||||
// ListenPacketWithDialer implements C.ProxyAdapter
|
||||
func (s *Snell) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, metadata *C.Metadata) (C.PacketConn, error) {
|
||||
func (s *Snell) ListenPacketContext(ctx context.Context, metadata *C.Metadata) (C.PacketConn, error) {
|
||||
var err error
|
||||
if len(s.option.DialerProxy) > 0 {
|
||||
dialer, err = proxydialer.NewByName(s.option.DialerProxy, dialer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = s.ResolveUDP(ctx, metadata); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c, err := dialer.DialContext(ctx, "tcp", s.addr)
|
||||
c, err := s.dialer.DialContext(ctx, "tcp", s.addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -138,11 +117,6 @@ func (s *Snell) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, met
|
||||
return newPacketConn(pc, s), nil
|
||||
}
|
||||
|
||||
// SupportWithDialer implements C.ProxyAdapter
|
||||
func (s *Snell) SupportWithDialer() C.NetWork {
|
||||
return C.ALLNet
|
||||
}
|
||||
|
||||
// SupportUOT implements C.ProxyAdapter
|
||||
func (s *Snell) SupportUOT() bool {
|
||||
return true
|
||||
@@ -191,30 +165,24 @@ func NewSnell(option SnellOption) (*Snell, error) {
|
||||
name: option.Name,
|
||||
addr: addr,
|
||||
tp: C.Snell,
|
||||
pdName: option.ProviderName,
|
||||
udp: option.UDP,
|
||||
tfo: option.TFO,
|
||||
mpTcp: option.MPTCP,
|
||||
iface: option.Interface,
|
||||
rmark: option.RoutingMark,
|
||||
prefer: C.NewDNSPrefer(option.IPVersion),
|
||||
prefer: option.IPVersion,
|
||||
},
|
||||
option: &option,
|
||||
psk: psk,
|
||||
obfsOption: obfsOption,
|
||||
version: option.Version,
|
||||
}
|
||||
s.dialer = option.NewDialer(s.DialOptions())
|
||||
|
||||
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()...)
|
||||
if len(s.option.DialerProxy) > 0 {
|
||||
cDialer, err = proxydialer.NewByName(s.option.DialerProxy, cDialer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
c, err := cDialer.DialContext(ctx, "tcp", addr)
|
||||
c, err := s.dialer.DialContext(ctx, "tcp", addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ package outbound
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
@@ -12,10 +11,10 @@ import (
|
||||
|
||||
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"
|
||||
|
||||
"github.com/metacubex/tls"
|
||||
)
|
||||
|
||||
type Socks5 struct {
|
||||
@@ -39,6 +38,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
|
||||
@@ -66,19 +67,8 @@ func (ss *Socks5) StreamConnContext(ctx context.Context, c net.Conn, metadata *C
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
// DialContextWithDialer implements C.ProxyAdapter
|
||||
func (ss *Socks5) DialContextWithDialer(ctx context.Context, dialer C.Dialer, metadata *C.Metadata) (_ C.Conn, err error) {
|
||||
if len(ss.option.DialerProxy) > 0 {
|
||||
dialer, err = proxydialer.NewByName(ss.option.DialerProxy, dialer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
c, err := dialer.DialContext(ctx, "tcp", ss.addr)
|
||||
func (ss *Socks5) DialContext(ctx context.Context, metadata *C.Metadata) (_ C.Conn, err error) {
|
||||
c, err := ss.dialer.DialContext(ctx, "tcp", ss.addr)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s connect error: %w", ss.addr, err)
|
||||
}
|
||||
@@ -95,21 +85,12 @@ func (ss *Socks5) DialContextWithDialer(ctx context.Context, dialer C.Dialer, me
|
||||
return NewConn(c, ss), nil
|
||||
}
|
||||
|
||||
// SupportWithDialer implements C.ProxyAdapter
|
||||
func (ss *Socks5) SupportWithDialer() C.NetWork {
|
||||
return C.TCP
|
||||
}
|
||||
|
||||
// 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...)...)
|
||||
if len(ss.option.DialerProxy) > 0 {
|
||||
cDialer, err = proxydialer.NewByName(ss.option.DialerProxy, cDialer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
func (ss *Socks5) ListenPacketContext(ctx context.Context, metadata *C.Metadata) (_ C.PacketConn, err error) {
|
||||
if err = ss.ResolveUDP(ctx, metadata); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c, err := cDialer.DialContext(ctx, "tcp", ss.addr)
|
||||
c, err := ss.dialer.DialContext(ctx, "tcp", ss.addr)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("%s connect error: %w", ss.addr, err)
|
||||
return
|
||||
@@ -156,7 +137,7 @@ func (ss *Socks5) ListenPacketContext(ctx context.Context, metadata *C.Metadata,
|
||||
bindUDPAddr.IP = serverAddr.IP
|
||||
}
|
||||
|
||||
pc, err := cDialer.ListenPacket(ctx, "udp", "", bindUDPAddr.AddrPort())
|
||||
pc, err := ss.dialer.ListenPacket(ctx, "udp", "", bindUDPAddr.AddrPort())
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -190,29 +171,33 @@ func (ss *Socks5) clientHandshakeContext(ctx context.Context, c net.Conn, addr s
|
||||
func NewSocks5(option Socks5Option) (*Socks5, error) {
|
||||
var tlsConfig *tls.Config
|
||||
if option.TLS {
|
||||
tlsConfig = &tls.Config{
|
||||
InsecureSkipVerify: option.SkipCertVerify,
|
||||
ServerName: option.Server,
|
||||
}
|
||||
|
||||
var err error
|
||||
tlsConfig, err = ca.GetSpecifiedFingerprintTLSConfig(tlsConfig, option.Fingerprint)
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
return &Socks5{
|
||||
outbound := &Socks5{
|
||||
Base: &Base{
|
||||
name: option.Name,
|
||||
addr: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)),
|
||||
tp: C.Socks5,
|
||||
pdName: option.ProviderName,
|
||||
udp: option.UDP,
|
||||
tfo: option.TFO,
|
||||
mpTcp: option.MPTCP,
|
||||
iface: option.Interface,
|
||||
rmark: option.RoutingMark,
|
||||
prefer: C.NewDNSPrefer(option.IPVersion),
|
||||
prefer: option.IPVersion,
|
||||
},
|
||||
option: &option,
|
||||
user: option.UserName,
|
||||
@@ -220,7 +205,9 @@ func NewSocks5(option Socks5Option) (*Socks5, error) {
|
||||
tls: option.TLS,
|
||||
skipCertVerify: option.SkipCertVerify,
|
||||
tlsConfig: tlsConfig,
|
||||
}, nil
|
||||
}
|
||||
outbound.dialer = option.NewDialer(outbound.DialOptions())
|
||||
return outbound, nil
|
||||
}
|
||||
|
||||
type socksPacketConn struct {
|
||||
|
||||
@@ -12,8 +12,6 @@ import (
|
||||
"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"
|
||||
@@ -43,15 +41,8 @@ type SshOption struct {
|
||||
HostKeyAlgorithms []string `proxy:"host-key-algorithms,omitempty"`
|
||||
}
|
||||
|
||||
func (s *Ssh) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) {
|
||||
var cDialer C.Dialer = dialer.NewDialer(s.Base.DialOptions(opts...)...)
|
||||
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)
|
||||
func (s *Ssh) DialContext(ctx context.Context, metadata *C.Metadata) (_ C.Conn, err error) {
|
||||
client, err := s.connect(ctx, s.addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -63,13 +54,13 @@ func (s *Ssh) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dia
|
||||
return NewConn(c, s), nil
|
||||
}
|
||||
|
||||
func (s *Ssh) connect(ctx context.Context, cDialer C.Dialer, addr string) (client *ssh.Client, err error) {
|
||||
func (s *Ssh) connect(ctx context.Context, 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)
|
||||
c, err := s.dialer.DialContext(ctx, "tcp", addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -136,7 +127,11 @@ func NewSsh(option SshOption) (*Ssh, error) {
|
||||
if strings.Contains(option.PrivateKey, "PRIVATE KEY") {
|
||||
b = []byte(option.PrivateKey)
|
||||
} else {
|
||||
b, err = os.ReadFile(C.Path.Resolve(option.PrivateKey))
|
||||
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
|
||||
}
|
||||
@@ -191,14 +186,15 @@ func NewSsh(option SshOption) (*Ssh, error) {
|
||||
name: option.Name,
|
||||
addr: addr,
|
||||
tp: C.Ssh,
|
||||
pdName: option.ProviderName,
|
||||
udp: false,
|
||||
iface: option.Interface,
|
||||
rmark: option.RoutingMark,
|
||||
prefer: C.NewDNSPrefer(option.IPVersion),
|
||||
prefer: option.IPVersion,
|
||||
},
|
||||
option: &option,
|
||||
config: &config,
|
||||
}
|
||||
|
||||
outbound.dialer = option.NewDialer(outbound.DialOptions())
|
||||
return outbound, nil
|
||||
}
|
||||
|
||||
436
adapter/outbound/sudoku.go
Normal file
436
adapter/outbound/sudoku.go
Normal file
@@ -0,0 +1,436 @@
|
||||
package outbound
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
N "github.com/metacubex/mihomo/common/net"
|
||||
C "github.com/metacubex/mihomo/constant"
|
||||
"github.com/metacubex/mihomo/transport/sudoku"
|
||||
)
|
||||
|
||||
type Sudoku struct {
|
||||
*Base
|
||||
option *SudokuOption
|
||||
baseConf sudoku.ProtocolConfig
|
||||
|
||||
httpMaskMu sync.Mutex
|
||||
httpMaskClient *sudoku.HTTPMaskTunnelClient
|
||||
|
||||
muxMu sync.Mutex
|
||||
muxClient *sudoku.MultiplexClient
|
||||
muxBackoffUntil time.Time
|
||||
muxLastErr error
|
||||
}
|
||||
|
||||
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"
|
||||
EnablePureDownlink *bool `proxy:"enable-pure-downlink,omitempty"`
|
||||
HTTPMask bool `proxy:"http-mask,omitempty"`
|
||||
HTTPMaskMode string `proxy:"http-mask-mode,omitempty"` // "legacy" (default), "stream", "poll", "auto"
|
||||
HTTPMaskTLS bool `proxy:"http-mask-tls,omitempty"` // only for http-mask-mode stream/poll/auto
|
||||
HTTPMaskHost string `proxy:"http-mask-host,omitempty"` // optional Host/SNI override (domain or domain:port)
|
||||
PathRoot string `proxy:"path-root,omitempty"` // optional first-level path prefix for HTTP tunnel endpoints
|
||||
HTTPMaskMultiplex string `proxy:"http-mask-multiplex,omitempty"` // "off" (default), "auto" (reuse h1/h2), "on" (single tunnel, multi-target)
|
||||
CustomTable string `proxy:"custom-table,omitempty"` // optional custom byte layout, e.g. xpxvvpvv
|
||||
CustomTables []string `proxy:"custom-tables,omitempty"` // optional table rotation patterns, overrides custom-table when non-empty
|
||||
}
|
||||
|
||||
// DialContext implements C.ProxyAdapter
|
||||
func (s *Sudoku) DialContext(ctx context.Context, metadata *C.Metadata) (_ C.Conn, err error) {
|
||||
cfg, err := s.buildConfig(metadata)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
muxMode := normalizeHTTPMaskMultiplex(cfg.HTTPMaskMultiplex)
|
||||
if muxMode == "on" && !cfg.DisableHTTPMask && httpTunnelModeEnabled(cfg.HTTPMaskMode) {
|
||||
stream, muxErr := s.dialMultiplex(ctx, cfg.TargetAddress, muxMode)
|
||||
if muxErr == nil {
|
||||
return NewConn(stream, s), nil
|
||||
}
|
||||
return nil, muxErr
|
||||
}
|
||||
|
||||
c, err := s.dialAndHandshake(ctx, cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func() { safeConnClose(c, err) }()
|
||||
|
||||
addrBuf, err := sudoku.EncodeAddress(cfg.TargetAddress)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("encode target address failed: %w", err)
|
||||
}
|
||||
|
||||
if _, err = c.Write(addrBuf); err != nil {
|
||||
return nil, fmt.Errorf("send target address failed: %w", err)
|
||||
}
|
||||
|
||||
return NewConn(c, s), nil
|
||||
}
|
||||
|
||||
// ListenPacketContext implements C.ProxyAdapter
|
||||
func (s *Sudoku) ListenPacketContext(ctx context.Context, metadata *C.Metadata) (C.PacketConn, error) {
|
||||
if err := s.ResolveUDP(ctx, metadata); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cfg, err := s.buildConfig(metadata)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
c, err := s.dialAndHandshake(ctx, cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = sudoku.WritePreface(c); err != nil {
|
||||
_ = c.Close()
|
||||
return nil, fmt.Errorf("send uot preface failed: %w", err)
|
||||
}
|
||||
|
||||
return newPacketConn(N.NewThreadSafePacketConn(sudoku.NewUoTPacketConn(c)), s), nil
|
||||
}
|
||||
|
||||
// SupportUOT implements C.ProxyAdapter
|
||||
func (s *Sudoku) SupportUOT() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// 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) (*sudoku.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 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")
|
||||
}
|
||||
|
||||
defaultConf := sudoku.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
|
||||
}
|
||||
enablePureDownlink := defaultConf.EnablePureDownlink
|
||||
if option.EnablePureDownlink != nil {
|
||||
enablePureDownlink = *option.EnablePureDownlink
|
||||
}
|
||||
|
||||
baseConf := sudoku.ProtocolConfig{
|
||||
ServerAddress: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)),
|
||||
Key: option.Key,
|
||||
AEADMethod: defaultConf.AEADMethod,
|
||||
PaddingMin: paddingMin,
|
||||
PaddingMax: paddingMax,
|
||||
EnablePureDownlink: enablePureDownlink,
|
||||
HandshakeTimeoutSeconds: defaultConf.HandshakeTimeoutSeconds,
|
||||
DisableHTTPMask: !option.HTTPMask,
|
||||
HTTPMaskMode: defaultConf.HTTPMaskMode,
|
||||
HTTPMaskTLSEnabled: option.HTTPMaskTLS,
|
||||
HTTPMaskHost: option.HTTPMaskHost,
|
||||
HTTPMaskPathRoot: strings.TrimSpace(option.PathRoot),
|
||||
HTTPMaskMultiplex: defaultConf.HTTPMaskMultiplex,
|
||||
}
|
||||
if option.HTTPMaskMode != "" {
|
||||
baseConf.HTTPMaskMode = option.HTTPMaskMode
|
||||
}
|
||||
if option.HTTPMaskMultiplex != "" {
|
||||
baseConf.HTTPMaskMultiplex = option.HTTPMaskMultiplex
|
||||
}
|
||||
tables, err := sudoku.NewTablesWithCustomPatterns(sudoku.ClientAEADSeed(option.Key), tableType, option.CustomTable, option.CustomTables)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("build table(s) failed: %w", err)
|
||||
}
|
||||
if len(tables) == 1 {
|
||||
baseConf.Table = tables[0]
|
||||
} else {
|
||||
baseConf.Tables = tables
|
||||
}
|
||||
if option.AEADMethod != "" {
|
||||
baseConf.AEADMethod = option.AEADMethod
|
||||
}
|
||||
|
||||
outbound := &Sudoku{
|
||||
Base: &Base{
|
||||
name: option.Name,
|
||||
addr: baseConf.ServerAddress,
|
||||
tp: C.Sudoku,
|
||||
pdName: option.ProviderName,
|
||||
udp: true,
|
||||
tfo: option.TFO,
|
||||
mpTcp: option.MPTCP,
|
||||
iface: option.Interface,
|
||||
rmark: option.RoutingMark,
|
||||
prefer: option.IPVersion,
|
||||
},
|
||||
option: &option,
|
||||
baseConf: baseConf,
|
||||
}
|
||||
outbound.dialer = option.NewDialer(outbound.DialOptions())
|
||||
return outbound, nil
|
||||
}
|
||||
|
||||
func (s *Sudoku) Close() error {
|
||||
s.resetMuxClient()
|
||||
s.resetHTTPMaskClient()
|
||||
return s.Base.Close()
|
||||
}
|
||||
|
||||
func normalizeHTTPMaskMultiplex(mode string) string {
|
||||
switch strings.ToLower(strings.TrimSpace(mode)) {
|
||||
case "", "off":
|
||||
return "off"
|
||||
case "auto":
|
||||
return "auto"
|
||||
case "on":
|
||||
return "on"
|
||||
default:
|
||||
return "off"
|
||||
}
|
||||
}
|
||||
|
||||
func httpTunnelModeEnabled(mode string) bool {
|
||||
switch strings.ToLower(strings.TrimSpace(mode)) {
|
||||
case "stream", "poll", "auto":
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Sudoku) dialAndHandshake(ctx context.Context, cfg *sudoku.ProtocolConfig) (_ net.Conn, err error) {
|
||||
if cfg == nil {
|
||||
return nil, fmt.Errorf("config is required")
|
||||
}
|
||||
|
||||
handshakeCfg := *cfg
|
||||
if !handshakeCfg.DisableHTTPMask && httpTunnelModeEnabled(handshakeCfg.HTTPMaskMode) {
|
||||
handshakeCfg.DisableHTTPMask = true
|
||||
}
|
||||
|
||||
upgrade := func(raw net.Conn) (net.Conn, error) {
|
||||
return sudoku.ClientHandshakeWithOptions(raw, &handshakeCfg, sudoku.ClientHandshakeOptions{})
|
||||
}
|
||||
|
||||
var (
|
||||
c net.Conn
|
||||
handshakeDone bool
|
||||
)
|
||||
if !cfg.DisableHTTPMask && httpTunnelModeEnabled(cfg.HTTPMaskMode) {
|
||||
muxMode := normalizeHTTPMaskMultiplex(cfg.HTTPMaskMultiplex)
|
||||
switch muxMode {
|
||||
case "auto", "on":
|
||||
client, errX := s.getOrCreateHTTPMaskClient(cfg)
|
||||
if errX != nil {
|
||||
return nil, errX
|
||||
}
|
||||
c, err = client.Dial(ctx, upgrade)
|
||||
default:
|
||||
c, err = sudoku.DialHTTPMaskTunnel(ctx, cfg.ServerAddress, cfg, s.dialer.DialContext, upgrade)
|
||||
}
|
||||
if err == nil && c != nil {
|
||||
handshakeDone = true
|
||||
}
|
||||
}
|
||||
if c == nil && err == nil {
|
||||
c, err = s.dialer.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)
|
||||
}
|
||||
|
||||
if !handshakeDone {
|
||||
c, err = sudoku.ClientHandshakeWithOptions(c, &handshakeCfg, sudoku.ClientHandshakeOptions{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func (s *Sudoku) dialMultiplex(ctx context.Context, targetAddress string, mode string) (net.Conn, error) {
|
||||
for attempt := 0; attempt < 2; attempt++ {
|
||||
client, err := s.getOrCreateMuxClient(ctx, mode)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
stream, err := client.Dial(ctx, targetAddress)
|
||||
if err != nil {
|
||||
s.resetMuxClient()
|
||||
continue
|
||||
}
|
||||
|
||||
return stream, nil
|
||||
}
|
||||
return nil, fmt.Errorf("multiplex open stream failed")
|
||||
}
|
||||
|
||||
func (s *Sudoku) getOrCreateMuxClient(ctx context.Context, mode string) (*sudoku.MultiplexClient, error) {
|
||||
if s == nil {
|
||||
return nil, fmt.Errorf("nil adapter")
|
||||
}
|
||||
|
||||
if mode == "auto" {
|
||||
s.muxMu.Lock()
|
||||
backoffUntil := s.muxBackoffUntil
|
||||
lastErr := s.muxLastErr
|
||||
s.muxMu.Unlock()
|
||||
if time.Now().Before(backoffUntil) {
|
||||
return nil, fmt.Errorf("multiplex temporarily disabled: %v", lastErr)
|
||||
}
|
||||
}
|
||||
|
||||
s.muxMu.Lock()
|
||||
if s.muxClient != nil && !s.muxClient.IsClosed() {
|
||||
client := s.muxClient
|
||||
s.muxMu.Unlock()
|
||||
return client, nil
|
||||
}
|
||||
s.muxMu.Unlock()
|
||||
|
||||
s.muxMu.Lock()
|
||||
defer s.muxMu.Unlock()
|
||||
|
||||
if s.muxClient != nil && !s.muxClient.IsClosed() {
|
||||
return s.muxClient, nil
|
||||
}
|
||||
|
||||
baseCfg := s.baseConf
|
||||
baseConn, err := s.dialAndHandshake(ctx, &baseCfg)
|
||||
if err != nil {
|
||||
if mode == "auto" {
|
||||
s.muxLastErr = err
|
||||
s.muxBackoffUntil = time.Now().Add(45 * time.Second)
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
client, err := sudoku.StartMultiplexClient(baseConn)
|
||||
if err != nil {
|
||||
_ = baseConn.Close()
|
||||
if mode == "auto" {
|
||||
s.muxLastErr = err
|
||||
s.muxBackoffUntil = time.Now().Add(45 * time.Second)
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
s.muxClient = client
|
||||
return client, nil
|
||||
}
|
||||
|
||||
func (s *Sudoku) noteMuxFailure(mode string, err error) {
|
||||
if mode != "auto" {
|
||||
return
|
||||
}
|
||||
s.muxMu.Lock()
|
||||
s.muxLastErr = err
|
||||
s.muxBackoffUntil = time.Now().Add(45 * time.Second)
|
||||
s.muxMu.Unlock()
|
||||
}
|
||||
|
||||
func (s *Sudoku) resetMuxClient() {
|
||||
s.muxMu.Lock()
|
||||
defer s.muxMu.Unlock()
|
||||
if s.muxClient != nil {
|
||||
_ = s.muxClient.Close()
|
||||
s.muxClient = nil
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Sudoku) getOrCreateHTTPMaskClient(cfg *sudoku.ProtocolConfig) (*sudoku.HTTPMaskTunnelClient, error) {
|
||||
if s == nil {
|
||||
return nil, fmt.Errorf("nil adapter")
|
||||
}
|
||||
if cfg == nil {
|
||||
return nil, fmt.Errorf("config is required")
|
||||
}
|
||||
|
||||
s.httpMaskMu.Lock()
|
||||
defer s.httpMaskMu.Unlock()
|
||||
|
||||
if s.httpMaskClient != nil {
|
||||
return s.httpMaskClient, nil
|
||||
}
|
||||
|
||||
c, err := sudoku.NewHTTPMaskTunnelClient(cfg.ServerAddress, cfg, s.dialer.DialContext)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s.httpMaskClient = c
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func (s *Sudoku) resetHTTPMaskClient() {
|
||||
s.httpMaskMu.Lock()
|
||||
defer s.httpMaskMu.Unlock()
|
||||
if s.httpMaskClient != nil {
|
||||
s.httpMaskClient.CloseIdleConnections()
|
||||
s.httpMaskClient = nil
|
||||
}
|
||||
}
|
||||
@@ -2,23 +2,23 @@ package outbound
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
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"
|
||||
"github.com/metacubex/mihomo/component/ech"
|
||||
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"
|
||||
|
||||
"github.com/metacubex/http"
|
||||
"github.com/metacubex/tls"
|
||||
)
|
||||
|
||||
type Trojan struct {
|
||||
@@ -32,6 +32,7 @@ type Trojan struct {
|
||||
transport *gun.TransportWrap
|
||||
|
||||
realityConfig *tlsC.RealityConfig
|
||||
echConfig *ech.Config
|
||||
|
||||
ssCipher core.Cipher
|
||||
}
|
||||
@@ -46,8 +47,11 @@ 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"`
|
||||
@@ -77,6 +81,7 @@ func (t *Trojan) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.
|
||||
V2rayHttpUpgrade: t.option.WSOpts.V2rayHttpUpgrade,
|
||||
V2rayHttpUpgradeFastOpen: t.option.WSOpts.V2rayHttpUpgradeFastOpen,
|
||||
ClientFingerprint: t.option.ClientFingerprint,
|
||||
ECHConfig: t.echConfig,
|
||||
Headers: http.Header{},
|
||||
}
|
||||
|
||||
@@ -91,39 +96,45 @@ func (t *Trojan) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.
|
||||
}
|
||||
|
||||
alpn := trojan.DefaultWebsocketALPN
|
||||
if len(t.option.ALPN) != 0 {
|
||||
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
|
||||
tlsConfig := &tls.Config{
|
||||
NextProtos: alpn,
|
||||
MinVersion: tls.VersionTLS12,
|
||||
InsecureSkipVerify: t.option.SkipCertVerify,
|
||||
ServerName: t.option.SNI,
|
||||
}
|
||||
|
||||
wsOpts.TLSConfig, err = ca.GetSpecifiedFingerprintTLSConfig(tlsConfig, t.option.Fingerprint)
|
||||
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.realityConfig)
|
||||
c, err = gun.StreamGunWithConn(c, t.gunTLSConfig, t.gunConfig, t.echConfig, t.realityConfig)
|
||||
default:
|
||||
// default tcp network
|
||||
// handle TLS
|
||||
alpn := trojan.DefaultALPN
|
||||
if len(t.option.ALPN) != 0 {
|
||||
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,
|
||||
})
|
||||
}
|
||||
@@ -165,10 +176,10 @@ func (t *Trojan) writeHeaderContext(ctx context.Context, c net.Conn, metadata *C
|
||||
}
|
||||
|
||||
// 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 && dialer.IsZeroOptions(opts) {
|
||||
if t.transport != nil {
|
||||
c, err = gun.StreamGunWithTransport(t.transport, t.gunConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -184,18 +195,7 @@ func (t *Trojan) DialContext(ctx context.Context, metadata *C.Metadata, opts ...
|
||||
|
||||
return NewConn(c, t), nil
|
||||
}
|
||||
return t.DialContextWithDialer(ctx, dialer.NewDialer(t.Base.DialOptions(opts...)...), metadata)
|
||||
}
|
||||
|
||||
// DialContextWithDialer implements C.ProxyAdapter
|
||||
func (t *Trojan) DialContextWithDialer(ctx context.Context, dialer C.Dialer, metadata *C.Metadata) (_ C.Conn, err error) {
|
||||
if len(t.option.DialerProxy) > 0 {
|
||||
dialer, err = proxydialer.NewByName(t.option.DialerProxy, dialer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
c, err := dialer.DialContext(ctx, "tcp", t.addr)
|
||||
c, err = t.dialer.DialContext(ctx, "tcp", t.addr)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s connect error: %w", t.addr, err)
|
||||
}
|
||||
@@ -213,11 +213,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 && dialer.IsZeroOptions(opts) {
|
||||
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)
|
||||
@@ -234,18 +238,10 @@ func (t *Trojan) ListenPacketContext(ctx context.Context, metadata *C.Metadata,
|
||||
pc := trojan.NewPacketConn(c)
|
||||
return newPacketConn(pc, t), err
|
||||
}
|
||||
return t.ListenPacketWithDialer(ctx, dialer.NewDialer(t.Base.DialOptions(opts...)...), metadata)
|
||||
}
|
||||
|
||||
// ListenPacketWithDialer implements C.ProxyAdapter
|
||||
func (t *Trojan) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, metadata *C.Metadata) (_ C.PacketConn, err error) {
|
||||
if len(t.option.DialerProxy) > 0 {
|
||||
dialer, err = proxydialer.NewByName(t.option.DialerProxy, dialer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = t.ResolveUDP(ctx, metadata); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c, err := dialer.DialContext(ctx, "tcp", t.addr)
|
||||
c, err = t.dialer.DialContext(ctx, "tcp", t.addr)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s connect error: %w", t.addr, err)
|
||||
}
|
||||
@@ -261,17 +257,6 @@ func (t *Trojan) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, me
|
||||
return newPacketConn(pc, t), err
|
||||
}
|
||||
|
||||
// SupportWithDialer implements C.ProxyAdapter
|
||||
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 := trojan.NewPacketConn(c)
|
||||
return newPacketConn(pc, t), err
|
||||
}
|
||||
|
||||
// SupportUOT implements C.ProxyAdapter
|
||||
func (t *Trojan) SupportUOT() bool {
|
||||
return true
|
||||
@@ -295,21 +280,27 @@ func (t *Trojan) Close() error {
|
||||
func NewTrojan(option TrojanOption) (*Trojan, error) {
|
||||
addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port))
|
||||
|
||||
if option.SNI == "" {
|
||||
option.SNI = option.Server
|
||||
}
|
||||
|
||||
t := &Trojan{
|
||||
Base: &Base{
|
||||
name: option.Name,
|
||||
addr: addr,
|
||||
tp: C.Trojan,
|
||||
pdName: option.ProviderName,
|
||||
udp: option.UDP,
|
||||
tfo: option.TFO,
|
||||
mpTcp: option.MPTCP,
|
||||
iface: option.Interface,
|
||||
rmark: option.RoutingMark,
|
||||
prefer: C.NewDNSPrefer(option.IPVersion),
|
||||
prefer: option.IPVersion,
|
||||
},
|
||||
option: &option,
|
||||
hexPassword: trojan.Key(option.Password),
|
||||
}
|
||||
t.dialer = option.NewDialer(t.DialOptions())
|
||||
|
||||
var err error
|
||||
t.realityConfig, err = option.RealityOpts.Parse()
|
||||
@@ -317,6 +308,11 @@ func NewTrojan(option TrojanOption) (*Trojan, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
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")
|
||||
@@ -333,39 +329,34 @@ func NewTrojan(option TrojanOption) (*Trojan, error) {
|
||||
|
||||
if option.Network == "grpc" {
|
||||
dialFn := func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||
var err error
|
||||
var cDialer C.Dialer = dialer.NewDialer(t.Base.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(ctx, "tcp", t.addr)
|
||||
c, err := t.dialer.DialContext(ctx, "tcp", t.addr)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s connect error: %s", t.addr, err.Error())
|
||||
}
|
||||
return c, nil
|
||||
}
|
||||
|
||||
tlsConfig := &tls.Config{
|
||||
NextProtos: option.ALPN,
|
||||
MinVersion: tls.VersionTLS12,
|
||||
InsecureSkipVerify: option.SkipCertVerify,
|
||||
ServerName: option.SNI,
|
||||
}
|
||||
|
||||
var err error
|
||||
tlsConfig, err = ca.GetSpecifiedFingerprintTLSConfig(tlsConfig, option.Fingerprint)
|
||||
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
|
||||
}
|
||||
|
||||
t.transport = gun.NewHTTP2Client(dialFn, tlsConfig, option.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,
|
||||
UserAgent: option.GrpcOpts.GrpcUserAgent,
|
||||
Host: option.SNI,
|
||||
ClientFingerprint: option.ClientFingerprint,
|
||||
}
|
||||
|
||||
@@ -2,8 +2,6 @@ package outbound
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"net"
|
||||
@@ -11,22 +9,24 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/metacubex/mihomo/component/ca"
|
||||
"github.com/metacubex/mihomo/component/dialer"
|
||||
"github.com/metacubex/mihomo/component/proxydialer"
|
||||
"github.com/metacubex/mihomo/component/resolver"
|
||||
"github.com/metacubex/mihomo/component/ech"
|
||||
C "github.com/metacubex/mihomo/constant"
|
||||
"github.com/metacubex/mihomo/transport/tuic"
|
||||
|
||||
"github.com/gofrs/uuid/v5"
|
||||
"github.com/metacubex/quic-go"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
"github.com/sagernet/sing/common/uot"
|
||||
M "github.com/metacubex/sing/common/metadata"
|
||||
"github.com/metacubex/sing/common/uot"
|
||||
"github.com/metacubex/tls"
|
||||
)
|
||||
|
||||
type Tuic struct {
|
||||
*Base
|
||||
option *TuicOption
|
||||
client *tuic.PoolClient
|
||||
|
||||
tlsConfig *tls.Config
|
||||
echConfig *ech.Config
|
||||
}
|
||||
|
||||
type TuicOption struct {
|
||||
@@ -47,31 +47,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"`
|
||||
CWND int `proxy:"cwnd,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)
|
||||
}
|
||||
|
||||
// DialContextWithDialer implements C.ProxyAdapter
|
||||
func (t *Tuic) DialContextWithDialer(ctx context.Context, dialer C.Dialer, metadata *C.Metadata) (C.Conn, error) {
|
||||
conn, err := t.client.DialContextWithDialer(ctx, metadata, dialer, t.dialWithDialer)
|
||||
func (t *Tuic) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
|
||||
conn, err := t.client.DialContext(ctx, metadata)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -79,30 +75,22 @@ 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) {
|
||||
if err = t.ResolveUDP(ctx, metadata); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// ListenPacketWithDialer implements C.ProxyAdapter
|
||||
func (t *Tuic) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, metadata *C.Metadata) (_ C.PacketConn, err error) {
|
||||
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)
|
||||
c, err := t.DialContext(ctx, &uotMetadata)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// tuic uos 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
|
||||
}
|
||||
|
||||
destination := M.SocksaddrFromNet(metadata.UDPAddr())
|
||||
if t.option.UDPOverStreamVersion == uot.LegacyVersion {
|
||||
@@ -111,32 +99,25 @@ func (t *Tuic) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, meta
|
||||
return newPacketConn(uot.NewLazyConn(c, uot.Request{Destination: destination}), t), nil
|
||||
}
|
||||
}
|
||||
pc, err := t.client.ListenPacketWithDialer(ctx, metadata, dialer, t.dialWithDialer)
|
||||
pc, err := t.client.ListenPacket(ctx, metadata)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return newPacketConn(pc, t), nil
|
||||
}
|
||||
|
||||
// SupportWithDialer implements C.ProxyAdapter
|
||||
func (t *Tuic) SupportWithDialer() C.NetWork {
|
||||
return C.ALLNet
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
func (t *Tuic) dial(ctx context.Context) (transport *quic.Transport, addr net.Addr, err error) {
|
||||
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())
|
||||
pc, err = t.dialer.ListenPacket(ctx, "udp", "", udpAddr.AddrPort())
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
@@ -156,17 +137,20 @@ func (t *Tuic) ProxyInfo() C.ProxyInfo {
|
||||
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 err error
|
||||
tlsConfig, err = ca.GetTLSConfig(tlsConfig, option.Fingerprint, option.CustomCA, 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
|
||||
}
|
||||
@@ -248,6 +232,12 @@ func NewTuic(option TuicOption) (*Tuic, error) {
|
||||
tlsConfig.InsecureSkipVerify = true // tls: either ServerName or InsecureSkipVerify must be specified in the tls.Config
|
||||
}
|
||||
|
||||
tlsClientConfig := tlsConfig
|
||||
echConfig, err := option.ECHOpts.Parse()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
switch option.UDPOverStreamVersion {
|
||||
case uot.Version, uot.LegacyVersion:
|
||||
case 0:
|
||||
@@ -261,14 +251,18 @@ func NewTuic(option TuicOption) (*Tuic, error) {
|
||||
name: option.Name,
|
||||
addr: addr,
|
||||
tp: C.Tuic,
|
||||
pdName: option.ProviderName,
|
||||
udp: true,
|
||||
tfo: option.FastOpen,
|
||||
iface: option.Interface,
|
||||
rmark: option.RoutingMark,
|
||||
prefer: C.NewDNSPrefer(option.IPVersion),
|
||||
prefer: option.IPVersion,
|
||||
},
|
||||
option: &option,
|
||||
option: &option,
|
||||
tlsConfig: tlsClientConfig,
|
||||
echConfig: echConfig,
|
||||
}
|
||||
t.dialer = option.NewDialer(t.DialOptions())
|
||||
|
||||
clientMaxOpenStreams := int64(option.MaxOpenStreams)
|
||||
|
||||
@@ -284,7 +278,7 @@ func NewTuic(option TuicOption) (*Tuic, error) {
|
||||
if len(option.Token) > 0 {
|
||||
tkn := tuic.GenTKN(option.Token)
|
||||
clientOption := &tuic.ClientOptionV4{
|
||||
TlsConfig: tlsConfig,
|
||||
TlsConfig: tlsClientConfig,
|
||||
QuicConfig: quicConfig,
|
||||
Token: tkn,
|
||||
UdpRelayMode: udpRelayMode,
|
||||
@@ -297,14 +291,14 @@ func NewTuic(option TuicOption) (*Tuic, error) {
|
||||
CWND: option.CWND,
|
||||
}
|
||||
|
||||
t.client = tuic.NewPoolClientV4(clientOption)
|
||||
t.client = tuic.NewPoolClientV4(clientOption, t.dial)
|
||||
} else {
|
||||
maxUdpRelayPacketSize := option.MaxUdpRelayPacketSize
|
||||
if maxUdpRelayPacketSize > tuic.MaxFragSizeV5 {
|
||||
maxUdpRelayPacketSize = tuic.MaxFragSizeV5
|
||||
}
|
||||
clientOption := &tuic.ClientOptionV5{
|
||||
TlsConfig: tlsConfig,
|
||||
TlsConfig: tlsClientConfig,
|
||||
QuicConfig: quicConfig,
|
||||
Uuid: uuid.FromStringOrNil(option.UUID),
|
||||
Password: option.Password,
|
||||
@@ -316,7 +310,7 @@ func NewTuic(option TuicOption) (*Tuic, error) {
|
||||
CWND: option.CWND,
|
||||
}
|
||||
|
||||
t.client = tuic.NewPoolClientV5(clientOption)
|
||||
t.client = tuic.NewPoolClientV5(clientOption, t.dial)
|
||||
}
|
||||
|
||||
return t, nil
|
||||
|
||||
@@ -39,6 +39,7 @@ func resolveUDPAddr(ctx context.Context, network, address string, prefer C.DNSPr
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var ip netip.Addr
|
||||
switch prefer {
|
||||
case C.IPv4Only:
|
||||
@@ -56,7 +57,17 @@ func resolveUDPAddr(ctx context.Context, network, address string, prefer C.DNSPr
|
||||
}
|
||||
|
||||
ip, port = resolver.LookupIP4P(ip, port)
|
||||
return net.ResolveUDPAddr(network, net.JoinHostPort(ip.String(), 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) {
|
||||
|
||||
@@ -2,37 +2,27 @@ package outbound
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"sync"
|
||||
|
||||
"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/proxydialer"
|
||||
"github.com/metacubex/mihomo/component/resolver"
|
||||
"github.com/metacubex/mihomo/component/ech"
|
||||
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"
|
||||
|
||||
"github.com/metacubex/http"
|
||||
vmessSing "github.com/metacubex/sing-vmess"
|
||||
"github.com/metacubex/sing-vmess/packetaddr"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
)
|
||||
|
||||
const (
|
||||
// max packet length
|
||||
maxLength = 1024 << 3
|
||||
M "github.com/metacubex/sing/common/metadata"
|
||||
"github.com/metacubex/tls"
|
||||
)
|
||||
|
||||
type Vless struct {
|
||||
@@ -40,12 +30,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 {
|
||||
@@ -61,16 +54,19 @@ type VlessOption struct {
|
||||
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"`
|
||||
}
|
||||
@@ -88,6 +84,7 @@ func (v *Vless) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.M
|
||||
V2rayHttpUpgrade: v.option.WSOpts.V2rayHttpUpgrade,
|
||||
V2rayHttpUpgradeFastOpen: v.option.WSOpts.V2rayHttpUpgradeFastOpen,
|
||||
ClientFingerprint: v.option.ClientFingerprint,
|
||||
ECHConfig: v.echConfig,
|
||||
Headers: http.Header{},
|
||||
}
|
||||
|
||||
@@ -98,14 +95,17 @@ 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"},
|
||||
}
|
||||
|
||||
wsOpts.TLSConfig, err = ca.GetSpecifiedFingerprintTLSConfig(tlsConfig, v.option.Fingerprint)
|
||||
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
|
||||
}
|
||||
@@ -151,7 +151,7 @@ func (v *Vless) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.M
|
||||
|
||||
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
|
||||
@@ -170,6 +170,12 @@ func (v *Vless) streamConnContext(ctx context.Context, c net.Conn, metadata *C.M
|
||||
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{
|
||||
@@ -185,9 +191,6 @@ func (v *Vless) streamConnContext(ctx context.Context, c net.Conn, metadata *C.M
|
||||
}
|
||||
}
|
||||
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))
|
||||
}
|
||||
@@ -205,7 +208,10 @@ func (v *Vless) streamTLSConn(ctx context.Context, conn net.Conn, isH2 bool) (ne
|
||||
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,
|
||||
}
|
||||
@@ -225,10 +231,10 @@ func (v *Vless) streamTLSConn(ctx context.Context, conn net.Conn, isH2 bool) (ne
|
||||
}
|
||||
|
||||
// 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 && dialer.IsZeroOptions(opts) {
|
||||
if v.transport != nil {
|
||||
c, err = gun.StreamGunWithTransport(v.transport, v.gunConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -244,18 +250,7 @@ func (v *Vless) DialContext(ctx context.Context, metadata *C.Metadata, opts ...d
|
||||
|
||||
return NewConn(c, v), nil
|
||||
}
|
||||
return v.DialContextWithDialer(ctx, dialer.NewDialer(v.Base.DialOptions(opts...)...), metadata)
|
||||
}
|
||||
|
||||
// DialContextWithDialer implements C.ProxyAdapter
|
||||
func (v *Vless) DialContextWithDialer(ctx context.Context, dialer C.Dialer, metadata *C.Metadata) (_ C.Conn, err error) {
|
||||
if len(v.option.DialerProxy) > 0 {
|
||||
dialer, err = proxydialer.NewByName(v.option.DialerProxy, dialer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
c, err := dialer.DialContext(ctx, "tcp", v.addr)
|
||||
c, err = v.dialer.DialContext(ctx, "tcp", v.addr)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error())
|
||||
}
|
||||
@@ -271,18 +266,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 && dialer.IsZeroOptions(opts) {
|
||||
if v.transport != nil {
|
||||
c, err = gun.StreamGunWithTransport(v.transport, v.gunConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -298,28 +288,12 @@ func (v *Vless) ListenPacketContext(ctx context.Context, metadata *C.Metadata, o
|
||||
|
||||
return v.ListenPacketOnStreamConn(ctx, c, metadata)
|
||||
}
|
||||
return v.ListenPacketWithDialer(ctx, dialer.NewDialer(v.Base.DialOptions(opts...)...), metadata)
|
||||
}
|
||||
|
||||
// ListenPacketWithDialer implements C.ProxyAdapter
|
||||
func (v *Vless) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, metadata *C.Metadata) (_ C.PacketConn, err error) {
|
||||
if len(v.option.DialerProxy) > 0 {
|
||||
dialer, err = proxydialer.NewByName(v.option.DialerProxy, dialer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = v.ResolveUDP(ctx, metadata); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
c, err := dialer.DialContext(ctx, "tcp", v.addr)
|
||||
c, err = v.dialer.DialContext(ctx, "tcp", v.addr)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error())
|
||||
}
|
||||
@@ -335,20 +309,10 @@ func (v *Vless) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, met
|
||||
return v.ListenPacketOnStreamConn(ctx, c, metadata)
|
||||
}
|
||||
|
||||
// SupportWithDialer implements C.ProxyAdapter
|
||||
func (v *Vless) SupportWithDialer() C.NetWork {
|
||||
return C.ALLNet
|
||||
}
|
||||
|
||||
// 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 {
|
||||
@@ -363,12 +327,11 @@ func (v *Vless) ListenPacketOnStreamConn(ctx context.Context, c net.Conn, metada
|
||||
), 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(N.NewThreadSafePacketConn(&vlessPacketConn{Conn: c, rAddr: metadata.UDPAddr()}), v), nil
|
||||
return newPacketConn(N.NewThreadSafePacketConn(v.client.PacketConn(c, metadata.UDPAddr())), v), nil
|
||||
}
|
||||
|
||||
// SupportUOT implements C.ProxyAdapter
|
||||
@@ -419,101 +382,9 @@ func parseVlessAddr(metadata *C.Metadata, xudp bool) *vless.DstAddr {
|
||||
}
|
||||
}
|
||||
|
||||
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]
|
||||
if option.Flow != vless.XRV {
|
||||
return nil, fmt.Errorf("unsupported xtls flow type: %s", option.Flow)
|
||||
@@ -546,23 +417,35 @@ func NewVless(option VlessOption) (*Vless, error) {
|
||||
name: option.Name,
|
||||
addr: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)),
|
||||
tp: C.Vless,
|
||||
pdName: option.ProviderName,
|
||||
udp: option.UDP,
|
||||
xudp: option.XUDP,
|
||||
tfo: option.TFO,
|
||||
mpTcp: option.MPTCP,
|
||||
iface: option.Interface,
|
||||
rmark: option.RoutingMark,
|
||||
prefer: C.NewDNSPrefer(option.IPVersion),
|
||||
prefer: option.IPVersion,
|
||||
},
|
||||
client: client,
|
||||
option: &option,
|
||||
}
|
||||
v.dialer = option.NewDialer(v.DialOptions())
|
||||
|
||||
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 {
|
||||
@@ -570,15 +453,7 @@ func NewVless(option VlessOption) (*Vless, error) {
|
||||
}
|
||||
case "grpc":
|
||||
dialFn := func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||
var err error
|
||||
var cDialer C.Dialer = dialer.NewDialer(v.Base.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(ctx, "tcp", v.addr)
|
||||
c, err := v.dialer.DialContext(ctx, "tcp", v.addr)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error())
|
||||
}
|
||||
@@ -587,6 +462,7 @@ func NewVless(option VlessOption) (*Vless, error) {
|
||||
|
||||
gunConfig := &gun.Config{
|
||||
ServiceName: v.option.GrpcOpts.GrpcServiceName,
|
||||
UserAgent: v.option.GrpcOpts.GrpcUserAgent,
|
||||
Host: v.option.ServerName,
|
||||
ClientFingerprint: v.option.ClientFingerprint,
|
||||
}
|
||||
@@ -595,10 +471,15 @@ func NewVless(option VlessOption) (*Vless, error) {
|
||||
}
|
||||
var tlsConfig *tls.Config
|
||||
if option.TLS {
|
||||
tlsConfig, err = ca.GetSpecifiedFingerprintTLSConfig(&tls.Config{
|
||||
InsecureSkipVerify: v.option.SkipCertVerify,
|
||||
ServerName: v.option.ServerName,
|
||||
}, v.option.Fingerprint)
|
||||
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
|
||||
}
|
||||
@@ -611,7 +492,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
|
||||
|
||||
@@ -2,11 +2,9 @@ package outbound
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
@@ -14,18 +12,18 @@ import (
|
||||
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/proxydialer"
|
||||
"github.com/metacubex/mihomo/component/resolver"
|
||||
"github.com/metacubex/mihomo/component/ech"
|
||||
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"
|
||||
|
||||
"github.com/metacubex/http"
|
||||
vmess "github.com/metacubex/sing-vmess"
|
||||
"github.com/metacubex/sing-vmess/packetaddr"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
M "github.com/metacubex/sing/common/metadata"
|
||||
"github.com/metacubex/tls"
|
||||
)
|
||||
|
||||
var ErrUDPRemoteAddrMismatch = errors.New("udp packet dropped due to mismatched remote address")
|
||||
@@ -41,6 +39,7 @@ type Vmess struct {
|
||||
transport *gun.TransportWrap
|
||||
|
||||
realityConfig *tlsC.RealityConfig
|
||||
echConfig *ech.Config
|
||||
}
|
||||
|
||||
type VmessOption struct {
|
||||
@@ -57,7 +56,10 @@ type VmessOption struct {
|
||||
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"`
|
||||
@@ -84,6 +86,7 @@ type HTTP2Options struct {
|
||||
|
||||
type GrpcOptions struct {
|
||||
GrpcServiceName string `proxy:"grpc-service-name,omitempty"`
|
||||
GrpcUserAgent string `proxy:"grpc-user-agent,omitempty"`
|
||||
}
|
||||
|
||||
type WSOptions struct {
|
||||
@@ -109,6 +112,7 @@ func (v *Vmess) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.M
|
||||
V2rayHttpUpgrade: v.option.WSOpts.V2rayHttpUpgrade,
|
||||
V2rayHttpUpgradeFastOpen: v.option.WSOpts.V2rayHttpUpgradeFastOpen,
|
||||
ClientFingerprint: v.option.ClientFingerprint,
|
||||
ECHConfig: v.echConfig,
|
||||
Headers: http.Header{},
|
||||
}
|
||||
|
||||
@@ -120,13 +124,16 @@ 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"},
|
||||
}
|
||||
|
||||
wsOpts.TLSConfig, err = ca.GetSpecifiedFingerprintTLSConfig(tlsConfig, v.option.Fingerprint)
|
||||
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
|
||||
}
|
||||
@@ -146,6 +153,7 @@ func (v *Vmess) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.M
|
||||
Host: host,
|
||||
SkipCertVerify: v.option.SkipCertVerify,
|
||||
ClientFingerprint: v.option.ClientFingerprint,
|
||||
ECH: v.echConfig,
|
||||
Reality: v.realityConfig,
|
||||
NextProtos: v.option.ALPN,
|
||||
}
|
||||
@@ -174,6 +182,8 @@ func (v *Vmess) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.M
|
||||
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,
|
||||
@@ -195,7 +205,7 @@ func (v *Vmess) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.M
|
||||
|
||||
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 {
|
||||
@@ -204,7 +214,10 @@ func (v *Vmess) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.M
|
||||
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,
|
||||
}
|
||||
@@ -280,10 +293,10 @@ func (v *Vmess) streamConnContext(ctx context.Context, c net.Conn, metadata *C.M
|
||||
}
|
||||
|
||||
// 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 && dialer.IsZeroOptions(opts) {
|
||||
if v.transport != nil {
|
||||
c, err = gun.StreamGunWithTransport(v.transport, v.gunConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -299,18 +312,7 @@ func (v *Vmess) DialContext(ctx context.Context, metadata *C.Metadata, opts ...d
|
||||
|
||||
return NewConn(c, v), nil
|
||||
}
|
||||
return v.DialContextWithDialer(ctx, dialer.NewDialer(v.Base.DialOptions(opts...)...), metadata)
|
||||
}
|
||||
|
||||
// DialContextWithDialer implements C.ProxyAdapter
|
||||
func (v *Vmess) DialContextWithDialer(ctx context.Context, dialer C.Dialer, metadata *C.Metadata) (_ C.Conn, err error) {
|
||||
if len(v.option.DialerProxy) > 0 {
|
||||
dialer, err = proxydialer.NewByName(v.option.DialerProxy, dialer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
c, err := dialer.DialContext(ctx, "tcp", v.addr)
|
||||
c, err = v.dialer.DialContext(ctx, "tcp", v.addr)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error())
|
||||
}
|
||||
@@ -323,18 +325,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 && dialer.IsZeroOptions(opts) {
|
||||
if v.transport != nil {
|
||||
c, err = gun.StreamGunWithTransport(v.transport, v.gunConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -349,28 +346,12 @@ func (v *Vmess) ListenPacketContext(ctx context.Context, metadata *C.Metadata, o
|
||||
}
|
||||
return v.ListenPacketOnStreamConn(ctx, c, metadata)
|
||||
}
|
||||
return v.ListenPacketWithDialer(ctx, dialer.NewDialer(v.Base.DialOptions(opts...)...), metadata)
|
||||
}
|
||||
|
||||
// ListenPacketWithDialer implements C.ProxyAdapter
|
||||
func (v *Vmess) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, metadata *C.Metadata) (_ C.PacketConn, err error) {
|
||||
if len(v.option.DialerProxy) > 0 {
|
||||
dialer, err = proxydialer.NewByName(v.option.DialerProxy, dialer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = v.ResolveUDP(ctx, metadata); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
c, err := dialer.DialContext(ctx, "tcp", v.addr)
|
||||
c, err = v.dialer.DialContext(ctx, "tcp", v.addr)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error())
|
||||
}
|
||||
@@ -385,11 +366,6 @@ func (v *Vmess) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, met
|
||||
return v.ListenPacketOnStreamConn(ctx, c, metadata)
|
||||
}
|
||||
|
||||
// SupportWithDialer implements C.ProxyAdapter
|
||||
func (v *Vmess) SupportWithDialer() C.NetWork {
|
||||
return C.ALLNet
|
||||
}
|
||||
|
||||
// ProxyInfo implements C.ProxyAdapter
|
||||
func (v *Vmess) ProxyInfo() C.ProxyInfo {
|
||||
info := v.Base.ProxyInfo()
|
||||
@@ -407,13 +383,8 @@ func (v *Vmess) Close() error {
|
||||
|
||||
// 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 {
|
||||
@@ -457,23 +428,30 @@ func NewVmess(option VmessOption) (*Vmess, error) {
|
||||
name: option.Name,
|
||||
addr: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)),
|
||||
tp: C.Vmess,
|
||||
pdName: option.ProviderName,
|
||||
udp: option.UDP,
|
||||
xudp: option.XUDP,
|
||||
tfo: option.TFO,
|
||||
mpTcp: option.MPTCP,
|
||||
iface: option.Interface,
|
||||
rmark: option.RoutingMark,
|
||||
prefer: C.NewDNSPrefer(option.IPVersion),
|
||||
prefer: option.IPVersion,
|
||||
},
|
||||
client: client,
|
||||
option: &option,
|
||||
}
|
||||
v.dialer = option.NewDialer(v.DialOptions())
|
||||
|
||||
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 {
|
||||
@@ -481,15 +459,7 @@ func NewVmess(option VmessOption) (*Vmess, error) {
|
||||
}
|
||||
case "grpc":
|
||||
dialFn := func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||
var err error
|
||||
var cDialer C.Dialer = dialer.NewDialer(v.Base.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(ctx, "tcp", v.addr)
|
||||
c, err := v.dialer.DialContext(ctx, "tcp", v.addr)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error())
|
||||
}
|
||||
@@ -498,6 +468,7 @@ func NewVmess(option VmessOption) (*Vmess, error) {
|
||||
|
||||
gunConfig := &gun.Config{
|
||||
ServiceName: v.option.GrpcOpts.GrpcServiceName,
|
||||
UserAgent: v.option.GrpcOpts.GrpcUserAgent,
|
||||
Host: v.option.ServerName,
|
||||
ClientFingerprint: v.option.ClientFingerprint,
|
||||
}
|
||||
@@ -506,10 +477,15 @@ func NewVmess(option VmessOption) (*Vmess, error) {
|
||||
}
|
||||
var tlsConfig *tls.Config
|
||||
if option.TLS {
|
||||
tlsConfig, err = ca.GetSpecifiedFingerprintTLSConfig(&tls.Config{
|
||||
InsecureSkipVerify: v.option.SkipCertVerify,
|
||||
ServerName: v.option.ServerName,
|
||||
}, v.option.Fingerprint)
|
||||
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
|
||||
}
|
||||
@@ -522,7 +498,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.transport = gun.NewHTTP2Client(dialFn, tlsConfig, v.option.ClientFingerprint, v.echConfig, v.realityConfig)
|
||||
}
|
||||
|
||||
return v, nil
|
||||
|
||||
@@ -4,7 +4,6 @@ import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/netip"
|
||||
@@ -26,9 +25,9 @@ import (
|
||||
wireguard "github.com/metacubex/sing-wireguard"
|
||||
"github.com/metacubex/wireguard-go/device"
|
||||
|
||||
"github.com/sagernet/sing/common/debug"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
"github.com/metacubex/sing/common/debug"
|
||||
E "github.com/metacubex/sing/common/exceptions"
|
||||
M "github.com/metacubex/sing/common/metadata"
|
||||
)
|
||||
|
||||
type wireguardGoDevice interface {
|
||||
@@ -41,7 +40,6 @@ type WireGuard struct {
|
||||
bind *wireguard.ClientBind
|
||||
device wireguardGoDevice
|
||||
tunDevice wireguard.Device
|
||||
dialer proxydialer.SingDialer
|
||||
resolver resolver.Resolver
|
||||
|
||||
initOk atomic.Bool
|
||||
@@ -88,15 +86,26 @@ type WireGuardPeerOption struct {
|
||||
}
|
||||
|
||||
type AmneziaWGOption struct {
|
||||
JC int `proxy:"jc"`
|
||||
JMin int `proxy:"jmin"`
|
||||
JMax int `proxy:"jmax"`
|
||||
S1 int `proxy:"s1"`
|
||||
S2 int `proxy:"s2"`
|
||||
H1 uint32 `proxy:"h1"`
|
||||
H2 uint32 `proxy:"h2"`
|
||||
H3 uint32 `proxy:"h3"`
|
||||
H4 uint32 `proxy:"h4"`
|
||||
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 {
|
||||
@@ -161,13 +170,15 @@ func NewWireGuard(option WireGuardOption) (*WireGuard, error) {
|
||||
name: option.Name,
|
||||
addr: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)),
|
||||
tp: C.WireGuard,
|
||||
pdName: option.ProviderName,
|
||||
udp: option.UDP,
|
||||
iface: option.Interface,
|
||||
rmark: option.RoutingMark,
|
||||
prefer: C.NewDNSPrefer(option.IPVersion),
|
||||
prefer: option.IPVersion,
|
||||
},
|
||||
dialer: proxydialer.NewSlowDownSingDialer(proxydialer.NewByNameSingDialer(option.DialerProxy, dialer.NewDialer()), slowdown.New()),
|
||||
}
|
||||
outbound.dialer = option.NewDialer(outbound.DialOptions())
|
||||
singDialer := proxydialer.NewSlowDownSingDialer(proxydialer.NewSingDialer(outbound.dialer), slowdown.New())
|
||||
|
||||
var reserved [3]uint8
|
||||
if len(option.Reserved) > 0 {
|
||||
@@ -185,7 +196,7 @@ func NewWireGuard(option WireGuardOption) (*WireGuard, error) {
|
||||
outbound.connectAddr = option.Addr()
|
||||
}
|
||||
}
|
||||
outbound.bind = wireguard.NewClientBind(context.Background(), wgSingErrorHandler{outbound.Name()}, outbound.dialer, isConnect, outbound.connectAddr.AddrPort(), reserved)
|
||||
outbound.bind = wireguard.NewClientBind(context.Background(), wgSingErrorHandler{outbound.Name()}, singDialer, isConnect, outbound.connectAddr.AddrPort(), reserved)
|
||||
|
||||
var err error
|
||||
outbound.localPrefixes, err = option.Prefixes()
|
||||
@@ -386,15 +397,66 @@ func (w *WireGuard) genIpcConf(ctx context.Context, updateOnly bool) (string, er
|
||||
if !updateOnly {
|
||||
ipcConf += "private_key=" + w.option.PrivateKey + "\n"
|
||||
if w.option.AmneziaWGOption != nil {
|
||||
ipcConf += "jc=" + strconv.Itoa(w.option.AmneziaWGOption.JC) + "\n"
|
||||
ipcConf += "jmin=" + strconv.Itoa(w.option.AmneziaWGOption.JMin) + "\n"
|
||||
ipcConf += "jmax=" + strconv.Itoa(w.option.AmneziaWGOption.JMax) + "\n"
|
||||
ipcConf += "s1=" + strconv.Itoa(w.option.AmneziaWGOption.S1) + "\n"
|
||||
ipcConf += "s2=" + strconv.Itoa(w.option.AmneziaWGOption.S2) + "\n"
|
||||
ipcConf += "h1=" + strconv.FormatUint(uint64(w.option.AmneziaWGOption.H1), 10) + "\n"
|
||||
ipcConf += "h2=" + strconv.FormatUint(uint64(w.option.AmneziaWGOption.H2), 10) + "\n"
|
||||
ipcConf += "h3=" + strconv.FormatUint(uint64(w.option.AmneziaWGOption.H3), 10) + "\n"
|
||||
ipcConf += "h4=" + strconv.FormatUint(uint64(w.option.AmneziaWGOption.H4), 10) + "\n"
|
||||
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 {
|
||||
@@ -488,9 +550,7 @@ func (w *WireGuard) Close() error {
|
||||
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.SetDialer(dialer.NewDialer(options...))
|
||||
func (w *WireGuard) DialContext(ctx context.Context, metadata *C.Metadata) (_ C.Conn, err error) {
|
||||
var conn net.Conn
|
||||
if err = w.init(ctx); err != nil {
|
||||
return nil, err
|
||||
@@ -500,6 +560,7 @@ func (w *WireGuard) DialContext(ctx context.Context, metadata *C.Metadata, opts
|
||||
if w.resolver != nil {
|
||||
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())
|
||||
@@ -515,23 +576,13 @@ func (w *WireGuard) DialContext(ctx context.Context, metadata *C.Metadata, opts
|
||||
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.SetDialer(dialer.NewDialer(options...))
|
||||
func (w *WireGuard) ListenPacketContext(ctx context.Context, metadata *C.Metadata) (_ C.PacketConn, err error) {
|
||||
var pc net.PacketConn
|
||||
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 {
|
||||
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
|
||||
}
|
||||
pc, err = w.tunDevice.ListenPacket(ctx, M.SocksaddrFrom(metadata.DstIP, metadata.DstPort).Unwrap())
|
||||
if err != nil {
|
||||
@@ -543,6 +594,28 @@ func (w *WireGuard) ListenPacketContext(ctx context.Context, metadata *C.Metadat
|
||||
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
|
||||
}
|
||||
|
||||
// ProxyInfo implements C.ProxyAdapter
|
||||
func (w *WireGuard) ProxyInfo() C.ProxyInfo {
|
||||
info := w.Base.ProxyInfo()
|
||||
info.DialerProxy = w.option.DialerProxy
|
||||
return info
|
||||
}
|
||||
|
||||
// IsL3Protocol implements C.ProxyAdapter
|
||||
func (w *WireGuard) IsL3Protocol(metadata *C.Metadata) bool {
|
||||
return true
|
||||
|
||||
@@ -6,13 +6,11 @@ import (
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"github.com/metacubex/mihomo/adapter/outbound"
|
||||
"github.com/metacubex/mihomo/common/callback"
|
||||
N "github.com/metacubex/mihomo/common/net"
|
||||
"github.com/metacubex/mihomo/common/utils"
|
||||
"github.com/metacubex/mihomo/component/dialer"
|
||||
C "github.com/metacubex/mihomo/constant"
|
||||
"github.com/metacubex/mihomo/constant/provider"
|
||||
P "github.com/metacubex/mihomo/constant/provider"
|
||||
)
|
||||
|
||||
type Fallback struct {
|
||||
@@ -31,9 +29,9 @@ 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 {
|
||||
@@ -54,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)
|
||||
}
|
||||
@@ -152,21 +150,25 @@ func (f *Fallback) ForceSet(name string) {
|
||||
f.selected = name
|
||||
}
|
||||
|
||||
func NewFallback(option *GroupCommonOption, providers []provider.ProxyProvider) *Fallback {
|
||||
func (f *Fallback) Providers() []P.ProxyProvider {
|
||||
return f.providers
|
||||
}
|
||||
|
||||
func (f *Fallback) Proxies() []C.Proxy {
|
||||
return f.GetProxies(false)
|
||||
}
|
||||
|
||||
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,
|
||||
option.TestTimeout,
|
||||
option.MaxFailedTimes,
|
||||
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,
|
||||
|
||||
@@ -12,8 +12,7 @@ import (
|
||||
"github.com/metacubex/mihomo/common/atomic"
|
||||
"github.com/metacubex/mihomo/common/utils"
|
||||
C "github.com/metacubex/mihomo/constant"
|
||||
"github.com/metacubex/mihomo/constant/provider"
|
||||
types "github.com/metacubex/mihomo/constant/provider"
|
||||
P "github.com/metacubex/mihomo/constant/provider"
|
||||
"github.com/metacubex/mihomo/log"
|
||||
"github.com/metacubex/mihomo/tunnel"
|
||||
|
||||
@@ -26,7 +25,7 @@ type GroupBase struct {
|
||||
filterRegs []*regexp2.Regexp
|
||||
excludeFilterRegs []*regexp2.Regexp
|
||||
excludeTypeArray []string
|
||||
providers []provider.ProxyProvider
|
||||
providers []P.ProxyProvider
|
||||
failedTestMux sync.Mutex
|
||||
failedTimes int
|
||||
failedTime time.Time
|
||||
@@ -41,53 +40,47 @@ type GroupBase struct {
|
||||
}
|
||||
|
||||
type GroupBaseOption struct {
|
||||
outbound.BaseOption
|
||||
filter string
|
||||
excludeFilter string
|
||||
excludeType string
|
||||
Name string
|
||||
Type C.AdapterType
|
||||
Filter string
|
||||
ExcludeFilter string
|
||||
ExcludeType string
|
||||
TestTimeout int
|
||||
maxFailedTimes int
|
||||
providers []provider.ProxyProvider
|
||||
MaxFailedTimes int
|
||||
Providers []P.ProxyProvider
|
||||
}
|
||||
|
||||
func NewGroupBase(opt GroupBaseOption) *GroupBase {
|
||||
if opt.RoutingMark != 0 {
|
||||
log.Warnln("The group [%s] with routing-mark configuration is deprecated, please set it directly on the proxy instead", opt.Name)
|
||||
}
|
||||
if opt.Interface != "" {
|
||||
log.Warnln("The group [%s] with interface-name configuration is deprecated, please set it directly on the proxy instead", opt.Name)
|
||||
}
|
||||
|
||||
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, "`") {
|
||||
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, "`") {
|
||||
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),
|
||||
Base: outbound.NewBase(outbound.BaseOption{Name: opt.Name, Type: opt.Type}),
|
||||
filterRegs: filterRegs,
|
||||
excludeFilterRegs: excludeFilterRegs,
|
||||
excludeTypeArray: excludeTypeArray,
|
||||
providers: opt.providers,
|
||||
providers: opt.Providers,
|
||||
failedTesting: atomic.NewBool(false),
|
||||
TestTimeout: opt.TestTimeout,
|
||||
maxFailedTimes: opt.maxFailedTimes,
|
||||
maxFailedTimes: opt.MaxFailedTimes,
|
||||
}
|
||||
|
||||
if gb.TestTimeout == 0 {
|
||||
@@ -131,7 +124,7 @@ func (gb *GroupBase) GetProxies(touch bool) []C.Proxy {
|
||||
}
|
||||
} else {
|
||||
for _, pd := range gb.providers {
|
||||
if pd.VehicleType() == types.Compatible { // compatible provider unneeded filter
|
||||
if pd.VehicleType() == P.Compatible { // compatible provider unneeded filter
|
||||
proxies = append(proxies, pd.Proxies()...)
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -9,14 +9,12 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/metacubex/mihomo/adapter/outbound"
|
||||
"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"
|
||||
"github.com/metacubex/mihomo/component/dialer"
|
||||
C "github.com/metacubex/mihomo/constant"
|
||||
"github.com/metacubex/mihomo/constant/provider"
|
||||
P "github.com/metacubex/mihomo/constant/provider"
|
||||
|
||||
"golang.org/x/net/publicsuffix"
|
||||
)
|
||||
@@ -88,9 +86,9 @@ 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)
|
||||
@@ -112,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)
|
||||
@@ -120,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
|
||||
@@ -196,7 +194,7 @@ func strategyStickySessions(url string) strategyFn {
|
||||
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)))
|
||||
}
|
||||
|
||||
@@ -241,7 +239,19 @@ func (lb *LoadBalance) MarshalJSON() ([]byte, error) {
|
||||
})
|
||||
}
|
||||
|
||||
func NewLoadBalance(option *GroupCommonOption, providers []provider.ProxyProvider, strategy string) (lb *LoadBalance, err error) {
|
||||
func (lb *LoadBalance) Providers() []P.ProxyProvider {
|
||||
return lb.providers
|
||||
}
|
||||
|
||||
func (lb *LoadBalance) Proxies() []C.Proxy {
|
||||
return lb.GetProxies(false)
|
||||
}
|
||||
|
||||
func (lb *LoadBalance) Now() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func NewLoadBalance(option *GroupCommonOption, providers []P.ProxyProvider, strategy string) (lb *LoadBalance, err error) {
|
||||
var strategyFn strategyFn
|
||||
switch strategy {
|
||||
case "consistent-hashing":
|
||||
@@ -255,18 +265,14 @@ func NewLoadBalance(option *GroupCommonOption, providers []provider.ProxyProvide
|
||||
}
|
||||
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,
|
||||
option.TestTimeout,
|
||||
option.MaxFailedTimes,
|
||||
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,
|
||||
|
||||
@@ -7,12 +7,12 @@ import (
|
||||
|
||||
"github.com/dlclark/regexp2"
|
||||
|
||||
"github.com/metacubex/mihomo/adapter/outbound"
|
||||
"github.com/metacubex/mihomo/adapter/provider"
|
||||
"github.com/metacubex/mihomo/common/structure"
|
||||
"github.com/metacubex/mihomo/common/utils"
|
||||
C "github.com/metacubex/mihomo/constant"
|
||||
types "github.com/metacubex/mihomo/constant/provider"
|
||||
P "github.com/metacubex/mihomo/constant/provider"
|
||||
"github.com/metacubex/mihomo/log"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -23,7 +23,6 @@ var (
|
||||
)
|
||||
|
||||
type GroupCommonOption struct {
|
||||
outbound.BasicOption
|
||||
Name string `group:"name"`
|
||||
Type string `group:"type"`
|
||||
Proxies []string `group:"proxies,omitempty"`
|
||||
@@ -43,9 +42,13 @@ type GroupCommonOption struct {
|
||||
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, AllProxies []string, AllProviders []string) (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{
|
||||
@@ -59,9 +62,16 @@ 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
|
||||
@@ -159,7 +169,7 @@ func ParseProxyGroup(config map[string]any, proxyMap map[string]C.Proxy, provide
|
||||
return nil, fmt.Errorf("%s: %w", groupName, err)
|
||||
}
|
||||
|
||||
providers = append([]types.ProxyProvider{pd}, providers...)
|
||||
providers = append([]P.ProxyProvider{pd}, providers...)
|
||||
providersMap[groupName] = pd
|
||||
}
|
||||
|
||||
@@ -176,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)
|
||||
}
|
||||
@@ -196,15 +206,15 @@ 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)
|
||||
@@ -212,7 +222,7 @@ func getProviders(mapping map[string]types.ProxyProvider, list []string) ([]type
|
||||
return ps, nil
|
||||
}
|
||||
|
||||
func addTestUrlToProviders(providers []types.ProxyProvider, url string, expectedStatus utils.IntRanges[uint16], filter string, interval uint) {
|
||||
func addTestUrlToProviders(providers []P.ProxyProvider, url string, expectedStatus utils.IntRanges[uint16], filter string, interval uint) {
|
||||
if len(providers) == 0 || len(url) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -1,64 +0,0 @@
|
||||
//go:build android && cmfa
|
||||
|
||||
package outboundgroup
|
||||
|
||||
import (
|
||||
C "github.com/metacubex/mihomo/constant"
|
||||
"github.com/metacubex/mihomo/constant/provider"
|
||||
)
|
||||
|
||||
type ProxyGroup interface {
|
||||
C.ProxyAdapter
|
||||
|
||||
Providers() []provider.ProxyProvider
|
||||
Proxies() []C.Proxy
|
||||
Now() string
|
||||
}
|
||||
|
||||
func (f *Fallback) Providers() []provider.ProxyProvider {
|
||||
return f.providers
|
||||
}
|
||||
|
||||
func (lb *LoadBalance) Providers() []provider.ProxyProvider {
|
||||
return lb.providers
|
||||
}
|
||||
|
||||
func (f *Fallback) Proxies() []C.Proxy {
|
||||
return f.GetProxies(false)
|
||||
}
|
||||
|
||||
func (lb *LoadBalance) Proxies() []C.Proxy {
|
||||
return lb.GetProxies(false)
|
||||
}
|
||||
|
||||
func (lb *LoadBalance) Now() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (r *Relay) Providers() []provider.ProxyProvider {
|
||||
return r.providers
|
||||
}
|
||||
|
||||
func (r *Relay) Proxies() []C.Proxy {
|
||||
return r.GetProxies(false)
|
||||
}
|
||||
|
||||
func (r *Relay) Now() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (s *Selector) Providers() []provider.ProxyProvider {
|
||||
return s.providers
|
||||
}
|
||||
|
||||
func (s *Selector) Proxies() []C.Proxy {
|
||||
return s.GetProxies(false)
|
||||
}
|
||||
|
||||
func (u *URLTest) Providers() []provider.ProxyProvider {
|
||||
return u.providers
|
||||
}
|
||||
|
||||
func (u *URLTest) Proxies() []C.Proxy {
|
||||
return u.GetProxies(false)
|
||||
}
|
||||
@@ -1,172 +0,0 @@
|
||||
package outboundgroup
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
|
||||
"github.com/metacubex/mihomo/adapter/outbound"
|
||||
"github.com/metacubex/mihomo/component/dialer"
|
||||
"github.com/metacubex/mihomo/component/proxydialer"
|
||||
C "github.com/metacubex/mihomo/constant"
|
||||
"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) {
|
||||
proxies, chainProxies := r.proxies(metadata, true)
|
||||
|
||||
switch len(proxies) {
|
||||
case 0:
|
||||
return outbound.NewDirect().DialContext(ctx, metadata, r.Base.DialOptions(opts...)...)
|
||||
case 1:
|
||||
return proxies[0].DialContext(ctx, metadata, r.Base.DialOptions(opts...)...)
|
||||
}
|
||||
var d C.Dialer
|
||||
d = dialer.NewDialer(r.Base.DialOptions(opts...)...)
|
||||
for _, proxy := range proxies[:len(proxies)-1] {
|
||||
d = proxydialer.New(proxy, d, false)
|
||||
}
|
||||
last := proxies[len(proxies)-1]
|
||||
conn, err := last.DialContextWithDialer(ctx, d, metadata)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for i := len(chainProxies) - 2; i >= 0; i-- {
|
||||
conn.AppendToChains(chainProxies[i])
|
||||
}
|
||||
|
||||
conn.AppendToChains(r)
|
||||
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
// ListenPacketContext implements C.ProxyAdapter
|
||||
func (r *Relay) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.PacketConn, err error) {
|
||||
proxies, chainProxies := r.proxies(metadata, true)
|
||||
|
||||
switch len(proxies) {
|
||||
case 0:
|
||||
return outbound.NewDirect().ListenPacketContext(ctx, metadata, r.Base.DialOptions(opts...)...)
|
||||
case 1:
|
||||
return proxies[0].ListenPacketContext(ctx, metadata, r.Base.DialOptions(opts...)...)
|
||||
}
|
||||
|
||||
var d C.Dialer
|
||||
d = dialer.NewDialer(r.Base.DialOptions(opts...)...)
|
||||
for _, proxy := range proxies[:len(proxies)-1] {
|
||||
d = proxydialer.New(proxy, d, false)
|
||||
}
|
||||
last := proxies[len(proxies)-1]
|
||||
pc, err := last.ListenPacketWithDialer(ctx, d, metadata)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for i := len(chainProxies) - 2; i >= 0; i-- {
|
||||
pc.AppendToChains(chainProxies[i])
|
||||
}
|
||||
|
||||
pc.AppendToChains(r)
|
||||
|
||||
return pc, nil
|
||||
}
|
||||
|
||||
// SupportUDP implements C.ProxyAdapter
|
||||
func (r *Relay) SupportUDP() bool {
|
||||
proxies, _ := r.proxies(nil, false)
|
||||
if len(proxies) == 0 { // C.Direct
|
||||
return true
|
||||
}
|
||||
for i := len(proxies) - 1; i >= 0; i-- {
|
||||
proxy := proxies[i]
|
||||
if !proxy.SupportUDP() {
|
||||
return false
|
||||
}
|
||||
if proxy.SupportUOT() {
|
||||
return true
|
||||
}
|
||||
switch proxy.SupportWithDialer() {
|
||||
case C.ALLNet:
|
||||
case C.UDP:
|
||||
default: // C.TCP and C.InvalidNet
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// MarshalJSON implements C.ProxyAdapter
|
||||
func (r *Relay) MarshalJSON() ([]byte, error) {
|
||||
all := []string{}
|
||||
for _, proxy := range r.GetProxies(false) {
|
||||
all = append(all, proxy.Name())
|
||||
}
|
||||
return json.Marshal(map[string]any{
|
||||
"type": r.Type().String(),
|
||||
"all": all,
|
||||
"hidden": r.Hidden,
|
||||
"icon": r.Icon,
|
||||
})
|
||||
}
|
||||
|
||||
func (r *Relay) proxies(metadata *C.Metadata, touch bool) ([]C.Proxy, []C.Proxy) {
|
||||
rawProxies := r.GetProxies(touch)
|
||||
|
||||
var proxies []C.Proxy
|
||||
var chainProxies []C.Proxy
|
||||
var targetProxies []C.Proxy
|
||||
|
||||
for n, proxy := range rawProxies {
|
||||
proxies = append(proxies, proxy)
|
||||
chainProxies = append(chainProxies, proxy)
|
||||
subproxy := proxy.Unwrap(metadata, touch)
|
||||
for subproxy != nil {
|
||||
chainProxies = append(chainProxies, subproxy)
|
||||
proxies[n] = subproxy
|
||||
subproxy = subproxy.Unwrap(metadata, touch)
|
||||
}
|
||||
}
|
||||
|
||||
for _, proxy := range proxies {
|
||||
if proxy.Type() != C.Direct && proxy.Type() != C.Compatible {
|
||||
targetProxies = append(targetProxies, proxy)
|
||||
}
|
||||
}
|
||||
|
||||
return targetProxies, chainProxies
|
||||
}
|
||||
|
||||
func (r *Relay) Addr() string {
|
||||
proxies, _ := r.proxies(nil, false)
|
||||
return proxies[len(proxies)-1].Addr()
|
||||
}
|
||||
|
||||
func NewRelay(option *GroupCommonOption, providers []provider.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,
|
||||
},
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
5000,
|
||||
5,
|
||||
providers,
|
||||
}),
|
||||
Hidden: option.Hidden,
|
||||
Icon: option.Icon,
|
||||
}
|
||||
}
|
||||
@@ -5,23 +5,22 @@ import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
|
||||
"github.com/metacubex/mihomo/adapter/outbound"
|
||||
"github.com/metacubex/mihomo/component/dialer"
|
||||
C "github.com/metacubex/mihomo/constant"
|
||||
"github.com/metacubex/mihomo/constant/provider"
|
||||
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)
|
||||
}
|
||||
@@ -29,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)
|
||||
}
|
||||
@@ -57,13 +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,
|
||||
"hidden": s.Hidden,
|
||||
"icon": s.Icon,
|
||||
"type": s.Type().String(),
|
||||
"now": s.Now(),
|
||||
"all": all,
|
||||
"testUrl": url,
|
||||
"hidden": s.Hidden,
|
||||
"icon": s.Icon,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -102,24 +108,29 @@ func (s *Selector) selectedProxy(touch bool) C.Proxy {
|
||||
return proxies[0]
|
||||
}
|
||||
|
||||
func NewSelector(option *GroupCommonOption, providers []provider.ProxyProvider) *Selector {
|
||||
func (s *Selector) Providers() []P.ProxyProvider {
|
||||
return s.providers
|
||||
}
|
||||
|
||||
func (s *Selector) Proxies() []C.Proxy {
|
||||
return s.GetProxies(false)
|
||||
}
|
||||
|
||||
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,
|
||||
option.TestTimeout,
|
||||
option.MaxFailedTimes,
|
||||
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,
|
||||
}
|
||||
|
||||
@@ -6,14 +6,12 @@ import (
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"github.com/metacubex/mihomo/adapter/outbound"
|
||||
"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"
|
||||
"github.com/metacubex/mihomo/component/dialer"
|
||||
C "github.com/metacubex/mihomo/constant"
|
||||
"github.com/metacubex/mihomo/constant/provider"
|
||||
P "github.com/metacubex/mihomo/constant/provider"
|
||||
)
|
||||
|
||||
type urlTestOption func(*URLTest)
|
||||
@@ -62,9 +60,9 @@ func (u *URLTest) ForceSet(name string) {
|
||||
}
|
||||
|
||||
// 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 {
|
||||
@@ -85,9 +83,9 @@ 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) {
|
||||
func (u *URLTest) ListenPacketContext(ctx context.Context, metadata *C.Metadata) (C.PacketConn, error) {
|
||||
proxy := u.fast(true)
|
||||
pc, err := proxy.ListenPacketContext(ctx, metadata, u.Base.DialOptions(opts...)...)
|
||||
pc, err := proxy.ListenPacketContext(ctx, metadata)
|
||||
if err == nil {
|
||||
pc.AppendToChains(u)
|
||||
} else {
|
||||
@@ -187,6 +185,14 @@ func (u *URLTest) MarshalJSON() ([]byte, error) {
|
||||
})
|
||||
}
|
||||
|
||||
func (u *URLTest) Providers() []P.ProxyProvider {
|
||||
return u.providers
|
||||
}
|
||||
|
||||
func (u *URLTest) Proxies() []C.Proxy {
|
||||
return u.GetProxies(false)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
@@ -204,22 +210,17 @@ 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,
|
||||
option.TestTimeout,
|
||||
option.MaxFailedTimes,
|
||||
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,
|
||||
|
||||
@@ -1,5 +1,29 @@
|
||||
package outboundgroup
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/metacubex/mihomo/common/utils"
|
||||
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
|
||||
Touch()
|
||||
|
||||
URLTest(ctx context.Context, url string, expectedStatus utils.IntRanges[uint16]) (mp map[string]uint16, err error)
|
||||
}
|
||||
|
||||
var _ ProxyGroup = (*Fallback)(nil)
|
||||
var _ ProxyGroup = (*LoadBalance)(nil)
|
||||
var _ ProxyGroup = (*URLTest)(nil)
|
||||
var _ ProxyGroup = (*Selector)(nil)
|
||||
|
||||
type SelectAble interface {
|
||||
Set(string) error
|
||||
ForceSet(name string)
|
||||
|
||||
@@ -8,150 +8,157 @@ import (
|
||||
C "github.com/metacubex/mihomo/constant"
|
||||
)
|
||||
|
||||
func ParseProxy(mapping map[string]any) (C.Proxy, error) {
|
||||
func ParseProxy(mapping map[string]any, options ...ProxyOption) (C.Proxy, error) {
|
||||
decoder := structure.NewDecoder(structure.Option{TagName: "proxy", WeaklyTypedInput: true, KeyReplacer: structure.DefaultKeyReplacer})
|
||||
proxyType, existType := mapping["type"].(string)
|
||||
if !existType {
|
||||
return nil, fmt.Errorf("missing type")
|
||||
}
|
||||
|
||||
opt := applyProxyOptions(options...)
|
||||
basicOption := outbound.BasicOption{
|
||||
DialerForAPI: opt.DialerForAPI,
|
||||
ProviderName: opt.ProviderName,
|
||||
}
|
||||
|
||||
var (
|
||||
proxy outbound.ProxyAdapter
|
||||
err error
|
||||
)
|
||||
switch proxyType {
|
||||
case "ss":
|
||||
ssOption := &outbound.ShadowSocksOption{}
|
||||
ssOption := &outbound.ShadowSocksOption{BasicOption: basicOption}
|
||||
err = decoder.Decode(mapping, ssOption)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
proxy, err = outbound.NewShadowSocks(*ssOption)
|
||||
case "ssr":
|
||||
ssrOption := &outbound.ShadowSocksROption{}
|
||||
ssrOption := &outbound.ShadowSocksROption{BasicOption: basicOption}
|
||||
err = decoder.Decode(mapping, ssrOption)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
proxy, err = outbound.NewShadowSocksR(*ssrOption)
|
||||
case "socks5":
|
||||
socksOption := &outbound.Socks5Option{}
|
||||
socksOption := &outbound.Socks5Option{BasicOption: basicOption}
|
||||
err = decoder.Decode(mapping, socksOption)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
proxy, err = outbound.NewSocks5(*socksOption)
|
||||
case "http":
|
||||
httpOption := &outbound.HttpOption{}
|
||||
httpOption := &outbound.HttpOption{BasicOption: basicOption}
|
||||
err = decoder.Decode(mapping, httpOption)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
proxy, err = outbound.NewHttp(*httpOption)
|
||||
case "vmess":
|
||||
vmessOption := &outbound.VmessOption{
|
||||
HTTPOpts: outbound.HTTPOptions{
|
||||
Method: "GET",
|
||||
Path: []string{"/"},
|
||||
},
|
||||
}
|
||||
|
||||
vmessOption := &outbound.VmessOption{BasicOption: basicOption}
|
||||
err = decoder.Decode(mapping, vmessOption)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
proxy, err = outbound.NewVmess(*vmessOption)
|
||||
case "vless":
|
||||
vlessOption := &outbound.VlessOption{}
|
||||
vlessOption := &outbound.VlessOption{BasicOption: basicOption}
|
||||
err = decoder.Decode(mapping, vlessOption)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
proxy, err = outbound.NewVless(*vlessOption)
|
||||
case "snell":
|
||||
snellOption := &outbound.SnellOption{}
|
||||
snellOption := &outbound.SnellOption{BasicOption: basicOption}
|
||||
err = decoder.Decode(mapping, snellOption)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
proxy, err = outbound.NewSnell(*snellOption)
|
||||
case "trojan":
|
||||
trojanOption := &outbound.TrojanOption{}
|
||||
trojanOption := &outbound.TrojanOption{BasicOption: basicOption}
|
||||
err = decoder.Decode(mapping, trojanOption)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
proxy, err = outbound.NewTrojan(*trojanOption)
|
||||
case "hysteria":
|
||||
hyOption := &outbound.HysteriaOption{}
|
||||
hyOption := &outbound.HysteriaOption{BasicOption: basicOption}
|
||||
err = decoder.Decode(mapping, hyOption)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
proxy, err = outbound.NewHysteria(*hyOption)
|
||||
case "hysteria2":
|
||||
hyOption := &outbound.Hysteria2Option{}
|
||||
hyOption := &outbound.Hysteria2Option{BasicOption: basicOption}
|
||||
err = decoder.Decode(mapping, hyOption)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
proxy, err = outbound.NewHysteria2(*hyOption)
|
||||
case "wireguard":
|
||||
wgOption := &outbound.WireGuardOption{}
|
||||
wgOption := &outbound.WireGuardOption{BasicOption: basicOption}
|
||||
err = decoder.Decode(mapping, wgOption)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
proxy, err = outbound.NewWireGuard(*wgOption)
|
||||
case "tuic":
|
||||
tuicOption := &outbound.TuicOption{}
|
||||
tuicOption := &outbound.TuicOption{BasicOption: basicOption}
|
||||
err = decoder.Decode(mapping, tuicOption)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
proxy, err = outbound.NewTuic(*tuicOption)
|
||||
case "direct":
|
||||
directOption := &outbound.DirectOption{}
|
||||
directOption := &outbound.DirectOption{BasicOption: basicOption}
|
||||
err = decoder.Decode(mapping, directOption)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
proxy = outbound.NewDirectWithOption(*directOption)
|
||||
case "dns":
|
||||
dnsOptions := &outbound.DnsOption{}
|
||||
dnsOptions := &outbound.DnsOption{BasicOption: basicOption}
|
||||
err = decoder.Decode(mapping, dnsOptions)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
proxy = outbound.NewDnsWithOption(*dnsOptions)
|
||||
case "reject":
|
||||
rejectOption := &outbound.RejectOption{}
|
||||
rejectOption := &outbound.RejectOption{BasicOption: basicOption}
|
||||
err = decoder.Decode(mapping, rejectOption)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
proxy = outbound.NewRejectWithOption(*rejectOption)
|
||||
case "ssh":
|
||||
sshOption := &outbound.SshOption{}
|
||||
sshOption := &outbound.SshOption{BasicOption: basicOption}
|
||||
err = decoder.Decode(mapping, sshOption)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
proxy, err = outbound.NewSsh(*sshOption)
|
||||
case "mieru":
|
||||
mieruOption := &outbound.MieruOption{}
|
||||
mieruOption := &outbound.MieruOption{BasicOption: basicOption}
|
||||
err = decoder.Decode(mapping, mieruOption)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
proxy, err = outbound.NewMieru(*mieruOption)
|
||||
case "anytls":
|
||||
anytlsOption := &outbound.AnyTLSOption{}
|
||||
anytlsOption := &outbound.AnyTLSOption{BasicOption: basicOption}
|
||||
err = decoder.Decode(mapping, anytlsOption)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
proxy, err = outbound.NewAnyTLS(*anytlsOption)
|
||||
case "sudoku":
|
||||
sudokuOption := &outbound.SudokuOption{BasicOption: basicOption}
|
||||
err = decoder.Decode(mapping, sudokuOption)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
proxy, err = outbound.NewSudoku(*sudokuOption)
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupport proxy type: %s", proxyType)
|
||||
}
|
||||
@@ -177,3 +184,30 @@ func ParseProxy(mapping map[string]any) (C.Proxy, error) {
|
||||
proxy = outbound.NewAutoCloseProxyAdapter(proxy)
|
||||
return NewProxy(proxy), nil
|
||||
}
|
||||
|
||||
type proxyOption struct {
|
||||
DialerForAPI C.Dialer
|
||||
ProviderName string
|
||||
}
|
||||
|
||||
func applyProxyOptions(options ...ProxyOption) proxyOption {
|
||||
opt := proxyOption{}
|
||||
for _, o := range options {
|
||||
o(&opt)
|
||||
}
|
||||
return opt
|
||||
}
|
||||
|
||||
type ProxyOption func(opt *proxyOption)
|
||||
|
||||
func WithDialerForAPI(dialer C.Dialer) ProxyOption {
|
||||
return func(opt *proxyOption) {
|
||||
opt.DialerForAPI = dialer
|
||||
}
|
||||
}
|
||||
|
||||
func WithProviderName(name string) ProxyOption {
|
||||
return func(opt *proxyOption) {
|
||||
opt.ProviderName = name
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,13 +7,13 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/metacubex/mihomo/common/atomic"
|
||||
"github.com/metacubex/mihomo/common/batch"
|
||||
"github.com/metacubex/mihomo/common/singledo"
|
||||
"github.com/metacubex/mihomo/common/utils"
|
||||
C "github.com/metacubex/mihomo/constant"
|
||||
"github.com/metacubex/mihomo/log"
|
||||
|
||||
"github.com/dlclark/regexp2"
|
||||
"golang.org/x/sync/errgroup"
|
||||
)
|
||||
|
||||
type HealthCheckOption struct {
|
||||
@@ -32,7 +32,6 @@ type HealthCheck struct {
|
||||
url string
|
||||
extra map[string]*extraOption
|
||||
mu sync.Mutex
|
||||
started atomic.Bool
|
||||
proxies []C.Proxy
|
||||
interval time.Duration
|
||||
lazy bool
|
||||
@@ -43,13 +42,8 @@ type HealthCheck struct {
|
||||
}
|
||||
|
||||
func (hc *HealthCheck) process() {
|
||||
if hc.started.Load() {
|
||||
log.Warnln("Skip start health check timer due to it's started")
|
||||
return
|
||||
}
|
||||
|
||||
ticker := time.NewTicker(hc.interval)
|
||||
hc.start()
|
||||
go hc.check()
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
@@ -62,13 +56,12 @@ func (hc *HealthCheck) process() {
|
||||
}
|
||||
case <-hc.ctx.Done():
|
||||
ticker.Stop()
|
||||
hc.stop()
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (hc *HealthCheck) setProxy(proxies []C.Proxy) {
|
||||
func (hc *HealthCheck) setProxies(proxies []C.Proxy) {
|
||||
hc.proxies = proxies
|
||||
}
|
||||
|
||||
@@ -105,10 +98,6 @@ func (hc *HealthCheck) registerHealthCheckTask(url string, expectedStatus utils.
|
||||
option := &extraOption{filters: map[string]struct{}{}, expectedStatus: expectedStatus}
|
||||
splitAndAddFiltersToExtra(filter, option)
|
||||
hc.extra[url] = option
|
||||
|
||||
if hc.auto() && !hc.started.Load() {
|
||||
go hc.process()
|
||||
}
|
||||
}
|
||||
|
||||
func splitAndAddFiltersToExtra(filter string, option *extraOption) {
|
||||
@@ -131,14 +120,6 @@ func (hc *HealthCheck) touch() {
|
||||
hc.lastTouch.Store(time.Now())
|
||||
}
|
||||
|
||||
func (hc *HealthCheck) start() {
|
||||
hc.started.Store(true)
|
||||
}
|
||||
|
||||
func (hc *HealthCheck) stop() {
|
||||
hc.started.Store(false)
|
||||
}
|
||||
|
||||
func (hc *HealthCheck) check() {
|
||||
if len(hc.proxies) == 0 {
|
||||
return
|
||||
@@ -147,7 +128,8 @@ func (hc *HealthCheck) check() {
|
||||
_, _, _ = hc.singleDo.Do(func() (struct{}, error) {
|
||||
id := utils.NewUUIDV4().String()
|
||||
log.Debugln("Start New Health Checking {%s}", id)
|
||||
b, _ := batch.New[bool](hc.ctx, batch.WithConcurrencyNum[bool](10))
|
||||
b := new(errgroup.Group)
|
||||
b.SetLimit(10)
|
||||
|
||||
// execute default health check
|
||||
option := &extraOption{filters: nil, expectedStatus: hc.expectedStatus}
|
||||
@@ -159,13 +141,13 @@ func (hc *HealthCheck) check() {
|
||||
hc.execute(b, url, id, option)
|
||||
}
|
||||
}
|
||||
b.Wait()
|
||||
_ = b.Wait()
|
||||
log.Debugln("Finish A Health Checking {%s}", id)
|
||||
return struct{}{}, nil
|
||||
})
|
||||
}
|
||||
|
||||
func (hc *HealthCheck) execute(b *batch.Batch[bool], url, uid string, option *extraOption) {
|
||||
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)
|
||||
@@ -195,13 +177,13 @@ func (hc *HealthCheck) execute(b *batch.Batch[bool], url, uid string, option *ex
|
||||
}
|
||||
|
||||
p := proxy
|
||||
b.Go(p.Name(), func() (bool, error) {
|
||||
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 false, nil
|
||||
return nil
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,14 +10,13 @@ import (
|
||||
"github.com/metacubex/mihomo/common/utils"
|
||||
"github.com/metacubex/mihomo/component/resource"
|
||||
C "github.com/metacubex/mihomo/constant"
|
||||
types "github.com/metacubex/mihomo/constant/provider"
|
||||
P "github.com/metacubex/mihomo/constant/provider"
|
||||
|
||||
"github.com/dlclark/regexp2"
|
||||
)
|
||||
|
||||
var (
|
||||
errVehicleType = errors.New("unsupport vehicle type")
|
||||
errSubPath = errors.New("path is not subpath of home directory")
|
||||
)
|
||||
|
||||
type healthCheckSchema struct {
|
||||
@@ -74,7 +73,7 @@ type proxyProviderSchema struct {
|
||||
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{
|
||||
@@ -100,22 +99,25 @@ func ParseProxyProvider(name string, mapping map[string]any) (types.ProxyProvide
|
||||
}
|
||||
hc := NewHealthCheck([]C.Proxy{}, schema.HealthCheck.URL, uint(schema.HealthCheck.TestTimeout), hcInterval, schema.HealthCheck.Lazy, expectedStatus)
|
||||
|
||||
parser, err := NewProxiesParser(schema.Filter, schema.ExcludeFilter, schema.ExcludeType, schema.DialerProxy, schema.Override)
|
||||
parser, err := NewProxiesParser(name, 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":
|
||||
path := C.Path.GetPathByHash("proxies", schema.URL)
|
||||
if schema.Path != "" {
|
||||
path = C.Path.Resolve(schema.Path)
|
||||
if !C.Path.IsSafePath(path) {
|
||||
return nil, fmt.Errorf("%w: %s", errSubPath, path)
|
||||
return nil, C.Path.ErrNotSafePath(path)
|
||||
}
|
||||
}
|
||||
vehicle = resource.NewHTTPVehicle(schema.URL, path, schema.Proxy, schema.Header, resource.DefaultHttpTimeout, schema.SizeLimit)
|
||||
@@ -127,5 +129,5 @@ func ParseProxyProvider(name string, mapping map[string]any) (types.ProxyProvide
|
||||
|
||||
interval := time.Duration(uint(schema.Interval)) * time.Second
|
||||
|
||||
return NewProxySetProvider(name, interval, parser, vehicle, hc)
|
||||
return NewProxySetProvider(name, interval, schema.Payload, parser, vehicle, hc)
|
||||
}
|
||||
|
||||
@@ -4,23 +4,24 @@ import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/metacubex/mihomo/adapter"
|
||||
"github.com/metacubex/mihomo/common/convert"
|
||||
"github.com/metacubex/mihomo/common/utils"
|
||||
"github.com/metacubex/mihomo/common/yaml"
|
||||
"github.com/metacubex/mihomo/component/profile/cachefile"
|
||||
"github.com/metacubex/mihomo/component/resource"
|
||||
C "github.com/metacubex/mihomo/constant"
|
||||
types "github.com/metacubex/mihomo/constant/provider"
|
||||
P "github.com/metacubex/mihomo/constant/provider"
|
||||
"github.com/metacubex/mihomo/tunnel/statistic"
|
||||
|
||||
"github.com/dlclark/regexp2"
|
||||
"gopkg.in/yaml.v3"
|
||||
"github.com/metacubex/http"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -43,6 +44,7 @@ type providerForApi struct {
|
||||
}
|
||||
|
||||
type baseProvider struct {
|
||||
mutex sync.RWMutex
|
||||
name string
|
||||
proxies []C.Proxy
|
||||
healthCheck *HealthCheck
|
||||
@@ -54,22 +56,35 @@ func (bp *baseProvider) Name() string {
|
||||
}
|
||||
|
||||
func (bp *baseProvider) Version() uint32 {
|
||||
bp.mutex.RLock()
|
||||
defer bp.mutex.RUnlock()
|
||||
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() types.ProviderType {
|
||||
return types.Proxy
|
||||
func (bp *baseProvider) Type() P.ProviderType {
|
||||
return P.Proxy
|
||||
}
|
||||
|
||||
func (bp *baseProvider) Proxies() []C.Proxy {
|
||||
bp.mutex.RLock()
|
||||
defer bp.mutex.RUnlock()
|
||||
return bp.proxies
|
||||
}
|
||||
|
||||
func (bp *baseProvider) Count() int {
|
||||
bp.mutex.RLock()
|
||||
defer bp.mutex.RUnlock()
|
||||
return len(bp.proxies)
|
||||
}
|
||||
|
||||
@@ -86,9 +101,11 @@ func (bp *baseProvider) RegisterHealthCheckTask(url string, expectedStatus utils
|
||||
}
|
||||
|
||||
func (bp *baseProvider) setProxies(proxies []C.Proxy) {
|
||||
bp.mutex.Lock()
|
||||
defer bp.mutex.Unlock()
|
||||
bp.proxies = proxies
|
||||
bp.version += 1
|
||||
bp.healthCheck.setProxy(proxies)
|
||||
bp.healthCheck.setProxies(proxies)
|
||||
if bp.healthCheck.auto() {
|
||||
go bp.healthCheck.check()
|
||||
}
|
||||
@@ -133,6 +150,9 @@ func (pp *proxySetProvider) Update() error {
|
||||
}
|
||||
|
||||
func (pp *proxySetProvider) Initial() error {
|
||||
if err := pp.baseProvider.Initial(); err != nil {
|
||||
return err
|
||||
}
|
||||
_, err := pp.Fetcher.Initial()
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -146,7 +166,7 @@ func (pp *proxySetProvider) Initial() error {
|
||||
|
||||
func (pp *proxySetProvider) closeAllConnections() {
|
||||
statistic.DefaultManager.Range(func(c statistic.Tracker) bool {
|
||||
for _, chain := range c.Chains() {
|
||||
for _, chain := range c.ProviderChains() {
|
||||
if chain == pp.Name() {
|
||||
_ = c.Close()
|
||||
break
|
||||
@@ -161,11 +181,7 @@ func (pp *proxySetProvider) Close() error {
|
||||
return pp.Fetcher.Close()
|
||||
}
|
||||
|
||||
func NewProxySetProvider(name string, interval time.Duration, parser resource.Parser[[]C.Proxy], vehicle types.Vehicle, hc *HealthCheck) (*ProxySetProvider, error) {
|
||||
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{
|
||||
baseProvider: baseProvider{
|
||||
name: name,
|
||||
@@ -174,6 +190,21 @@ func NewProxySetProvider(name string, interval time.Duration, parser resource.Pa
|
||||
},
|
||||
}
|
||||
|
||||
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 {
|
||||
@@ -217,12 +248,8 @@ func (ip *inlineProvider) MarshalJSON() ([]byte, error) {
|
||||
})
|
||||
}
|
||||
|
||||
func (ip *inlineProvider) VehicleType() types.VehicleType {
|
||||
return types.Inline
|
||||
}
|
||||
|
||||
func (ip *inlineProvider) Initial() error {
|
||||
return nil
|
||||
func (ip *inlineProvider) VehicleType() P.VehicleType {
|
||||
return P.Inline
|
||||
}
|
||||
|
||||
func (ip *inlineProvider) Update() error {
|
||||
@@ -232,10 +259,6 @@ func (ip *inlineProvider) Update() error {
|
||||
}
|
||||
|
||||
func NewInlineProvider(name string, payload []map[string]any, parser resource.Parser[[]C.Proxy], hc *HealthCheck) (*InlineProvider, error) {
|
||||
if hc.auto() {
|
||||
go hc.process()
|
||||
}
|
||||
|
||||
ps := ProxySchema{Proxies: payload}
|
||||
buf, err := yaml.Marshal(ps)
|
||||
if err != nil {
|
||||
@@ -245,6 +268,8 @@ func NewInlineProvider(name string, payload []map[string]any, parser resource.Pa
|
||||
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{
|
||||
@@ -288,15 +313,8 @@ func (cp *compatibleProvider) Update() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cp *compatibleProvider) Initial() error {
|
||||
if cp.healthCheck.interval != 0 && cp.healthCheck.url != "" {
|
||||
cp.HealthCheck()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cp *compatibleProvider) VehicleType() types.VehicleType {
|
||||
return types.Compatible
|
||||
func (cp *compatibleProvider) VehicleType() P.VehicleType {
|
||||
return P.Compatible
|
||||
}
|
||||
|
||||
func NewCompatibleProvider(name string, proxies []C.Proxy, hc *HealthCheck) (*CompatibleProvider, error) {
|
||||
@@ -304,10 +322,6 @@ 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{
|
||||
baseProvider: baseProvider{
|
||||
name: name,
|
||||
@@ -326,16 +340,23 @@ func (cp *CompatibleProvider) Close() error {
|
||||
return cp.compatibleProvider.Close()
|
||||
}
|
||||
|
||||
func NewProxiesParser(filter string, excludeFilter string, excludeType string, dialerProxy string, override OverrideSchema) (resource.Parser[[]C.Proxy], error) {
|
||||
excludeFilterReg, err := regexp2.Compile(excludeFilter, regexp2.None)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid excludeFilter regex: %w", err)
|
||||
}
|
||||
func NewProxiesParser(pdName string, 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)
|
||||
@@ -363,8 +384,9 @@ func NewProxiesParser(filter string, excludeFilter string, excludeType string, d
|
||||
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
|
||||
@@ -373,18 +395,11 @@ func NewProxiesParser(filter string, excludeFilter string, excludeType string, d
|
||||
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 {
|
||||
@@ -394,9 +409,11 @@ func NewProxiesParser(filter string, excludeFilter string, excludeType string, d
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if len(excludeFilter) > 0 {
|
||||
if mat, _ := excludeFilterReg.MatchString(name); mat {
|
||||
continue
|
||||
if len(excludeFilterRegs) > 0 {
|
||||
for _, excludeFilterReg := range excludeFilterRegs {
|
||||
if mat, _ := excludeFilterReg.MatchString(name); mat {
|
||||
continue LOOP1
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(filter) > 0 {
|
||||
@@ -441,7 +458,7 @@ func NewProxiesParser(filter string, excludeFilter string, excludeType string, d
|
||||
}
|
||||
}
|
||||
|
||||
proxy, err := adapter.ParseProxy(mapping)
|
||||
proxy, err := adapter.ParseProxy(mapping, adapter.WithProviderName(pdName))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("proxy %d error: %w", idx, err)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
// 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.
|
||||
//go:build android && cgo
|
||||
// +build android,cgo
|
||||
|
||||
// kanged from https://github.com/golang/mobile/blob/c713f31d574bb632a93f169b2cc99c9e753fef0e/app/android.go#L89
|
||||
|
||||
|
||||
63
common/atomic/enum.go
Normal file
63
common/atomic/enum.go
Normal 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
|
||||
}
|
||||
@@ -29,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)
|
||||
@@ -58,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())
|
||||
}
|
||||
@@ -84,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)
|
||||
@@ -111,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)
|
||||
@@ -138,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)
|
||||
@@ -165,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)
|
||||
@@ -192,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)
|
||||
|
||||
@@ -5,49 +5,50 @@ import (
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
func DefaultValue[T any]() T {
|
||||
var defaultValue T
|
||||
return defaultValue
|
||||
}
|
||||
|
||||
type TypedValue[T any] struct {
|
||||
_ noCopy
|
||||
value atomic.Value
|
||||
value atomic.Pointer[T]
|
||||
}
|
||||
|
||||
// tValue is a struct with determined type to resolve atomic.Value usages with interface types
|
||||
// https://github.com/golang/go/issues/22550
|
||||
//
|
||||
// The intention to have an atomic value store for errors. However, running this code panics:
|
||||
// panic: sync/atomic: store of inconsistently typed value into Value
|
||||
// This is because atomic.Value requires that the underlying concrete type be the same (which is a reasonable expectation for its implementation).
|
||||
// When going through the atomic.Value.Store method call, the fact that both these are of the error interface is lost.
|
||||
type tValue[T any] struct {
|
||||
value T
|
||||
func (t *TypedValue[T]) Load() (v T) {
|
||||
v, _ = t.LoadOk()
|
||||
return
|
||||
}
|
||||
|
||||
func (t *TypedValue[T]) Load() T {
|
||||
func (t *TypedValue[T]) LoadOk() (v T, ok bool) {
|
||||
value := t.value.Load()
|
||||
if value == nil {
|
||||
return DefaultValue[T]()
|
||||
return
|
||||
}
|
||||
return value.(tValue[T]).value
|
||||
return *value, true
|
||||
}
|
||||
|
||||
func (t *TypedValue[T]) Store(value T) {
|
||||
t.value.Store(tValue[T]{value})
|
||||
t.value.Store(&value)
|
||||
}
|
||||
|
||||
func (t *TypedValue[T]) Swap(new T) T {
|
||||
old := t.value.Swap(tValue[T]{new})
|
||||
func (t *TypedValue[T]) Swap(new T) (v T) {
|
||||
old := t.value.Swap(&new)
|
||||
if old == nil {
|
||||
return DefaultValue[T]()
|
||||
return
|
||||
}
|
||||
return old.(tValue[T]).value
|
||||
return *old
|
||||
}
|
||||
|
||||
func (t *TypedValue[T]) CompareAndSwap(old, new T) bool {
|
||||
return t.value.CompareAndSwap(tValue[T]{old}, tValue[T]{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) {
|
||||
@@ -63,13 +64,20 @@ func (t *TypedValue[T]) UnmarshalJSON(b []byte) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
type noCopy struct{}
|
||||
|
||||
// Lock is a no-op used by -copylocks checker from `go vet`.
|
||||
func (*noCopy) Lock() {}
|
||||
func (*noCopy) Unlock() {}
|
||||
|
||||
169
common/atomic/value_test.go
Normal file
169
common/atomic/value_test.go
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -14,6 +14,7 @@ var NewPacket = buf.NewPacket
|
||||
var NewSize = buf.NewSize
|
||||
var With = buf.With
|
||||
var As = buf.As
|
||||
var ReleaseMulti = buf.ReleaseMulti
|
||||
|
||||
var (
|
||||
Must = common.Must
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
@@ -208,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)
|
||||
@@ -218,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":
|
||||
@@ -456,12 +462,12 @@ func ConvertsV2Ray(buf []byte) ([]map[string]any, error) {
|
||||
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 {
|
||||
@@ -490,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)
|
||||
|
||||
@@ -513,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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,12 +2,12 @@ package convert
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/metacubex/mihomo/common/utils"
|
||||
|
||||
"github.com/metacubex/http"
|
||||
"github.com/metacubex/randv2"
|
||||
"github.com/metacubex/sing-shadowsocks/shadowimpl"
|
||||
)
|
||||
|
||||
674
common/deque/deque.go
Normal file
674
common/deque/deque.go
Normal file
@@ -0,0 +1,674 @@
|
||||
package deque
|
||||
|
||||
// copy and modified from https://github.com/gammazero/deque/blob/v1.2.0/deque.go
|
||||
// which is licensed under MIT.
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// minCapacity is the smallest capacity that deque may have. Must be power of 2
|
||||
// for bitwise modulus: x % n == x & (n - 1).
|
||||
const minCapacity = 8
|
||||
|
||||
// Deque represents a single instance of the deque data structure. A Deque
|
||||
// instance contains items of the type specified by the type argument.
|
||||
//
|
||||
// For example, to create a Deque that contains strings do one of the
|
||||
// following:
|
||||
//
|
||||
// var stringDeque deque.Deque[string]
|
||||
// stringDeque := new(deque.Deque[string])
|
||||
// stringDeque := &deque.Deque[string]{}
|
||||
//
|
||||
// To create a Deque that will never resize to have space for less than 64
|
||||
// items, specify a base capacity:
|
||||
//
|
||||
// var d deque.Deque[int]
|
||||
// d.SetBaseCap(64)
|
||||
//
|
||||
// To ensure the Deque can store 1000 items without needing to resize while
|
||||
// items are added:
|
||||
//
|
||||
// d.Grow(1000)
|
||||
//
|
||||
// Any values supplied to [SetBaseCap] and [Grow] are rounded up to the nearest
|
||||
// power of 2, since the Deque grows by powers of 2.
|
||||
type Deque[T any] struct {
|
||||
buf []T
|
||||
head int
|
||||
tail int
|
||||
count int
|
||||
minCap int
|
||||
}
|
||||
|
||||
// Cap returns the current capacity of the Deque. If q is nil, q.Cap() is zero.
|
||||
func (q *Deque[T]) Cap() int {
|
||||
if q == nil {
|
||||
return 0
|
||||
}
|
||||
return len(q.buf)
|
||||
}
|
||||
|
||||
// Len returns the number of elements currently stored in the queue. If q is
|
||||
// nil, q.Len() returns zero.
|
||||
func (q *Deque[T]) Len() int {
|
||||
if q == nil {
|
||||
return 0
|
||||
}
|
||||
return q.count
|
||||
}
|
||||
|
||||
// PushBack appends an element to the back of the queue. Implements FIFO when
|
||||
// elements are removed with [PopFront], and LIFO when elements are removed with
|
||||
// [PopBack].
|
||||
func (q *Deque[T]) PushBack(elem T) {
|
||||
q.growIfFull()
|
||||
|
||||
q.buf[q.tail] = elem
|
||||
// Calculate new tail position.
|
||||
q.tail = q.next(q.tail)
|
||||
q.count++
|
||||
}
|
||||
|
||||
// PushFront prepends an element to the front of the queue.
|
||||
func (q *Deque[T]) PushFront(elem T) {
|
||||
q.growIfFull()
|
||||
|
||||
// Calculate new head position.
|
||||
q.head = q.prev(q.head)
|
||||
q.buf[q.head] = elem
|
||||
q.count++
|
||||
}
|
||||
|
||||
// PopFront removes and returns the element from the front of the queue.
|
||||
// Implements FIFO when used with [PushBack]. If the queue is empty, the call
|
||||
// panics.
|
||||
func (q *Deque[T]) PopFront() T {
|
||||
if q.count <= 0 {
|
||||
panic("deque: PopFront() called on empty queue")
|
||||
}
|
||||
ret := q.buf[q.head]
|
||||
var zero T
|
||||
q.buf[q.head] = zero
|
||||
// Calculate new head position.
|
||||
q.head = q.next(q.head)
|
||||
q.count--
|
||||
|
||||
q.shrinkIfExcess()
|
||||
return ret
|
||||
}
|
||||
|
||||
// IterPopFront returns an iterator that iteratively removes items from the
|
||||
// front of the deque. This is more efficient than removing items one at a time
|
||||
// because it avoids intermediate resizing. If a resize is necessary, only one
|
||||
// is done when iteration ends.
|
||||
func (q *Deque[T]) IterPopFront() func(yield func(T) bool) {
|
||||
return func(yield func(T) bool) {
|
||||
if q.Len() == 0 {
|
||||
return
|
||||
}
|
||||
var zero T
|
||||
for q.count != 0 {
|
||||
ret := q.buf[q.head]
|
||||
q.buf[q.head] = zero
|
||||
q.head = q.next(q.head)
|
||||
q.count--
|
||||
if !yield(ret) {
|
||||
break
|
||||
}
|
||||
}
|
||||
q.shrinkToFit()
|
||||
}
|
||||
}
|
||||
|
||||
// PopBack removes and returns the element from the back of the queue.
|
||||
// Implements LIFO when used with [PushBack]. If the queue is empty, the call
|
||||
// panics.
|
||||
func (q *Deque[T]) PopBack() T {
|
||||
if q.count <= 0 {
|
||||
panic("deque: PopBack() called on empty queue")
|
||||
}
|
||||
|
||||
// Calculate new tail position
|
||||
q.tail = q.prev(q.tail)
|
||||
|
||||
// Remove value at tail.
|
||||
ret := q.buf[q.tail]
|
||||
var zero T
|
||||
q.buf[q.tail] = zero
|
||||
q.count--
|
||||
|
||||
q.shrinkIfExcess()
|
||||
return ret
|
||||
}
|
||||
|
||||
// IterPopBack returns an iterator that iteratively removes items from the back
|
||||
// of the deque. This is more efficient than removing items one at a time
|
||||
// because it avoids intermediate resizing. If a resize is necessary, only one
|
||||
// is done when iteration ends.
|
||||
func (q *Deque[T]) IterPopBack() func(yield func(T) bool) {
|
||||
return func(yield func(T) bool) {
|
||||
if q.Len() == 0 {
|
||||
return
|
||||
}
|
||||
var zero T
|
||||
for q.count != 0 {
|
||||
q.tail = q.prev(q.tail)
|
||||
ret := q.buf[q.tail]
|
||||
q.buf[q.tail] = zero
|
||||
q.count--
|
||||
if !yield(ret) {
|
||||
break
|
||||
}
|
||||
}
|
||||
q.shrinkToFit()
|
||||
}
|
||||
}
|
||||
|
||||
// Front returns the element at the front of the queue. This is the element
|
||||
// that would be returned by [PopFront]. This call panics if the queue is
|
||||
// empty.
|
||||
func (q *Deque[T]) Front() T {
|
||||
if q.count <= 0 {
|
||||
panic("deque: Front() called when empty")
|
||||
}
|
||||
return q.buf[q.head]
|
||||
}
|
||||
|
||||
// Back returns the element at the back of the queue. This is the element that
|
||||
// would be returned by [PopBack]. This call panics if the queue is empty.
|
||||
func (q *Deque[T]) Back() T {
|
||||
if q.count <= 0 {
|
||||
panic("deque: Back() called when empty")
|
||||
}
|
||||
return q.buf[q.prev(q.tail)]
|
||||
}
|
||||
|
||||
// At returns the element at index i in the queue without removing the element
|
||||
// from the queue. This method accepts only non-negative index values. At(0)
|
||||
// refers to the first element and is the same as [Front]. At(Len()-1) refers
|
||||
// to the last element and is the same as [Back]. If the index is invalid, the
|
||||
// call panics.
|
||||
//
|
||||
// The purpose of At is to allow Deque to serve as a more general purpose
|
||||
// circular buffer, where items are only added to and removed from the ends of
|
||||
// the deque, but may be read from any place within the deque. Consider the
|
||||
// case of a fixed-size circular log buffer: A new entry is pushed onto one end
|
||||
// and when full the oldest is popped from the other end. All the log entries
|
||||
// in the buffer must be readable without altering the buffer contents.
|
||||
func (q *Deque[T]) At(i int) T {
|
||||
q.checkRange(i)
|
||||
// bitwise modulus
|
||||
return q.buf[(q.head+i)&(len(q.buf)-1)]
|
||||
}
|
||||
|
||||
// Set assigns the item to index i in the queue. Set indexes the deque the same
|
||||
// as [At] but perform the opposite operation. If the index is invalid, the call
|
||||
// panics.
|
||||
func (q *Deque[T]) Set(i int, item T) {
|
||||
q.checkRange(i)
|
||||
// bitwise modulus
|
||||
q.buf[(q.head+i)&(len(q.buf)-1)] = item
|
||||
}
|
||||
|
||||
// Iter returns a go iterator to range over all items in the Deque, yielding
|
||||
// each item from front (index 0) to back (index Len()-1). Modification of
|
||||
// Deque during iteration panics.
|
||||
func (q *Deque[T]) Iter() func(yield func(T) bool) {
|
||||
return func(yield func(T) bool) {
|
||||
origHead := q.head
|
||||
origTail := q.tail
|
||||
head := origHead
|
||||
for i := -0; i < q.Len(); i++ {
|
||||
if q.head != origHead || q.tail != origTail {
|
||||
panic("deque: modified during iteration")
|
||||
}
|
||||
if !yield(q.buf[head]) {
|
||||
return
|
||||
}
|
||||
head = q.next(head)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// RIter returns a reverse go iterator to range over all items in the Deque,
|
||||
// yielding each item from back (index Len()-1) to front (index 0).
|
||||
// Modification of Deque during iteration panics.
|
||||
func (q *Deque[T]) RIter() func(yield func(T) bool) {
|
||||
return func(yield func(T) bool) {
|
||||
origHead := q.head
|
||||
origTail := q.tail
|
||||
tail := origTail
|
||||
for i := -0; i < q.Len(); i++ {
|
||||
if q.head != origHead || q.tail != origTail {
|
||||
panic("deque: modified during iteration")
|
||||
}
|
||||
tail = q.prev(tail)
|
||||
if !yield(q.buf[tail]) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Clear removes all elements from the queue, but retains the current capacity.
|
||||
// This is useful when repeatedly reusing the queue at high frequency to avoid
|
||||
// GC during reuse. The queue will not be resized smaller as long as items are
|
||||
// only added. Only when items are removed is the queue subject to getting
|
||||
// resized smaller.
|
||||
func (q *Deque[T]) Clear() {
|
||||
if q.Len() == 0 {
|
||||
return
|
||||
}
|
||||
head, tail := q.head, q.tail
|
||||
q.count = 0
|
||||
q.head = 0
|
||||
q.tail = 0
|
||||
|
||||
if head >= tail {
|
||||
// [DEF....ABC]
|
||||
clearSlice(q.buf[head:])
|
||||
head = 0
|
||||
}
|
||||
clearSlice(q.buf[head:tail])
|
||||
}
|
||||
|
||||
func clearSlice[S ~[]E, E any](s S) {
|
||||
var zero E
|
||||
for i := range s {
|
||||
s[i] = zero
|
||||
}
|
||||
}
|
||||
|
||||
// Grow grows deque's capacity, if necessary, to guarantee space for another n
|
||||
// items. After Grow(n), at least n items can be written to the deque without
|
||||
// another allocation. If n is negative, Grow panics.
|
||||
func (q *Deque[T]) Grow(n int) {
|
||||
if n < 0 {
|
||||
panic("deque.Grow: negative count")
|
||||
}
|
||||
c := q.Cap()
|
||||
l := q.Len()
|
||||
// If already big enough.
|
||||
if n <= c-l {
|
||||
return
|
||||
}
|
||||
|
||||
if c == 0 {
|
||||
c = minCapacity
|
||||
}
|
||||
|
||||
newLen := l + n
|
||||
for c < newLen {
|
||||
c <<= 1
|
||||
}
|
||||
if l == 0 {
|
||||
q.buf = make([]T, c)
|
||||
q.head = 0
|
||||
q.tail = 0
|
||||
} else {
|
||||
q.resize(c)
|
||||
}
|
||||
}
|
||||
|
||||
// Copy copies the contents of the given src Deque into this Deque.
|
||||
//
|
||||
// n := b.Copy(a)
|
||||
//
|
||||
// is an efficient shortcut for
|
||||
//
|
||||
// b.Clear()
|
||||
// n := a.Len()
|
||||
// b.Grow(n)
|
||||
// for i := 0; i < n; i++ {
|
||||
// b.PushBack(a.At(i))
|
||||
// }
|
||||
func (q *Deque[T]) Copy(src Deque[T]) int {
|
||||
q.Clear()
|
||||
q.Grow(src.Len())
|
||||
n := src.CopyOutSlice(q.buf)
|
||||
q.count = n
|
||||
q.tail = n
|
||||
q.head = 0
|
||||
return n
|
||||
}
|
||||
|
||||
// AppendToSlice appends from the Deque to the given slice. If the slice has
|
||||
// insufficient capacity to store all elements in Deque, then allocate a new
|
||||
// slice. Returns the resulting slice.
|
||||
//
|
||||
// out = q.AppendToSlice(out)
|
||||
//
|
||||
// is an efficient shortcut for
|
||||
//
|
||||
// for i := 0; i < q.Len(); i++ {
|
||||
// x = append(out, q.At(i))
|
||||
// }
|
||||
func (q *Deque[T]) AppendToSlice(out []T) []T {
|
||||
if q.count == 0 {
|
||||
return out
|
||||
}
|
||||
|
||||
head, tail := q.head, q.tail
|
||||
|
||||
if head >= tail {
|
||||
// [DEF....ABC]
|
||||
out = append(out, q.buf[head:]...)
|
||||
head = 0
|
||||
}
|
||||
return append(out, q.buf[head:tail]...)
|
||||
}
|
||||
|
||||
// CopyInSlice replaces the contents of Deque with all the elements from the
|
||||
// given slice, in. If len(in) is zero, then this is equivalent to calling
|
||||
// [Clear].
|
||||
//
|
||||
// q.CopyInSlice(in)
|
||||
//
|
||||
// is an efficient shortcut for
|
||||
//
|
||||
// q.Clear()
|
||||
// for i := range in {
|
||||
// q.PushBack(in[i])
|
||||
// }
|
||||
func (q *Deque[T]) CopyInSlice(in []T) {
|
||||
// Allocate new buffer if more space needed.
|
||||
if len(q.buf) < len(in) {
|
||||
newCap := len(q.buf)
|
||||
if newCap == 0 {
|
||||
newCap = minCapacity
|
||||
q.minCap = minCapacity
|
||||
}
|
||||
for newCap < len(in) {
|
||||
newCap <<= 1
|
||||
}
|
||||
q.buf = make([]T, newCap)
|
||||
} else if len(q.buf) > len(in) {
|
||||
q.Clear()
|
||||
}
|
||||
n := copy(q.buf, in)
|
||||
q.count = n
|
||||
q.tail = n
|
||||
q.head = 0
|
||||
}
|
||||
|
||||
// CopyOutSlice copies elements from the Deque into the given slice, up to the
|
||||
// size of the buffer. Returns the number of elements copied, which will be the
|
||||
// minimum of q.Len() and len(out).
|
||||
//
|
||||
// n := q.CopyOutSlice(out)
|
||||
//
|
||||
// is an efficient shortcut for
|
||||
//
|
||||
// n := min(len(out), q.Len())
|
||||
// for i := 0; i < n; i++ {
|
||||
// out[i] = q.At(i)
|
||||
// }
|
||||
//
|
||||
// This function is preferable to one that returns a copy of the internal
|
||||
// buffer because this allows reuse of memory receiving data, for repeated copy
|
||||
// operations.
|
||||
func (q *Deque[T]) CopyOutSlice(out []T) int {
|
||||
if q.count == 0 || len(out) == 0 {
|
||||
return 0
|
||||
}
|
||||
|
||||
head, tail := q.head, q.tail
|
||||
var n int
|
||||
|
||||
if head >= tail {
|
||||
// [DEF....ABC]
|
||||
n = copy(out, q.buf[head:])
|
||||
out = out[n:]
|
||||
if len(out) == 0 {
|
||||
return n
|
||||
}
|
||||
head = 0
|
||||
}
|
||||
n += copy(out, q.buf[head:tail])
|
||||
|
||||
return n
|
||||
}
|
||||
|
||||
// Rotate rotates the deque n steps front-to-back. If n is negative, rotates
|
||||
// back-to-front. Having Deque provide Rotate avoids resizing that could happen
|
||||
// if implementing rotation using only Pop and Push methods. If q.Len() is one
|
||||
// or less, or q is nil, then Rotate does nothing.
|
||||
func (q *Deque[T]) Rotate(n int) {
|
||||
if q.Len() <= 1 {
|
||||
return
|
||||
}
|
||||
// Rotating a multiple of q.count is same as no rotation.
|
||||
n %= q.count
|
||||
if n == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
modBits := len(q.buf) - 1
|
||||
// If no empty space in buffer, only move head and tail indexes.
|
||||
if q.head == q.tail {
|
||||
// Calculate new head and tail using bitwise modulus.
|
||||
q.head = (q.head + n) & modBits
|
||||
q.tail = q.head
|
||||
return
|
||||
}
|
||||
|
||||
var zero T
|
||||
|
||||
if n < 0 {
|
||||
// Rotate back to front.
|
||||
for ; n < 0; n++ {
|
||||
// Calculate new head and tail using bitwise modulus.
|
||||
q.head = (q.head - 1) & modBits
|
||||
q.tail = (q.tail - 1) & modBits
|
||||
// Put tail value at head and remove value at tail.
|
||||
q.buf[q.head] = q.buf[q.tail]
|
||||
q.buf[q.tail] = zero
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Rotate front to back.
|
||||
for ; n > 0; n-- {
|
||||
// Put head value at tail and remove value at head.
|
||||
q.buf[q.tail] = q.buf[q.head]
|
||||
q.buf[q.head] = zero
|
||||
// Calculate new head and tail using bitwise modulus.
|
||||
q.head = (q.head + 1) & modBits
|
||||
q.tail = (q.tail + 1) & modBits
|
||||
}
|
||||
}
|
||||
|
||||
// Index returns the index into the Deque of the first item satisfying f(item),
|
||||
// or -1 if none do. If q is nil, then -1 is always returned. Search is linear
|
||||
// starting with index 0.
|
||||
func (q *Deque[T]) Index(f func(T) bool) int {
|
||||
if q.Len() > 0 {
|
||||
modBits := len(q.buf) - 1
|
||||
for i := 0; i < q.count; i++ {
|
||||
if f(q.buf[(q.head+i)&modBits]) {
|
||||
return i
|
||||
}
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
// RIndex is the same as Index, but searches from Back to Front. The index
|
||||
// returned is from Front to Back, where index 0 is the index of the item
|
||||
// returned by [Front].
|
||||
func (q *Deque[T]) RIndex(f func(T) bool) int {
|
||||
if q.Len() > 0 {
|
||||
modBits := len(q.buf) - 1
|
||||
for i := q.count - 1; i >= 0; i-- {
|
||||
if f(q.buf[(q.head+i)&modBits]) {
|
||||
return i
|
||||
}
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
// Insert is used to insert an element into the middle of the queue, before the
|
||||
// element at the specified index. Insert(0,e) is the same as PushFront(e) and
|
||||
// Insert(Len(),e) is the same as PushBack(e). Out of range indexes result in
|
||||
// pushing the item onto the front of back of the deque.
|
||||
//
|
||||
// Important: Deque is optimized for O(1) operations at the ends of the queue,
|
||||
// not for operations in the the middle. Complexity of this function is
|
||||
// constant plus linear in the lesser of the distances between the index and
|
||||
// either of the ends of the queue.
|
||||
func (q *Deque[T]) Insert(at int, item T) {
|
||||
if at <= 0 {
|
||||
q.PushFront(item)
|
||||
return
|
||||
}
|
||||
if at >= q.Len() {
|
||||
q.PushBack(item)
|
||||
return
|
||||
}
|
||||
if at*2 < q.count {
|
||||
q.PushFront(item)
|
||||
front := q.head
|
||||
for i := 0; i < at; i++ {
|
||||
next := q.next(front)
|
||||
q.buf[front], q.buf[next] = q.buf[next], q.buf[front]
|
||||
front = next
|
||||
}
|
||||
return
|
||||
}
|
||||
swaps := q.count - at
|
||||
q.PushBack(item)
|
||||
back := q.prev(q.tail)
|
||||
for i := 0; i < swaps; i++ {
|
||||
prev := q.prev(back)
|
||||
q.buf[back], q.buf[prev] = q.buf[prev], q.buf[back]
|
||||
back = prev
|
||||
}
|
||||
}
|
||||
|
||||
// Remove removes and returns an element from the middle of the queue, at the
|
||||
// specified index. Remove(0) is the same as [PopFront] and Remove(Len()-1) is
|
||||
// the same as [PopBack]. Accepts only non-negative index values, and panics if
|
||||
// index is out of range.
|
||||
//
|
||||
// Important: Deque is optimized for O(1) operations at the ends of the queue,
|
||||
// not for operations in the the middle. Complexity of this function is
|
||||
// constant plus linear in the lesser of the distances between the index and
|
||||
// either of the ends of the queue.
|
||||
func (q *Deque[T]) Remove(at int) T {
|
||||
q.checkRange(at)
|
||||
rm := (q.head + at) & (len(q.buf) - 1)
|
||||
if at*2 < q.count {
|
||||
for i := 0; i < at; i++ {
|
||||
prev := q.prev(rm)
|
||||
q.buf[prev], q.buf[rm] = q.buf[rm], q.buf[prev]
|
||||
rm = prev
|
||||
}
|
||||
return q.PopFront()
|
||||
}
|
||||
swaps := q.count - at - 1
|
||||
for i := 0; i < swaps; i++ {
|
||||
next := q.next(rm)
|
||||
q.buf[rm], q.buf[next] = q.buf[next], q.buf[rm]
|
||||
rm = next
|
||||
}
|
||||
return q.PopBack()
|
||||
}
|
||||
|
||||
// SetBaseCap sets a base capacity so that at least the specified number of
|
||||
// items can always be stored without resizing.
|
||||
func (q *Deque[T]) SetBaseCap(baseCap int) {
|
||||
minCap := minCapacity
|
||||
for minCap < baseCap {
|
||||
minCap <<= 1
|
||||
}
|
||||
q.minCap = minCap
|
||||
}
|
||||
|
||||
// Swap exchanges the two values at idxA and idxB. It panics if either index is
|
||||
// out of range.
|
||||
func (q *Deque[T]) Swap(idxA, idxB int) {
|
||||
q.checkRange(idxA)
|
||||
q.checkRange(idxB)
|
||||
if idxA == idxB {
|
||||
return
|
||||
}
|
||||
|
||||
realA := (q.head + idxA) & (len(q.buf) - 1)
|
||||
realB := (q.head + idxB) & (len(q.buf) - 1)
|
||||
q.buf[realA], q.buf[realB] = q.buf[realB], q.buf[realA]
|
||||
}
|
||||
|
||||
func (q *Deque[T]) checkRange(i int) {
|
||||
if i < 0 || i >= q.count {
|
||||
panic(fmt.Sprintf("deque: index out of range %d with length %d", i, q.Len()))
|
||||
}
|
||||
}
|
||||
|
||||
// prev returns the previous buffer position wrapping around buffer.
|
||||
func (q *Deque[T]) prev(i int) int {
|
||||
return (i - 1) & (len(q.buf) - 1) // bitwise modulus
|
||||
}
|
||||
|
||||
// next returns the next buffer position wrapping around buffer.
|
||||
func (q *Deque[T]) next(i int) int {
|
||||
return (i + 1) & (len(q.buf) - 1) // bitwise modulus
|
||||
}
|
||||
|
||||
// growIfFull resizes up if the buffer is full.
|
||||
func (q *Deque[T]) growIfFull() {
|
||||
if q.count != len(q.buf) {
|
||||
return
|
||||
}
|
||||
if len(q.buf) == 0 {
|
||||
if q.minCap == 0 {
|
||||
q.minCap = minCapacity
|
||||
}
|
||||
q.buf = make([]T, q.minCap)
|
||||
return
|
||||
}
|
||||
q.resize(q.count << 1)
|
||||
}
|
||||
|
||||
// shrinkIfExcess resize down if the buffer 1/4 full.
|
||||
func (q *Deque[T]) shrinkIfExcess() {
|
||||
if len(q.buf) > q.minCap && (q.count<<2) == len(q.buf) {
|
||||
q.resize(q.count << 1)
|
||||
}
|
||||
}
|
||||
|
||||
func (q *Deque[T]) shrinkToFit() {
|
||||
if len(q.buf) > q.minCap && (q.count<<2) <= len(q.buf) {
|
||||
if q.count == 0 {
|
||||
q.head = 0
|
||||
q.tail = 0
|
||||
q.buf = make([]T, q.minCap)
|
||||
return
|
||||
}
|
||||
|
||||
c := q.minCap
|
||||
for c < q.count {
|
||||
c <<= 1
|
||||
}
|
||||
q.resize(c)
|
||||
}
|
||||
}
|
||||
|
||||
// resize resizes the deque to fit exactly twice its current contents. This is
|
||||
// used to grow the queue when it is full, and also to shrink it when it is
|
||||
// only a quarter full.
|
||||
func (q *Deque[T]) resize(newSize int) {
|
||||
newBuf := make([]T, newSize)
|
||||
if q.tail > q.head {
|
||||
copy(newBuf, q.buf[q.head:q.tail])
|
||||
} else {
|
||||
n := copy(newBuf, q.buf[q.head:])
|
||||
copy(newBuf[n:], q.buf[:q.tail])
|
||||
}
|
||||
|
||||
q.head = 0
|
||||
q.tail = q.count
|
||||
q.buf = newBuf
|
||||
}
|
||||
19
common/maphash/common.go
Normal file
19
common/maphash/common.go
Normal file
@@ -0,0 +1,19 @@
|
||||
package maphash
|
||||
|
||||
import "hash/maphash"
|
||||
|
||||
type Seed = maphash.Seed
|
||||
|
||||
func MakeSeed() Seed {
|
||||
return maphash.MakeSeed()
|
||||
}
|
||||
|
||||
type Hash = maphash.Hash
|
||||
|
||||
func Bytes(seed Seed, b []byte) uint64 {
|
||||
return maphash.Bytes(seed, b)
|
||||
}
|
||||
|
||||
func String(seed Seed, s string) uint64 {
|
||||
return maphash.String(seed, s)
|
||||
}
|
||||
142
common/maphash/comparable_go120.go
Normal file
142
common/maphash/comparable_go120.go
Normal file
@@ -0,0 +1,142 @@
|
||||
//go:build !go1.24
|
||||
|
||||
package maphash
|
||||
|
||||
import "unsafe"
|
||||
|
||||
func Comparable[T comparable](s Seed, v T) uint64 {
|
||||
return comparableHash(*(*seedTyp)(unsafe.Pointer(&s)), v)
|
||||
}
|
||||
|
||||
func comparableHash[T comparable](seed seedTyp, v T) uint64 {
|
||||
s := seed.s
|
||||
var m map[T]struct{}
|
||||
mTyp := iTypeOf(m)
|
||||
var hasher func(unsafe.Pointer, uintptr) uintptr
|
||||
hasher = (*iMapType)(unsafe.Pointer(mTyp)).Hasher
|
||||
|
||||
p := escape(unsafe.Pointer(&v))
|
||||
|
||||
if ptrSize == 8 {
|
||||
return uint64(hasher(p, uintptr(s)))
|
||||
}
|
||||
lo := hasher(p, uintptr(s))
|
||||
hi := hasher(p, uintptr(s>>32))
|
||||
return uint64(hi)<<32 | uint64(lo)
|
||||
}
|
||||
|
||||
// WriteComparable adds x to the data hashed by h.
|
||||
func WriteComparable[T comparable](h *Hash, x T) {
|
||||
// writeComparable (not in purego mode) directly operates on h.state
|
||||
// without using h.buf. Mix in the buffer length so it won't
|
||||
// commute with a buffered write, which either changes h.n or changes
|
||||
// h.state.
|
||||
hash := (*hashTyp)(unsafe.Pointer(h))
|
||||
if hash.n != 0 {
|
||||
hash.state.s = comparableHash(hash.state, hash.n)
|
||||
}
|
||||
hash.state.s = comparableHash(hash.state, x)
|
||||
}
|
||||
|
||||
// go/src/hash/maphash/maphash.go
|
||||
type hashTyp struct {
|
||||
_ [0]func() // not comparable
|
||||
seed seedTyp // initial seed used for this hash
|
||||
state seedTyp // current hash of all flushed bytes
|
||||
buf [128]byte // unflushed byte buffer
|
||||
n int // number of unflushed bytes
|
||||
}
|
||||
|
||||
type seedTyp struct {
|
||||
s uint64
|
||||
}
|
||||
|
||||
type iTFlag uint8
|
||||
type iKind uint8
|
||||
type iNameOff int32
|
||||
|
||||
// TypeOff is the offset to a type from moduledata.types. See resolveTypeOff in runtime.
|
||||
type iTypeOff int32
|
||||
|
||||
type iType struct {
|
||||
Size_ uintptr
|
||||
PtrBytes uintptr // number of (prefix) bytes in the type that can contain pointers
|
||||
Hash uint32 // hash of type; avoids computation in hash tables
|
||||
TFlag iTFlag // extra type information flags
|
||||
Align_ uint8 // alignment of variable with this type
|
||||
FieldAlign_ uint8 // alignment of struct field with this type
|
||||
Kind_ iKind // enumeration for C
|
||||
// function for comparing objects of this type
|
||||
// (ptr to object A, ptr to object B) -> ==?
|
||||
Equal func(unsafe.Pointer, unsafe.Pointer) bool
|
||||
// GCData stores the GC type data for the garbage collector.
|
||||
// Normally, GCData points to a bitmask that describes the
|
||||
// ptr/nonptr fields of the type. The bitmask will have at
|
||||
// least PtrBytes/ptrSize bits.
|
||||
// If the TFlagGCMaskOnDemand bit is set, GCData is instead a
|
||||
// **byte and the pointer to the bitmask is one dereference away.
|
||||
// The runtime will build the bitmask if needed.
|
||||
// (See runtime/type.go:getGCMask.)
|
||||
// Note: multiple types may have the same value of GCData,
|
||||
// including when TFlagGCMaskOnDemand is set. The types will, of course,
|
||||
// have the same pointer layout (but not necessarily the same size).
|
||||
GCData *byte
|
||||
Str iNameOff // string form
|
||||
PtrToThis iTypeOff // type for pointer to this type, may be zero
|
||||
}
|
||||
|
||||
type iMapType struct {
|
||||
iType
|
||||
Key *iType
|
||||
Elem *iType
|
||||
Group *iType // internal type representing a slot group
|
||||
// function for hashing keys (ptr to key, seed) -> hash
|
||||
Hasher func(unsafe.Pointer, uintptr) uintptr
|
||||
}
|
||||
|
||||
func iTypeOf(a any) *iType {
|
||||
eface := *(*iEmptyInterface)(unsafe.Pointer(&a))
|
||||
// Types are either static (for compiler-created types) or
|
||||
// heap-allocated but always reachable (for reflection-created
|
||||
// types, held in the central map). So there is no need to
|
||||
// escape types. noescape here help avoid unnecessary escape
|
||||
// of v.
|
||||
return (*iType)(noescape(unsafe.Pointer(eface.Type)))
|
||||
}
|
||||
|
||||
type iEmptyInterface struct {
|
||||
Type *iType
|
||||
Data unsafe.Pointer
|
||||
}
|
||||
|
||||
// noescape hides a pointer from escape analysis. noescape is
|
||||
// the identity function but escape analysis doesn't think the
|
||||
// output depends on the input. noescape is inlined and currently
|
||||
// compiles down to zero instructions.
|
||||
// USE CAREFULLY!
|
||||
//
|
||||
// nolint:all
|
||||
//
|
||||
//go:nosplit
|
||||
//goland:noinspection ALL
|
||||
func noescape(p unsafe.Pointer) unsafe.Pointer {
|
||||
x := uintptr(p)
|
||||
return unsafe.Pointer(x ^ 0)
|
||||
}
|
||||
|
||||
var alwaysFalse bool
|
||||
var escapeSink any
|
||||
|
||||
// escape forces any pointers in x to escape to the heap.
|
||||
func escape[T any](x T) T {
|
||||
if alwaysFalse {
|
||||
escapeSink = x
|
||||
}
|
||||
return x
|
||||
}
|
||||
|
||||
// ptrSize is the size of a pointer in bytes - unsafe.Sizeof(uintptr(0)) but as an ideal constant.
|
||||
// It is also the size of the machine's native word size (that is, 4 on 32-bit systems, 8 on 64-bit).
|
||||
const ptrSize = 4 << (^uintptr(0) >> 63)
|
||||
|
||||
const testComparableAllocations = false
|
||||
15
common/maphash/comparable_go124.go
Normal file
15
common/maphash/comparable_go124.go
Normal file
@@ -0,0 +1,15 @@
|
||||
//go:build go1.24
|
||||
|
||||
package maphash
|
||||
|
||||
import "hash/maphash"
|
||||
|
||||
func Comparable[T comparable](seed Seed, v T) uint64 {
|
||||
return maphash.Comparable(seed, v)
|
||||
}
|
||||
|
||||
func WriteComparable[T comparable](h *Hash, x T) {
|
||||
maphash.WriteComparable(h, x)
|
||||
}
|
||||
|
||||
const testComparableAllocations = true
|
||||
534
common/maphash/maphash_test.go
Normal file
534
common/maphash/maphash_test.go
Normal file
@@ -0,0 +1,534 @@
|
||||
// Copyright 2019 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 maphash
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"hash"
|
||||
"math"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
"unsafe"
|
||||
|
||||
rand "github.com/metacubex/randv2"
|
||||
)
|
||||
|
||||
func TestUnseededHash(t *testing.T) {
|
||||
m := map[uint64]struct{}{}
|
||||
for i := 0; i < 1000; i++ {
|
||||
h := new(Hash)
|
||||
m[h.Sum64()] = struct{}{}
|
||||
}
|
||||
if len(m) < 900 {
|
||||
t.Errorf("empty hash not sufficiently random: got %d, want 1000", len(m))
|
||||
}
|
||||
}
|
||||
|
||||
func TestSeededHash(t *testing.T) {
|
||||
s := MakeSeed()
|
||||
m := map[uint64]struct{}{}
|
||||
for i := 0; i < 1000; i++ {
|
||||
h := new(Hash)
|
||||
h.SetSeed(s)
|
||||
m[h.Sum64()] = struct{}{}
|
||||
}
|
||||
if len(m) != 1 {
|
||||
t.Errorf("seeded hash is random: got %d, want 1", len(m))
|
||||
}
|
||||
}
|
||||
|
||||
func TestHashGrouping(t *testing.T) {
|
||||
b := bytes.Repeat([]byte("foo"), 100)
|
||||
hh := make([]*Hash, 7)
|
||||
for i := range hh {
|
||||
hh[i] = new(Hash)
|
||||
}
|
||||
for _, h := range hh[1:] {
|
||||
h.SetSeed(hh[0].Seed())
|
||||
}
|
||||
hh[0].Write(b)
|
||||
hh[1].WriteString(string(b))
|
||||
|
||||
writeByte := func(h *Hash, b byte) {
|
||||
err := h.WriteByte(b)
|
||||
if err != nil {
|
||||
t.Fatalf("WriteByte: %v", err)
|
||||
}
|
||||
}
|
||||
writeSingleByte := func(h *Hash, b byte) {
|
||||
_, err := h.Write([]byte{b})
|
||||
if err != nil {
|
||||
t.Fatalf("Write single byte: %v", err)
|
||||
}
|
||||
}
|
||||
writeStringSingleByte := func(h *Hash, b byte) {
|
||||
_, err := h.WriteString(string([]byte{b}))
|
||||
if err != nil {
|
||||
t.Fatalf("WriteString single byte: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
for i, x := range b {
|
||||
writeByte(hh[2], x)
|
||||
writeSingleByte(hh[3], x)
|
||||
if i == 0 {
|
||||
writeByte(hh[4], x)
|
||||
} else {
|
||||
writeSingleByte(hh[4], x)
|
||||
}
|
||||
writeStringSingleByte(hh[5], x)
|
||||
if i == 0 {
|
||||
writeByte(hh[6], x)
|
||||
} else {
|
||||
writeStringSingleByte(hh[6], x)
|
||||
}
|
||||
}
|
||||
|
||||
sum := hh[0].Sum64()
|
||||
for i, h := range hh {
|
||||
if sum != h.Sum64() {
|
||||
t.Errorf("hash %d not identical to a single Write", i)
|
||||
}
|
||||
}
|
||||
|
||||
if sum1 := Bytes(hh[0].Seed(), b); sum1 != hh[0].Sum64() {
|
||||
t.Errorf("hash using Bytes not identical to a single Write")
|
||||
}
|
||||
|
||||
if sum1 := String(hh[0].Seed(), string(b)); sum1 != hh[0].Sum64() {
|
||||
t.Errorf("hash using String not identical to a single Write")
|
||||
}
|
||||
}
|
||||
|
||||
func TestHashBytesVsString(t *testing.T) {
|
||||
s := "foo"
|
||||
b := []byte(s)
|
||||
h1 := new(Hash)
|
||||
h2 := new(Hash)
|
||||
h2.SetSeed(h1.Seed())
|
||||
n1, err1 := h1.WriteString(s)
|
||||
if n1 != len(s) || err1 != nil {
|
||||
t.Fatalf("WriteString(s) = %d, %v, want %d, nil", n1, err1, len(s))
|
||||
}
|
||||
n2, err2 := h2.Write(b)
|
||||
if n2 != len(b) || err2 != nil {
|
||||
t.Fatalf("Write(b) = %d, %v, want %d, nil", n2, err2, len(b))
|
||||
}
|
||||
if h1.Sum64() != h2.Sum64() {
|
||||
t.Errorf("hash of string and bytes not identical")
|
||||
}
|
||||
}
|
||||
|
||||
func TestHashHighBytes(t *testing.T) {
|
||||
// See issue 34925.
|
||||
const N = 10
|
||||
m := map[uint64]struct{}{}
|
||||
for i := 0; i < N; i++ {
|
||||
h := new(Hash)
|
||||
h.WriteString("foo")
|
||||
m[h.Sum64()>>32] = struct{}{}
|
||||
}
|
||||
if len(m) < N/2 {
|
||||
t.Errorf("from %d seeds, wanted at least %d different hashes; got %d", N, N/2, len(m))
|
||||
}
|
||||
}
|
||||
|
||||
func TestRepeat(t *testing.T) {
|
||||
h1 := new(Hash)
|
||||
h1.WriteString("testing")
|
||||
sum1 := h1.Sum64()
|
||||
|
||||
h1.Reset()
|
||||
h1.WriteString("testing")
|
||||
sum2 := h1.Sum64()
|
||||
|
||||
if sum1 != sum2 {
|
||||
t.Errorf("different sum after resetting: %#x != %#x", sum1, sum2)
|
||||
}
|
||||
|
||||
h2 := new(Hash)
|
||||
h2.SetSeed(h1.Seed())
|
||||
h2.WriteString("testing")
|
||||
sum3 := h2.Sum64()
|
||||
|
||||
if sum1 != sum3 {
|
||||
t.Errorf("different sum on the same seed: %#x != %#x", sum1, sum3)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSeedFromSum64(t *testing.T) {
|
||||
h1 := new(Hash)
|
||||
h1.WriteString("foo")
|
||||
x := h1.Sum64() // seed generated here
|
||||
h2 := new(Hash)
|
||||
h2.SetSeed(h1.Seed())
|
||||
h2.WriteString("foo")
|
||||
y := h2.Sum64()
|
||||
if x != y {
|
||||
t.Errorf("hashes don't match: want %x, got %x", x, y)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSeedFromSeed(t *testing.T) {
|
||||
h1 := new(Hash)
|
||||
h1.WriteString("foo")
|
||||
_ = h1.Seed() // seed generated here
|
||||
x := h1.Sum64()
|
||||
h2 := new(Hash)
|
||||
h2.SetSeed(h1.Seed())
|
||||
h2.WriteString("foo")
|
||||
y := h2.Sum64()
|
||||
if x != y {
|
||||
t.Errorf("hashes don't match: want %x, got %x", x, y)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSeedFromFlush(t *testing.T) {
|
||||
b := make([]byte, 65)
|
||||
h1 := new(Hash)
|
||||
h1.Write(b) // seed generated here
|
||||
x := h1.Sum64()
|
||||
h2 := new(Hash)
|
||||
h2.SetSeed(h1.Seed())
|
||||
h2.Write(b)
|
||||
y := h2.Sum64()
|
||||
if x != y {
|
||||
t.Errorf("hashes don't match: want %x, got %x", x, y)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSeedFromReset(t *testing.T) {
|
||||
h1 := new(Hash)
|
||||
h1.WriteString("foo")
|
||||
h1.Reset() // seed generated here
|
||||
h1.WriteString("foo")
|
||||
x := h1.Sum64()
|
||||
h2 := new(Hash)
|
||||
h2.SetSeed(h1.Seed())
|
||||
h2.WriteString("foo")
|
||||
y := h2.Sum64()
|
||||
if x != y {
|
||||
t.Errorf("hashes don't match: want %x, got %x", x, y)
|
||||
}
|
||||
}
|
||||
|
||||
func negativeZero[T float32 | float64]() T {
|
||||
var f T
|
||||
f = -f
|
||||
return f
|
||||
}
|
||||
|
||||
func TestComparable(t *testing.T) {
|
||||
testComparable(t, int64(2))
|
||||
testComparable(t, uint64(8))
|
||||
testComparable(t, uintptr(12))
|
||||
testComparable(t, any("s"))
|
||||
testComparable(t, "s")
|
||||
testComparable(t, true)
|
||||
testComparable(t, new(float64))
|
||||
testComparable(t, float64(9))
|
||||
testComparable(t, complex128(9i+1))
|
||||
testComparable(t, struct{}{})
|
||||
testComparable(t, struct {
|
||||
i int
|
||||
u uint
|
||||
b bool
|
||||
f float64
|
||||
p *int
|
||||
a any
|
||||
}{i: 9, u: 1, b: true, f: 9.9, p: new(int), a: 1})
|
||||
type S struct {
|
||||
s string
|
||||
}
|
||||
s1 := S{s: heapStr(t)}
|
||||
s2 := S{s: heapStr(t)}
|
||||
if unsafe.StringData(s1.s) == unsafe.StringData(s2.s) {
|
||||
t.Fatalf("unexpected two heapStr ptr equal")
|
||||
}
|
||||
if s1.s != s2.s {
|
||||
t.Fatalf("unexpected two heapStr value not equal")
|
||||
}
|
||||
testComparable(t, s1, s2)
|
||||
testComparable(t, s1.s, s2.s)
|
||||
testComparable(t, float32(0), negativeZero[float32]())
|
||||
testComparable(t, float64(0), negativeZero[float64]())
|
||||
testComparableNoEqual(t, math.NaN(), math.NaN())
|
||||
testComparableNoEqual(t, [2]string{"a", ""}, [2]string{"", "a"})
|
||||
testComparableNoEqual(t, struct{ a, b string }{"foo", ""}, struct{ a, b string }{"", "foo"})
|
||||
testComparableNoEqual(t, struct{ a, b any }{int(0), struct{}{}}, struct{ a, b any }{struct{}{}, int(0)})
|
||||
}
|
||||
|
||||
func testComparableNoEqual[T comparable](t *testing.T, v1, v2 T) {
|
||||
seed := MakeSeed()
|
||||
if Comparable(seed, v1) == Comparable(seed, v2) {
|
||||
t.Fatalf("Comparable(seed, %v) == Comparable(seed, %v)", v1, v2)
|
||||
}
|
||||
}
|
||||
|
||||
var heapStrValue = []byte("aTestString")
|
||||
|
||||
func heapStr(t *testing.T) string {
|
||||
return string(heapStrValue)
|
||||
}
|
||||
|
||||
func testComparable[T comparable](t *testing.T, v T, v2 ...T) {
|
||||
t.Run(TypeFor[T]().String(), func(t *testing.T) {
|
||||
var a, b T = v, v
|
||||
if len(v2) != 0 {
|
||||
b = v2[0]
|
||||
}
|
||||
var pa *T = &a
|
||||
seed := MakeSeed()
|
||||
if Comparable(seed, a) != Comparable(seed, b) {
|
||||
t.Fatalf("Comparable(seed, %v) != Comparable(seed, %v)", a, b)
|
||||
}
|
||||
old := Comparable(seed, pa)
|
||||
stackGrow(8192)
|
||||
new := Comparable(seed, pa)
|
||||
if old != new {
|
||||
t.Fatal("Comparable(seed, ptr) != Comparable(seed, ptr)")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
var use byte
|
||||
|
||||
//go:noinline
|
||||
func stackGrow(dep int) {
|
||||
if dep == 0 {
|
||||
return
|
||||
}
|
||||
var local [1024]byte
|
||||
// make sure local is allocated on the stack.
|
||||
local[rand.Uint64()%1024] = byte(rand.Uint64())
|
||||
use = local[rand.Uint64()%1024]
|
||||
stackGrow(dep - 1)
|
||||
}
|
||||
|
||||
func TestWriteComparable(t *testing.T) {
|
||||
testWriteComparable(t, int64(2))
|
||||
testWriteComparable(t, uint64(8))
|
||||
testWriteComparable(t, uintptr(12))
|
||||
testWriteComparable(t, any("s"))
|
||||
testWriteComparable(t, "s")
|
||||
testComparable(t, true)
|
||||
testWriteComparable(t, new(float64))
|
||||
testWriteComparable(t, float64(9))
|
||||
testWriteComparable(t, complex128(9i+1))
|
||||
testWriteComparable(t, struct{}{})
|
||||
testWriteComparable(t, struct {
|
||||
i int
|
||||
u uint
|
||||
b bool
|
||||
f float64
|
||||
p *int
|
||||
a any
|
||||
}{i: 9, u: 1, b: true, f: 9.9, p: new(int), a: 1})
|
||||
type S struct {
|
||||
s string
|
||||
}
|
||||
s1 := S{s: heapStr(t)}
|
||||
s2 := S{s: heapStr(t)}
|
||||
if unsafe.StringData(s1.s) == unsafe.StringData(s2.s) {
|
||||
t.Fatalf("unexpected two heapStr ptr equal")
|
||||
}
|
||||
if s1.s != s2.s {
|
||||
t.Fatalf("unexpected two heapStr value not equal")
|
||||
}
|
||||
testWriteComparable(t, s1, s2)
|
||||
testWriteComparable(t, s1.s, s2.s)
|
||||
testWriteComparable(t, float32(0), negativeZero[float32]())
|
||||
testWriteComparable(t, float64(0), negativeZero[float64]())
|
||||
testWriteComparableNoEqual(t, math.NaN(), math.NaN())
|
||||
testWriteComparableNoEqual(t, [2]string{"a", ""}, [2]string{"", "a"})
|
||||
testWriteComparableNoEqual(t, struct{ a, b string }{"foo", ""}, struct{ a, b string }{"", "foo"})
|
||||
testWriteComparableNoEqual(t, struct{ a, b any }{int(0), struct{}{}}, struct{ a, b any }{struct{}{}, int(0)})
|
||||
}
|
||||
|
||||
func testWriteComparableNoEqual[T comparable](t *testing.T, v1, v2 T) {
|
||||
seed := MakeSeed()
|
||||
h1 := Hash{}
|
||||
h2 := Hash{}
|
||||
*(*Seed)(unsafe.Pointer(&h1)), *(*Seed)(unsafe.Pointer(&h2)) = seed, seed
|
||||
WriteComparable(&h1, v1)
|
||||
WriteComparable(&h2, v2)
|
||||
if h1.Sum64() == h2.Sum64() {
|
||||
t.Fatalf("WriteComparable(seed, %v) == WriteComparable(seed, %v)", v1, v2)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func testWriteComparable[T comparable](t *testing.T, v T, v2 ...T) {
|
||||
t.Run(TypeFor[T]().String(), func(t *testing.T) {
|
||||
var a, b T = v, v
|
||||
if len(v2) != 0 {
|
||||
b = v2[0]
|
||||
}
|
||||
var pa *T = &a
|
||||
h1 := Hash{}
|
||||
h2 := Hash{}
|
||||
*(*Seed)(unsafe.Pointer(&h1)) = MakeSeed()
|
||||
h2 = h1
|
||||
WriteComparable(&h1, a)
|
||||
WriteComparable(&h2, b)
|
||||
if h1.Sum64() != h2.Sum64() {
|
||||
t.Fatalf("WriteComparable(h, %v) != WriteComparable(h, %v)", a, b)
|
||||
}
|
||||
WriteComparable(&h1, pa)
|
||||
old := h1.Sum64()
|
||||
stackGrow(8192)
|
||||
WriteComparable(&h2, pa)
|
||||
new := h2.Sum64()
|
||||
if old != new {
|
||||
t.Fatal("WriteComparable(seed, ptr) != WriteComparable(seed, ptr)")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestComparableShouldPanic(t *testing.T) {
|
||||
s := []byte("s")
|
||||
a := any(s)
|
||||
defer func() {
|
||||
e := recover()
|
||||
err, ok := e.(error)
|
||||
if !ok {
|
||||
t.Fatalf("Comaparable(any([]byte)) should panic")
|
||||
}
|
||||
want := "hash of unhashable type []uint8"
|
||||
if s := err.Error(); !strings.Contains(s, want) {
|
||||
t.Fatalf("want %s, got %s", want, s)
|
||||
}
|
||||
}()
|
||||
Comparable(MakeSeed(), a)
|
||||
}
|
||||
|
||||
func TestWriteComparableNoncommute(t *testing.T) {
|
||||
seed := MakeSeed()
|
||||
var h1, h2 Hash
|
||||
h1.SetSeed(seed)
|
||||
h2.SetSeed(seed)
|
||||
|
||||
h1.WriteString("abc")
|
||||
WriteComparable(&h1, 123)
|
||||
WriteComparable(&h2, 123)
|
||||
h2.WriteString("abc")
|
||||
|
||||
if h1.Sum64() == h2.Sum64() {
|
||||
t.Errorf("WriteComparable and WriteString unexpectedly commute")
|
||||
}
|
||||
}
|
||||
|
||||
func TestComparableAllocations(t *testing.T) {
|
||||
if !testComparableAllocations {
|
||||
t.Skip("test broken in old golang version")
|
||||
}
|
||||
seed := MakeSeed()
|
||||
x := heapStr(t)
|
||||
allocs := testing.AllocsPerRun(10, func() {
|
||||
s := "s" + x
|
||||
Comparable(seed, s)
|
||||
})
|
||||
if allocs > 0 {
|
||||
t.Errorf("got %v allocs, want 0", allocs)
|
||||
}
|
||||
|
||||
type S struct {
|
||||
a int
|
||||
b string
|
||||
}
|
||||
allocs = testing.AllocsPerRun(10, func() {
|
||||
s := S{123, "s" + x}
|
||||
Comparable(seed, s)
|
||||
})
|
||||
if allocs > 0 {
|
||||
t.Errorf("got %v allocs, want 0", allocs)
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure a Hash implements the hash.Hash and hash.Hash64 interfaces.
|
||||
var _ hash.Hash = &Hash{}
|
||||
var _ hash.Hash64 = &Hash{}
|
||||
|
||||
func benchmarkSize(b *testing.B, size int) {
|
||||
h := &Hash{}
|
||||
buf := make([]byte, size)
|
||||
s := string(buf)
|
||||
|
||||
b.Run("Write", func(b *testing.B) {
|
||||
b.SetBytes(int64(size))
|
||||
for i := 0; i < b.N; i++ {
|
||||
h.Reset()
|
||||
h.Write(buf)
|
||||
h.Sum64()
|
||||
}
|
||||
})
|
||||
|
||||
b.Run("Bytes", func(b *testing.B) {
|
||||
b.SetBytes(int64(size))
|
||||
seed := h.Seed()
|
||||
for i := 0; i < b.N; i++ {
|
||||
Bytes(seed, buf)
|
||||
}
|
||||
})
|
||||
|
||||
b.Run("String", func(b *testing.B) {
|
||||
b.SetBytes(int64(size))
|
||||
seed := h.Seed()
|
||||
for i := 0; i < b.N; i++ {
|
||||
String(seed, s)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkHash(b *testing.B) {
|
||||
sizes := []int{4, 8, 16, 32, 64, 256, 320, 1024, 4096, 16384}
|
||||
for _, size := range sizes {
|
||||
b.Run(fmt.Sprint("n=", size), func(b *testing.B) {
|
||||
benchmarkSize(b, size)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func benchmarkComparable[T comparable](b *testing.B, v T) {
|
||||
b.Run(TypeFor[T]().String(), func(b *testing.B) {
|
||||
seed := MakeSeed()
|
||||
for i := 0; i < b.N; i++ {
|
||||
Comparable(seed, v)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkComparable(b *testing.B) {
|
||||
type testStruct struct {
|
||||
i int
|
||||
u uint
|
||||
b bool
|
||||
f float64
|
||||
p *int
|
||||
a any
|
||||
}
|
||||
benchmarkComparable(b, int64(2))
|
||||
benchmarkComparable(b, uint64(8))
|
||||
benchmarkComparable(b, uintptr(12))
|
||||
benchmarkComparable(b, any("s"))
|
||||
benchmarkComparable(b, "s")
|
||||
benchmarkComparable(b, true)
|
||||
benchmarkComparable(b, new(float64))
|
||||
benchmarkComparable(b, float64(9))
|
||||
benchmarkComparable(b, complex128(9i+1))
|
||||
benchmarkComparable(b, struct{}{})
|
||||
benchmarkComparable(b, testStruct{i: 9, u: 1, b: true, f: 9.9, p: new(int), a: 1})
|
||||
}
|
||||
|
||||
// TypeFor returns the [Type] that represents the type argument T.
|
||||
func TypeFor[T any]() reflect.Type {
|
||||
var v T
|
||||
if t := reflect.TypeOf(v); t != nil {
|
||||
return t // optimize for T being a non-interface kind
|
||||
}
|
||||
return reflect.TypeOf((*T)(nil)).Elem() // only for an interface kind
|
||||
}
|
||||
@@ -7,9 +7,9 @@ import (
|
||||
|
||||
"github.com/metacubex/mihomo/common/atomic"
|
||||
|
||||
"github.com/sagernet/sing/common/buf"
|
||||
"github.com/sagernet/sing/common/bufio"
|
||||
"github.com/sagernet/sing/common/network"
|
||||
"github.com/metacubex/sing/common/buf"
|
||||
"github.com/metacubex/sing/common/bufio"
|
||||
"github.com/metacubex/sing/common/network"
|
||||
)
|
||||
|
||||
type connReadResult struct {
|
||||
@@ -20,7 +20,7 @@ type connReadResult struct {
|
||||
type Conn struct {
|
||||
network.ExtendedConn
|
||||
deadline atomic.TypedValue[time.Time]
|
||||
pipeDeadline pipeDeadline
|
||||
pipeDeadline PipeDeadline
|
||||
disablePipe atomic.Bool
|
||||
inRead atomic.Bool
|
||||
resultCh chan *connReadResult
|
||||
@@ -34,7 +34,7 @@ func IsConn(conn any) bool {
|
||||
func NewConn(conn net.Conn) *Conn {
|
||||
c := &Conn{
|
||||
ExtendedConn: bufio.NewExtendedConn(conn),
|
||||
pipeDeadline: makePipeDeadline(),
|
||||
pipeDeadline: MakePipeDeadline(),
|
||||
resultCh: make(chan *connReadResult, 1),
|
||||
}
|
||||
c.resultCh <- nil
|
||||
@@ -58,7 +58,7 @@ func (c *Conn) Read(p []byte) (n int, err error) {
|
||||
c.resultCh <- nil
|
||||
break
|
||||
}
|
||||
case <-c.pipeDeadline.wait():
|
||||
case <-c.pipeDeadline.Wait():
|
||||
return 0, os.ErrDeadlineExceeded
|
||||
}
|
||||
|
||||
@@ -104,7 +104,7 @@ func (c *Conn) ReadBuffer(buffer *buf.Buffer) (err error) {
|
||||
c.resultCh <- nil
|
||||
break
|
||||
}
|
||||
case <-c.pipeDeadline.wait():
|
||||
case <-c.pipeDeadline.Wait():
|
||||
return os.ErrDeadlineExceeded
|
||||
}
|
||||
|
||||
@@ -130,7 +130,7 @@ func (c *Conn) SetReadDeadline(t time.Time) error {
|
||||
return c.ExtendedConn.SetReadDeadline(t)
|
||||
}
|
||||
c.deadline.Store(t)
|
||||
c.pipeDeadline.set(t)
|
||||
c.pipeDeadline.Set(t)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -149,6 +149,10 @@ func (c *Conn) ReaderReplaceable() bool {
|
||||
return c.disablePipe.Load() || c.deadline.Load().IsZero()
|
||||
}
|
||||
|
||||
func (c *Conn) WriterReplaceable() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (c *Conn) Upstream() any {
|
||||
return c.ExtendedConn
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ type readResult struct {
|
||||
type NetPacketConn struct {
|
||||
net.PacketConn
|
||||
deadline atomic.TypedValue[time.Time]
|
||||
pipeDeadline pipeDeadline
|
||||
pipeDeadline PipeDeadline
|
||||
disablePipe atomic.Bool
|
||||
inRead atomic.Bool
|
||||
resultCh chan any
|
||||
@@ -28,7 +28,7 @@ type NetPacketConn struct {
|
||||
func NewNetPacketConn(pc net.PacketConn) net.PacketConn {
|
||||
npc := &NetPacketConn{
|
||||
PacketConn: pc,
|
||||
pipeDeadline: makePipeDeadline(),
|
||||
pipeDeadline: MakePipeDeadline(),
|
||||
resultCh: make(chan any, 1),
|
||||
}
|
||||
npc.resultCh <- nil
|
||||
@@ -83,7 +83,7 @@ FOR:
|
||||
c.resultCh <- nil
|
||||
break FOR
|
||||
}
|
||||
case <-c.pipeDeadline.wait():
|
||||
case <-c.pipeDeadline.Wait():
|
||||
return 0, nil, os.ErrDeadlineExceeded
|
||||
}
|
||||
}
|
||||
@@ -122,7 +122,7 @@ func (c *NetPacketConn) SetReadDeadline(t time.Time) error {
|
||||
return c.PacketConn.SetReadDeadline(t)
|
||||
}
|
||||
c.deadline.Store(t)
|
||||
c.pipeDeadline.set(t)
|
||||
c.pipeDeadline.Set(t)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -52,7 +52,7 @@ FOR:
|
||||
c.netPacketConn.resultCh <- nil
|
||||
break FOR
|
||||
}
|
||||
case <-c.netPacketConn.pipeDeadline.wait():
|
||||
case <-c.netPacketConn.pipeDeadline.Wait():
|
||||
return nil, nil, nil, os.ErrDeadlineExceeded
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,10 +6,10 @@ import (
|
||||
|
||||
"github.com/metacubex/mihomo/common/net/packet"
|
||||
|
||||
"github.com/sagernet/sing/common/buf"
|
||||
"github.com/sagernet/sing/common/bufio"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
"github.com/metacubex/sing/common/buf"
|
||||
"github.com/metacubex/sing/common/bufio"
|
||||
M "github.com/metacubex/sing/common/metadata"
|
||||
N "github.com/metacubex/sing/common/network"
|
||||
)
|
||||
|
||||
type SingPacketConn struct {
|
||||
@@ -69,7 +69,7 @@ FOR:
|
||||
c.netPacketConn.resultCh <- nil
|
||||
break FOR
|
||||
}
|
||||
case <-c.netPacketConn.pipeDeadline.wait():
|
||||
case <-c.netPacketConn.pipeDeadline.Wait():
|
||||
return M.Socksaddr{}, os.ErrDeadlineExceeded
|
||||
}
|
||||
}
|
||||
@@ -146,7 +146,7 @@ FOR:
|
||||
c.netPacketConn.resultCh <- nil
|
||||
break FOR
|
||||
}
|
||||
case <-c.netPacketConn.pipeDeadline.wait():
|
||||
case <-c.netPacketConn.pipeDeadline.Wait():
|
||||
return nil, M.Socksaddr{}, os.ErrDeadlineExceeded
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,24 +9,24 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// pipeDeadline is an abstraction for handling timeouts.
|
||||
type pipeDeadline struct {
|
||||
// PipeDeadline is an abstraction for handling timeouts.
|
||||
type PipeDeadline struct {
|
||||
mu sync.Mutex // Guards timer and cancel
|
||||
timer *time.Timer
|
||||
cancel chan struct{} // Must be non-nil
|
||||
}
|
||||
|
||||
func makePipeDeadline() pipeDeadline {
|
||||
return pipeDeadline{cancel: make(chan struct{})}
|
||||
func MakePipeDeadline() PipeDeadline {
|
||||
return PipeDeadline{cancel: make(chan struct{})}
|
||||
}
|
||||
|
||||
// set sets the point in time when the deadline will time out.
|
||||
// Set sets the point in time when the deadline will time out.
|
||||
// A timeout event is signaled by closing the channel returned by waiter.
|
||||
// Once a timeout has occurred, the deadline can be refreshed by specifying a
|
||||
// t value in the future.
|
||||
//
|
||||
// A zero value for t prevents timeout.
|
||||
func (d *pipeDeadline) set(t time.Time) {
|
||||
func (d *PipeDeadline) Set(t time.Time) {
|
||||
d.mu.Lock()
|
||||
defer d.mu.Unlock()
|
||||
|
||||
@@ -61,8 +61,8 @@ func (d *pipeDeadline) set(t time.Time) {
|
||||
}
|
||||
}
|
||||
|
||||
// wait returns a channel that is closed when the deadline is exceeded.
|
||||
func (d *pipeDeadline) wait() chan struct{} {
|
||||
// Wait returns a channel that is closed when the deadline is exceeded.
|
||||
func (d *PipeDeadline) Wait() chan struct{} {
|
||||
d.mu.Lock()
|
||||
defer d.mu.Unlock()
|
||||
return d.cancel
|
||||
|
||||
@@ -7,8 +7,8 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/sing/common/buf"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
"github.com/metacubex/sing/common/buf"
|
||||
N "github.com/metacubex/sing/common/network"
|
||||
)
|
||||
|
||||
type pipeAddr struct{}
|
||||
@@ -33,8 +33,8 @@ type pipe struct {
|
||||
localDone chan struct{}
|
||||
remoteDone <-chan struct{}
|
||||
|
||||
readDeadline pipeDeadline
|
||||
writeDeadline pipeDeadline
|
||||
readDeadline PipeDeadline
|
||||
writeDeadline PipeDeadline
|
||||
|
||||
readWaitOptions N.ReadWaitOptions
|
||||
}
|
||||
@@ -56,15 +56,15 @@ func Pipe() (net.Conn, net.Conn) {
|
||||
rdRx: cb1, rdTx: cn1,
|
||||
wrTx: cb2, wrRx: cn2,
|
||||
localDone: done1, remoteDone: done2,
|
||||
readDeadline: makePipeDeadline(),
|
||||
writeDeadline: makePipeDeadline(),
|
||||
readDeadline: MakePipeDeadline(),
|
||||
writeDeadline: MakePipeDeadline(),
|
||||
}
|
||||
p2 := &pipe{
|
||||
rdRx: cb2, rdTx: cn2,
|
||||
wrTx: cb1, wrRx: cn1,
|
||||
localDone: done2, remoteDone: done1,
|
||||
readDeadline: makePipeDeadline(),
|
||||
writeDeadline: makePipeDeadline(),
|
||||
readDeadline: MakePipeDeadline(),
|
||||
writeDeadline: MakePipeDeadline(),
|
||||
}
|
||||
return p1, p2
|
||||
}
|
||||
@@ -86,7 +86,7 @@ func (p *pipe) read(b []byte) (n int, err error) {
|
||||
return 0, io.ErrClosedPipe
|
||||
case isClosedChan(p.remoteDone):
|
||||
return 0, io.EOF
|
||||
case isClosedChan(p.readDeadline.wait()):
|
||||
case isClosedChan(p.readDeadline.Wait()):
|
||||
return 0, os.ErrDeadlineExceeded
|
||||
}
|
||||
|
||||
@@ -99,7 +99,7 @@ func (p *pipe) read(b []byte) (n int, err error) {
|
||||
return 0, io.ErrClosedPipe
|
||||
case <-p.remoteDone:
|
||||
return 0, io.EOF
|
||||
case <-p.readDeadline.wait():
|
||||
case <-p.readDeadline.Wait():
|
||||
return 0, os.ErrDeadlineExceeded
|
||||
}
|
||||
}
|
||||
@@ -118,7 +118,7 @@ func (p *pipe) write(b []byte) (n int, err error) {
|
||||
return 0, io.ErrClosedPipe
|
||||
case isClosedChan(p.remoteDone):
|
||||
return 0, io.ErrClosedPipe
|
||||
case isClosedChan(p.writeDeadline.wait()):
|
||||
case isClosedChan(p.writeDeadline.Wait()):
|
||||
return 0, os.ErrDeadlineExceeded
|
||||
}
|
||||
|
||||
@@ -134,7 +134,7 @@ func (p *pipe) write(b []byte) (n int, err error) {
|
||||
return n, io.ErrClosedPipe
|
||||
case <-p.remoteDone:
|
||||
return n, io.ErrClosedPipe
|
||||
case <-p.writeDeadline.wait():
|
||||
case <-p.writeDeadline.Wait():
|
||||
return n, os.ErrDeadlineExceeded
|
||||
}
|
||||
}
|
||||
@@ -145,8 +145,8 @@ func (p *pipe) SetDeadline(t time.Time) error {
|
||||
if isClosedChan(p.localDone) || isClosedChan(p.remoteDone) {
|
||||
return io.ErrClosedPipe
|
||||
}
|
||||
p.readDeadline.set(t)
|
||||
p.writeDeadline.set(t)
|
||||
p.readDeadline.Set(t)
|
||||
p.writeDeadline.Set(t)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -154,7 +154,7 @@ func (p *pipe) SetReadDeadline(t time.Time) error {
|
||||
if isClosedChan(p.localDone) || isClosedChan(p.remoteDone) {
|
||||
return io.ErrClosedPipe
|
||||
}
|
||||
p.readDeadline.set(t)
|
||||
p.readDeadline.Set(t)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -162,7 +162,7 @@ func (p *pipe) SetWriteDeadline(t time.Time) error {
|
||||
if isClosedChan(p.localDone) || isClosedChan(p.remoteDone) {
|
||||
return io.ErrClosedPipe
|
||||
}
|
||||
p.writeDeadline.set(t)
|
||||
p.writeDeadline.Set(t)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -192,7 +192,7 @@ func (p *pipe) waitReadBuffer() (buffer *buf.Buffer, err error) {
|
||||
return nil, io.ErrClosedPipe
|
||||
case isClosedChan(p.remoteDone):
|
||||
return nil, io.EOF
|
||||
case isClosedChan(p.readDeadline.wait()):
|
||||
case isClosedChan(p.readDeadline.Wait()):
|
||||
return nil, os.ErrDeadlineExceeded
|
||||
}
|
||||
select {
|
||||
@@ -211,7 +211,7 @@ func (p *pipe) waitReadBuffer() (buffer *buf.Buffer, err error) {
|
||||
return nil, io.ErrClosedPipe
|
||||
case <-p.remoteDone:
|
||||
return nil, io.EOF
|
||||
case <-p.readDeadline.wait():
|
||||
case <-p.readDeadline.Wait():
|
||||
return nil, os.ErrDeadlineExceeded
|
||||
}
|
||||
}
|
||||
|
||||
90
common/net/listener.go
Normal file
90
common/net/listener.go
Normal file
@@ -0,0 +1,90 @@
|
||||
package net
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type handleContextListener struct {
|
||||
net.Listener
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
conns chan net.Conn
|
||||
err error
|
||||
once sync.Once
|
||||
handle func(context.Context, net.Conn) (net.Conn, error)
|
||||
panicLog func(any)
|
||||
}
|
||||
|
||||
func (l *handleContextListener) init() {
|
||||
go func() {
|
||||
for {
|
||||
c, err := l.Listener.Accept()
|
||||
if err != nil {
|
||||
l.err = err
|
||||
close(l.conns)
|
||||
return
|
||||
}
|
||||
go func() {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
if l.panicLog != nil {
|
||||
l.panicLog(r)
|
||||
}
|
||||
}
|
||||
}()
|
||||
if conn, err := l.handle(l.ctx, c); err == nil {
|
||||
l.conns <- conn
|
||||
} else {
|
||||
// handle failed, close the underlying connection.
|
||||
_ = c.Close()
|
||||
}
|
||||
}()
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func (l *handleContextListener) Accept() (net.Conn, error) {
|
||||
l.once.Do(l.init)
|
||||
if c, ok := <-l.conns; ok {
|
||||
return c, nil
|
||||
}
|
||||
return nil, l.err
|
||||
}
|
||||
|
||||
func (l *handleContextListener) Close() error {
|
||||
l.cancel()
|
||||
l.once.Do(func() { // l.init has not been called yet, so close related resources directly.
|
||||
l.err = net.ErrClosed
|
||||
close(l.conns)
|
||||
})
|
||||
defer func() {
|
||||
// at here, listener has been closed, so we should close all connections in the channel
|
||||
for c := range l.conns {
|
||||
go func(c net.Conn) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
if l.panicLog != nil {
|
||||
l.panicLog(r)
|
||||
}
|
||||
}
|
||||
}()
|
||||
_ = c.Close()
|
||||
}(c)
|
||||
}
|
||||
}()
|
||||
return l.Listener.Close()
|
||||
}
|
||||
|
||||
func NewHandleContextListener(ctx context.Context, l net.Listener, handle func(context.Context, net.Conn) (net.Conn, error), panicLog func(any)) net.Listener {
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
return &handleContextListener{
|
||||
Listener: l,
|
||||
ctx: ctx,
|
||||
cancel: cancel,
|
||||
conns: make(chan net.Conn),
|
||||
handle: handle,
|
||||
panicLog: panicLog,
|
||||
}
|
||||
}
|
||||
@@ -3,10 +3,10 @@ package packet
|
||||
import (
|
||||
"net"
|
||||
|
||||
"github.com/sagernet/sing/common/buf"
|
||||
"github.com/sagernet/sing/common/bufio"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
"github.com/metacubex/sing/common/buf"
|
||||
"github.com/metacubex/sing/common/bufio"
|
||||
M "github.com/metacubex/sing/common/metadata"
|
||||
N "github.com/metacubex/sing/common/network"
|
||||
)
|
||||
|
||||
type SingPacketConn = N.NetPacketConn
|
||||
|
||||
@@ -3,9 +3,9 @@ package packet
|
||||
import (
|
||||
"runtime"
|
||||
|
||||
"github.com/sagernet/sing/common/buf"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
"github.com/metacubex/sing/common/buf"
|
||||
M "github.com/metacubex/sing/common/metadata"
|
||||
N "github.com/metacubex/sing/common/network"
|
||||
)
|
||||
|
||||
type refSingPacketConn struct {
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
package packet
|
||||
|
||||
import (
|
||||
"github.com/sagernet/sing/common/buf"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
"github.com/metacubex/sing/common/buf"
|
||||
M "github.com/metacubex/sing/common/metadata"
|
||||
N "github.com/metacubex/sing/common/network"
|
||||
)
|
||||
|
||||
type threadSafeSingPacketConn struct {
|
||||
|
||||
@@ -1,15 +1,14 @@
|
||||
package net
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"net"
|
||||
"runtime"
|
||||
|
||||
"github.com/metacubex/mihomo/common/net/deadline"
|
||||
|
||||
"github.com/sagernet/sing/common"
|
||||
"github.com/sagernet/sing/common/bufio"
|
||||
"github.com/sagernet/sing/common/network"
|
||||
"github.com/metacubex/sing/common"
|
||||
"github.com/metacubex/sing/common/bufio"
|
||||
"github.com/metacubex/sing/common/network"
|
||||
)
|
||||
|
||||
var NewExtendedConn = bufio.NewExtendedConn
|
||||
@@ -22,11 +21,26 @@ type ExtendedReader = network.ExtendedReader
|
||||
|
||||
var WriteBuffer = bufio.WriteBuffer
|
||||
|
||||
type ReadWaitOptions = network.ReadWaitOptions
|
||||
|
||||
var NewReadWaitOptions = network.NewReadWaitOptions
|
||||
var CalculateFrontHeadroom = network.CalculateFrontHeadroom
|
||||
var CalculateRearHeadroom = network.CalculateRearHeadroom
|
||||
|
||||
type ReaderWithUpstream = network.ReaderWithUpstream
|
||||
type WithUpstreamReader = network.WithUpstreamReader
|
||||
type WriterWithUpstream = network.WriterWithUpstream
|
||||
type WithUpstreamWriter = network.WithUpstreamWriter
|
||||
type WithUpstream = common.WithUpstream
|
||||
|
||||
var UnwrapReader = network.UnwrapReader
|
||||
var UnwrapWriter = network.UnwrapWriter
|
||||
|
||||
func NewDeadlineConn(conn net.Conn) ExtendedConn {
|
||||
if deadline.IsPipe(conn) || deadline.IsPipe(network.UnwrapReader(conn)) {
|
||||
if deadline.IsPipe(conn) || deadline.IsPipe(UnwrapReader(conn)) {
|
||||
return NewExtendedConn(conn) // pipe always have correctly deadline implement
|
||||
}
|
||||
if deadline.IsConn(conn) || deadline.IsConn(network.UnwrapReader(conn)) {
|
||||
if deadline.IsConn(conn) || deadline.IsConn(UnwrapReader(conn)) {
|
||||
return NewExtendedConn(conn) // was a *deadline.Conn
|
||||
}
|
||||
return deadline.NewConn(conn)
|
||||
@@ -43,9 +57,37 @@ type CountFunc = network.CountFunc
|
||||
|
||||
var Pipe = deadline.Pipe
|
||||
|
||||
// Relay copies between left and right bidirectionally.
|
||||
func Relay(leftConn, rightConn net.Conn) {
|
||||
defer runtime.KeepAlive(leftConn)
|
||||
defer runtime.KeepAlive(rightConn)
|
||||
_ = bufio.CopyConn(context.TODO(), leftConn, rightConn)
|
||||
func closeWrite(writer io.Closer) error {
|
||||
if c, ok := common.Cast[network.WriteCloser](writer); ok {
|
||||
return c.CloseWrite()
|
||||
}
|
||||
return writer.Close()
|
||||
}
|
||||
|
||||
// Relay copies between left and right bidirectionally.
|
||||
// like [bufio.CopyConn] but remove unneeded [context.Context] handle and the cost of [task.Group]
|
||||
func Relay(leftConn, rightConn net.Conn) {
|
||||
defer func() {
|
||||
_ = leftConn.Close()
|
||||
_ = rightConn.Close()
|
||||
}()
|
||||
|
||||
ch := make(chan struct{})
|
||||
go func() {
|
||||
_, err := bufio.Copy(leftConn, rightConn)
|
||||
if err == nil {
|
||||
_ = closeWrite(leftConn)
|
||||
} else {
|
||||
_ = leftConn.Close()
|
||||
}
|
||||
close(ch)
|
||||
}()
|
||||
|
||||
_, err := bufio.Copy(rightConn, leftConn)
|
||||
if err == nil {
|
||||
_ = closeWrite(rightConn)
|
||||
} else {
|
||||
_ = rightConn.Close()
|
||||
}
|
||||
<-ch
|
||||
}
|
||||
|
||||
@@ -1,65 +0,0 @@
|
||||
package net
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/sha256"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/hex"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"math/big"
|
||||
)
|
||||
|
||||
type Path interface {
|
||||
Resolve(path string) string
|
||||
}
|
||||
|
||||
func ParseCert(certificate, privateKey string, path Path) (tls.Certificate, error) {
|
||||
if certificate == "" && privateKey == "" {
|
||||
var err error
|
||||
certificate, privateKey, _, err = NewRandomTLSKeyPair()
|
||||
if err != nil {
|
||||
return tls.Certificate{}, err
|
||||
}
|
||||
}
|
||||
cert, painTextErr := tls.X509KeyPair([]byte(certificate), []byte(privateKey))
|
||||
if painTextErr == nil {
|
||||
return cert, nil
|
||||
}
|
||||
|
||||
certificate = path.Resolve(certificate)
|
||||
privateKey = path.Resolve(privateKey)
|
||||
cert, loadErr := tls.LoadX509KeyPair(certificate, privateKey)
|
||||
if loadErr != nil {
|
||||
return tls.Certificate{}, fmt.Errorf("parse certificate failed, maybe format error:%s, or path error: %s", painTextErr.Error(), loadErr.Error())
|
||||
}
|
||||
return cert, nil
|
||||
}
|
||||
|
||||
func NewRandomTLSKeyPair() (certificate string, privateKey string, fingerprint string, err error) {
|
||||
key, err := rsa.GenerateKey(rand.Reader, 2048)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
template := x509.Certificate{SerialNumber: big.NewInt(1)}
|
||||
certDER, err := x509.CreateCertificate(
|
||||
rand.Reader,
|
||||
&template,
|
||||
&template,
|
||||
&key.PublicKey,
|
||||
key)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
cert, err := x509.ParseCertificate(certDER)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
hash := sha256.Sum256(cert.Raw)
|
||||
fingerprint = hex.EncodeToString(hash[:])
|
||||
privateKey = string(pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(key)}))
|
||||
certificate = string(pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certDER}))
|
||||
return
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
package net
|
||||
|
||||
import (
|
||||
"crypto/sha1"
|
||||
"encoding/base64"
|
||||
"encoding/binary"
|
||||
"math/bits"
|
||||
)
|
||||
@@ -129,3 +131,13 @@ func MaskWebSocket(key uint32, b []byte) uint32 {
|
||||
|
||||
return key
|
||||
}
|
||||
|
||||
func GetWebSocketSecAccept(secKey string) string {
|
||||
const magic = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
|
||||
const nonceSize = 24 // base64.StdEncoding.EncodedLen(nonceKeySize)
|
||||
p := make([]byte, nonceSize+len(magic))
|
||||
copy(p[:nonceSize], secKey)
|
||||
copy(p[nonceSize:], magic)
|
||||
sum := sha1.Sum(p)
|
||||
return base64.StdEncoding.EncodeToString(sum[:])
|
||||
}
|
||||
|
||||
102
common/once/oncefunc.go
Normal file
102
common/once/oncefunc.go
Normal file
@@ -0,0 +1,102 @@
|
||||
// Copyright 2022 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 once
|
||||
|
||||
import "sync"
|
||||
|
||||
// OnceFunc returns a function that invokes f only once. The returned function
|
||||
// may be called concurrently.
|
||||
//
|
||||
// If f panics, the returned function will panic with the same value on every call.
|
||||
func OnceFunc(f func()) func() {
|
||||
var (
|
||||
once sync.Once
|
||||
valid bool
|
||||
p any
|
||||
)
|
||||
// Construct the inner closure just once to reduce costs on the fast path.
|
||||
g := func() {
|
||||
defer func() {
|
||||
p = recover()
|
||||
if !valid {
|
||||
// Re-panic immediately so on the first call the user gets a
|
||||
// complete stack trace into f.
|
||||
panic(p)
|
||||
}
|
||||
}()
|
||||
f()
|
||||
f = nil // Do not keep f alive after invoking it.
|
||||
valid = true // Set only if f does not panic.
|
||||
}
|
||||
return func() {
|
||||
once.Do(g)
|
||||
if !valid {
|
||||
panic(p)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// OnceValue returns a function that invokes f only once and returns the value
|
||||
// returned by f. The returned function may be called concurrently.
|
||||
//
|
||||
// If f panics, the returned function will panic with the same value on every call.
|
||||
func OnceValue[T any](f func() T) func() T {
|
||||
var (
|
||||
once sync.Once
|
||||
valid bool
|
||||
p any
|
||||
result T
|
||||
)
|
||||
g := func() {
|
||||
defer func() {
|
||||
p = recover()
|
||||
if !valid {
|
||||
panic(p)
|
||||
}
|
||||
}()
|
||||
result = f()
|
||||
f = nil
|
||||
valid = true
|
||||
}
|
||||
return func() T {
|
||||
once.Do(g)
|
||||
if !valid {
|
||||
panic(p)
|
||||
}
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
// OnceValues returns a function that invokes f only once and returns the values
|
||||
// returned by f. The returned function may be called concurrently.
|
||||
//
|
||||
// If f panics, the returned function will panic with the same value on every call.
|
||||
func OnceValues[T1, T2 any](f func() (T1, T2)) func() (T1, T2) {
|
||||
var (
|
||||
once sync.Once
|
||||
valid bool
|
||||
p any
|
||||
r1 T1
|
||||
r2 T2
|
||||
)
|
||||
g := func() {
|
||||
defer func() {
|
||||
p = recover()
|
||||
if !valid {
|
||||
panic(p)
|
||||
}
|
||||
}()
|
||||
r1, r2 = f()
|
||||
f = nil
|
||||
valid = true
|
||||
}
|
||||
return func() (T1, T2) {
|
||||
once.Do(g)
|
||||
if !valid {
|
||||
panic(p)
|
||||
}
|
||||
return r1, r2
|
||||
}
|
||||
}
|
||||
@@ -8,18 +8,23 @@ import (
|
||||
"sync"
|
||||
)
|
||||
|
||||
var defaultAllocator = NewAllocator()
|
||||
var DefaultAllocator = NewAllocator()
|
||||
|
||||
// Allocator for incoming frames, optimized to prevent overwriting after zeroing
|
||||
type Allocator struct {
|
||||
type Allocator interface {
|
||||
Get(size int) []byte
|
||||
Put(buf []byte) error
|
||||
}
|
||||
|
||||
// defaultAllocator for incoming frames, optimized to prevent overwriting after zeroing
|
||||
type defaultAllocator struct {
|
||||
buffers [11]sync.Pool
|
||||
}
|
||||
|
||||
// NewAllocator initiates a []byte allocator for frames less than 65536 bytes,
|
||||
// the waste(memory fragmentation) of space allocation is guaranteed to be
|
||||
// no more than 50%.
|
||||
func NewAllocator() *Allocator {
|
||||
return &Allocator{
|
||||
func NewAllocator() Allocator {
|
||||
return &defaultAllocator{
|
||||
buffers: [...]sync.Pool{ // 64B -> 64K
|
||||
{New: func() any { return new([1 << 6]byte) }},
|
||||
{New: func() any { return new([1 << 7]byte) }},
|
||||
@@ -37,7 +42,7 @@ func NewAllocator() *Allocator {
|
||||
}
|
||||
|
||||
// Get a []byte from pool with most appropriate cap
|
||||
func (alloc *Allocator) Get(size int) []byte {
|
||||
func (alloc *defaultAllocator) Get(size int) []byte {
|
||||
switch {
|
||||
case size < 0:
|
||||
panic("alloc.Get: len out of range")
|
||||
@@ -87,7 +92,7 @@ func (alloc *Allocator) Get(size int) []byte {
|
||||
|
||||
// Put returns a []byte to pool for future use,
|
||||
// which the cap must be exactly 2^n
|
||||
func (alloc *Allocator) Put(buf []byte) error {
|
||||
func (alloc *defaultAllocator) Put(buf []byte) error {
|
||||
if cap(buf) == 0 || cap(buf) > 65536 {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -3,13 +3,12 @@
|
||||
package pool
|
||||
|
||||
const (
|
||||
// RelayBufferSize using for tcp
|
||||
// io.Copy default buffer size is 32 KiB
|
||||
// but the maximum packet size of vmess/shadowsocks is about 16 KiB
|
||||
// so define a buffer of 20 KiB to reduce the memory of each TCP relay
|
||||
RelayBufferSize = 16 * 1024
|
||||
|
||||
// RelayBufferSize uses 20KiB, but due to the allocator it will actually
|
||||
// request 32Kib. Most UDPs are smaller than the MTU, and the TUN's MTU
|
||||
// UDPBufferSize using for udp
|
||||
// Most UDPs are smaller than the MTU, and the TUN's MTU
|
||||
// set to 9000, so the UDP Buffer size set to 16Kib
|
||||
UDPBufferSize = 8 * 1024
|
||||
)
|
||||
|
||||
@@ -3,13 +3,12 @@
|
||||
package pool
|
||||
|
||||
const (
|
||||
// RelayBufferSize using for tcp
|
||||
// io.Copy default buffer size is 32 KiB
|
||||
// but the maximum packet size of vmess/shadowsocks is about 16 KiB
|
||||
// so define a buffer of 20 KiB to reduce the memory of each TCP relay
|
||||
RelayBufferSize = 20 * 1024
|
||||
RelayBufferSize = 32 * 1024
|
||||
|
||||
// RelayBufferSize uses 20KiB, but due to the allocator it will actually
|
||||
// request 32Kib. Most UDPs are smaller than the MTU, and the TUN's MTU
|
||||
// UDPBufferSize using for udp
|
||||
// Most UDPs are smaller than the MTU, and the TUN's MTU
|
||||
// set to 9000, so the UDP Buffer size set to 16Kib
|
||||
UDPBufferSize = 16 * 1024
|
||||
)
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
package pool
|
||||
|
||||
func Get(size int) []byte {
|
||||
return defaultAllocator.Get(size)
|
||||
return DefaultAllocator.Get(size)
|
||||
}
|
||||
|
||||
func Put(buf []byte) error {
|
||||
return defaultAllocator.Put(buf)
|
||||
return DefaultAllocator.Put(buf)
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package pool
|
||||
|
||||
import "github.com/sagernet/sing/common/buf"
|
||||
import "github.com/metacubex/sing/common/buf"
|
||||
|
||||
func init() {
|
||||
buf.DefaultAllocator = defaultAllocator
|
||||
buf.DefaultAllocator = DefaultAllocator
|
||||
}
|
||||
|
||||
@@ -2,8 +2,6 @@ package queue
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/samber/lo"
|
||||
)
|
||||
|
||||
// Queue is a simple concurrent safe queue
|
||||
@@ -24,33 +22,32 @@ func (q *Queue[T]) Put(items ...T) {
|
||||
}
|
||||
|
||||
// Pop returns the head of items.
|
||||
func (q *Queue[T]) Pop() T {
|
||||
func (q *Queue[T]) Pop() (head T) {
|
||||
if len(q.items) == 0 {
|
||||
return lo.Empty[T]()
|
||||
return
|
||||
}
|
||||
|
||||
q.lock.Lock()
|
||||
head := q.items[0]
|
||||
head = q.items[0]
|
||||
q.items = q.items[1:]
|
||||
q.lock.Unlock()
|
||||
return head
|
||||
}
|
||||
|
||||
// Last returns the last of item.
|
||||
func (q *Queue[T]) Last() T {
|
||||
func (q *Queue[T]) Last() (last T) {
|
||||
if len(q.items) == 0 {
|
||||
return lo.Empty[T]()
|
||||
return
|
||||
}
|
||||
|
||||
q.lock.RLock()
|
||||
last := q.items[len(q.items)-1]
|
||||
last = q.items[len(q.items)-1]
|
||||
q.lock.RUnlock()
|
||||
return last
|
||||
}
|
||||
|
||||
// Copy get the copy of queue.
|
||||
func (q *Queue[T]) Copy() []T {
|
||||
items := []T{}
|
||||
func (q *Queue[T]) Copy() (items []T) {
|
||||
q.lock.RLock()
|
||||
items = append(items, q.items...)
|
||||
q.lock.RUnlock()
|
||||
|
||||
215
common/queue/queue_test.go
Normal file
215
common/queue/queue_test.go
Normal file
@@ -0,0 +1,215 @@
|
||||
package queue
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
// TestQueuePut tests the Put method of Queue
|
||||
func TestQueuePut(t *testing.T) {
|
||||
// Initialize a new queue
|
||||
q := New[int](10)
|
||||
|
||||
// Test putting a single item
|
||||
q.Put(1)
|
||||
assert.Equal(t, int64(1), q.Len(), "Queue length should be 1 after putting one item")
|
||||
|
||||
// Test putting multiple items
|
||||
q.Put(2, 3, 4)
|
||||
assert.Equal(t, int64(4), q.Len(), "Queue length should be 4 after putting three more items")
|
||||
|
||||
// Test putting zero items (should not change queue)
|
||||
q.Put()
|
||||
assert.Equal(t, int64(4), q.Len(), "Queue length should remain unchanged when putting zero items")
|
||||
}
|
||||
|
||||
// TestQueuePop tests the Pop method of Queue
|
||||
func TestQueuePop(t *testing.T) {
|
||||
// Initialize a new queue with items
|
||||
q := New[int](10)
|
||||
q.Put(1, 2, 3)
|
||||
|
||||
// Test popping items in FIFO order
|
||||
item := q.Pop()
|
||||
assert.Equal(t, 1, item, "First item popped should be 1")
|
||||
assert.Equal(t, int64(2), q.Len(), "Queue length should be 2 after popping one item")
|
||||
|
||||
item = q.Pop()
|
||||
assert.Equal(t, 2, item, "Second item popped should be 2")
|
||||
assert.Equal(t, int64(1), q.Len(), "Queue length should be 1 after popping two items")
|
||||
|
||||
item = q.Pop()
|
||||
assert.Equal(t, 3, item, "Third item popped should be 3")
|
||||
assert.Equal(t, int64(0), q.Len(), "Queue length should be 0 after popping all items")
|
||||
}
|
||||
|
||||
// TestQueuePopEmpty tests the Pop method on an empty queue
|
||||
func TestQueuePopEmpty(t *testing.T) {
|
||||
// Initialize a new empty queue
|
||||
q := New[int](0)
|
||||
|
||||
// Test popping from an empty queue
|
||||
item := q.Pop()
|
||||
assert.Equal(t, 0, item, "Popping from an empty queue should return the zero value")
|
||||
assert.Equal(t, int64(0), q.Len(), "Queue length should remain 0 after popping from an empty queue")
|
||||
}
|
||||
|
||||
// TestQueueLast tests the Last method of Queue
|
||||
func TestQueueLast(t *testing.T) {
|
||||
// Initialize a new queue with items
|
||||
q := New[int](10)
|
||||
q.Put(1, 2, 3)
|
||||
|
||||
// Test getting the last item
|
||||
item := q.Last()
|
||||
assert.Equal(t, 3, item, "Last item should be 3")
|
||||
assert.Equal(t, int64(3), q.Len(), "Queue length should remain unchanged after calling Last")
|
||||
|
||||
// Test Last on an empty queue
|
||||
emptyQ := New[int](0)
|
||||
emptyItem := emptyQ.Last()
|
||||
assert.Equal(t, 0, emptyItem, "Last on an empty queue should return the zero value")
|
||||
}
|
||||
|
||||
// TestQueueCopy tests the Copy method of Queue
|
||||
func TestQueueCopy(t *testing.T) {
|
||||
// Initialize a new queue with items
|
||||
q := New[int](10)
|
||||
q.Put(1, 2, 3)
|
||||
|
||||
// Test copying the queue
|
||||
copy := q.Copy()
|
||||
assert.Equal(t, 3, len(copy), "Copy should have the same number of items as the original queue")
|
||||
assert.Equal(t, 1, copy[0], "First item in copy should be 1")
|
||||
assert.Equal(t, 2, copy[1], "Second item in copy should be 2")
|
||||
assert.Equal(t, 3, copy[2], "Third item in copy should be 3")
|
||||
|
||||
// Verify that modifying the copy doesn't affect the original queue
|
||||
copy[0] = 99
|
||||
assert.Equal(t, 1, q.Pop(), "Original queue should not be affected by modifying the copy")
|
||||
}
|
||||
|
||||
// TestQueueLen tests the Len method of Queue
|
||||
func TestQueueLen(t *testing.T) {
|
||||
// Initialize a new empty queue
|
||||
q := New[int](10)
|
||||
assert.Equal(t, int64(0), q.Len(), "New queue should have length 0")
|
||||
|
||||
// Add items and check length
|
||||
q.Put(1, 2)
|
||||
assert.Equal(t, int64(2), q.Len(), "Queue length should be 2 after putting two items")
|
||||
|
||||
// Remove an item and check length
|
||||
q.Pop()
|
||||
assert.Equal(t, int64(1), q.Len(), "Queue length should be 1 after popping one item")
|
||||
}
|
||||
|
||||
// TestQueueNew tests the New constructor
|
||||
func TestQueueNew(t *testing.T) {
|
||||
// Test creating a new queue with different hints
|
||||
q1 := New[int](0)
|
||||
assert.NotNil(t, q1, "New queue should not be nil")
|
||||
assert.Equal(t, int64(0), q1.Len(), "New queue should have length 0")
|
||||
|
||||
q2 := New[int](10)
|
||||
assert.NotNil(t, q2, "New queue should not be nil")
|
||||
assert.Equal(t, int64(0), q2.Len(), "New queue should have length 0")
|
||||
|
||||
// Test with a different type
|
||||
q3 := New[string](5)
|
||||
assert.NotNil(t, q3, "New queue should not be nil")
|
||||
assert.Equal(t, int64(0), q3.Len(), "New queue should have length 0")
|
||||
}
|
||||
|
||||
// TestQueueConcurrency tests the concurrency safety of Queue
|
||||
func TestQueueConcurrency(t *testing.T) {
|
||||
// Initialize a new queue
|
||||
q := New[int](100)
|
||||
|
||||
// Number of goroutines and operations
|
||||
goroutines := 10
|
||||
operations := 100
|
||||
|
||||
// Wait group to synchronize goroutines
|
||||
wg := sync.WaitGroup{}
|
||||
wg.Add(goroutines * 2) // For both producers and consumers
|
||||
|
||||
// Start producer goroutines
|
||||
for i := 0; i < goroutines; i++ {
|
||||
go func(id int) {
|
||||
defer wg.Done()
|
||||
for j := 0; j < operations; j++ {
|
||||
q.Put(id*operations + j)
|
||||
// Small sleep to increase chance of race conditions
|
||||
time.Sleep(time.Microsecond)
|
||||
}
|
||||
}(i)
|
||||
}
|
||||
|
||||
// Start consumer goroutines
|
||||
consumed := make(chan int, goroutines*operations)
|
||||
for i := 0; i < goroutines; i++ {
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for j := 0; j < operations; j++ {
|
||||
// Try to pop an item, but don't block if queue is empty
|
||||
// Use a mutex to avoid race condition between Len() check and Pop()
|
||||
q.lock.Lock()
|
||||
if len(q.items) > 0 {
|
||||
item := q.items[0]
|
||||
q.items = q.items[1:]
|
||||
q.lock.Unlock()
|
||||
consumed <- item
|
||||
} else {
|
||||
q.lock.Unlock()
|
||||
}
|
||||
// Small sleep to increase chance of race conditions
|
||||
time.Sleep(time.Microsecond)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// Wait for all goroutines to finish
|
||||
wg.Wait()
|
||||
// Close the consumed channel
|
||||
close(consumed)
|
||||
|
||||
// Count the number of consumed items
|
||||
consumedCount := 0
|
||||
for range consumed {
|
||||
consumedCount++
|
||||
}
|
||||
|
||||
// Check that the queue is in a consistent state
|
||||
totalItems := goroutines * operations
|
||||
remaining := int(q.Len())
|
||||
assert.Equal(t, totalItems, consumedCount+remaining, "Total items should equal consumed items plus remaining items")
|
||||
}
|
||||
|
||||
// TestQueueWithDifferentTypes tests the Queue with different types
|
||||
func TestQueueWithDifferentTypes(t *testing.T) {
|
||||
// Test with string type
|
||||
qString := New[string](5)
|
||||
qString.Put("hello", "world")
|
||||
assert.Equal(t, int64(2), qString.Len(), "Queue length should be 2")
|
||||
assert.Equal(t, "hello", qString.Pop(), "First item should be 'hello'")
|
||||
assert.Equal(t, "world", qString.Pop(), "Second item should be 'world'")
|
||||
|
||||
// Test with struct type
|
||||
type Person struct {
|
||||
Name string
|
||||
Age int
|
||||
}
|
||||
|
||||
qStruct := New[Person](5)
|
||||
qStruct.Put(Person{Name: "Alice", Age: 30}, Person{Name: "Bob", Age: 25})
|
||||
assert.Equal(t, int64(2), qStruct.Len(), "Queue length should be 2")
|
||||
|
||||
firstPerson := qStruct.Pop()
|
||||
assert.Equal(t, "Alice", firstPerson.Name, "First person's name should be 'Alice'")
|
||||
secondPerson := qStruct.Pop()
|
||||
assert.Equal(t, "Bob", secondPerson.Name, "Second person's name should be 'Bob'")
|
||||
}
|
||||
@@ -53,6 +53,12 @@ func (d *Decoder) Decode(src map[string]any, dst any) error {
|
||||
key, omitKey, found := strings.Cut(tag, ",")
|
||||
omitempty := found && omitKey == "omitempty"
|
||||
|
||||
// As a special case, if the field tag is "-", the field is always omitted.
|
||||
// Note that a field with name "-" can still be generated using the tag "-,".
|
||||
if key == "-" {
|
||||
continue
|
||||
}
|
||||
|
||||
value, ok := src[key]
|
||||
if !ok {
|
||||
if d.option.KeyReplacer != nil {
|
||||
@@ -283,7 +289,7 @@ func (d *Decoder) decodeBool(name string, data any, val reflect.Value) (err erro
|
||||
case isInt(kind) && d.option.WeaklyTypedInput:
|
||||
val.SetBool(dataVal.Int() != 0)
|
||||
case isUint(kind) && d.option.WeaklyTypedInput:
|
||||
val.SetString(strconv.FormatUint(dataVal.Uint(), 10))
|
||||
val.SetBool(dataVal.Uint() != 0)
|
||||
default:
|
||||
err = fmt.Errorf(
|
||||
"'%s' expected type '%s', got unconvertible type '%s'",
|
||||
@@ -511,6 +517,10 @@ func (d *Decoder) decodeStructFromMap(name string, dataVal, val reflect.Value) e
|
||||
fieldName = tagValue
|
||||
}
|
||||
|
||||
if tagValue == "-" {
|
||||
continue
|
||||
}
|
||||
|
||||
rawMapKey := reflect.ValueOf(fieldName)
|
||||
rawMapVal := dataVal.MapIndex(rawMapKey)
|
||||
if !rawMapVal.IsValid() {
|
||||
|
||||
@@ -239,13 +239,15 @@ func (n *num) UnmarshalText(text []byte) (err error) {
|
||||
|
||||
func TestStructure_TextUnmarshaller(t *testing.T) {
|
||||
rawMap := map[string]any{
|
||||
"num": "255",
|
||||
"num_p": "127",
|
||||
"num": "255",
|
||||
"num_p": "127",
|
||||
"num_arr": []string{"1", "2", "3"},
|
||||
}
|
||||
|
||||
s := &struct {
|
||||
Num num `test:"num"`
|
||||
NumP *num `test:"num_p"`
|
||||
Num num `test:"num"`
|
||||
NumP *num `test:"num_p"`
|
||||
NumArr []num `test:"num_arr"`
|
||||
}{}
|
||||
|
||||
err := decoder.Decode(rawMap, s)
|
||||
@@ -253,6 +255,7 @@ func TestStructure_TextUnmarshaller(t *testing.T) {
|
||||
assert.Equal(t, 255, s.Num.a)
|
||||
assert.NotNil(t, s.NumP)
|
||||
assert.Equal(t, s.NumP.a, 127)
|
||||
assert.Equal(t, s.NumArr, []num{{1}, {2}, {3}})
|
||||
|
||||
// test WeaklyTypedInput
|
||||
rawMap["num"] = 256
|
||||
@@ -285,3 +288,47 @@ func TestStructure_Null(t *testing.T) {
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, s.Opt.Bar, "")
|
||||
}
|
||||
|
||||
func TestStructure_Ignore(t *testing.T) {
|
||||
rawMap := map[string]any{
|
||||
"-": "newData",
|
||||
}
|
||||
|
||||
s := struct {
|
||||
MustIgnore string `test:"-"`
|
||||
}{MustIgnore: "oldData"}
|
||||
|
||||
err := decoder.Decode(rawMap, &s)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, s.MustIgnore, "oldData")
|
||||
|
||||
// test omitempty
|
||||
delete(rawMap, "-")
|
||||
err = decoder.Decode(rawMap, &s)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, s.MustIgnore, "oldData")
|
||||
}
|
||||
|
||||
func TestStructure_IgnoreInNest(t *testing.T) {
|
||||
rawMap := map[string]any{
|
||||
"-": "newData",
|
||||
}
|
||||
|
||||
type TP struct {
|
||||
MustIgnore string `test:"-"`
|
||||
}
|
||||
|
||||
s := struct {
|
||||
TP
|
||||
}{TP{MustIgnore: "oldData"}}
|
||||
|
||||
err := decoder.Decode(rawMap, &s)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, s.MustIgnore, "oldData")
|
||||
|
||||
// test omitempty
|
||||
delete(rawMap, "-")
|
||||
err = decoder.Decode(rawMap, &s)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, s.MustIgnore, "oldData")
|
||||
}
|
||||
|
||||
1098
common/xsync/map.go
Normal file
1098
common/xsync/map.go
Normal file
File diff suppressed because it is too large
Load Diff
28
common/xsync/map_extra.go
Normal file
28
common/xsync/map_extra.go
Normal file
@@ -0,0 +1,28 @@
|
||||
package xsync
|
||||
|
||||
// LoadOrStoreFn returns the existing value for the key if
|
||||
// present. Otherwise, it tries to compute the value using the
|
||||
// provided function and, if successful, stores and returns
|
||||
// the computed value. The loaded result is true if the value was
|
||||
// loaded, or false if computed.
|
||||
//
|
||||
// This call locks a hash table bucket while the compute function
|
||||
// is executed. It means that modifications on other entries in
|
||||
// the bucket will be blocked until the valueFn executes. Consider
|
||||
// this when the function includes long-running operations.
|
||||
//
|
||||
// Recovery this API and renamed from xsync/v3's LoadOrCompute.
|
||||
// We unneeded support no-op (cancel) compute operation, it will only add complexity to existing code.
|
||||
func (m *Map[K, V]) LoadOrStoreFn(key K, valueFn func() V) (actual V, loaded bool) {
|
||||
return m.doCompute(
|
||||
key,
|
||||
func(oldValue V, loaded bool) (V, ComputeOp) {
|
||||
if loaded {
|
||||
return oldValue, CancelOp
|
||||
}
|
||||
return valueFn(), UpdateOp
|
||||
},
|
||||
loadOrComputeOp,
|
||||
false,
|
||||
)
|
||||
}
|
||||
49
common/xsync/map_extra_test.go
Normal file
49
common/xsync/map_extra_test.go
Normal file
@@ -0,0 +1,49 @@
|
||||
package xsync
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestMapOfLoadOrStoreFn(t *testing.T) {
|
||||
const numEntries = 1000
|
||||
m := NewMap[string, int]()
|
||||
for i := 0; i < numEntries; i++ {
|
||||
v, loaded := m.LoadOrStoreFn(strconv.Itoa(i), func() int {
|
||||
return i
|
||||
})
|
||||
if loaded {
|
||||
t.Fatalf("value not computed for %d", i)
|
||||
}
|
||||
if v != i {
|
||||
t.Fatalf("values do not match for %d: %v", i, v)
|
||||
}
|
||||
}
|
||||
for i := 0; i < numEntries; i++ {
|
||||
v, loaded := m.LoadOrStoreFn(strconv.Itoa(i), func() int {
|
||||
return i
|
||||
})
|
||||
if !loaded {
|
||||
t.Fatalf("value not loaded for %d", i)
|
||||
}
|
||||
if v != i {
|
||||
t.Fatalf("values do not match for %d: %v", i, v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestMapOfLoadOrStoreFn_FunctionCalledOnce(t *testing.T) {
|
||||
m := NewMap[int, int]()
|
||||
for i := 0; i < 100; {
|
||||
m.LoadOrStoreFn(i, func() (v int) {
|
||||
v, i = i, i+1
|
||||
return v
|
||||
})
|
||||
}
|
||||
m.Range(func(k, v int) bool {
|
||||
if k != v {
|
||||
t.Fatalf("%dth key is not equal to value %d", k, v)
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
1922
common/xsync/map_test.go
Normal file
1922
common/xsync/map_test.go
Normal file
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user