Compare commits

..

87 Commits

Author SHA1 Message Date
H1JK
58c4110b57 draft: Tun respond ICMP port unreachable when rejecting UDP packets 2026-01-01 06:51:57 +08:00
wwqgtxx
1f8bee9710 chore: force to disable mptcp for tproxy 2025-12-31 08:43:23 +08:00
wwqgtxx
eb30d3f331 chore: add a code comment for tproxy listener 2025-12-31 02:07:52 +08:00
wwqgtxx
10f4bebdfa fix: only clear dstIP if it is confirmed to be a fake IP 2025-12-30 17:16:09 +08:00
David
06387d5045 feat: support fake-ip-filter-mode: rule mode (#2469) 2025-12-29 08:14:09 +08:00
wwqgtxx
c393e917eb fix: gvisor compatibility on go1.26 2025-12-27 17:57:30 +08:00
wwqgtxx
4f0a6fa117 fix: gvisor panic 2025-12-27 17:16:35 +08:00
wwqgtxx
4f9bfd216f chore: add some comments for the finalizer 2025-12-27 16:38:58 +08:00
joshua
498f81aad3 feat: add header support for rule provider (#2463) 2025-12-24 23:10:38 +08:00
wwqgtxx
9168bee6b7 chore: align internal logic 2025-12-24 18:26:55 +08:00
HolgerHuo
e6c0e3b19c fix: handle geoip:lan when GetRecodeSize() (#2460) 2025-12-24 08:34:19 +08:00
wwqgtxx
287f9e5185 chore: temporarily skip mieru inbound test in go1.26 on windows 2025-12-23 23:49:19 +08:00
wwqgtxx
c456370f4f fix: missing context cancel in pullLoop 2025-12-23 23:26:05 +08:00
wwqgtxx
10ef29f5cd chore: apply global ca in sudoku code 2025-12-23 23:15:52 +08:00
wwqgtxx
85ba7f6a0a chore: change import paths in sudoku code 2025-12-23 23:14:39 +08:00
saba-futai
7daf37bc15 feat: support http-mask-mode, http-mask-tls and http-mask-host for sudoku (#2456) 2025-12-23 23:08:38 +08:00
wwqgtxx
64015b7634 chore: update quic-go to 0.58.0 2025-12-22 17:29:28 +08:00
wwqgtxx
5585304d68 chore: allow custom path for gRPC (grpc-service-name start with /) 2025-12-21 10:28:05 +08:00
wwqgtxx
911211578c action: add Go 1.26rc1 to test 2025-12-21 00:12:03 +08:00
wwqgtxx
abb55199f2 fix: os.RemoveAll not working on Windows7 2025-12-20 23:02:26 +08:00
wwqgtxx
87c3f700e5 chore: add TODO comment to ca.LoadCertificates 2025-12-19 21:43:55 +08:00
wwqgtxx
4a723e8d3f chore: allow automatic reloading when the TLS server's certificate, private-key or ech-key is a local file 2025-12-19 20:23:48 +08:00
wwqgtxx
93cf46e430 chore: remove unused import path 2025-12-19 20:14:02 +08:00
Howard Wu
35a1130c92 chore: use HasPrefix instead of Contains for key checks (#2447) 2025-12-19 18:43:06 +08:00
Howard Wu
1ebcb25e4a fix: typo in sniffer skip-dst-address config parsing (#2446) 2025-12-19 18:16:56 +08:00
wwqgtxx
cbcacdbb8c chore: using tls.Config.GetCertificate/GetClientCertificate to load TLS certificates 2025-12-19 12:24:16 +08:00
wwqgtxx
17966b5418 fix: close sing-tun maybe panic on windows 2025-12-18 10:37:50 +08:00
wwqgtxx
bc8f0dcf77 fix: missing ntp call 2025-12-17 18:50:33 +08:00
wwqgtxx
827cd616e8 chore: cleanup import path 2025-12-17 17:35:58 +08:00
wwqgtxx
e1384e86ab chore: update http2 using in test 2025-12-17 17:19:09 +08:00
wwqgtxx
b92b38701c chore: update ech handling 2025-12-17 17:19:06 +08:00
wwqgtxx
1cab34d257 chore: update quic-go to 0.57.1 2025-12-17 16:13:12 +08:00
saba-futai
a06097c2c4 chore: add xvp rotation andd new header generation strategy for sudoku (#2437) 2025-12-16 18:39:39 +08:00
wwqgtxx
bc9db11cb4 chore: hub/route module handle websocket itself 2025-12-14 19:56:30 +08:00
Ealrang
69e301820c action: fix architecture check for riscv64 in script (#2435) 2025-12-14 17:30:40 +08:00
wwqgtxx
e7a04e0762 chore: don't process msg.Extra in msgToHTTPSRRInfo 2025-12-12 19:42:29 +08:00
Eric Moore
7e8c2876fb chore: improve HTTPS RR logging (#2431) 2025-12-12 17:43:54 +08:00
wwqgtxx
936ebc7718 chore: add echparser package for parse ECHConfigList and ECHConfig 2025-12-12 16:05:11 +08:00
Eric Moore
b753a57e6a fix: ech not work with websocket+clientFingerprint 2025-12-11 23:15:40 +08:00
wwqgtxx
dd99bfc892 doc: fix custom-table doc 2025-12-11 14:53:02 +08:00
wwqgtxx
2a1b3b2aed chore: allow sudoku inbound handle sing-mux request 2025-12-11 14:14:21 +08:00
saba-futai
2211789a7c chore: add customized byte style for sudoku (#2427) 2025-12-10 17:47:59 +08:00
wwqgtxx
e652e277a7 fix: missing ProxyInfo information in wireguard outbound 2025-12-10 17:06:13 +08:00
wwqgtxx
40863d248d chore: add lock in baseProvider for thread-safe 2025-12-10 08:42:40 +08:00
wwqgtxx
17b8eb8772 chore: skip icmp forwarding when destination in tun interface addr range 2025-12-08 09:56:15 +08:00
Vincent Loeng/Leong
6b40072bc5 chore: support find process on freebsd 14 and 15 (#2422) 2025-12-06 14:03:59 +08:00
wwqgtxx
f44aa22d50 chore: add sudoku ed25519key test 2025-12-05 09:06:06 +08:00
wwqgtxx
c33d9ad857 chore: cleanup sudoku internal code 2025-12-05 08:53:18 +08:00
saba-futai
25041b599e chore: sudoku support enable-pure-downlink mode to increase download bandwidth (#2419) 2025-12-05 07:52:49 +08:00
wwqgtxx
6539b509cb chore: restful api contains providerChains for connections 2025-12-04 17:29:01 +08:00
Xi Xu
d2007fdc22 chore: improves thread safety in adapter 2025-12-04 16:02:22 +08:00
wwqgtxx
b5fa3ee99a chore: restful api contains provider-name for proxies 2025-12-04 15:10:13 +08:00
wwqgtxx
91f5593f4e fix: structure ignore tag not working in nest struct 2025-12-04 14:44:34 +08:00
wwqgtxx
90470ac304 chore: cleanup import path for common/net 2025-12-04 13:44:46 +08:00
wwqgtxx
b509affe5b chore: simplify DNSPrefer serialization process 2025-12-04 13:41:44 +08:00
wwqgtxx
32ce513977 chore: discard domain addr input in sudoku uot 2025-12-03 22:54:26 +08:00
wwqgtxx
30891f8781 chore: sharing sudoku internal code 2025-12-03 22:23:37 +08:00
saba-futai
e4cdb9b600 feat: add uot for sudoku (#2415) 2025-12-03 22:11:56 +08:00
wwqgtxx
d33dbbe2f9 fix: QUIC events with session tickets disabled will panic on Go 1.26 2025-12-03 15:40:23 +08:00
wwqgtxx
d8dcaa7500 chore: add upTotal and downTotal data to /traffic restful api 2025-12-03 11:31:13 +08:00
wwqgtxx
9df8392c65 chore: clean up internal interface definitions 2025-12-03 11:08:16 +08:00
wwqgtxx
fdb7cb1f58 chore: allow setting DialerForAPI in adapter.ParseProxy for library user 2025-12-03 00:05:27 +08:00
wwqgtxx
7cd58fbdf6 chore: add DialerForAPI to outbound option for library user 2025-12-02 23:33:07 +08:00
wwqgtxx
bc719eb96d chore: simplify tuic client 2025-12-02 21:07:51 +08:00
wwqgtxx
ac90543548 chore: code cleanup 2025-12-02 17:18:20 +08:00
futai
9a5e506f66 chore: simplify server config and add keygen for sudoku (#2407) 2025-12-01 19:26:41 +08:00
enfein
a001b1b110 chore: update mieru version (#2403) 2025-12-01 08:42:28 +08:00
wwqgtxx
d1f89fa05e chore: update tfo-go ready for go1.26 2025-12-01 01:12:31 +08:00
wwqgtxx
e2796e2d5c chore: apply ping destination filter for windows 2025-11-30 17:00:04 +08:00
futai
93de49d20c chore: sync sudoku with mihomo log (#2402) 2025-11-29 15:21:29 +08:00
wwqgtxx
4d3167ff2f chore: completely remove relay group type using dialer-proxy instead
which was marked as deprecated in v1.18.6
2025-11-29 09:39:28 +08:00
enfein
5998956a72 fix: a nil pointer error when closing mieru underlay (#2401) 2025-11-29 08:39:45 +08:00
futai
6cf1743961 feat: add Sudoku protocol inbound & outbound support (#2397) 2025-11-28 23:40:00 +08:00
Sinspired
8b6ba22b90 fix: replace wrong SetString() with SetBool() for uint weak-typed input (#2394)
The uint branch in decodeBool() incorrectly used SetString(). Use SetBool(dataVal.Uint() != 0) to match expected behavior.
2025-11-26 10:35:26 +08:00
wwqgtxx
7571c87afb chore: add fake-ip-ttl to dns section 2025-11-23 21:34:30 +08:00
wwqgtxx
d4d2c062a3 test: skip inbound test on darwin 2025-11-23 21:34:30 +08:00
TargetLocked
438d4138d6 fix: compare authentication scheme case-insensitively (#2386) 2025-11-23 19:34:02 +08:00
wwqgtxx
140d892ccf chore: better log 2025-11-22 20:59:53 +08:00
enfein
5aa140c493 feat: support mieru UDP outbound (#2384) 2025-11-22 08:54:14 +08:00
enfein
c107c6a824 fix: crash due to nil net.Conn from mieru inbound (#2361) 2025-11-15 07:42:29 +08:00
wwqgtxx
f6e494e73f chore: upgrade the embedded xsync.Map to v4.2.0 2025-11-14 00:23:01 +08:00
hi
0b3159bf9b chore: remove redundant code (#2355) 2025-11-11 17:06:49 +08:00
wwqgtxx
45fd628788 fix: bugs in kcp-go and smux 2025-11-11 09:33:41 +08:00
wwqgtxx
2f545ef634 fix: hosts not working
https://github.com/MetaCubeX/mihomo/issues/2351
2025-11-10 10:55:02 +08:00
wwqgtxx
054e63cb3f chore: remove depend of purego 2025-11-10 00:46:09 +08:00
wwqgtxx
d48bcf1e1e fix: fakeip6 logic not work correctly 2025-11-09 19:19:20 +08:00
wwqgtxx
0df2f79ece fix: missing metadata in mieru inbound 2025-11-09 11:41:37 +08:00
196 changed files with 10067 additions and 2056 deletions

View File

@@ -1,4 +1,5 @@
Subject: [PATCH] Revert "runtime: always use LoadLibraryEx to load system libraries" Subject: [PATCH] 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 "syscall: remove Windows 7 console handle workaround"
Revert "net: remove sysSocket fallback for Windows 7" Revert "net: remove sysSocket fallback for Windows 7"
Revert "crypto/rand,runtime: switch RtlGenRandom for ProcessPrng" Revert "crypto/rand,runtime: switch RtlGenRandom for ProcessPrng"
@@ -655,3 +656,188 @@ diff --git a/src/syscall/dll_windows.go b/src/syscall/dll_windows.go
} else { } else {
h, e = loadlibrary(namep) 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)
+}

842
.github/patch/go1.26.patch vendored Normal file
View File

@@ -0,0 +1,842 @@
Subject: [PATCH] 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)
+}

View File

@@ -59,6 +59,8 @@ jobs:
- { goos: linux, goarch: s390x, output: s390x, debian: s390x, rpm: s390x } - { goos: linux, goarch: s390x, output: s390x, debian: s390x, rpm: s390x }
- { goos: linux, goarch: ppc64le, output: ppc64le, debian: ppc64el, rpm: ppc64le } - { 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: '386', output: '386' }
- { goos: windows, goarch: amd64, goamd64: v1, output: amd64-compatible } # old style file name will be removed in next released - { goos: windows, goarch: amd64, goamd64: 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: v3, output: amd64 }
@@ -176,6 +178,8 @@ jobs:
# 7c1157f9544922e96945196b47b95664b1e39108: "net: remove sysSocket fallback for Windows 7" # 7c1157f9544922e96945196b47b95664b1e39108: "net: remove sysSocket fallback for Windows 7"
# 48042aa09c2f878c4faa576948b07fe625c4707a: "syscall: remove Windows 7 console handle workaround" # 48042aa09c2f878c4faa576948b07fe625c4707a: "syscall: remove Windows 7 console handle workaround"
# a17d959debdb04cd550016a3501dd09d50cd62e7: "runtime: always use LoadLibraryEx to load system libraries" # a17d959debdb04cd550016a3501dd09d50cd62e7: "runtime: always use LoadLibraryEx to load system libraries"
# sepical fix:
# - os.RemoveAll not working on Windows7
- name: Revert Golang1.25 commit for Windows7/8 - name: Revert Golang1.25 commit for Windows7/8
if: ${{ matrix.jobs.goos == 'windows' && matrix.jobs.goversion == '' }} if: ${{ matrix.jobs.goos == 'windows' && matrix.jobs.goversion == '' }}
run: | run: |

View File

@@ -24,6 +24,7 @@ jobs:
- 'ubuntu-24.04-arm' # arm64 linux - 'ubuntu-24.04-arm' # arm64 linux
- 'macos-15-intel' # amd64 macos - 'macos-15-intel' # amd64 macos
go-version: go-version:
- '1.26.0-rc.1'
- '1.25' - '1.25'
- '1.24' - '1.24'
- '1.23' - '1.23'
@@ -49,11 +50,22 @@ jobs:
go-version: ${{ matrix.go-version }} go-version: ${{ matrix.go-version }}
- name: Revert Golang commit for Windows7/8 - name: Revert Golang commit for Windows7/8
if: ${{ runner.os == 'Windows' && matrix.go-version != '1.20' }} if: ${{ runner.os == 'Windows' && matrix.go-version != '1.20' && matrix.go-version != '1.26.0-rc.1' }}
run: | run: |
cd $(go env GOROOT) cd $(go env GOROOT)
patch --verbose -p 1 < $GITHUB_WORKSPACE/.github/patch/go${{matrix.go-version}}.patch patch --verbose -p 1 < $GITHUB_WORKSPACE/.github/patch/go${{matrix.go-version}}.patch
- name: Revert Golang commit for Windows7/8
if: ${{ runner.os == 'Windows' && matrix.go-version == '1.26.0-rc.1' }}
run: |
cd $(go env GOROOT)
patch --verbose -p 1 < $GITHUB_WORKSPACE/.github/patch/go1.26.patch
- name: Remove inbound test for macOS
if: ${{ runner.os == 'macOS' }}
run: |
rm -rf listener/inbound/*_test.go
- name: Test - name: Test
run: go test ./... -v -count=1 run: go test ./... -v -count=1

View File

@@ -5,7 +5,6 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"net" "net"
"net/http"
"net/url" "net/url"
"strings" "strings"
"time" "time"
@@ -17,6 +16,8 @@ import (
"github.com/metacubex/mihomo/component/ca" "github.com/metacubex/mihomo/component/ca"
C "github.com/metacubex/mihomo/constant" C "github.com/metacubex/mihomo/constant"
"github.com/metacubex/mihomo/log" "github.com/metacubex/mihomo/log"
"github.com/metacubex/http"
) )
var UnifiedDelay = atomic.NewBool(false) var UnifiedDelay = atomic.NewBool(false)
@@ -153,8 +154,9 @@ func (p *Proxy) MarshalJSON() ([]byte, error) {
mapping["mptcp"] = proxyInfo.MPTCP mapping["mptcp"] = proxyInfo.MPTCP
mapping["smux"] = proxyInfo.SMUX mapping["smux"] = proxyInfo.SMUX
mapping["interface"] = proxyInfo.Interface mapping["interface"] = proxyInfo.Interface
mapping["dialer-proxy"] = proxyInfo.DialerProxy
mapping["routing-mark"] = proxyInfo.RoutingMark mapping["routing-mark"] = proxyInfo.RoutingMark
mapping["provider-name"] = proxyInfo.ProviderName
mapping["dialer-proxy"] = proxyInfo.DialerProxy
return json.Marshal(mapping) return json.Marshal(mapping)
} }
@@ -177,14 +179,12 @@ func (p *Proxy) URLTest(ctx context.Context, url string, expectedStatus utils.In
p.history.Pop() p.history.Pop()
} }
state, ok := p.extra.Load(url) state, _ := p.extra.LoadOrStoreFn(url, func() *internalProxyState {
if !ok { return &internalProxyState{
state = &internalProxyState{
history: queue.New[C.DelayHistory](defaultHistoriesNum), history: queue.New[C.DelayHistory](defaultHistoriesNum),
alive: atomic.NewBool(true), alive: atomic.NewBool(true),
} }
p.extra.Store(url, state) })
}
if !satisfied { if !satisfied {
record.Delay = 0 record.Delay = 0

View File

@@ -2,9 +2,10 @@ package inbound
import ( import (
"net" "net"
"net/http"
C "github.com/metacubex/mihomo/constant" C "github.com/metacubex/mihomo/constant"
"github.com/metacubex/http"
) )
// NewHTTPS receive CONNECT request and return ConnContext // NewHTTPS receive CONNECT request and return ConnContext

View File

@@ -8,6 +8,7 @@ import (
"sync" "sync"
"github.com/metacubex/mihomo/component/keepalive" "github.com/metacubex/mihomo/component/keepalive"
"github.com/metacubex/mihomo/component/mptcp"
"github.com/metacubex/tfo-go" "github.com/metacubex/tfo-go"
) )
@@ -34,13 +35,13 @@ func Tfo() bool {
func SetMPTCP(open bool) { func SetMPTCP(open bool) {
mutex.Lock() mutex.Lock()
defer mutex.Unlock() defer mutex.Unlock()
setMultiPathTCP(&lc.ListenConfig, open) mptcp.SetNetListenConfig(&lc.ListenConfig, open)
} }
func MPTCP() bool { func MPTCP() bool {
mutex.RLock() mutex.RLock()
defer mutex.RUnlock() defer mutex.RUnlock()
return getMultiPathTCP(&lc.ListenConfig) return mptcp.GetNetListenConfig(&lc.ListenConfig)
} }
func preResolve(network, address string) (string, error) { func preResolve(network, address string) (string, error) {

View File

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

View File

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

View File

@@ -2,12 +2,13 @@ package inbound
import ( import (
"net" "net"
"net/http"
"net/netip" "net/netip"
"strings" "strings"
C "github.com/metacubex/mihomo/constant" C "github.com/metacubex/mihomo/constant"
"github.com/metacubex/mihomo/transport/socks5" "github.com/metacubex/mihomo/transport/socks5"
"github.com/metacubex/http"
) )
func parseSocksAddr(target socks5.Addr) *C.Metadata { func parseSocksAddr(target socks5.Addr) *C.Metadata {

View File

@@ -6,8 +6,7 @@ import (
"strconv" "strconv"
"time" "time"
CN "github.com/metacubex/mihomo/common/net" N "github.com/metacubex/mihomo/common/net"
"github.com/metacubex/mihomo/component/dialer"
"github.com/metacubex/mihomo/component/proxydialer" "github.com/metacubex/mihomo/component/proxydialer"
C "github.com/metacubex/mihomo/constant" C "github.com/metacubex/mihomo/constant"
"github.com/metacubex/mihomo/transport/anytls" "github.com/metacubex/mihomo/transport/anytls"
@@ -20,7 +19,6 @@ import (
type AnyTLS struct { type AnyTLS struct {
*Base *Base
client *anytls.Client client *anytls.Client
dialer proxydialer.SingDialer
option *AnyTLSOption option *AnyTLSOption
} }
@@ -65,7 +63,7 @@ func (t *AnyTLS) ListenPacketContext(ctx context.Context, metadata *C.Metadata)
// create uot on tcp // create uot on tcp
destination := M.SocksaddrFromNet(metadata.UDPAddr()) 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 // SupportUOT implements C.ProxyAdapter
@@ -92,18 +90,18 @@ func NewAnyTLS(option AnyTLSOption) (*AnyTLS, error) {
name: option.Name, name: option.Name,
addr: addr, addr: addr,
tp: C.AnyTLS, tp: C.AnyTLS,
pdName: option.ProviderName,
udp: option.UDP, udp: option.UDP,
tfo: option.TFO, tfo: option.TFO,
mpTcp: option.MPTCP, mpTcp: option.MPTCP,
iface: option.Interface, iface: option.Interface,
rmark: option.RoutingMark, rmark: option.RoutingMark,
prefer: C.NewDNSPrefer(option.IPVersion), prefer: option.IPVersion,
}, },
option: &option, option: &option,
} }
outbound.dialer = option.NewDialer(outbound.DialOptions())
singDialer := proxydialer.NewByNameSingDialer(option.DialerProxy, dialer.NewDialer(outbound.DialOptions()...)) singDialer := proxydialer.NewSingDialer(outbound.dialer)
outbound.dialer = singDialer
tOption := anytls.ClientConfig{ tOption := anytls.ClientConfig{
Password: option.Password, Password: option.Password,

View File

@@ -12,6 +12,7 @@ import (
N "github.com/metacubex/mihomo/common/net" N "github.com/metacubex/mihomo/common/net"
"github.com/metacubex/mihomo/common/utils" "github.com/metacubex/mihomo/common/utils"
"github.com/metacubex/mihomo/component/dialer" "github.com/metacubex/mihomo/component/dialer"
"github.com/metacubex/mihomo/component/proxydialer"
"github.com/metacubex/mihomo/component/resolver" "github.com/metacubex/mihomo/component/resolver"
C "github.com/metacubex/mihomo/constant" C "github.com/metacubex/mihomo/constant"
"github.com/metacubex/mihomo/log" "github.com/metacubex/mihomo/log"
@@ -26,15 +27,17 @@ type ProxyAdapter interface {
type Base struct { type Base struct {
name string name string
addr string addr string
iface string
tp C.AdapterType tp C.AdapterType
pdName string
udp bool udp bool
xudp bool xudp bool
tfo bool tfo bool
mpTcp bool mpTcp bool
iface string
rmark int rmark int
id string
prefer C.DNSPrefer prefer C.DNSPrefer
dialer C.Dialer
id string
} }
// Name implements C.ProxyAdapter // Name implements C.ProxyAdapter
@@ -56,35 +59,15 @@ func (b *Base) Type() C.AdapterType {
return b.tp 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) (C.Conn, error) { func (b *Base) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
return nil, C.ErrNotSupport 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) {
return nil, C.ErrNotSupport
}
// ListenPacketContext implements C.ProxyAdapter // ListenPacketContext implements C.ProxyAdapter
func (b *Base) ListenPacketContext(ctx context.Context, metadata *C.Metadata) (C.PacketConn, error) { func (b *Base) ListenPacketContext(ctx context.Context, metadata *C.Metadata) (C.PacketConn, error) {
return nil, C.ErrNotSupport 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 // SupportUOT implements C.ProxyAdapter
func (b *Base) SupportUOT() bool { func (b *Base) SupportUOT() bool {
return false return false
@@ -103,6 +86,7 @@ func (b *Base) ProxyInfo() (info C.ProxyInfo) {
info.SMUX = false info.SMUX = false
info.Interface = b.iface info.Interface = b.iface
info.RoutingMark = b.rmark info.RoutingMark = b.rmark
info.ProviderName = b.pdName
return return
} }
@@ -178,12 +162,30 @@ func (b *Base) Close() error {
} }
type BasicOption struct { type BasicOption struct {
TFO bool `proxy:"tfo,omitempty"` TFO bool `proxy:"tfo,omitempty"`
MPTCP bool `proxy:"mptcp,omitempty"` MPTCP bool `proxy:"mptcp,omitempty"`
Interface string `proxy:"interface-name,omitempty"` Interface string `proxy:"interface-name,omitempty"`
RoutingMark int `proxy:"routing-mark,omitempty"` RoutingMark int `proxy:"routing-mark,omitempty"`
IPVersion string `proxy:"ip-version,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 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 { type BaseOption struct {
@@ -217,6 +219,7 @@ func NewBase(opt BaseOption) *Base {
type conn struct { type conn struct {
N.ExtendedConn N.ExtendedConn
chain C.Chain chain C.Chain
pdChain C.Chain
adapterAddr string adapterAddr string
} }
@@ -238,9 +241,15 @@ func (c *conn) Chains() C.Chain {
return c.chain return c.chain
} }
// ProviderChains implements C.Connection
func (c *conn) ProviderChains() C.Chain {
return c.pdChain
}
// AppendToChains implements C.Connection // AppendToChains implements C.Connection
func (c *conn) AppendToChains(a C.ProxyAdapter) { func (c *conn) AppendToChains(a C.ProxyAdapter) {
c.chain = append(c.chain, a.Name()) c.chain = append(c.chain, a.Name())
c.pdChain = append(c.pdChain, a.ProxyInfo().ProviderName)
} }
func (c *conn) Upstream() any { func (c *conn) Upstream() any {
@@ -263,7 +272,7 @@ func NewConn(c net.Conn, a C.ProxyAdapter) C.Conn {
if _, ok := c.(syscall.Conn); !ok { // exclusion system conn like *net.TCPConn 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 c = N.NewDeadlineConn(c) // most conn from outbound can't handle readDeadline correctly
} }
cc := &conn{N.NewExtendedConn(c), nil, a.Addr()} cc := &conn{N.NewExtendedConn(c), nil, nil, a.Addr()}
cc.AppendToChains(a) cc.AppendToChains(a)
return cc return cc
} }
@@ -271,6 +280,7 @@ func NewConn(c net.Conn, a C.ProxyAdapter) C.Conn {
type packetConn struct { type packetConn struct {
N.EnhancePacketConn N.EnhancePacketConn
chain C.Chain chain C.Chain
pdChain C.Chain
adapterName string adapterName string
connID string connID string
adapterAddr string adapterAddr string
@@ -291,9 +301,15 @@ func (c *packetConn) Chains() C.Chain {
return c.chain return c.chain
} }
// ProviderChains implements C.Connection
func (c *packetConn) ProviderChains() C.Chain {
return c.pdChain
}
// AppendToChains implements C.Connection // AppendToChains implements C.Connection
func (c *packetConn) AppendToChains(a C.ProxyAdapter) { func (c *packetConn) AppendToChains(a C.ProxyAdapter) {
c.chain = append(c.chain, a.Name()) c.chain = append(c.chain, a.Name())
c.pdChain = append(c.pdChain, a.ProxyInfo().ProviderName)
} }
func (c *packetConn) LocalAddr() net.Addr { func (c *packetConn) LocalAddr() net.Addr {
@@ -322,7 +338,7 @@ func newPacketConn(pc net.PacketConn, a ProxyAdapter) C.PacketConn {
if _, ok := pc.(syscall.Conn); !ok { // exclusion system conn like *net.UDPConn 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 epc = N.NewDeadlineEnhancePacketConn(epc) // most conn from outbound can't handle readDeadline correctly
} }
cpc := &packetConn{epc, nil, a.Name(), utils.NewUUIDV4().String(), a.Addr(), a.ResolveUDP} cpc := &packetConn{epc, nil, nil, a.Name(), utils.NewUUIDV4().String(), a.Addr(), a.ResolveUDP}
cpc.AppendToChains(a) cpc.AppendToChains(a)
return cpc return cpc
} }
@@ -348,17 +364,6 @@ func (p *autoCloseProxyAdapter) DialContext(ctx context.Context, metadata *C.Met
return c, nil return c, nil
} }
func (p *autoCloseProxyAdapter) DialContextWithDialer(ctx context.Context, dialer C.Dialer, metadata *C.Metadata) (_ C.Conn, err error) {
c, err := p.ProxyAdapter.DialContextWithDialer(ctx, dialer, metadata)
if err != nil {
return nil, err
}
if c, ok := c.(AddRef); ok {
c.AddRef(p)
}
return c, nil
}
func (p *autoCloseProxyAdapter) ListenPacketContext(ctx context.Context, metadata *C.Metadata) (_ C.PacketConn, err error) { func (p *autoCloseProxyAdapter) ListenPacketContext(ctx context.Context, metadata *C.Metadata) (_ C.PacketConn, err error) {
pc, err := p.ProxyAdapter.ListenPacketContext(ctx, metadata) pc, err := p.ProxyAdapter.ListenPacketContext(ctx, metadata)
if err != nil { if err != nil {
@@ -370,17 +375,6 @@ func (p *autoCloseProxyAdapter) ListenPacketContext(ctx context.Context, metadat
return pc, nil return pc, nil
} }
func (p *autoCloseProxyAdapter) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, metadata *C.Metadata) (_ C.PacketConn, err error) {
pc, err := p.ProxyAdapter.ListenPacketWithDialer(ctx, dialer, metadata)
if err != nil {
return nil, err
}
if pc, ok := pc.(AddRef); ok {
pc.AddRef(p)
}
return pc, nil
}
func (p *autoCloseProxyAdapter) Close() error { func (p *autoCloseProxyAdapter) Close() error {
p.closeOnce.Do(func() { p.closeOnce.Do(func() {
log.Debugln("Closing outdated proxy [%s]", p.Name()) log.Debugln("Closing outdated proxy [%s]", p.Name())

View File

@@ -69,12 +69,13 @@ func NewDirectWithOption(option DirectOption) *Direct {
Base: &Base{ Base: &Base{
name: option.Name, name: option.Name,
tp: C.Direct, tp: C.Direct,
pdName: option.ProviderName,
udp: true, udp: true,
tfo: option.TFO, tfo: option.TFO,
mpTcp: option.MPTCP, mpTcp: option.MPTCP,
iface: option.Interface, iface: option.Interface,
rmark: option.RoutingMark, rmark: option.RoutingMark,
prefer: C.NewDNSPrefer(option.IPVersion), prefer: option.IPVersion,
}, },
loopBack: loopback.NewDetector(), loopBack: loopback.NewDetector(),
} }

View File

@@ -158,12 +158,13 @@ func NewDnsWithOption(option DnsOption) *Dns {
Base: &Base{ Base: &Base{
name: option.Name, name: option.Name,
tp: C.Dns, tp: C.Dns,
pdName: option.ProviderName,
udp: true, udp: true,
tfo: option.TFO, tfo: option.TFO,
mpTcp: option.MPTCP, mpTcp: option.MPTCP,
iface: option.Interface, iface: option.Interface,
rmark: option.RoutingMark, rmark: option.RoutingMark,
prefer: C.NewDNSPrefer(option.IPVersion), prefer: option.IPVersion,
}, },
} }
} }

View File

@@ -3,19 +3,18 @@ package outbound
import ( import (
"bufio" "bufio"
"context" "context"
"crypto/tls"
"encoding/base64" "encoding/base64"
"errors" "errors"
"fmt" "fmt"
"net" "net"
"net/http"
"strconv" "strconv"
N "github.com/metacubex/mihomo/common/net" N "github.com/metacubex/mihomo/common/net"
"github.com/metacubex/mihomo/component/ca" "github.com/metacubex/mihomo/component/ca"
"github.com/metacubex/mihomo/component/dialer"
"github.com/metacubex/mihomo/component/proxydialer"
C "github.com/metacubex/mihomo/constant" C "github.com/metacubex/mihomo/constant"
"github.com/metacubex/http"
"github.com/metacubex/tls"
) )
type Http struct { type Http struct {
@@ -61,18 +60,7 @@ func (h *Http) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.Me
// DialContext implements C.ProxyAdapter // DialContext implements C.ProxyAdapter
func (h *Http) DialContext(ctx context.Context, metadata *C.Metadata) (_ C.Conn, err error) { func (h *Http) DialContext(ctx context.Context, metadata *C.Metadata) (_ C.Conn, err error) {
return h.DialContextWithDialer(ctx, dialer.NewDialer(h.DialOptions()...), metadata) c, err := h.dialer.DialContext(ctx, "tcp", h.addr)
}
// 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)
if err != nil { if err != nil {
return nil, fmt.Errorf("%s connect error: %w", h.addr, err) return nil, fmt.Errorf("%s connect error: %w", h.addr, err)
} }
@@ -89,11 +77,6 @@ func (h *Http) DialContextWithDialer(ctx context.Context, dialer C.Dialer, metad
return NewConn(c, h), nil return NewConn(c, h), nil
} }
// SupportWithDialer implements C.ProxyAdapter
func (h *Http) SupportWithDialer() C.NetWork {
return C.TCP
}
// ProxyInfo implements C.ProxyAdapter // ProxyInfo implements C.ProxyAdapter
func (h *Http) ProxyInfo() C.ProxyInfo { func (h *Http) ProxyInfo() C.ProxyInfo {
info := h.Base.ProxyInfo() info := h.Base.ProxyInfo()
@@ -183,20 +166,23 @@ func NewHttp(option HttpOption) (*Http, error) {
} }
} }
return &Http{ outbound := &Http{
Base: &Base{ Base: &Base{
name: option.Name, name: option.Name,
addr: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)), addr: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)),
tp: C.Http, tp: C.Http,
pdName: option.ProviderName,
tfo: option.TFO, tfo: option.TFO,
mpTcp: option.MPTCP, mpTcp: option.MPTCP,
iface: option.Interface, iface: option.Interface,
rmark: option.RoutingMark, rmark: option.RoutingMark,
prefer: C.NewDNSPrefer(option.IPVersion), prefer: option.IPVersion,
}, },
user: option.UserName, user: option.UserName,
pass: option.Password, pass: option.Password,
tlsConfig: tlsConfig, tlsConfig: tlsConfig,
option: &option, option: &option,
}, nil }
outbound.dialer = option.NewDialer(outbound.DialOptions())
return outbound, nil
} }

View File

@@ -2,7 +2,6 @@ package outbound
import ( import (
"context" "context"
"crypto/tls"
"encoding/base64" "encoding/base64"
"fmt" "fmt"
"net" "net"
@@ -13,8 +12,6 @@ import (
"github.com/metacubex/mihomo/component/ca" "github.com/metacubex/mihomo/component/ca"
"github.com/metacubex/mihomo/component/dialer" "github.com/metacubex/mihomo/component/dialer"
"github.com/metacubex/mihomo/component/ech" "github.com/metacubex/mihomo/component/ech"
"github.com/metacubex/mihomo/component/proxydialer"
tlsC "github.com/metacubex/mihomo/component/tls"
C "github.com/metacubex/mihomo/constant" C "github.com/metacubex/mihomo/constant"
"github.com/metacubex/mihomo/log" "github.com/metacubex/mihomo/log"
hyCongestion "github.com/metacubex/mihomo/transport/hysteria/congestion" hyCongestion "github.com/metacubex/mihomo/transport/hysteria/congestion"
@@ -24,6 +21,8 @@ import (
"github.com/metacubex/mihomo/transport/hysteria/transport" "github.com/metacubex/mihomo/transport/hysteria/transport"
"github.com/metacubex/mihomo/transport/hysteria/utils" "github.com/metacubex/mihomo/transport/hysteria/utils"
"github.com/metacubex/tls"
"github.com/metacubex/quic-go" "github.com/metacubex/quic-go"
"github.com/metacubex/quic-go/congestion" "github.com/metacubex/quic-go/congestion"
M "github.com/metacubex/sing/common/metadata" M "github.com/metacubex/sing/common/metadata"
@@ -46,7 +45,7 @@ type Hysteria struct {
option *HysteriaOption option *HysteriaOption
client *core.Client client *core.Client
tlsConfig *tlsC.Config tlsConfig *tls.Config
echConfig *ech.Config echConfig *ech.Config
} }
@@ -74,16 +73,8 @@ func (h *Hysteria) genHdc(ctx context.Context) utils.PacketDialer {
return &hyDialerWithContext{ return &hyDialerWithContext{
ctx: context.Background(), ctx: context.Background(),
hyDialer: func(network string, rAddr net.Addr) (net.PacketConn, error) { hyDialer: func(network string, rAddr net.Addr) (net.PacketConn, error) {
var err error
var cDialer C.Dialer = dialer.NewDialer(h.DialOptions()...)
if len(h.option.DialerProxy) > 0 {
cDialer, err = proxydialer.NewByName(h.option.DialerProxy, cDialer)
if err != nil {
return nil, err
}
}
rAddrPort, _ := netip.ParseAddrPort(rAddr.String()) 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) { remoteAddr: func(addr string) (net.Addr, error) {
udpAddr, err := resolveUDPAddr(ctx, "udp", addr, h.prefer) udpAddr, err := resolveUDPAddr(ctx, "udp", addr, h.prefer)
@@ -184,7 +175,7 @@ func NewHysteria(option HysteriaOption) (*Hysteria, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
tlsClientConfig := tlsC.UConfig(tlsConfig) tlsClientConfig := tlsConfig
quicConfig := &quic.Config{ quicConfig := &quic.Config{
InitialStreamReceiveWindow: uint64(option.ReceiveWindowConn), InitialStreamReceiveWindow: uint64(option.ReceiveWindowConn),
@@ -252,17 +243,19 @@ func NewHysteria(option HysteriaOption) (*Hysteria, error) {
name: option.Name, name: option.Name,
addr: addr, addr: addr,
tp: C.Hysteria, tp: C.Hysteria,
pdName: option.ProviderName,
udp: true, udp: true,
tfo: option.FastOpen, tfo: option.FastOpen,
iface: option.Interface, iface: option.Interface,
rmark: option.RoutingMark, rmark: option.RoutingMark,
prefer: C.NewDNSPrefer(option.IPVersion), prefer: option.IPVersion,
}, },
option: &option, option: &option,
client: client, client: client,
tlsConfig: tlsClientConfig, tlsConfig: tlsClientConfig,
echConfig: echConfig, echConfig: echConfig,
} }
outbound.dialer = option.NewDialer(outbound.DialOptions())
return outbound, nil return outbound, nil
} }

View File

@@ -2,19 +2,16 @@ package outbound
import ( import (
"context" "context"
"crypto/tls"
"errors" "errors"
"fmt" "fmt"
"net" "net"
"strconv" "strconv"
"time" "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/common/utils"
"github.com/metacubex/mihomo/component/ca" "github.com/metacubex/mihomo/component/ca"
"github.com/metacubex/mihomo/component/dialer"
"github.com/metacubex/mihomo/component/proxydialer" "github.com/metacubex/mihomo/component/proxydialer"
tlsC "github.com/metacubex/mihomo/component/tls"
C "github.com/metacubex/mihomo/constant" C "github.com/metacubex/mihomo/constant"
"github.com/metacubex/mihomo/log" "github.com/metacubex/mihomo/log"
tuicCommon "github.com/metacubex/mihomo/transport/tuic/common" tuicCommon "github.com/metacubex/mihomo/transport/tuic/common"
@@ -22,6 +19,7 @@ import (
"github.com/metacubex/quic-go" "github.com/metacubex/quic-go"
"github.com/metacubex/sing-quic/hysteria2" "github.com/metacubex/sing-quic/hysteria2"
M "github.com/metacubex/sing/common/metadata" M "github.com/metacubex/sing/common/metadata"
"github.com/metacubex/tls"
) )
func init() { func init() {
@@ -36,7 +34,6 @@ type Hysteria2 struct {
option *Hysteria2Option option *Hysteria2Option
client *hysteria2.Client client *hysteria2.Client
dialer proxydialer.SingDialer
} }
type Hysteria2Option struct { type Hysteria2Option struct {
@@ -87,7 +84,7 @@ func (h *Hysteria2) ListenPacketContext(ctx context.Context, metadata *C.Metadat
if pc == nil { if pc == nil {
return nil, errors.New("packetConn is 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 // Close implements C.ProxyAdapter
@@ -112,16 +109,16 @@ func NewHysteria2(option Hysteria2Option) (*Hysteria2, error) {
name: option.Name, name: option.Name,
addr: addr, addr: addr,
tp: C.Hysteria2, tp: C.Hysteria2,
pdName: option.ProviderName,
udp: true, udp: true,
iface: option.Interface, iface: option.Interface,
rmark: option.RoutingMark, rmark: option.RoutingMark,
prefer: C.NewDNSPrefer(option.IPVersion), prefer: option.IPVersion,
}, },
option: &option, option: &option,
} }
outbound.dialer = option.NewDialer(outbound.DialOptions())
singDialer := proxydialer.NewByNameSingDialer(option.DialerProxy, dialer.NewDialer(outbound.DialOptions()...)) singDialer := proxydialer.NewSingDialer(outbound.dialer)
outbound.dialer = singDialer
var salamanderPassword string var salamanderPassword string
if len(option.Obfs) > 0 { if len(option.Obfs) > 0 {
@@ -159,7 +156,7 @@ func NewHysteria2(option Hysteria2Option) (*Hysteria2, error) {
tlsConfig.NextProtos = option.ALPN tlsConfig.NextProtos = option.ALPN
} }
tlsClientConfig := tlsC.UConfig(tlsConfig) tlsClientConfig := tlsConfig
echConfig, err := option.ECHOpts.Parse() echConfig, err := option.ECHOpts.Parse()
if err != nil { if err != nil {
return nil, err return nil, err
@@ -192,7 +189,7 @@ func NewHysteria2(option Hysteria2Option) (*Hysteria2, error) {
CWND: option.CWND, CWND: option.CWND,
UdpMTU: option.UdpMTU, UdpMTU: option.UdpMTU,
ServerAddress: func(ctx context.Context) (*net.UDPAddr, error) { ServerAddress: func(ctx context.Context) (*net.UDPAddr, error) {
udpAddr, err := resolveUDPAddr(ctx, "udp", addr, C.NewDNSPrefer(option.IPVersion)) udpAddr, err := resolveUDPAddr(ctx, "udp", addr, option.IPVersion)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@@ -4,12 +4,12 @@ import (
"context" "context"
"fmt" "fmt"
"net" "net"
"net/netip"
"strconv" "strconv"
"sync" "sync"
CN "github.com/metacubex/mihomo/common/net" N "github.com/metacubex/mihomo/common/net"
"github.com/metacubex/mihomo/component/dialer" "github.com/metacubex/mihomo/component/resolver"
"github.com/metacubex/mihomo/component/proxydialer"
C "github.com/metacubex/mihomo/constant" C "github.com/metacubex/mihomo/constant"
mieruclient "github.com/enfein/mieru/v3/apis/client" mieruclient "github.com/enfein/mieru/v3/apis/client"
@@ -40,6 +40,45 @@ type MieruOption struct {
HandshakeMode string `proxy:"handshake-mode,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 // DialContext implements C.ProxyAdapter
func (m *Mieru) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) { func (m *Mieru) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
if err := m.ensureClientIsRunning(); err != nil { if err := m.ensureClientIsRunning(); err != nil {
@@ -65,7 +104,7 @@ func (m *Mieru) ListenPacketContext(ctx context.Context, metadata *C.Metadata) (
if err != nil { if err != nil {
return nil, fmt.Errorf("dial to %s failed: %w", metadata.UDPAddr(), err) 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 // SupportUOT implements C.ProxyAdapter
@@ -89,19 +128,13 @@ func (m *Mieru) ensureClientIsRunning() error {
} }
// Create a dialer and add it to the client config, before starting the client. // Create a dialer and add it to the client config, before starting the client.
var dialer C.Dialer = dialer.NewDialer(m.DialOptions()...)
var err error
if len(m.option.DialerProxy) > 0 {
dialer, err = proxydialer.NewByName(m.option.DialerProxy, dialer)
if err != nil {
return err
}
}
config, err := m.client.Load() config, err := m.client.Load()
if err != nil { if err != nil {
return err 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 { if err := m.client.Store(config); err != nil {
return err return err
} }
@@ -134,16 +167,18 @@ func NewMieru(option MieruOption) (*Mieru, error) {
Base: &Base{ Base: &Base{
name: option.Name, name: option.Name,
addr: addr, addr: addr,
iface: option.Interface,
tp: C.Mieru, tp: C.Mieru,
pdName: option.ProviderName,
udp: option.UDP, udp: option.UDP,
xudp: false, xudp: false,
iface: option.Interface,
rmark: option.RoutingMark, rmark: option.RoutingMark,
prefer: C.NewDNSPrefer(option.IPVersion), prefer: option.IPVersion,
}, },
option: &option, option: &option,
client: c, client: c,
} }
outbound.dialer = option.NewDialer(outbound.DialOptions())
return outbound, nil return outbound, nil
} }
@@ -158,23 +193,21 @@ func (m *Mieru) Close() error {
} }
func metadataToMieruNetAddrSpec(metadata *C.Metadata) mierumodel.NetAddrSpec { func metadataToMieruNetAddrSpec(metadata *C.Metadata) mierumodel.NetAddrSpec {
spec := mierumodel.NetAddrSpec{
Net: metadata.NetWork.String(),
}
if metadata.Host != "" { if metadata.Host != "" {
return mierumodel.NetAddrSpec{ spec.AddrSpec = mierumodel.AddrSpec{
AddrSpec: mierumodel.AddrSpec{ FQDN: metadata.Host,
FQDN: metadata.Host, Port: int(metadata.DstPort),
Port: int(metadata.DstPort),
},
Net: "tcp",
} }
} else { } else {
return mierumodel.NetAddrSpec{ spec.AddrSpec = mierumodel.AddrSpec{
AddrSpec: mierumodel.AddrSpec{ IP: metadata.DstIP.AsSlice(),
IP: metadata.DstIP.AsSlice(), Port: int(metadata.DstPort),
Port: int(metadata.DstPort),
},
Net: "tcp",
} }
} }
return spec
} }
func buildMieruClientConfig(option MieruOption) (*mieruclient.ClientConfig, error) { func buildMieruClientConfig(option MieruOption) (*mieruclient.ClientConfig, error) {
@@ -182,7 +215,13 @@ func buildMieruClientConfig(option MieruOption) (*mieruclient.ClientConfig, erro
return nil, fmt.Errorf("failed to validate mieru option: %w", err) 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 var server *mierupb.ServerEndpoint
if net.ParseIP(option.Server) != nil { if net.ParseIP(option.Server) != nil {
// server is an IP address // server is an IP address
@@ -240,6 +279,9 @@ func buildMieruClientConfig(option MieruOption) (*mieruclient.ClientConfig, erro
}, },
Servers: []*mierupb.ServerEndpoint{server}, Servers: []*mierupb.ServerEndpoint{server},
}, },
DNSConfig: &mierucommon.ClientDNSConfig{
BypassDialerDNS: true,
},
} }
if multiplexing, ok := mierupb.MultiplexingLevel_value[option.Multiplexing]; ok { if multiplexing, ok := mierupb.MultiplexingLevel_value[option.Multiplexing]; ok {
config.Profile.Multiplexing = &mierupb.MultiplexingConfig{ config.Profile.Multiplexing = &mierupb.MultiplexingConfig{
@@ -284,8 +326,8 @@ func validateMieruOption(option MieruOption) error {
} }
} }
if option.Transport != "TCP" { if option.Transport != "TCP" && option.Transport != "UDP" {
return fmt.Errorf("transport must be TCP") return fmt.Errorf("transport must be TCP or UDP")
} }
if option.UserName == "" { if option.UserName == "" {
return fmt.Errorf("username is empty") return fmt.Errorf("username is empty")

View File

@@ -34,7 +34,7 @@ func TestNewMieru(t *testing.T) {
Name: "test", Name: "test",
Server: "example.com", Server: "example.com",
Port: 10003, Port: 10003,
Transport: "TCP", Transport: "UDP",
UserName: "test", UserName: "test",
Password: "test", Password: "test",
}, },

View File

@@ -17,6 +17,7 @@ type Reject struct {
} }
type RejectOption struct { type RejectOption struct {
BasicOption
Name string `proxy:"name"` Name string `proxy:"name"`
} }
@@ -33,7 +34,10 @@ func (r *Reject) ListenPacketContext(ctx context.Context, metadata *C.Metadata)
if err := r.ResolveUDP(ctx, metadata); err != nil { if err := r.ResolveUDP(ctx, metadata); err != nil {
return nil, err return nil, err
} }
return newPacketConn(&nopPacketConn{}, r), nil if r.drop {
return newPacketConn(dropPacketConn{}, r), nil
}
return newPacketConn(nopPacketConn{}, r), C.ErrResetByRule
} }
func (r *Reject) ResolveUDP(ctx context.Context, metadata *C.Metadata) error { func (r *Reject) ResolveUDP(ctx context.Context, metadata *C.Metadata) error {
@@ -89,12 +93,10 @@ func NewPass() *Reject {
type nopConn struct{} type nopConn struct{}
func (rw nopConn) Read(b []byte) (int, error) { return 0, io.EOF } func (rw nopConn) Read(b []byte) (int, error) { return 0, C.ErrResetByRule }
func (rw nopConn) ReadBuffer(buffer *buf.Buffer) error { return C.ErrResetByRule }
func (rw nopConn) ReadBuffer(buffer *buf.Buffer) error { return io.EOF } func (rw nopConn) Write(b []byte) (int, error) { return 0, C.ErrResetByRule }
func (rw nopConn) WriteBuffer(buffer *buf.Buffer) error { return C.ErrResetByRule }
func (rw nopConn) Write(b []byte) (int, error) { return 0, io.EOF }
func (rw nopConn) WriteBuffer(buffer *buf.Buffer) error { return io.EOF }
func (rw nopConn) Close() error { return nil } func (rw nopConn) Close() error { return nil }
func (rw nopConn) LocalAddr() net.Addr { return nil } func (rw nopConn) LocalAddr() net.Addr { return nil }
func (rw nopConn) RemoteAddr() net.Addr { return nil } func (rw nopConn) RemoteAddr() net.Addr { return nil }
@@ -109,11 +111,9 @@ type nopPacketConn struct{}
func (npc nopPacketConn) WriteTo(b []byte, addr net.Addr) (n int, err error) { func (npc nopPacketConn) WriteTo(b []byte, addr net.Addr) (n int, err error) {
return len(b), nil return len(b), nil
} }
func (npc nopPacketConn) ReadFrom(b []byte) (int, net.Addr, error) { func (npc nopPacketConn) ReadFrom(b []byte) (int, net.Addr, error) { return 0, nil, C.ErrResetByRule }
return 0, nil, io.EOF
}
func (npc nopPacketConn) WaitReadFrom() ([]byte, func(), net.Addr, error) { func (npc nopPacketConn) WaitReadFrom() ([]byte, func(), net.Addr, error) {
return nil, nil, nil, io.EOF return nil, nil, nil, C.ErrResetByRule
} }
func (npc nopPacketConn) Close() error { return nil } func (npc nopPacketConn) Close() error { return nil }
func (npc nopPacketConn) LocalAddr() net.Addr { return udpAddrIPv4Unspecified } func (npc nopPacketConn) LocalAddr() net.Addr { return udpAddrIPv4Unspecified }
@@ -136,3 +136,18 @@ func (rw dropConn) RemoteAddr() net.Addr { return nil }
func (rw dropConn) SetDeadline(time.Time) error { return nil } func (rw dropConn) SetDeadline(time.Time) error { return nil }
func (rw dropConn) SetReadDeadline(time.Time) error { return nil } func (rw dropConn) SetReadDeadline(time.Time) error { return nil }
func (rw dropConn) SetWriteDeadline(time.Time) error { return nil } func (rw dropConn) SetWriteDeadline(time.Time) error { return nil }
type dropPacketConn struct{}
func (npc dropPacketConn) WriteTo(b []byte, addr net.Addr) (n int, err error) {
return len(b), nil
}
func (npc dropPacketConn) ReadFrom(b []byte) (int, net.Addr, error) { return 0, nil, io.EOF }
func (npc dropPacketConn) WaitReadFrom() ([]byte, func(), net.Addr, error) {
return nil, nil, nil, io.EOF
}
func (npc dropPacketConn) Close() error { return nil }
func (npc dropPacketConn) LocalAddr() net.Addr { return udpAddrIPv4Unspecified }
func (npc dropPacketConn) SetDeadline(time.Time) error { return nil }
func (npc dropPacketConn) SetReadDeadline(time.Time) error { return nil }
func (npc dropPacketConn) SetWriteDeadline(time.Time) error { return nil }

View File

@@ -8,8 +8,6 @@ import (
N "github.com/metacubex/mihomo/common/net" N "github.com/metacubex/mihomo/common/net"
"github.com/metacubex/mihomo/common/structure" "github.com/metacubex/mihomo/common/structure"
"github.com/metacubex/mihomo/component/dialer"
"github.com/metacubex/mihomo/component/proxydialer"
C "github.com/metacubex/mihomo/constant" C "github.com/metacubex/mihomo/constant"
"github.com/metacubex/mihomo/ntp" "github.com/metacubex/mihomo/ntp"
gost "github.com/metacubex/mihomo/transport/gost-plugin" gost "github.com/metacubex/mihomo/transport/gost-plugin"
@@ -191,17 +189,6 @@ func (ss *ShadowSocks) StreamConnContext(ctx context.Context, c net.Conn, metada
// DialContext implements C.ProxyAdapter // DialContext implements C.ProxyAdapter
func (ss *ShadowSocks) DialContext(ctx context.Context, metadata *C.Metadata) (_ C.Conn, err error) { func (ss *ShadowSocks) DialContext(ctx context.Context, metadata *C.Metadata) (_ C.Conn, err error) {
return ss.DialContextWithDialer(ctx, dialer.NewDialer(ss.DialOptions()...), metadata)
}
// DialContextWithDialer implements C.ProxyAdapter
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
}
}
var c net.Conn var c net.Conn
if ss.kcptunClient != nil { if ss.kcptunClient != nil {
c, err = ss.kcptunClient.OpenStream(ctx, func(ctx context.Context) (net.PacketConn, net.Addr, error) { c, err = ss.kcptunClient.OpenStream(ctx, func(ctx context.Context) (net.PacketConn, net.Addr, error) {
@@ -213,7 +200,7 @@ func (ss *ShadowSocks) DialContextWithDialer(ctx context.Context, dialer C.Diale
return nil, nil, err return nil, nil, err
} }
pc, err := dialer.ListenPacket(ctx, "udp", "", addr.AddrPort()) pc, err := ss.dialer.ListenPacket(ctx, "udp", "", addr.AddrPort())
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
@@ -221,7 +208,7 @@ func (ss *ShadowSocks) DialContextWithDialer(ctx context.Context, dialer C.Diale
return pc, addr, nil return pc, addr, nil
}) })
} else { } else {
c, err = dialer.DialContext(ctx, "tcp", ss.addr) c, err = ss.dialer.DialContext(ctx, "tcp", ss.addr)
} }
if err != nil { if err != nil {
return nil, fmt.Errorf("%s connect error: %w", ss.addr, err) return nil, fmt.Errorf("%s connect error: %w", ss.addr, err)
@@ -237,25 +224,14 @@ func (ss *ShadowSocks) DialContextWithDialer(ctx context.Context, dialer C.Diale
// ListenPacketContext implements C.ProxyAdapter // ListenPacketContext implements C.ProxyAdapter
func (ss *ShadowSocks) ListenPacketContext(ctx context.Context, metadata *C.Metadata) (C.PacketConn, error) { func (ss *ShadowSocks) ListenPacketContext(ctx context.Context, metadata *C.Metadata) (C.PacketConn, error) {
return ss.ListenPacketWithDialer(ctx, dialer.NewDialer(ss.DialOptions()...), metadata)
}
// ListenPacketWithDialer implements C.ProxyAdapter
func (ss *ShadowSocks) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, metadata *C.Metadata) (_ C.PacketConn, err error) {
if ss.option.UDPOverTCP { if ss.option.UDPOverTCP {
tcpConn, err := ss.DialContextWithDialer(ctx, dialer, metadata) tcpConn, err := ss.DialContext(ctx, metadata)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return ss.ListenPacketOnStreamConn(ctx, tcpConn, metadata) return ss.ListenPacketOnStreamConn(ctx, tcpConn, metadata)
} }
if len(ss.option.DialerProxy) > 0 { if err := ss.ResolveUDP(ctx, metadata); err != nil {
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 return nil, err
} }
addr, err := resolveUDPAddr(ctx, "udp", ss.addr, ss.prefer) addr, err := resolveUDPAddr(ctx, "udp", ss.addr, ss.prefer)
@@ -263,7 +239,7 @@ func (ss *ShadowSocks) ListenPacketWithDialer(ctx context.Context, dialer C.Dial
return nil, err return nil, err
} }
pc, err := dialer.ListenPacket(ctx, "udp", "", addr.AddrPort()) pc, err := ss.dialer.ListenPacket(ctx, "udp", "", addr.AddrPort())
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -271,11 +247,6 @@ func (ss *ShadowSocks) ListenPacketWithDialer(ctx context.Context, dialer C.Dial
return newPacketConn(pc, ss), nil return newPacketConn(pc, ss), nil
} }
// SupportWithDialer implements C.ProxyAdapter
func (ss *ShadowSocks) SupportWithDialer() C.NetWork {
return C.ALLNet
}
// ProxyInfo implements C.ProxyAdapter // ProxyInfo implements C.ProxyAdapter
func (ss *ShadowSocks) ProxyInfo() C.ProxyInfo { func (ss *ShadowSocks) ProxyInfo() C.ProxyInfo {
info := ss.Base.ProxyInfo() info := ss.Base.ProxyInfo()
@@ -482,17 +453,18 @@ func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) {
return nil, fmt.Errorf("ss %s unknown udp over tcp protocol version: %d", addr, option.UDPOverTCPVersion) return nil, fmt.Errorf("ss %s unknown udp over tcp protocol version: %d", addr, option.UDPOverTCPVersion)
} }
return &ShadowSocks{ outbound := &ShadowSocks{
Base: &Base{ Base: &Base{
name: option.Name, name: option.Name,
addr: addr, addr: addr,
tp: C.Shadowsocks, tp: C.Shadowsocks,
pdName: option.ProviderName,
udp: option.UDP, udp: option.UDP,
tfo: option.TFO, tfo: option.TFO,
mpTcp: option.MPTCP, mpTcp: option.MPTCP,
iface: option.Interface, iface: option.Interface,
rmark: option.RoutingMark, rmark: option.RoutingMark,
prefer: C.NewDNSPrefer(option.IPVersion), prefer: option.IPVersion,
}, },
method: method, method: method,
@@ -504,5 +476,7 @@ func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) {
shadowTLSOption: shadowTLSOpt, shadowTLSOption: shadowTLSOpt,
restlsConfig: restlsConfig, restlsConfig: restlsConfig,
kcptunClient: kcptunClient, kcptunClient: kcptunClient,
}, nil }
outbound.dialer = option.NewDialer(outbound.DialOptions())
return outbound, nil
} }

View File

@@ -8,8 +8,6 @@ import (
"strconv" "strconv"
N "github.com/metacubex/mihomo/common/net" 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" C "github.com/metacubex/mihomo/constant"
"github.com/metacubex/mihomo/transport/shadowsocks/core" "github.com/metacubex/mihomo/transport/shadowsocks/core"
"github.com/metacubex/mihomo/transport/shadowsocks/shadowaead" "github.com/metacubex/mihomo/transport/shadowsocks/shadowaead"
@@ -68,18 +66,7 @@ func (ssr *ShadowSocksR) StreamConnContext(ctx context.Context, c net.Conn, meta
// DialContext implements C.ProxyAdapter // DialContext implements C.ProxyAdapter
func (ssr *ShadowSocksR) DialContext(ctx context.Context, metadata *C.Metadata) (_ C.Conn, err error) { func (ssr *ShadowSocksR) DialContext(ctx context.Context, metadata *C.Metadata) (_ C.Conn, err error) {
return ssr.DialContextWithDialer(ctx, dialer.NewDialer(ssr.DialOptions()...), metadata) c, err := ssr.dialer.DialContext(ctx, "tcp", ssr.addr)
}
// 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)
if err != nil { if err != nil {
return nil, fmt.Errorf("%s connect error: %w", ssr.addr, err) return nil, fmt.Errorf("%s connect error: %w", ssr.addr, err)
} }
@@ -94,18 +81,7 @@ func (ssr *ShadowSocksR) DialContextWithDialer(ctx context.Context, dialer C.Dia
// ListenPacketContext implements C.ProxyAdapter // ListenPacketContext implements C.ProxyAdapter
func (ssr *ShadowSocksR) ListenPacketContext(ctx context.Context, metadata *C.Metadata) (C.PacketConn, error) { func (ssr *ShadowSocksR) ListenPacketContext(ctx context.Context, metadata *C.Metadata) (C.PacketConn, error) {
return ssr.ListenPacketWithDialer(ctx, dialer.NewDialer(ssr.DialOptions()...), metadata) if err := ssr.ResolveUDP(ctx, metadata); err != nil {
}
// 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
}
}
if err = ssr.ResolveUDP(ctx, metadata); err != nil {
return nil, err return nil, err
} }
addr, err := resolveUDPAddr(ctx, "udp", ssr.addr, ssr.prefer) addr, err := resolveUDPAddr(ctx, "udp", ssr.addr, ssr.prefer)
@@ -113,7 +89,7 @@ func (ssr *ShadowSocksR) ListenPacketWithDialer(ctx context.Context, dialer C.Di
return nil, err return nil, err
} }
pc, err := dialer.ListenPacket(ctx, "udp", "", addr.AddrPort()) pc, err := ssr.dialer.ListenPacket(ctx, "udp", "", addr.AddrPort())
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -123,11 +99,6 @@ func (ssr *ShadowSocksR) ListenPacketWithDialer(ctx context.Context, dialer C.Di
return newPacketConn(&ssrPacketConn{EnhancePacketConn: epc, rAddr: addr}, ssr), nil 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 // ProxyInfo implements C.ProxyAdapter
func (ssr *ShadowSocksR) ProxyInfo() C.ProxyInfo { func (ssr *ShadowSocksR) ProxyInfo() C.ProxyInfo {
info := ssr.Base.ProxyInfo() info := ssr.Base.ProxyInfo()
@@ -186,23 +157,26 @@ func NewShadowSocksR(option ShadowSocksROption) (*ShadowSocksR, error) {
return nil, fmt.Errorf("ssr %s initialize protocol error: %w", addr, err) return nil, fmt.Errorf("ssr %s initialize protocol error: %w", addr, err)
} }
return &ShadowSocksR{ outbound := &ShadowSocksR{
Base: &Base{ Base: &Base{
name: option.Name, name: option.Name,
addr: addr, addr: addr,
tp: C.ShadowsocksR, tp: C.ShadowsocksR,
pdName: option.ProviderName,
udp: option.UDP, udp: option.UDP,
tfo: option.TFO, tfo: option.TFO,
mpTcp: option.MPTCP, mpTcp: option.MPTCP,
iface: option.Interface, iface: option.Interface,
rmark: option.RoutingMark, rmark: option.RoutingMark,
prefer: C.NewDNSPrefer(option.IPVersion), prefer: option.IPVersion,
}, },
option: &option, option: &option,
cipher: coreCiph, cipher: coreCiph,
obfs: obfs, obfs: obfs,
protocol: protocol, protocol: protocol,
}, nil }
outbound.dialer = option.NewDialer(outbound.DialOptions())
return outbound, nil
} }
type ssrPacketConn struct { type ssrPacketConn struct {

View File

@@ -3,8 +3,7 @@ package outbound
import ( import (
"context" "context"
CN "github.com/metacubex/mihomo/common/net" N "github.com/metacubex/mihomo/common/net"
"github.com/metacubex/mihomo/component/dialer"
"github.com/metacubex/mihomo/component/proxydialer" "github.com/metacubex/mihomo/component/proxydialer"
C "github.com/metacubex/mihomo/constant" C "github.com/metacubex/mihomo/constant"
"github.com/metacubex/mihomo/log" "github.com/metacubex/mihomo/log"
@@ -17,7 +16,6 @@ import (
type SingMux struct { type SingMux struct {
ProxyAdapter ProxyAdapter
client *mux.Client client *mux.Client
dialer proxydialer.SingDialer
onlyTcp bool onlyTcp bool
} }
@@ -61,7 +59,7 @@ func (s *SingMux) ListenPacketContext(ctx context.Context, metadata *C.Metadata)
if pc == nil { if pc == nil {
return nil, E.New("packetConn is 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 { func (s *SingMux) SupportUDP() bool {
@@ -96,7 +94,7 @@ func NewSingMux(option SingMuxOption, proxy ProxyAdapter) (ProxyAdapter, error)
// TODO // TODO
// "TCP Brutal is only supported on Linux-based systems" // "TCP Brutal is only supported on Linux-based systems"
singDialer := proxydialer.NewSingDialer(proxy, dialer.NewDialer(proxy.DialOptions()...), option.Statistic) singDialer := proxydialer.NewSingDialer(proxydialer.New(proxy, option.Statistic))
client, err := mux.NewClient(mux.Options{ client, err := mux.NewClient(mux.Options{
Dialer: singDialer, Dialer: singDialer,
Logger: log.SingLogger, Logger: log.SingLogger,
@@ -117,7 +115,6 @@ func NewSingMux(option SingMuxOption, proxy ProxyAdapter) (ProxyAdapter, error)
outbound := &SingMux{ outbound := &SingMux{
ProxyAdapter: proxy, ProxyAdapter: proxy,
client: client, client: client,
dialer: singDialer,
onlyTcp: option.OnlyTcp, onlyTcp: option.OnlyTcp,
} }
return outbound, nil return outbound, nil

View File

@@ -8,8 +8,6 @@ import (
N "github.com/metacubex/mihomo/common/net" N "github.com/metacubex/mihomo/common/net"
"github.com/metacubex/mihomo/common/structure" "github.com/metacubex/mihomo/common/structure"
"github.com/metacubex/mihomo/component/dialer"
"github.com/metacubex/mihomo/component/proxydialer"
C "github.com/metacubex/mihomo/constant" C "github.com/metacubex/mihomo/constant"
obfs "github.com/metacubex/mihomo/transport/simple-obfs" obfs "github.com/metacubex/mihomo/transport/simple-obfs"
"github.com/metacubex/mihomo/transport/snell" "github.com/metacubex/mihomo/transport/snell"
@@ -89,18 +87,7 @@ func (s *Snell) DialContext(ctx context.Context, metadata *C.Metadata) (_ C.Conn
return NewConn(c, s), err return NewConn(c, s), err
} }
return s.DialContextWithDialer(ctx, dialer.NewDialer(s.DialOptions()...), metadata) c, err := s.dialer.DialContext(ctx, "tcp", s.addr)
}
// 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)
if err != nil { if err != nil {
return nil, fmt.Errorf("%s connect error: %w", s.addr, err) return nil, fmt.Errorf("%s connect error: %w", s.addr, err)
} }
@@ -115,22 +102,11 @@ func (s *Snell) DialContextWithDialer(ctx context.Context, dialer C.Dialer, meta
// ListenPacketContext implements C.ProxyAdapter // ListenPacketContext implements C.ProxyAdapter
func (s *Snell) ListenPacketContext(ctx context.Context, metadata *C.Metadata) (C.PacketConn, error) { func (s *Snell) ListenPacketContext(ctx context.Context, metadata *C.Metadata) (C.PacketConn, error) {
return s.ListenPacketWithDialer(ctx, dialer.NewDialer(s.DialOptions()...), metadata)
}
// ListenPacketWithDialer implements C.ProxyAdapter
func (s *Snell) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, metadata *C.Metadata) (C.PacketConn, error) {
var err 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 { if err = s.ResolveUDP(ctx, metadata); err != nil {
return nil, err return nil, err
} }
c, err := dialer.DialContext(ctx, "tcp", s.addr) c, err := s.dialer.DialContext(ctx, "tcp", s.addr)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -141,11 +117,6 @@ func (s *Snell) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, met
return newPacketConn(pc, s), nil return newPacketConn(pc, s), nil
} }
// SupportWithDialer implements C.ProxyAdapter
func (s *Snell) SupportWithDialer() C.NetWork {
return C.ALLNet
}
// SupportUOT implements C.ProxyAdapter // SupportUOT implements C.ProxyAdapter
func (s *Snell) SupportUOT() bool { func (s *Snell) SupportUOT() bool {
return true return true
@@ -194,30 +165,24 @@ func NewSnell(option SnellOption) (*Snell, error) {
name: option.Name, name: option.Name,
addr: addr, addr: addr,
tp: C.Snell, tp: C.Snell,
pdName: option.ProviderName,
udp: option.UDP, udp: option.UDP,
tfo: option.TFO, tfo: option.TFO,
mpTcp: option.MPTCP, mpTcp: option.MPTCP,
iface: option.Interface, iface: option.Interface,
rmark: option.RoutingMark, rmark: option.RoutingMark,
prefer: C.NewDNSPrefer(option.IPVersion), prefer: option.IPVersion,
}, },
option: &option, option: &option,
psk: psk, psk: psk,
obfsOption: obfsOption, obfsOption: obfsOption,
version: option.Version, version: option.Version,
} }
s.dialer = option.NewDialer(s.DialOptions())
if option.Version == snell.Version2 { if option.Version == snell.Version2 {
s.pool = snell.NewPool(func(ctx context.Context) (*snell.Snell, error) { s.pool = snell.NewPool(func(ctx context.Context) (*snell.Snell, error) {
var err error c, err := s.dialer.DialContext(ctx, "tcp", addr)
var cDialer C.Dialer = dialer.NewDialer(s.DialOptions()...)
if len(s.option.DialerProxy) > 0 {
cDialer, err = proxydialer.NewByName(s.option.DialerProxy, cDialer)
if err != nil {
return nil, err
}
}
c, err := cDialer.DialContext(ctx, "tcp", addr)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@@ -2,7 +2,6 @@ package outbound
import ( import (
"context" "context"
"crypto/tls"
"errors" "errors"
"fmt" "fmt"
"io" "io"
@@ -12,10 +11,10 @@ import (
N "github.com/metacubex/mihomo/common/net" N "github.com/metacubex/mihomo/common/net"
"github.com/metacubex/mihomo/component/ca" "github.com/metacubex/mihomo/component/ca"
"github.com/metacubex/mihomo/component/dialer"
"github.com/metacubex/mihomo/component/proxydialer"
C "github.com/metacubex/mihomo/constant" C "github.com/metacubex/mihomo/constant"
"github.com/metacubex/mihomo/transport/socks5" "github.com/metacubex/mihomo/transport/socks5"
"github.com/metacubex/tls"
) )
type Socks5 struct { type Socks5 struct {
@@ -69,18 +68,7 @@ func (ss *Socks5) StreamConnContext(ctx context.Context, c net.Conn, metadata *C
// DialContext implements C.ProxyAdapter // DialContext implements C.ProxyAdapter
func (ss *Socks5) DialContext(ctx context.Context, metadata *C.Metadata) (_ C.Conn, err error) { func (ss *Socks5) DialContext(ctx context.Context, metadata *C.Metadata) (_ C.Conn, err error) {
return ss.DialContextWithDialer(ctx, dialer.NewDialer(ss.DialOptions()...), metadata) c, err := ss.dialer.DialContext(ctx, "tcp", ss.addr)
}
// 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)
if err != nil { if err != nil {
return nil, fmt.Errorf("%s connect error: %w", ss.addr, err) return nil, fmt.Errorf("%s connect error: %w", ss.addr, err)
} }
@@ -97,24 +85,12 @@ func (ss *Socks5) DialContextWithDialer(ctx context.Context, dialer C.Dialer, me
return NewConn(c, ss), nil return NewConn(c, ss), nil
} }
// SupportWithDialer implements C.ProxyAdapter
func (ss *Socks5) SupportWithDialer() C.NetWork {
return C.TCP
}
// ListenPacketContext implements C.ProxyAdapter // ListenPacketContext implements C.ProxyAdapter
func (ss *Socks5) ListenPacketContext(ctx context.Context, metadata *C.Metadata) (_ C.PacketConn, err error) { func (ss *Socks5) ListenPacketContext(ctx context.Context, metadata *C.Metadata) (_ C.PacketConn, err error) {
var cDialer C.Dialer = dialer.NewDialer(ss.DialOptions()...)
if len(ss.option.DialerProxy) > 0 {
cDialer, err = proxydialer.NewByName(ss.option.DialerProxy, cDialer)
if err != nil {
return nil, err
}
}
if err = ss.ResolveUDP(ctx, metadata); err != nil { if err = ss.ResolveUDP(ctx, metadata); err != nil {
return nil, err return nil, err
} }
c, err := cDialer.DialContext(ctx, "tcp", ss.addr) c, err := ss.dialer.DialContext(ctx, "tcp", ss.addr)
if err != nil { if err != nil {
err = fmt.Errorf("%s connect error: %w", ss.addr, err) err = fmt.Errorf("%s connect error: %w", ss.addr, err)
return return
@@ -161,7 +137,7 @@ func (ss *Socks5) ListenPacketContext(ctx context.Context, metadata *C.Metadata)
bindUDPAddr.IP = serverAddr.IP bindUDPAddr.IP = serverAddr.IP
} }
pc, err := cDialer.ListenPacket(ctx, "udp", "", bindUDPAddr.AddrPort()) pc, err := ss.dialer.ListenPacket(ctx, "udp", "", bindUDPAddr.AddrPort())
if err != nil { if err != nil {
return return
} }
@@ -210,17 +186,18 @@ func NewSocks5(option Socks5Option) (*Socks5, error) {
} }
} }
return &Socks5{ outbound := &Socks5{
Base: &Base{ Base: &Base{
name: option.Name, name: option.Name,
addr: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)), addr: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)),
tp: C.Socks5, tp: C.Socks5,
pdName: option.ProviderName,
udp: option.UDP, udp: option.UDP,
tfo: option.TFO, tfo: option.TFO,
mpTcp: option.MPTCP, mpTcp: option.MPTCP,
iface: option.Interface, iface: option.Interface,
rmark: option.RoutingMark, rmark: option.RoutingMark,
prefer: C.NewDNSPrefer(option.IPVersion), prefer: option.IPVersion,
}, },
option: &option, option: &option,
user: option.UserName, user: option.UserName,
@@ -228,7 +205,9 @@ func NewSocks5(option Socks5Option) (*Socks5, error) {
tls: option.TLS, tls: option.TLS,
skipCertVerify: option.SkipCertVerify, skipCertVerify: option.SkipCertVerify,
tlsConfig: tlsConfig, tlsConfig: tlsConfig,
}, nil }
outbound.dialer = option.NewDialer(outbound.DialOptions())
return outbound, nil
} }
type socksPacketConn struct { type socksPacketConn struct {

View File

@@ -12,8 +12,6 @@ import (
"sync" "sync"
N "github.com/metacubex/mihomo/common/net" 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" C "github.com/metacubex/mihomo/constant"
"github.com/metacubex/randv2" "github.com/metacubex/randv2"
@@ -44,14 +42,7 @@ type SshOption struct {
} }
func (s *Ssh) DialContext(ctx context.Context, metadata *C.Metadata) (_ C.Conn, err error) { func (s *Ssh) DialContext(ctx context.Context, metadata *C.Metadata) (_ C.Conn, err error) {
var cDialer C.Dialer = dialer.NewDialer(s.DialOptions()...) client, err := s.connect(ctx, s.addr)
if len(s.option.DialerProxy) > 0 {
cDialer, err = proxydialer.NewByName(s.option.DialerProxy, cDialer)
if err != nil {
return nil, err
}
}
client, err := s.connect(ctx, cDialer, s.addr)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -63,13 +54,13 @@ func (s *Ssh) DialContext(ctx context.Context, metadata *C.Metadata) (_ C.Conn,
return NewConn(c, s), nil 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() s.cMutex.Lock()
defer s.cMutex.Unlock() defer s.cMutex.Unlock()
if s.client != nil { if s.client != nil {
return 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 { if err != nil {
return nil, err return nil, err
} }
@@ -195,14 +186,15 @@ func NewSsh(option SshOption) (*Ssh, error) {
name: option.Name, name: option.Name,
addr: addr, addr: addr,
tp: C.Ssh, tp: C.Ssh,
pdName: option.ProviderName,
udp: false, udp: false,
iface: option.Interface, iface: option.Interface,
rmark: option.RoutingMark, rmark: option.RoutingMark,
prefer: C.NewDNSPrefer(option.IPVersion), prefer: option.IPVersion,
}, },
option: &option, option: &option,
config: &config, config: &config,
} }
outbound.dialer = option.NewDialer(outbound.DialOptions())
return outbound, nil return outbound, nil
} }

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

@@ -0,0 +1,262 @@
package outbound
import (
"context"
"fmt"
"net"
"strconv"
"strings"
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
}
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)
HTTPMaskStrategy string `proxy:"http-mask-strategy,omitempty"` // "random" (default), "post", "websocket"
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
}
var c net.Conn
if !cfg.DisableHTTPMask {
switch strings.ToLower(strings.TrimSpace(cfg.HTTPMaskMode)) {
case "stream", "poll", "auto":
c, err = sudoku.DialHTTPMaskTunnel(ctx, cfg.ServerAddress, cfg, s.dialer.DialContext)
}
}
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)
}
handshakeCfg := *cfg
if !handshakeCfg.DisableHTTPMask {
switch strings.ToLower(strings.TrimSpace(handshakeCfg.HTTPMaskMode)) {
case "stream", "poll", "auto":
handshakeCfg.DisableHTTPMask = true
}
}
c, err = sudoku.ClientHandshakeWithOptions(c, &handshakeCfg, sudoku.ClientHandshakeOptions{HTTPMaskStrategy: s.option.HTTPMaskStrategy})
if err != nil {
return nil, 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 {
_ = c.Close()
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
}
var c net.Conn
if !cfg.DisableHTTPMask {
switch strings.ToLower(strings.TrimSpace(cfg.HTTPMaskMode)) {
case "stream", "poll", "auto":
c, err = sudoku.DialHTTPMaskTunnel(ctx, cfg.ServerAddress, cfg, s.dialer.DialContext)
}
}
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)
}
handshakeCfg := *cfg
if !handshakeCfg.DisableHTTPMask {
switch strings.ToLower(strings.TrimSpace(handshakeCfg.HTTPMaskMode)) {
case "stream", "poll", "auto":
handshakeCfg.DisableHTTPMask = true
}
}
c, err = sudoku.ClientHandshakeWithOptions(c, &handshakeCfg, sudoku.ClientHandshakeOptions{HTTPMaskStrategy: s.option.HTTPMaskStrategy})
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,
}
if option.HTTPMaskMode != "" {
baseConf.HTTPMaskMode = option.HTTPMaskMode
}
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
}

View File

@@ -2,24 +2,23 @@ package outbound
import ( import (
"context" "context"
"crypto/tls"
"errors" "errors"
"fmt" "fmt"
"net" "net"
"net/http"
"strconv" "strconv"
N "github.com/metacubex/mihomo/common/net" N "github.com/metacubex/mihomo/common/net"
"github.com/metacubex/mihomo/component/ca" "github.com/metacubex/mihomo/component/ca"
"github.com/metacubex/mihomo/component/dialer"
"github.com/metacubex/mihomo/component/ech" "github.com/metacubex/mihomo/component/ech"
"github.com/metacubex/mihomo/component/proxydialer"
tlsC "github.com/metacubex/mihomo/component/tls" tlsC "github.com/metacubex/mihomo/component/tls"
C "github.com/metacubex/mihomo/constant" C "github.com/metacubex/mihomo/constant"
"github.com/metacubex/mihomo/transport/gun" "github.com/metacubex/mihomo/transport/gun"
"github.com/metacubex/mihomo/transport/shadowsocks/core" "github.com/metacubex/mihomo/transport/shadowsocks/core"
"github.com/metacubex/mihomo/transport/trojan" "github.com/metacubex/mihomo/transport/trojan"
"github.com/metacubex/mihomo/transport/vmess" "github.com/metacubex/mihomo/transport/vmess"
"github.com/metacubex/http"
"github.com/metacubex/tls"
) )
type Trojan struct { type Trojan struct {
@@ -196,18 +195,7 @@ func (t *Trojan) DialContext(ctx context.Context, metadata *C.Metadata) (_ C.Con
return NewConn(c, t), nil return NewConn(c, t), nil
} }
return t.DialContextWithDialer(ctx, dialer.NewDialer(t.DialOptions()...), metadata) c, err = t.dialer.DialContext(ctx, "tcp", t.addr)
}
// 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)
if err != nil { if err != nil {
return nil, fmt.Errorf("%s connect error: %w", t.addr, err) return nil, fmt.Errorf("%s connect error: %w", t.addr, err)
} }
@@ -250,21 +238,10 @@ func (t *Trojan) ListenPacketContext(ctx context.Context, metadata *C.Metadata)
pc := trojan.NewPacketConn(c) pc := trojan.NewPacketConn(c)
return newPacketConn(pc, t), err return newPacketConn(pc, t), err
} }
return t.ListenPacketWithDialer(ctx, dialer.NewDialer(t.DialOptions()...), 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 { if err = t.ResolveUDP(ctx, metadata); err != nil {
return nil, err return nil, err
} }
c, err := dialer.DialContext(ctx, "tcp", t.addr) c, err = t.dialer.DialContext(ctx, "tcp", t.addr)
if err != nil { if err != nil {
return nil, fmt.Errorf("%s connect error: %w", t.addr, err) return nil, fmt.Errorf("%s connect error: %w", t.addr, err)
} }
@@ -280,11 +257,6 @@ func (t *Trojan) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, me
return newPacketConn(pc, t), err return newPacketConn(pc, t), err
} }
// SupportWithDialer implements C.ProxyAdapter
func (t *Trojan) SupportWithDialer() C.NetWork {
return C.ALLNet
}
// SupportUOT implements C.ProxyAdapter // SupportUOT implements C.ProxyAdapter
func (t *Trojan) SupportUOT() bool { func (t *Trojan) SupportUOT() bool {
return true return true
@@ -317,16 +289,18 @@ func NewTrojan(option TrojanOption) (*Trojan, error) {
name: option.Name, name: option.Name,
addr: addr, addr: addr,
tp: C.Trojan, tp: C.Trojan,
pdName: option.ProviderName,
udp: option.UDP, udp: option.UDP,
tfo: option.TFO, tfo: option.TFO,
mpTcp: option.MPTCP, mpTcp: option.MPTCP,
iface: option.Interface, iface: option.Interface,
rmark: option.RoutingMark, rmark: option.RoutingMark,
prefer: C.NewDNSPrefer(option.IPVersion), prefer: option.IPVersion,
}, },
option: &option, option: &option,
hexPassword: trojan.Key(option.Password), hexPassword: trojan.Key(option.Password),
} }
t.dialer = option.NewDialer(t.DialOptions())
var err error var err error
t.realityConfig, err = option.RealityOpts.Parse() t.realityConfig, err = option.RealityOpts.Parse()
@@ -355,15 +329,7 @@ func NewTrojan(option TrojanOption) (*Trojan, error) {
if option.Network == "grpc" { if option.Network == "grpc" {
dialFn := func(ctx context.Context, network, addr string) (net.Conn, error) { dialFn := func(ctx context.Context, network, addr string) (net.Conn, error) {
var err error c, err := t.dialer.DialContext(ctx, "tcp", t.addr)
var cDialer C.Dialer = dialer.NewDialer(t.DialOptions()...)
if len(t.option.DialerProxy) > 0 {
cDialer, err = proxydialer.NewByName(t.option.DialerProxy, cDialer)
if err != nil {
return nil, err
}
}
c, err := cDialer.DialContext(ctx, "tcp", t.addr)
if err != nil { if err != nil {
return nil, fmt.Errorf("%s connect error: %s", t.addr, err.Error()) return nil, fmt.Errorf("%s connect error: %s", t.addr, err.Error())
} }

View File

@@ -2,7 +2,6 @@ package outbound
import ( import (
"context" "context"
"crypto/tls"
"fmt" "fmt"
"math" "math"
"net" "net"
@@ -10,10 +9,7 @@ import (
"time" "time"
"github.com/metacubex/mihomo/component/ca" "github.com/metacubex/mihomo/component/ca"
"github.com/metacubex/mihomo/component/dialer"
"github.com/metacubex/mihomo/component/ech" "github.com/metacubex/mihomo/component/ech"
"github.com/metacubex/mihomo/component/proxydialer"
tlsC "github.com/metacubex/mihomo/component/tls"
C "github.com/metacubex/mihomo/constant" C "github.com/metacubex/mihomo/constant"
"github.com/metacubex/mihomo/transport/tuic" "github.com/metacubex/mihomo/transport/tuic"
@@ -21,6 +17,7 @@ import (
"github.com/metacubex/quic-go" "github.com/metacubex/quic-go"
M "github.com/metacubex/sing/common/metadata" M "github.com/metacubex/sing/common/metadata"
"github.com/metacubex/sing/common/uot" "github.com/metacubex/sing/common/uot"
"github.com/metacubex/tls"
) )
type Tuic struct { type Tuic struct {
@@ -28,7 +25,7 @@ type Tuic struct {
option *TuicOption option *TuicOption
client *tuic.PoolClient client *tuic.PoolClient
tlsConfig *tlsC.Config tlsConfig *tls.Config
echConfig *ech.Config echConfig *ech.Config
} }
@@ -70,12 +67,7 @@ type TuicOption struct {
// DialContext implements C.ProxyAdapter // DialContext implements C.ProxyAdapter
func (t *Tuic) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) { func (t *Tuic) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
return t.DialContextWithDialer(ctx, dialer.NewDialer(t.DialOptions()...), metadata) conn, err := t.client.DialContext(ctx, 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)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -84,11 +76,6 @@ func (t *Tuic) DialContextWithDialer(ctx context.Context, dialer C.Dialer, metad
// ListenPacketContext implements C.ProxyAdapter // ListenPacketContext implements C.ProxyAdapter
func (t *Tuic) ListenPacketContext(ctx context.Context, metadata *C.Metadata) (_ C.PacketConn, err error) { func (t *Tuic) ListenPacketContext(ctx context.Context, metadata *C.Metadata) (_ C.PacketConn, err error) {
return t.ListenPacketWithDialer(ctx, dialer.NewDialer(t.DialOptions()...), metadata)
}
// ListenPacketWithDialer implements C.ProxyAdapter
func (t *Tuic) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, metadata *C.Metadata) (_ C.PacketConn, err error) {
if err = t.ResolveUDP(ctx, metadata); err != nil { if err = t.ResolveUDP(ctx, metadata); err != nil {
return nil, err return nil, err
} }
@@ -98,7 +85,7 @@ func (t *Tuic) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, meta
uotMetadata := *metadata uotMetadata := *metadata
uotMetadata.Host = uotDestination.Fqdn uotMetadata.Host = uotDestination.Fqdn
uotMetadata.DstPort = uotDestination.Port uotMetadata.DstPort = uotDestination.Port
c, err := t.DialContextWithDialer(ctx, dialer, &uotMetadata) c, err := t.DialContext(ctx, &uotMetadata)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -112,25 +99,14 @@ func (t *Tuic) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, meta
return newPacketConn(uot.NewLazyConn(c, uot.Request{Destination: destination}), t), nil 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 { if err != nil {
return nil, err return nil, err
} }
return newPacketConn(pc, t), nil return newPacketConn(pc, t), nil
} }
// SupportWithDialer implements C.ProxyAdapter func (t *Tuic) dial(ctx context.Context) (transport *quic.Transport, addr net.Addr, err error) {
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
}
}
udpAddr, err := resolveUDPAddr(ctx, "udp", t.addr, t.prefer) udpAddr, err := resolveUDPAddr(ctx, "udp", t.addr, t.prefer)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
@@ -141,7 +117,7 @@ func (t *Tuic) dialWithDialer(ctx context.Context, dialer C.Dialer) (transport *
} }
addr = udpAddr addr = udpAddr
var pc net.PacketConn var pc net.PacketConn
pc, err = dialer.ListenPacket(ctx, "udp", "", udpAddr.AddrPort()) pc, err = t.dialer.ListenPacket(ctx, "udp", "", udpAddr.AddrPort())
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
@@ -256,7 +232,7 @@ func NewTuic(option TuicOption) (*Tuic, error) {
tlsConfig.InsecureSkipVerify = true // tls: either ServerName or InsecureSkipVerify must be specified in the tls.Config tlsConfig.InsecureSkipVerify = true // tls: either ServerName or InsecureSkipVerify must be specified in the tls.Config
} }
tlsClientConfig := tlsC.UConfig(tlsConfig) tlsClientConfig := tlsConfig
echConfig, err := option.ECHOpts.Parse() echConfig, err := option.ECHOpts.Parse()
if err != nil { if err != nil {
return nil, err return nil, err
@@ -275,16 +251,18 @@ func NewTuic(option TuicOption) (*Tuic, error) {
name: option.Name, name: option.Name,
addr: addr, addr: addr,
tp: C.Tuic, tp: C.Tuic,
pdName: option.ProviderName,
udp: true, udp: true,
tfo: option.FastOpen, tfo: option.FastOpen,
iface: option.Interface, iface: option.Interface,
rmark: option.RoutingMark, rmark: option.RoutingMark,
prefer: C.NewDNSPrefer(option.IPVersion), prefer: option.IPVersion,
}, },
option: &option, option: &option,
tlsConfig: tlsClientConfig, tlsConfig: tlsClientConfig,
echConfig: echConfig, echConfig: echConfig,
} }
t.dialer = option.NewDialer(t.DialOptions())
clientMaxOpenStreams := int64(option.MaxOpenStreams) clientMaxOpenStreams := int64(option.MaxOpenStreams)
@@ -313,7 +291,7 @@ func NewTuic(option TuicOption) (*Tuic, error) {
CWND: option.CWND, CWND: option.CWND,
} }
t.client = tuic.NewPoolClientV4(clientOption) t.client = tuic.NewPoolClientV4(clientOption, t.dial)
} else { } else {
maxUdpRelayPacketSize := option.MaxUdpRelayPacketSize maxUdpRelayPacketSize := option.MaxUdpRelayPacketSize
if maxUdpRelayPacketSize > tuic.MaxFragSizeV5 { if maxUdpRelayPacketSize > tuic.MaxFragSizeV5 {
@@ -332,7 +310,7 @@ func NewTuic(option TuicOption) (*Tuic, error) {
CWND: option.CWND, CWND: option.CWND,
} }
t.client = tuic.NewPoolClientV5(clientOption) t.client = tuic.NewPoolClientV5(clientOption, t.dial)
} }
return t, nil return t, nil

View File

@@ -2,19 +2,15 @@ package outbound
import ( import (
"context" "context"
"crypto/tls"
"fmt" "fmt"
"net" "net"
"net/http"
"strconv" "strconv"
"github.com/metacubex/mihomo/common/convert" "github.com/metacubex/mihomo/common/convert"
N "github.com/metacubex/mihomo/common/net" N "github.com/metacubex/mihomo/common/net"
"github.com/metacubex/mihomo/common/utils" "github.com/metacubex/mihomo/common/utils"
"github.com/metacubex/mihomo/component/ca" "github.com/metacubex/mihomo/component/ca"
"github.com/metacubex/mihomo/component/dialer"
"github.com/metacubex/mihomo/component/ech" "github.com/metacubex/mihomo/component/ech"
"github.com/metacubex/mihomo/component/proxydialer"
tlsC "github.com/metacubex/mihomo/component/tls" tlsC "github.com/metacubex/mihomo/component/tls"
C "github.com/metacubex/mihomo/constant" C "github.com/metacubex/mihomo/constant"
"github.com/metacubex/mihomo/transport/gun" "github.com/metacubex/mihomo/transport/gun"
@@ -22,9 +18,11 @@ import (
"github.com/metacubex/mihomo/transport/vless/encryption" "github.com/metacubex/mihomo/transport/vless/encryption"
"github.com/metacubex/mihomo/transport/vmess" "github.com/metacubex/mihomo/transport/vmess"
"github.com/metacubex/http"
vmessSing "github.com/metacubex/sing-vmess" vmessSing "github.com/metacubex/sing-vmess"
"github.com/metacubex/sing-vmess/packetaddr" "github.com/metacubex/sing-vmess/packetaddr"
M "github.com/metacubex/sing/common/metadata" M "github.com/metacubex/sing/common/metadata"
"github.com/metacubex/tls"
) )
type Vless struct { type Vless struct {
@@ -252,18 +250,7 @@ func (v *Vless) DialContext(ctx context.Context, metadata *C.Metadata) (_ C.Conn
return NewConn(c, v), nil return NewConn(c, v), nil
} }
return v.DialContextWithDialer(ctx, dialer.NewDialer(v.DialOptions()...), metadata) c, err = v.dialer.DialContext(ctx, "tcp", v.addr)
}
// 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)
if err != nil { if err != nil {
return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error()) return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error())
} }
@@ -301,23 +288,12 @@ func (v *Vless) ListenPacketContext(ctx context.Context, metadata *C.Metadata) (
return v.ListenPacketOnStreamConn(ctx, c, metadata) return v.ListenPacketOnStreamConn(ctx, c, metadata)
} }
return v.ListenPacketWithDialer(ctx, dialer.NewDialer(v.DialOptions()...), 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 { if err = v.ResolveUDP(ctx, metadata); err != nil {
return nil, err return nil, err
} }
c, err := dialer.DialContext(ctx, "tcp", v.addr) c, err = v.dialer.DialContext(ctx, "tcp", v.addr)
if err != nil { if err != nil {
return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error()) return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error())
} }
@@ -333,11 +309,6 @@ func (v *Vless) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, met
return v.ListenPacketOnStreamConn(ctx, c, metadata) return v.ListenPacketOnStreamConn(ctx, c, metadata)
} }
// SupportWithDialer implements C.ProxyAdapter
func (v *Vless) SupportWithDialer() C.NetWork {
return C.ALLNet
}
// ListenPacketOnStreamConn implements C.ProxyAdapter // ListenPacketOnStreamConn implements C.ProxyAdapter
func (v *Vless) ListenPacketOnStreamConn(ctx context.Context, c net.Conn, metadata *C.Metadata) (_ C.PacketConn, err error) { func (v *Vless) ListenPacketOnStreamConn(ctx context.Context, c net.Conn, metadata *C.Metadata) (_ C.PacketConn, err error) {
if err = v.ResolveUDP(ctx, metadata); err != nil { if err = v.ResolveUDP(ctx, metadata); err != nil {
@@ -446,17 +417,19 @@ func NewVless(option VlessOption) (*Vless, error) {
name: option.Name, name: option.Name,
addr: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)), addr: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)),
tp: C.Vless, tp: C.Vless,
pdName: option.ProviderName,
udp: option.UDP, udp: option.UDP,
xudp: option.XUDP, xudp: option.XUDP,
tfo: option.TFO, tfo: option.TFO,
mpTcp: option.MPTCP, mpTcp: option.MPTCP,
iface: option.Interface, iface: option.Interface,
rmark: option.RoutingMark, rmark: option.RoutingMark,
prefer: C.NewDNSPrefer(option.IPVersion), prefer: option.IPVersion,
}, },
client: client, client: client,
option: &option, option: &option,
} }
v.dialer = option.NewDialer(v.DialOptions())
v.encryption, err = encryption.NewClient(option.Encryption) v.encryption, err = encryption.NewClient(option.Encryption)
if err != nil { if err != nil {
@@ -480,15 +453,7 @@ func NewVless(option VlessOption) (*Vless, error) {
} }
case "grpc": case "grpc":
dialFn := func(ctx context.Context, network, addr string) (net.Conn, error) { dialFn := func(ctx context.Context, network, addr string) (net.Conn, error) {
var err error c, err := v.dialer.DialContext(ctx, "tcp", v.addr)
var cDialer C.Dialer = dialer.NewDialer(v.DialOptions()...)
if len(v.option.DialerProxy) > 0 {
cDialer, err = proxydialer.NewByName(v.option.DialerProxy, cDialer)
if err != nil {
return nil, err
}
}
c, err := cDialer.DialContext(ctx, "tcp", v.addr)
if err != nil { if err != nil {
return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error()) return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error())
} }

View File

@@ -2,11 +2,9 @@ package outbound
import ( import (
"context" "context"
"crypto/tls"
"errors" "errors"
"fmt" "fmt"
"net" "net"
"net/http"
"strconv" "strconv"
"strings" "strings"
"sync" "sync"
@@ -14,18 +12,18 @@ import (
N "github.com/metacubex/mihomo/common/net" N "github.com/metacubex/mihomo/common/net"
"github.com/metacubex/mihomo/common/utils" "github.com/metacubex/mihomo/common/utils"
"github.com/metacubex/mihomo/component/ca" "github.com/metacubex/mihomo/component/ca"
"github.com/metacubex/mihomo/component/dialer"
"github.com/metacubex/mihomo/component/ech" "github.com/metacubex/mihomo/component/ech"
"github.com/metacubex/mihomo/component/proxydialer"
tlsC "github.com/metacubex/mihomo/component/tls" tlsC "github.com/metacubex/mihomo/component/tls"
C "github.com/metacubex/mihomo/constant" C "github.com/metacubex/mihomo/constant"
"github.com/metacubex/mihomo/ntp" "github.com/metacubex/mihomo/ntp"
"github.com/metacubex/mihomo/transport/gun" "github.com/metacubex/mihomo/transport/gun"
mihomoVMess "github.com/metacubex/mihomo/transport/vmess" mihomoVMess "github.com/metacubex/mihomo/transport/vmess"
"github.com/metacubex/http"
vmess "github.com/metacubex/sing-vmess" vmess "github.com/metacubex/sing-vmess"
"github.com/metacubex/sing-vmess/packetaddr" "github.com/metacubex/sing-vmess/packetaddr"
M "github.com/metacubex/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") var ErrUDPRemoteAddrMismatch = errors.New("udp packet dropped due to mismatched remote address")
@@ -313,18 +311,7 @@ func (v *Vmess) DialContext(ctx context.Context, metadata *C.Metadata) (_ C.Conn
return NewConn(c, v), nil return NewConn(c, v), nil
} }
return v.DialContextWithDialer(ctx, dialer.NewDialer(v.DialOptions()...), metadata) c, err = v.dialer.DialContext(ctx, "tcp", v.addr)
}
// 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)
if err != nil { if err != nil {
return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error()) return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error())
} }
@@ -358,23 +345,12 @@ func (v *Vmess) ListenPacketContext(ctx context.Context, metadata *C.Metadata) (
} }
return v.ListenPacketOnStreamConn(ctx, c, metadata) return v.ListenPacketOnStreamConn(ctx, c, metadata)
} }
return v.ListenPacketWithDialer(ctx, dialer.NewDialer(v.DialOptions()...), 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 { if err = v.ResolveUDP(ctx, metadata); err != nil {
return nil, err return nil, err
} }
c, err := dialer.DialContext(ctx, "tcp", v.addr) c, err = v.dialer.DialContext(ctx, "tcp", v.addr)
if err != nil { if err != nil {
return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error()) return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error())
} }
@@ -389,11 +365,6 @@ func (v *Vmess) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, met
return v.ListenPacketOnStreamConn(ctx, c, metadata) return v.ListenPacketOnStreamConn(ctx, c, metadata)
} }
// SupportWithDialer implements C.ProxyAdapter
func (v *Vmess) SupportWithDialer() C.NetWork {
return C.ALLNet
}
// ProxyInfo implements C.ProxyAdapter // ProxyInfo implements C.ProxyAdapter
func (v *Vmess) ProxyInfo() C.ProxyInfo { func (v *Vmess) ProxyInfo() C.ProxyInfo {
info := v.Base.ProxyInfo() info := v.Base.ProxyInfo()
@@ -456,17 +427,19 @@ func NewVmess(option VmessOption) (*Vmess, error) {
name: option.Name, name: option.Name,
addr: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)), addr: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)),
tp: C.Vmess, tp: C.Vmess,
pdName: option.ProviderName,
udp: option.UDP, udp: option.UDP,
xudp: option.XUDP, xudp: option.XUDP,
tfo: option.TFO, tfo: option.TFO,
mpTcp: option.MPTCP, mpTcp: option.MPTCP,
iface: option.Interface, iface: option.Interface,
rmark: option.RoutingMark, rmark: option.RoutingMark,
prefer: C.NewDNSPrefer(option.IPVersion), prefer: option.IPVersion,
}, },
client: client, client: client,
option: &option, option: &option,
} }
v.dialer = option.NewDialer(v.DialOptions())
v.realityConfig, err = v.option.RealityOpts.Parse() v.realityConfig, err = v.option.RealityOpts.Parse()
if err != nil { if err != nil {
@@ -485,15 +458,7 @@ func NewVmess(option VmessOption) (*Vmess, error) {
} }
case "grpc": case "grpc":
dialFn := func(ctx context.Context, network, addr string) (net.Conn, error) { dialFn := func(ctx context.Context, network, addr string) (net.Conn, error) {
var err error c, err := v.dialer.DialContext(ctx, "tcp", v.addr)
var cDialer C.Dialer = dialer.NewDialer(v.DialOptions()...)
if len(v.option.DialerProxy) > 0 {
cDialer, err = proxydialer.NewByName(v.option.DialerProxy, cDialer)
if err != nil {
return nil, err
}
}
c, err := cDialer.DialContext(ctx, "tcp", v.addr)
if err != nil { if err != nil {
return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error()) return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error())
} }

View File

@@ -40,7 +40,6 @@ type WireGuard struct {
bind *wireguard.ClientBind bind *wireguard.ClientBind
device wireguardGoDevice device wireguardGoDevice
tunDevice wireguard.Device tunDevice wireguard.Device
dialer proxydialer.SingDialer
resolver resolver.Resolver resolver resolver.Resolver
initOk atomic.Bool initOk atomic.Bool
@@ -171,14 +170,15 @@ func NewWireGuard(option WireGuardOption) (*WireGuard, error) {
name: option.Name, name: option.Name,
addr: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)), addr: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)),
tp: C.WireGuard, tp: C.WireGuard,
pdName: option.ProviderName,
udp: option.UDP, udp: option.UDP,
iface: option.Interface, iface: option.Interface,
rmark: option.RoutingMark, rmark: option.RoutingMark,
prefer: C.NewDNSPrefer(option.IPVersion), prefer: option.IPVersion,
}, },
} }
singDialer := proxydialer.NewSlowDownSingDialer(proxydialer.NewByNameSingDialer(option.DialerProxy, dialer.NewDialer(outbound.DialOptions()...)), slowdown.New()) outbound.dialer = option.NewDialer(outbound.DialOptions())
outbound.dialer = singDialer singDialer := proxydialer.NewSlowDownSingDialer(proxydialer.NewSingDialer(outbound.dialer), slowdown.New())
var reserved [3]uint8 var reserved [3]uint8
if len(option.Reserved) > 0 { if len(option.Reserved) > 0 {
@@ -196,7 +196,7 @@ func NewWireGuard(option WireGuardOption) (*WireGuard, error) {
outbound.connectAddr = option.Addr() 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 var err error
outbound.localPrefixes, err = option.Prefixes() outbound.localPrefixes, err = option.Prefixes()
@@ -609,6 +609,13 @@ func (w *WireGuard) ResolveUDP(ctx context.Context, metadata *C.Metadata) error
return nil 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 // IsL3Protocol implements C.ProxyAdapter
func (w *WireGuard) IsL3Protocol(metadata *C.Metadata) bool { func (w *WireGuard) IsL3Protocol(metadata *C.Metadata) bool {
return true return true

View File

@@ -150,6 +150,14 @@ func (f *Fallback) ForceSet(name string) {
f.selected = name f.selected = name
} }
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 { func NewFallback(option *GroupCommonOption, providers []P.ProxyProvider) *Fallback {
return &Fallback{ return &Fallback{
GroupBase: NewGroupBase(GroupBaseOption{ GroupBase: NewGroupBase(GroupBaseOption{

View File

@@ -239,6 +239,18 @@ func (lb *LoadBalance) MarshalJSON() ([]byte, 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) { func NewLoadBalance(option *GroupCommonOption, providers []P.ProxyProvider, strategy string) (lb *LoadBalance, err error) {
var strategyFn strategyFn var strategyFn strategyFn
switch strategy { switch strategy {

View File

@@ -186,7 +186,7 @@ func ParseProxyGroup(config map[string]any, proxyMap map[string]C.Proxy, provide
strategy := parseStrategy(config) strategy := parseStrategy(config)
return NewLoadBalance(groupOption, providers, strategy) return NewLoadBalance(groupOption, providers, strategy)
case "relay": 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: default:
return nil, fmt.Errorf("%w: %s", errType, groupOption.Type) return nil, fmt.Errorf("%w: %s", errType, groupOption.Type)
} }

View File

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

View File

@@ -1,163 +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"
P "github.com/metacubex/mihomo/constant/provider"
"github.com/metacubex/mihomo/log"
)
type Relay struct {
*GroupBase
Hidden bool
Icon string
}
// DialContext implements C.ProxyAdapter
func (r *Relay) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
proxies, chainProxies := r.proxies(metadata, true)
switch len(proxies) {
case 0:
return outbound.NewDirect().DialContext(ctx, metadata)
case 1:
return proxies[0].DialContext(ctx, metadata)
}
var d C.Dialer
d = dialer.NewDialer()
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) (_ C.PacketConn, err error) {
proxies, chainProxies := r.proxies(metadata, true)
switch len(proxies) {
case 0:
return outbound.NewDirect().ListenPacketContext(ctx, metadata)
case 1:
return proxies[0].ListenPacketContext(ctx, metadata)
}
var d C.Dialer
d = dialer.NewDialer()
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 []P.ProxyProvider) *Relay {
log.Warnln("The group [%s] with relay type is deprecated, please using dialer-proxy instead", option.Name)
return &Relay{
GroupBase: NewGroupBase(GroupBaseOption{
Name: option.Name,
Type: C.Relay,
Providers: providers,
}),
Hidden: option.Hidden,
Icon: option.Icon,
}
}

View File

@@ -108,6 +108,14 @@ func (s *Selector) selectedProxy(touch bool) C.Proxy {
return proxies[0] return proxies[0]
} }
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 { func NewSelector(option *GroupCommonOption, providers []P.ProxyProvider) *Selector {
return &Selector{ return &Selector{
GroupBase: NewGroupBase(GroupBaseOption{ GroupBase: NewGroupBase(GroupBaseOption{

View File

@@ -185,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) { 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) return u.GroupBase.URLTest(ctx, u.testUrl, expectedStatus)
} }

View File

@@ -1,5 +1,29 @@
package outboundgroup 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 { type SelectAble interface {
Set(string) error Set(string) error
ForceSet(name string) ForceSet(name string)

View File

@@ -8,144 +8,157 @@ import (
C "github.com/metacubex/mihomo/constant" 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}) decoder := structure.NewDecoder(structure.Option{TagName: "proxy", WeaklyTypedInput: true, KeyReplacer: structure.DefaultKeyReplacer})
proxyType, existType := mapping["type"].(string) proxyType, existType := mapping["type"].(string)
if !existType { if !existType {
return nil, fmt.Errorf("missing type") return nil, fmt.Errorf("missing type")
} }
opt := applyProxyOptions(options...)
basicOption := outbound.BasicOption{
DialerForAPI: opt.DialerForAPI,
ProviderName: opt.ProviderName,
}
var ( var (
proxy outbound.ProxyAdapter proxy outbound.ProxyAdapter
err error err error
) )
switch proxyType { switch proxyType {
case "ss": case "ss":
ssOption := &outbound.ShadowSocksOption{} ssOption := &outbound.ShadowSocksOption{BasicOption: basicOption}
err = decoder.Decode(mapping, ssOption) err = decoder.Decode(mapping, ssOption)
if err != nil { if err != nil {
break break
} }
proxy, err = outbound.NewShadowSocks(*ssOption) proxy, err = outbound.NewShadowSocks(*ssOption)
case "ssr": case "ssr":
ssrOption := &outbound.ShadowSocksROption{} ssrOption := &outbound.ShadowSocksROption{BasicOption: basicOption}
err = decoder.Decode(mapping, ssrOption) err = decoder.Decode(mapping, ssrOption)
if err != nil { if err != nil {
break break
} }
proxy, err = outbound.NewShadowSocksR(*ssrOption) proxy, err = outbound.NewShadowSocksR(*ssrOption)
case "socks5": case "socks5":
socksOption := &outbound.Socks5Option{} socksOption := &outbound.Socks5Option{BasicOption: basicOption}
err = decoder.Decode(mapping, socksOption) err = decoder.Decode(mapping, socksOption)
if err != nil { if err != nil {
break break
} }
proxy, err = outbound.NewSocks5(*socksOption) proxy, err = outbound.NewSocks5(*socksOption)
case "http": case "http":
httpOption := &outbound.HttpOption{} httpOption := &outbound.HttpOption{BasicOption: basicOption}
err = decoder.Decode(mapping, httpOption) err = decoder.Decode(mapping, httpOption)
if err != nil { if err != nil {
break break
} }
proxy, err = outbound.NewHttp(*httpOption) proxy, err = outbound.NewHttp(*httpOption)
case "vmess": case "vmess":
vmessOption := &outbound.VmessOption{} vmessOption := &outbound.VmessOption{BasicOption: basicOption}
err = decoder.Decode(mapping, vmessOption) err = decoder.Decode(mapping, vmessOption)
if err != nil { if err != nil {
break break
} }
proxy, err = outbound.NewVmess(*vmessOption) proxy, err = outbound.NewVmess(*vmessOption)
case "vless": case "vless":
vlessOption := &outbound.VlessOption{} vlessOption := &outbound.VlessOption{BasicOption: basicOption}
err = decoder.Decode(mapping, vlessOption) err = decoder.Decode(mapping, vlessOption)
if err != nil { if err != nil {
break break
} }
proxy, err = outbound.NewVless(*vlessOption) proxy, err = outbound.NewVless(*vlessOption)
case "snell": case "snell":
snellOption := &outbound.SnellOption{} snellOption := &outbound.SnellOption{BasicOption: basicOption}
err = decoder.Decode(mapping, snellOption) err = decoder.Decode(mapping, snellOption)
if err != nil { if err != nil {
break break
} }
proxy, err = outbound.NewSnell(*snellOption) proxy, err = outbound.NewSnell(*snellOption)
case "trojan": case "trojan":
trojanOption := &outbound.TrojanOption{} trojanOption := &outbound.TrojanOption{BasicOption: basicOption}
err = decoder.Decode(mapping, trojanOption) err = decoder.Decode(mapping, trojanOption)
if err != nil { if err != nil {
break break
} }
proxy, err = outbound.NewTrojan(*trojanOption) proxy, err = outbound.NewTrojan(*trojanOption)
case "hysteria": case "hysteria":
hyOption := &outbound.HysteriaOption{} hyOption := &outbound.HysteriaOption{BasicOption: basicOption}
err = decoder.Decode(mapping, hyOption) err = decoder.Decode(mapping, hyOption)
if err != nil { if err != nil {
break break
} }
proxy, err = outbound.NewHysteria(*hyOption) proxy, err = outbound.NewHysteria(*hyOption)
case "hysteria2": case "hysteria2":
hyOption := &outbound.Hysteria2Option{} hyOption := &outbound.Hysteria2Option{BasicOption: basicOption}
err = decoder.Decode(mapping, hyOption) err = decoder.Decode(mapping, hyOption)
if err != nil { if err != nil {
break break
} }
proxy, err = outbound.NewHysteria2(*hyOption) proxy, err = outbound.NewHysteria2(*hyOption)
case "wireguard": case "wireguard":
wgOption := &outbound.WireGuardOption{} wgOption := &outbound.WireGuardOption{BasicOption: basicOption}
err = decoder.Decode(mapping, wgOption) err = decoder.Decode(mapping, wgOption)
if err != nil { if err != nil {
break break
} }
proxy, err = outbound.NewWireGuard(*wgOption) proxy, err = outbound.NewWireGuard(*wgOption)
case "tuic": case "tuic":
tuicOption := &outbound.TuicOption{} tuicOption := &outbound.TuicOption{BasicOption: basicOption}
err = decoder.Decode(mapping, tuicOption) err = decoder.Decode(mapping, tuicOption)
if err != nil { if err != nil {
break break
} }
proxy, err = outbound.NewTuic(*tuicOption) proxy, err = outbound.NewTuic(*tuicOption)
case "direct": case "direct":
directOption := &outbound.DirectOption{} directOption := &outbound.DirectOption{BasicOption: basicOption}
err = decoder.Decode(mapping, directOption) err = decoder.Decode(mapping, directOption)
if err != nil { if err != nil {
break break
} }
proxy = outbound.NewDirectWithOption(*directOption) proxy = outbound.NewDirectWithOption(*directOption)
case "dns": case "dns":
dnsOptions := &outbound.DnsOption{} dnsOptions := &outbound.DnsOption{BasicOption: basicOption}
err = decoder.Decode(mapping, dnsOptions) err = decoder.Decode(mapping, dnsOptions)
if err != nil { if err != nil {
break break
} }
proxy = outbound.NewDnsWithOption(*dnsOptions) proxy = outbound.NewDnsWithOption(*dnsOptions)
case "reject": case "reject":
rejectOption := &outbound.RejectOption{} rejectOption := &outbound.RejectOption{BasicOption: basicOption}
err = decoder.Decode(mapping, rejectOption) err = decoder.Decode(mapping, rejectOption)
if err != nil { if err != nil {
break break
} }
proxy = outbound.NewRejectWithOption(*rejectOption) proxy = outbound.NewRejectWithOption(*rejectOption)
case "ssh": case "ssh":
sshOption := &outbound.SshOption{} sshOption := &outbound.SshOption{BasicOption: basicOption}
err = decoder.Decode(mapping, sshOption) err = decoder.Decode(mapping, sshOption)
if err != nil { if err != nil {
break break
} }
proxy, err = outbound.NewSsh(*sshOption) proxy, err = outbound.NewSsh(*sshOption)
case "mieru": case "mieru":
mieruOption := &outbound.MieruOption{} mieruOption := &outbound.MieruOption{BasicOption: basicOption}
err = decoder.Decode(mapping, mieruOption) err = decoder.Decode(mapping, mieruOption)
if err != nil { if err != nil {
break break
} }
proxy, err = outbound.NewMieru(*mieruOption) proxy, err = outbound.NewMieru(*mieruOption)
case "anytls": case "anytls":
anytlsOption := &outbound.AnyTLSOption{} anytlsOption := &outbound.AnyTLSOption{BasicOption: basicOption}
err = decoder.Decode(mapping, anytlsOption) err = decoder.Decode(mapping, anytlsOption)
if err != nil { if err != nil {
break break
} }
proxy, err = outbound.NewAnyTLS(*anytlsOption) 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: default:
return nil, fmt.Errorf("unsupport proxy type: %s", proxyType) return nil, fmt.Errorf("unsupport proxy type: %s", proxyType)
} }
@@ -171,3 +184,30 @@ func ParseProxy(mapping map[string]any) (C.Proxy, error) {
proxy = outbound.NewAutoCloseProxyAdapter(proxy) proxy = outbound.NewAutoCloseProxyAdapter(proxy)
return NewProxy(proxy), nil 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
}
}

View File

@@ -99,7 +99,7 @@ func ParseProxyProvider(name string, mapping map[string]any) (P.ProxyProvider, e
} }
hc := NewHealthCheck([]C.Proxy{}, schema.HealthCheck.URL, uint(schema.HealthCheck.TestTimeout), hcInterval, schema.HealthCheck.Lazy, expectedStatus) 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 { if err != nil {
return nil, err return nil, err
} }

View File

@@ -4,10 +4,10 @@ import (
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
"net/http"
"reflect" "reflect"
"runtime" "runtime"
"strings" "strings"
"sync"
"time" "time"
"github.com/metacubex/mihomo/adapter" "github.com/metacubex/mihomo/adapter"
@@ -20,6 +20,7 @@ import (
"github.com/metacubex/mihomo/tunnel/statistic" "github.com/metacubex/mihomo/tunnel/statistic"
"github.com/dlclark/regexp2" "github.com/dlclark/regexp2"
"github.com/metacubex/http"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
) )
@@ -43,6 +44,7 @@ type providerForApi struct {
} }
type baseProvider struct { type baseProvider struct {
mutex sync.RWMutex
name string name string
proxies []C.Proxy proxies []C.Proxy
healthCheck *HealthCheck healthCheck *HealthCheck
@@ -54,6 +56,8 @@ func (bp *baseProvider) Name() string {
} }
func (bp *baseProvider) Version() uint32 { func (bp *baseProvider) Version() uint32 {
bp.mutex.RLock()
defer bp.mutex.RUnlock()
return bp.version return bp.version
} }
@@ -73,10 +77,14 @@ func (bp *baseProvider) Type() P.ProviderType {
} }
func (bp *baseProvider) Proxies() []C.Proxy { func (bp *baseProvider) Proxies() []C.Proxy {
bp.mutex.RLock()
defer bp.mutex.RUnlock()
return bp.proxies return bp.proxies
} }
func (bp *baseProvider) Count() int { func (bp *baseProvider) Count() int {
bp.mutex.RLock()
defer bp.mutex.RUnlock()
return len(bp.proxies) return len(bp.proxies)
} }
@@ -93,6 +101,8 @@ func (bp *baseProvider) RegisterHealthCheckTask(url string, expectedStatus utils
} }
func (bp *baseProvider) setProxies(proxies []C.Proxy) { func (bp *baseProvider) setProxies(proxies []C.Proxy) {
bp.mutex.Lock()
defer bp.mutex.Unlock()
bp.proxies = proxies bp.proxies = proxies
bp.version += 1 bp.version += 1
bp.healthCheck.setProxies(proxies) bp.healthCheck.setProxies(proxies)
@@ -156,7 +166,7 @@ func (pp *proxySetProvider) Initial() error {
func (pp *proxySetProvider) closeAllConnections() { func (pp *proxySetProvider) closeAllConnections() {
statistic.DefaultManager.Range(func(c statistic.Tracker) bool { statistic.DefaultManager.Range(func(c statistic.Tracker) bool {
for _, chain := range c.Chains() { for _, chain := range c.ProviderChains() {
if chain == pp.Name() { if chain == pp.Name() {
_ = c.Close() _ = c.Close()
break break
@@ -330,7 +340,7 @@ func (cp *CompatibleProvider) Close() error {
return cp.compatibleProvider.Close() return cp.compatibleProvider.Close()
} }
func NewProxiesParser(filter string, excludeFilter string, excludeType string, dialerProxy string, override OverrideSchema) (resource.Parser[[]C.Proxy], error) { func NewProxiesParser(pdName string, filter string, excludeFilter string, excludeType string, dialerProxy string, override OverrideSchema) (resource.Parser[[]C.Proxy], error) {
var excludeTypeArray []string var excludeTypeArray []string
if excludeType != "" { if excludeType != "" {
excludeTypeArray = strings.Split(excludeType, "|") excludeTypeArray = strings.Split(excludeType, "|")
@@ -448,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 { if err != nil {
return nil, fmt.Errorf("proxy %d error: %w", idx, err) return nil, fmt.Errorf("proxy %d error: %w", idx, err)
} }

View File

@@ -2,12 +2,12 @@ package convert
import ( import (
"encoding/base64" "encoding/base64"
"net/http"
"strings" "strings"
"time" "time"
"github.com/metacubex/mihomo/common/utils" "github.com/metacubex/mihomo/common/utils"
"github.com/metacubex/http"
"github.com/metacubex/randv2" "github.com/metacubex/randv2"
"github.com/metacubex/sing-shadowsocks/shadowimpl" "github.com/metacubex/sing-shadowsocks/shadowimpl"
) )

View File

@@ -33,6 +33,9 @@ type WriterWithUpstream = network.WriterWithUpstream
type WithUpstreamWriter = network.WithUpstreamWriter type WithUpstreamWriter = network.WithUpstreamWriter
type WithUpstream = common.WithUpstream type WithUpstream = common.WithUpstream
type HandshakeSuccess = network.HandshakeSuccess
type HandshakeFailure = network.HandshakeFailure
var UnwrapReader = network.UnwrapReader var UnwrapReader = network.UnwrapReader
var UnwrapWriter = network.UnwrapWriter var UnwrapWriter = network.UnwrapWriter

View File

@@ -1,6 +1,8 @@
package net package net
import ( import (
"crypto/sha1"
"encoding/base64"
"encoding/binary" "encoding/binary"
"math/bits" "math/bits"
) )
@@ -129,3 +131,13 @@ func MaskWebSocket(key uint32, b []byte) uint32 {
return key 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[:])
}

View File

@@ -289,7 +289,7 @@ func (d *Decoder) decodeBool(name string, data any, val reflect.Value) (err erro
case isInt(kind) && d.option.WeaklyTypedInput: case isInt(kind) && d.option.WeaklyTypedInput:
val.SetBool(dataVal.Int() != 0) val.SetBool(dataVal.Int() != 0)
case isUint(kind) && d.option.WeaklyTypedInput: case isUint(kind) && d.option.WeaklyTypedInput:
val.SetString(strconv.FormatUint(dataVal.Uint(), 10)) val.SetBool(dataVal.Uint() != 0)
default: default:
err = fmt.Errorf( err = fmt.Errorf(
"'%s' expected type '%s', got unconvertible type '%s'", "'%s' expected type '%s', got unconvertible type '%s'",
@@ -517,6 +517,10 @@ func (d *Decoder) decodeStructFromMap(name string, dataVal, val reflect.Value) e
fieldName = tagValue fieldName = tagValue
} }
if tagValue == "-" {
continue
}
rawMapKey := reflect.ValueOf(fieldName) rawMapKey := reflect.ValueOf(fieldName)
rawMapVal := dataVal.MapIndex(rawMapKey) rawMapVal := dataVal.MapIndex(rawMapKey)
if !rawMapVal.IsValid() { if !rawMapVal.IsValid() {

View File

@@ -308,3 +308,27 @@ func TestStructure_Ignore(t *testing.T) {
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, s.MustIgnore, "oldData") 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")
}

31
common/utils/cast.go Normal file
View File

@@ -0,0 +1,31 @@
package utils
import (
"fmt"
"net"
)
type WithUpstream interface {
Upstream() any
}
type stdWithUpstreamNetConn interface {
NetConn() net.Conn
}
func Cast[T any](obj any) (_ T, _ bool) {
if c, ok := obj.(T); ok {
fmt.Printf("Got 1: %T\n", obj) // TODO
return c, true
}
if u, ok := obj.(WithUpstream); ok {
fmt.Printf("Upstream 2: %T\n", obj) // TODO
return Cast[T](u.Upstream())
}
if u, ok := obj.(stdWithUpstreamNetConn); ok {
fmt.Printf("Std 3: %T\n", obj) // TODO
return Cast[T](u.NetConn())
}
fmt.Printf("Failed: %T\n", obj) // TODO
return
}

View File

@@ -1,16 +1,17 @@
package xsync package xsync
// copy and modified from https://github.com/puzpuzpuz/xsync/blob/v4.1.0/map.go // copy and modified from https://github.com/puzpuzpuz/xsync/blob/v4.2.0/map.go
// which is licensed under Apache v2. // which is licensed under Apache v2.
// //
// mihomo modified: // mihomo modified:
// 1. parallel Map resize has been removed to decrease the memory using. // 1. restore xsync/v3's LoadOrCompute api and rename to LoadOrStoreFn.
// 2. the zero Map is ready for use. // 2. the zero Map is ready for use.
import ( import (
"fmt" "fmt"
"math" "math"
"math/bits" "math/bits"
"runtime"
"strings" "strings"
"sync" "sync"
"sync/atomic" "sync/atomic"
@@ -41,8 +42,28 @@ const (
metaMask uint64 = 0xffffffffff metaMask uint64 = 0xffffffffff
defaultMetaMasked uint64 = defaultMeta & metaMask defaultMetaMasked uint64 = defaultMeta & metaMask
emptyMetaSlot uint8 = 0x80 emptyMetaSlot uint8 = 0x80
// minimal number of buckets to transfer when participating in cooperative
// resize; should be at least defaultMinMapTableLen
minResizeTransferStride = 64
// upper limit for max number of additional goroutines that participate
// in cooperative resize; must be changed simultaneously with resizeCtl
// and the related code
maxResizeHelpersLimit = (1 << 5) - 1
) )
// max number of additional goroutines that participate in cooperative resize;
// "resize owner" goroutine isn't counted
var maxResizeHelpers = func() int32 {
v := int32(parallelism() - 1)
if v < 1 {
v = 1
}
if v > maxResizeHelpersLimit {
v = maxResizeHelpersLimit
}
return v
}()
type mapResizeHint int type mapResizeHint int
const ( const (
@@ -100,16 +121,25 @@ type Map[K comparable, V any] struct {
initOnce sync.Once initOnce sync.Once
totalGrowths atomic.Int64 totalGrowths atomic.Int64
totalShrinks atomic.Int64 totalShrinks atomic.Int64
resizing atomic.Bool // resize in progress flag
resizeMu sync.Mutex // only used along with resizeCond
resizeCond sync.Cond // used to wake up resize waiters (concurrent modifications)
table atomic.Pointer[mapTable[K, V]] table atomic.Pointer[mapTable[K, V]]
minTableLen int // table being transferred to
growOnly bool nextTable atomic.Pointer[mapTable[K, V]]
// resize control state: combines resize sequence number (upper 59 bits) and
// the current number of resize helpers (lower 5 bits);
// odd values of resize sequence mean in-progress resize
resizeCtl atomic.Uint64
// only used along with resizeCond
resizeMu sync.Mutex
// used to wake up resize waiters (concurrent writes)
resizeCond sync.Cond
// transfer progress index for resize
resizeIdx atomic.Int64
minTableLen int
growOnly bool
} }
type mapTable[K comparable, V any] struct { type mapTable[K comparable, V any] struct {
buckets []bucketPadded[K, V] buckets []bucketPadded
// striped counter for number of table entries; // striped counter for number of table entries;
// used to determine if a table shrinking is needed // used to determine if a table shrinking is needed
// occupies min(buckets_memory/1024, 64KB) of memory // occupies min(buckets_memory/1024, 64KB) of memory
@@ -125,16 +155,16 @@ type counterStripe struct {
// bucketPadded is a CL-sized map bucket holding up to // bucketPadded is a CL-sized map bucket holding up to
// entriesPerMapBucket entries. // entriesPerMapBucket entries.
type bucketPadded[K comparable, V any] struct { type bucketPadded struct {
//lint:ignore U1000 ensure each bucket takes two cache lines on both 32 and 64-bit archs //lint:ignore U1000 ensure each bucket takes two cache lines on both 32 and 64-bit archs
pad [cacheLineSize - unsafe.Sizeof(bucket[K, V]{})]byte pad [cacheLineSize - unsafe.Sizeof(bucket{})]byte
bucket[K, V] bucket
} }
type bucket[K comparable, V any] struct { type bucket struct {
meta atomic.Uint64 meta uint64
entries [entriesPerMapBucket]atomic.Pointer[entry[K, V]] // *entry entries [entriesPerMapBucket]unsafe.Pointer // *entry
next atomic.Pointer[bucketPadded[K, V]] // *bucketPadded next unsafe.Pointer // *bucketPadded
mu sync.Mutex mu sync.Mutex
} }
@@ -194,15 +224,15 @@ func (m *Map[K, V]) init() {
m.minTableLen = defaultMinMapTableLen m.minTableLen = defaultMinMapTableLen
} }
m.resizeCond = *sync.NewCond(&m.resizeMu) m.resizeCond = *sync.NewCond(&m.resizeMu)
table := newMapTable[K, V](m.minTableLen) table := newMapTable[K, V](m.minTableLen, maphash.MakeSeed())
m.minTableLen = len(table.buckets) m.minTableLen = len(table.buckets)
m.table.Store(table) m.table.Store(table)
} }
func newMapTable[K comparable, V any](minTableLen int) *mapTable[K, V] { func newMapTable[K comparable, V any](minTableLen int, seed maphash.Seed) *mapTable[K, V] {
buckets := make([]bucketPadded[K, V], minTableLen) buckets := make([]bucketPadded, minTableLen)
for i := range buckets { for i := range buckets {
buckets[i].meta.Store(defaultMeta) buckets[i].meta = defaultMeta
} }
counterLen := minTableLen >> 10 counterLen := minTableLen >> 10
if counterLen < minMapCounterLen { if counterLen < minMapCounterLen {
@@ -214,7 +244,7 @@ func newMapTable[K comparable, V any](minTableLen int) *mapTable[K, V] {
t := &mapTable[K, V]{ t := &mapTable[K, V]{
buckets: buckets, buckets: buckets,
size: counter, size: counter,
seed: maphash.MakeSeed(), seed: seed,
} }
return t return t
} }
@@ -246,22 +276,24 @@ func (m *Map[K, V]) Load(key K) (value V, ok bool) {
bidx := uint64(len(table.buckets)-1) & h1 bidx := uint64(len(table.buckets)-1) & h1
b := &table.buckets[bidx] b := &table.buckets[bidx]
for { for {
metaw := b.meta.Load() metaw := atomic.LoadUint64(&b.meta)
markedw := markZeroBytes(metaw^h2w) & metaMask markedw := markZeroBytes(metaw^h2w) & metaMask
for markedw != 0 { for markedw != 0 {
idx := firstMarkedByteIndex(markedw) idx := firstMarkedByteIndex(markedw)
e := b.entries[idx].Load() eptr := atomic.LoadPointer(&b.entries[idx])
if e != nil { if eptr != nil {
e := (*entry[K, V])(eptr)
if e.key == key { if e.key == key {
return e.value, true return e.value, true
} }
} }
markedw &= markedw - 1 markedw &= markedw - 1
} }
b = b.next.Load() bptr := atomic.LoadPointer(&b.next)
if b == nil { if bptr == nil {
return return
} }
b = (*bucketPadded)(bptr)
} }
} }
@@ -399,7 +431,7 @@ func (m *Map[K, V]) doCompute(
for { for {
compute_attempt: compute_attempt:
var ( var (
emptyb *bucketPadded[K, V] emptyb *bucketPadded
emptyidx int emptyidx int
) )
table := m.table.Load() table := m.table.Load()
@@ -415,12 +447,13 @@ func (m *Map[K, V]) doCompute(
b := rootb b := rootb
load: load:
for { for {
metaw := b.meta.Load() metaw := atomic.LoadUint64(&b.meta)
markedw := markZeroBytes(metaw^h2w) & metaMask markedw := markZeroBytes(metaw^h2w) & metaMask
for markedw != 0 { for markedw != 0 {
idx := firstMarkedByteIndex(markedw) idx := firstMarkedByteIndex(markedw)
e := b.entries[idx].Load() eptr := atomic.LoadPointer(&b.entries[idx])
if e != nil { if eptr != nil {
e := (*entry[K, V])(eptr)
if e.key == key { if e.key == key {
if loadOp == loadOrComputeOp { if loadOp == loadOrComputeOp {
return e.value, true return e.value, true
@@ -430,23 +463,24 @@ func (m *Map[K, V]) doCompute(
} }
markedw &= markedw - 1 markedw &= markedw - 1
} }
b = b.next.Load() bptr := atomic.LoadPointer(&b.next)
if b == nil { if bptr == nil {
if loadOp == loadAndDeleteOp { if loadOp == loadAndDeleteOp {
return *new(V), false return *new(V), false
} }
break load break load
} }
b = (*bucketPadded)(bptr)
} }
} }
rootb.mu.Lock() rootb.mu.Lock()
// The following two checks must go in reverse to what's // The following two checks must go in reverse to what's
// in the resize method. // in the resize method.
if m.resizeInProgress() { if seq := resizeSeq(m.resizeCtl.Load()); seq&1 == 1 {
// Resize is in progress. Wait, then go for another attempt. // Resize is in progress. Help with the transfer, then go for another attempt.
rootb.mu.Unlock() rootb.mu.Unlock()
m.waitForResize() m.helpResize(seq)
goto compute_attempt goto compute_attempt
} }
if m.newerTableExists(table) { if m.newerTableExists(table) {
@@ -456,12 +490,13 @@ func (m *Map[K, V]) doCompute(
} }
b := rootb b := rootb
for { for {
metaw := b.meta.Load() metaw := b.meta
markedw := markZeroBytes(metaw^h2w) & metaMask markedw := markZeroBytes(metaw^h2w) & metaMask
for markedw != 0 { for markedw != 0 {
idx := firstMarkedByteIndex(markedw) idx := firstMarkedByteIndex(markedw)
e := b.entries[idx].Load() eptr := b.entries[idx]
if e != nil { if eptr != nil {
e := (*entry[K, V])(eptr)
if e.key == key { if e.key == key {
// In-place update/delete. // In-place update/delete.
// We get a copy of the value via an interface{} on each call, // We get a copy of the value via an interface{} on each call,
@@ -475,8 +510,8 @@ func (m *Map[K, V]) doCompute(
// Deletion. // Deletion.
// First we update the hash, then the entry. // First we update the hash, then the entry.
newmetaw := setByte(metaw, emptyMetaSlot, idx) newmetaw := setByte(metaw, emptyMetaSlot, idx)
b.meta.Store(newmetaw) atomic.StoreUint64(&b.meta, newmetaw)
b.entries[idx].Store(nil) atomic.StorePointer(&b.entries[idx], nil)
rootb.mu.Unlock() rootb.mu.Unlock()
table.addSize(bidx, -1) table.addSize(bidx, -1)
// Might need to shrink the table if we left bucket empty. // Might need to shrink the table if we left bucket empty.
@@ -488,7 +523,7 @@ func (m *Map[K, V]) doCompute(
newe := new(entry[K, V]) newe := new(entry[K, V])
newe.key = key newe.key = key
newe.value = newv newe.value = newv
b.entries[idx].Store(newe) atomic.StorePointer(&b.entries[idx], unsafe.Pointer(newe))
case CancelOp: case CancelOp:
newv = oldv newv = oldv
} }
@@ -512,7 +547,7 @@ func (m *Map[K, V]) doCompute(
emptyidx = idx emptyidx = idx
} }
} }
if b.next.Load() == nil { if b.next == nil {
if emptyb != nil { if emptyb != nil {
// Insertion into an existing bucket. // Insertion into an existing bucket.
var zeroV V var zeroV V
@@ -526,8 +561,8 @@ func (m *Map[K, V]) doCompute(
newe.key = key newe.key = key
newe.value = newValue newe.value = newValue
// First we update meta, then the entry. // First we update meta, then the entry.
emptyb.meta.Store(setByte(emptyb.meta.Load(), h2, emptyidx)) atomic.StoreUint64(&emptyb.meta, setByte(emptyb.meta, h2, emptyidx))
emptyb.entries[emptyidx].Store(newe) atomic.StorePointer(&emptyb.entries[emptyidx], unsafe.Pointer(newe))
rootb.mu.Unlock() rootb.mu.Unlock()
table.addSize(bidx, 1) table.addSize(bidx, 1)
return newValue, computeOnly return newValue, computeOnly
@@ -549,19 +584,19 @@ func (m *Map[K, V]) doCompute(
return newValue, false return newValue, false
default: default:
// Create and append a bucket. // Create and append a bucket.
newb := new(bucketPadded[K, V]) newb := new(bucketPadded)
newb.meta.Store(setByte(defaultMeta, h2, 0)) newb.meta = setByte(defaultMeta, h2, 0)
newe := new(entry[K, V]) newe := new(entry[K, V])
newe.key = key newe.key = key
newe.value = newValue newe.value = newValue
newb.entries[0].Store(newe) newb.entries[0] = unsafe.Pointer(newe)
b.next.Store(newb) atomic.StorePointer(&b.next, unsafe.Pointer(newb))
rootb.mu.Unlock() rootb.mu.Unlock()
table.addSize(bidx, 1) table.addSize(bidx, 1)
return newValue, computeOnly return newValue, computeOnly
} }
} }
b = b.next.Load() b = (*bucketPadded)(b.next)
} }
} }
} }
@@ -570,13 +605,21 @@ func (m *Map[K, V]) newerTableExists(table *mapTable[K, V]) bool {
return table != m.table.Load() return table != m.table.Load()
} }
func (m *Map[K, V]) resizeInProgress() bool { func resizeSeq(ctl uint64) uint64 {
return m.resizing.Load() return ctl >> 5
}
func resizeHelpers(ctl uint64) uint64 {
return ctl & maxResizeHelpersLimit
}
func resizeCtl(seq uint64, helpers uint64) uint64 {
return (seq << 5) | (helpers & maxResizeHelpersLimit)
} }
func (m *Map[K, V]) waitForResize() { func (m *Map[K, V]) waitForResize() {
m.resizeMu.Lock() m.resizeMu.Lock()
for m.resizeInProgress() { for resizeSeq(m.resizeCtl.Load())&1 == 1 {
m.resizeCond.Wait() m.resizeCond.Wait()
} }
m.resizeMu.Unlock() m.resizeMu.Unlock()
@@ -593,9 +636,9 @@ func (m *Map[K, V]) resize(knownTable *mapTable[K, V], hint mapResizeHint) {
} }
} }
// Slow path. // Slow path.
if !m.resizing.CompareAndSwap(false, true) { seq := resizeSeq(m.resizeCtl.Load())
// Someone else started resize. Wait for it to finish. if seq&1 == 1 || !m.resizeCtl.CompareAndSwap(resizeCtl(seq, 0), resizeCtl(seq+1, 0)) {
m.waitForResize() m.helpResize(seq)
return return
} }
var newTable *mapTable[K, V] var newTable *mapTable[K, V]
@@ -604,64 +647,189 @@ func (m *Map[K, V]) resize(knownTable *mapTable[K, V], hint mapResizeHint) {
switch hint { switch hint {
case mapGrowHint: case mapGrowHint:
// Grow the table with factor of 2. // Grow the table with factor of 2.
// We must keep the same table seed here to keep the same hash codes
// allowing us to avoid locking destination buckets when resizing.
m.totalGrowths.Add(1) m.totalGrowths.Add(1)
newTable = newMapTable[K, V](tableLen << 1) newTable = newMapTable[K, V](tableLen<<1, table.seed)
case mapShrinkHint: case mapShrinkHint:
shrinkThreshold := int64((tableLen * entriesPerMapBucket) / mapShrinkFraction) shrinkThreshold := int64((tableLen * entriesPerMapBucket) / mapShrinkFraction)
if tableLen > m.minTableLen && table.sumSize() <= shrinkThreshold { if tableLen > m.minTableLen && table.sumSize() <= shrinkThreshold {
// Shrink the table with factor of 2. // Shrink the table with factor of 2.
// It's fine to generate a new seed since full locking
// is required anyway.
m.totalShrinks.Add(1) m.totalShrinks.Add(1)
newTable = newMapTable[K, V](tableLen >> 1) newTable = newMapTable[K, V](tableLen>>1, maphash.MakeSeed())
} else { } else {
// No need to shrink. Wake up all waiters and give up. // No need to shrink. Wake up all waiters and give up.
m.resizeMu.Lock() m.resizeMu.Lock()
m.resizing.Store(false) m.resizeCtl.Store(resizeCtl(seq+2, 0))
m.resizeCond.Broadcast() m.resizeCond.Broadcast()
m.resizeMu.Unlock() m.resizeMu.Unlock()
return return
} }
case mapClearHint: case mapClearHint:
newTable = newMapTable[K, V](m.minTableLen) newTable = newMapTable[K, V](m.minTableLen, maphash.MakeSeed())
default: default:
panic(fmt.Sprintf("unexpected resize hint: %d", hint)) panic(fmt.Sprintf("unexpected resize hint: %d", hint))
} }
// Copy the data only if we're not clearing the map. // Copy the data only if we're not clearing the map.
if hint != mapClearHint { if hint != mapClearHint {
for i := 0; i < tableLen; i++ { // Set up cooperative transfer state.
copied := copyBucket(&table.buckets[i], newTable) // Next table must be published as the last step.
newTable.addSizePlain(uint64(i), copied) m.resizeIdx.Store(0)
} m.nextTable.Store(newTable)
// Copy the buckets.
m.transfer(table, newTable)
}
// We're about to publish the new table, but before that
// we must wait for all helpers to finish.
for resizeHelpers(m.resizeCtl.Load()) != 0 {
runtime.Gosched()
} }
// Publish the new table and wake up all waiters.
m.table.Store(newTable) m.table.Store(newTable)
m.nextTable.Store(nil)
ctl := resizeCtl(seq+1, 0)
newCtl := resizeCtl(seq+2, 0)
// Increment the sequence number and wake up all waiters.
m.resizeMu.Lock() m.resizeMu.Lock()
m.resizing.Store(false) // There may be slowpoke helpers who have just incremented
// the helper counter. This CAS loop makes sure to wait
// for them to back off.
for !m.resizeCtl.CompareAndSwap(ctl, newCtl) {
runtime.Gosched()
}
m.resizeCond.Broadcast() m.resizeCond.Broadcast()
m.resizeMu.Unlock() m.resizeMu.Unlock()
} }
func copyBucket[K comparable, V any]( func (m *Map[K, V]) helpResize(seq uint64) {
b *bucketPadded[K, V], for {
table := m.table.Load()
nextTable := m.nextTable.Load()
if resizeSeq(m.resizeCtl.Load()) == seq {
if nextTable == nil || nextTable == table {
// Carry on until the next table is set by the main
// resize goroutine or until the resize finishes.
runtime.Gosched()
continue
}
// The resize is still in-progress, so let's try registering
// as a helper.
for {
ctl := m.resizeCtl.Load()
if resizeSeq(ctl) != seq || resizeHelpers(ctl) >= uint64(maxResizeHelpers) {
// The resize has ended or there are too many helpers.
break
}
if m.resizeCtl.CompareAndSwap(ctl, ctl+1) {
// Yay, we're a resize helper!
m.transfer(table, nextTable)
// Don't forget to unregister as a helper.
m.resizeCtl.Add(^uint64(0))
break
}
}
m.waitForResize()
}
break
}
}
func (m *Map[K, V]) transfer(table, newTable *mapTable[K, V]) {
tableLen := len(table.buckets)
newTableLen := len(newTable.buckets)
stride := (tableLen >> 3) / int(maxResizeHelpers)
if stride < minResizeTransferStride {
stride = minResizeTransferStride
}
for {
// Claim work by incrementing resizeIdx.
nextIdx := m.resizeIdx.Add(int64(stride))
start := int(nextIdx) - stride
if start < 0 {
start = 0
}
if start > tableLen {
break
}
end := int(nextIdx)
if end > tableLen {
end = tableLen
}
// Transfer buckets in this range.
total := 0
if newTableLen > tableLen {
// We're growing the table with 2x multiplier, so entries from a N bucket can
// only be transferred to N and 2*N buckets in the new table. Thus, destination
// buckets written by the resize helpers don't intersect, so we don't need to
// acquire locks in the destination buckets.
for i := start; i < end; i++ {
total += transferBucketUnsafe(&table.buckets[i], newTable)
}
} else {
// We're shrinking the table, so all locks must be acquired.
for i := start; i < end; i++ {
total += transferBucket(&table.buckets[i], newTable)
}
}
// The exact counter stripe doesn't matter here, so pick up the one
// that corresponds to the start value to avoid contention.
newTable.addSize(uint64(start), total)
}
}
// Doesn't acquire dest bucket lock.
func transferBucketUnsafe[K comparable, V any](
b *bucketPadded,
destTable *mapTable[K, V], destTable *mapTable[K, V],
) (copied int) { ) (copied int) {
rootb := b rootb := b
rootb.mu.Lock() rootb.mu.Lock()
for { for {
for i := 0; i < entriesPerMapBucket; i++ { for i := 0; i < entriesPerMapBucket; i++ {
if e := b.entries[i].Load(); e != nil { if eptr := b.entries[i]; eptr != nil {
e := (*entry[K, V])(eptr)
hash := maphash.Comparable(destTable.seed, e.key) hash := maphash.Comparable(destTable.seed, e.key)
bidx := uint64(len(destTable.buckets)-1) & h1(hash) bidx := uint64(len(destTable.buckets)-1) & h1(hash)
destb := &destTable.buckets[bidx] destb := &destTable.buckets[bidx]
appendToBucket(h2(hash), b.entries[i].Load(), destb) appendToBucket(h2(hash), e, destb)
copied++ copied++
} }
} }
if next := b.next.Load(); next == nil { if b.next == nil {
rootb.mu.Unlock() rootb.mu.Unlock()
return return
} else {
b = next
} }
b = (*bucketPadded)(b.next)
}
}
func transferBucket[K comparable, V any](
b *bucketPadded,
destTable *mapTable[K, V],
) (copied int) {
rootb := b
rootb.mu.Lock()
for {
for i := 0; i < entriesPerMapBucket; i++ {
if eptr := b.entries[i]; eptr != nil {
e := (*entry[K, V])(eptr)
hash := maphash.Comparable(destTable.seed, e.key)
bidx := uint64(len(destTable.buckets)-1) & h1(hash)
destb := &destTable.buckets[bidx]
destb.mu.Lock()
appendToBucket(h2(hash), e, destb)
destb.mu.Unlock()
copied++
}
}
if b.next == nil {
rootb.mu.Unlock()
return
}
b = (*bucketPadded)(b.next)
} }
} }
@@ -691,16 +859,15 @@ func (m *Map[K, V]) Range(f func(key K, value V) bool) {
rootb.mu.Lock() rootb.mu.Lock()
for { for {
for i := 0; i < entriesPerMapBucket; i++ { for i := 0; i < entriesPerMapBucket; i++ {
if entry := b.entries[i].Load(); entry != nil { if b.entries[i] != nil {
bentries = append(bentries, entry) bentries = append(bentries, (*entry[K, V])(b.entries[i]))
} }
} }
if next := b.next.Load(); next == nil { if b.next == nil {
rootb.mu.Unlock() rootb.mu.Unlock()
break break
} else {
b = next
} }
b = (*bucketPadded)(b.next)
} }
// Call the function for all copied entries. // Call the function for all copied entries.
for j, e := range bentries { for j, e := range bentries {
@@ -727,24 +894,25 @@ func (m *Map[K, V]) Size() int {
return int(m.table.Load().sumSize()) return int(m.table.Load().sumSize())
} }
func appendToBucket[K comparable, V any](h2 uint8, e *entry[K, V], b *bucketPadded[K, V]) { // It is safe to use plain stores here because the destination bucket must be
// either locked or exclusively written to by the helper during resize.
func appendToBucket[K comparable, V any](h2 uint8, e *entry[K, V], b *bucketPadded) {
for { for {
for i := 0; i < entriesPerMapBucket; i++ { for i := 0; i < entriesPerMapBucket; i++ {
if b.entries[i].Load() == nil { if b.entries[i] == nil {
b.meta.Store(setByte(b.meta.Load(), h2, i)) b.meta = setByte(b.meta, h2, i)
b.entries[i].Store(e) b.entries[i] = unsafe.Pointer(e)
return return
} }
} }
if next := b.next.Load(); next == nil { if b.next == nil {
newb := new(bucketPadded[K, V]) newb := new(bucketPadded)
newb.meta.Store(setByte(defaultMeta, h2, 0)) newb.meta = setByte(defaultMeta, h2, 0)
newb.entries[0].Store(e) newb.entries[0] = unsafe.Pointer(e)
b.next.Store(newb) b.next = unsafe.Pointer(newb)
return return
} else {
b = next
} }
b = (*bucketPadded)(b.next)
} }
} }
@@ -753,11 +921,6 @@ func (table *mapTable[K, V]) addSize(bucketIdx uint64, delta int) {
atomic.AddInt64(&table.size[cidx].c, int64(delta)) atomic.AddInt64(&table.size[cidx].c, int64(delta))
} }
func (table *mapTable[K, V]) addSizePlain(bucketIdx uint64, delta int) {
cidx := uint64(len(table.size)-1) & bucketIdx
table.size[cidx].c += int64(delta)
}
func (table *mapTable[K, V]) sumSize() int64 { func (table *mapTable[K, V]) sumSize() int64 {
sum := int64(0) sum := int64(0)
for i := range table.size { for i := range table.size {
@@ -856,7 +1019,7 @@ func (m *Map[K, V]) Stats() MapStats {
nentriesLocal := 0 nentriesLocal := 0
stats.Capacity += entriesPerMapBucket stats.Capacity += entriesPerMapBucket
for i := 0; i < entriesPerMapBucket; i++ { for i := 0; i < entriesPerMapBucket; i++ {
if b.entries[i].Load() != nil { if atomic.LoadPointer(&b.entries[i]) != nil {
stats.Size++ stats.Size++
nentriesLocal++ nentriesLocal++
} }
@@ -865,11 +1028,10 @@ func (m *Map[K, V]) Stats() MapStats {
if nentriesLocal == 0 { if nentriesLocal == 0 {
stats.EmptyBuckets++ stats.EmptyBuckets++
} }
if next := b.next.Load(); next == nil { if b.next == nil {
break break
} else {
b = next
} }
b = (*bucketPadded)(atomic.LoadPointer(&b.next))
stats.TotalBuckets++ stats.TotalBuckets++
} }
if nentries < stats.MinEntries { if nentries < stats.MinEntries {
@@ -906,6 +1068,15 @@ func nextPowOf2(v uint32) uint32 {
return v return v
} }
func parallelism() uint32 {
maxProcs := uint32(runtime.GOMAXPROCS(0))
numCores := uint32(runtime.NumCPU())
if maxProcs < numCores {
return maxProcs
}
return numCores
}
func broadcast(b uint8) uint64 { func broadcast(b uint8) uint64 {
return 0x101010101010101 * uint64(b) return 0x101010101010101 * uint64(b)
} }
@@ -920,6 +1091,7 @@ func markZeroBytes(w uint64) uint64 {
return ((w - 0x0101010101010101) & (^w) & 0x8080808080808080) return ((w - 0x0101010101010101) & (^w) & 0x8080808080808080)
} }
// Sets byte of the input word at the specified index to the given value.
func setByte(w uint64, b uint8, idx int) uint64 { func setByte(w uint64, b uint8, idx int) uint64 {
shift := idx << 3 shift := idx << 3
return (w &^ (0xff << shift)) | (uint64(b) << shift) return (w &^ (0xff << shift)) | (uint64(b) << shift)

View File

@@ -3,6 +3,7 @@ package xsync
import ( import (
"math" "math"
"math/rand" "math/rand"
"runtime"
"strconv" "strconv"
"sync" "sync"
"sync/atomic" "sync/atomic"
@@ -53,11 +54,11 @@ func runParallel(b *testing.B, benchFn func(pb *testing.PB)) {
} }
func TestMap_BucketStructSize(t *testing.T) { func TestMap_BucketStructSize(t *testing.T) {
size := unsafe.Sizeof(bucketPadded[string, int64]{}) size := unsafe.Sizeof(bucketPadded{})
if size != 64 { if size != 64 {
t.Fatalf("size of 64B (one cache line) is expected, got: %d", size) t.Fatalf("size of 64B (one cache line) is expected, got: %d", size)
} }
size = unsafe.Sizeof(bucketPadded[struct{}, int32]{}) size = unsafe.Sizeof(bucketPadded{})
if size != 64 { if size != 64 {
t.Fatalf("size of 64B (one cache line) is expected, got: %d", size) t.Fatalf("size of 64B (one cache line) is expected, got: %d", size)
} }
@@ -743,10 +744,7 @@ func TestNewMapGrowOnly_OnlyShrinksOnClear(t *testing.T) {
} }
func TestMapResize(t *testing.T) { func TestMapResize(t *testing.T) {
testMapResize(t, NewMap[string, int]()) m := NewMap[string, int]()
}
func testMapResize(t *testing.T, m *Map[string, int]) {
const numEntries = 100_000 const numEntries = 100_000
for i := 0; i < numEntries; i++ { for i := 0; i < numEntries; i++ {
@@ -810,6 +808,147 @@ func TestMapResize_CounterLenLimit(t *testing.T) {
} }
} }
func testParallelResize(t *testing.T, numGoroutines int) {
m := NewMap[int, int]()
// Fill the map to trigger resizing
const initialEntries = 10000
const newEntries = 5000
for i := 0; i < initialEntries; i++ {
m.Store(i, i*2)
}
// Start concurrent operations that should trigger helping behavior
var wg sync.WaitGroup
// Launch goroutines that will encounter resize operations
for g := 0; g < numGoroutines; g++ {
wg.Add(1)
go func(goroutineID int) {
defer wg.Done()
// Perform many operations to trigger resize and helping
for i := 0; i < newEntries; i++ {
key := goroutineID*newEntries + i + initialEntries
m.Store(key, key*2)
// Verify the value
if val, ok := m.Load(key); !ok || val != key*2 {
t.Errorf("Failed to load key %d: got %v, %v", key, val, ok)
return
}
}
}(g)
}
wg.Wait()
// Verify all entries are present
finalSize := m.Size()
expectedSize := initialEntries + numGoroutines*newEntries
if finalSize != expectedSize {
t.Errorf("Expected size %d, got %d", expectedSize, finalSize)
}
stats := m.Stats()
if stats.TotalGrowths == 0 {
t.Error("Expected at least one table growth due to concurrent operations")
}
}
func TestMapParallelResize(t *testing.T) {
testParallelResize(t, 1)
testParallelResize(t, runtime.GOMAXPROCS(0))
testParallelResize(t, 100)
}
func testParallelResizeWithSameKeys(t *testing.T, numGoroutines int) {
m := NewMap[int, int]()
// Fill the map to trigger resizing
const entries = 1000
for i := 0; i < entries; i++ {
m.Store(2*i, 2*i)
}
// Start concurrent operations that should trigger helping behavior
var wg sync.WaitGroup
// Launch goroutines that will encounter resize operations
for g := 0; g < numGoroutines; g++ {
wg.Add(1)
go func(goroutineID int) {
defer wg.Done()
for i := 0; i < 10*entries; i++ {
m.Store(i, i)
}
}(g)
}
wg.Wait()
// Verify all entries are present
finalSize := m.Size()
expectedSize := 10 * entries
if finalSize != expectedSize {
t.Errorf("Expected size %d, got %d", expectedSize, finalSize)
}
stats := m.Stats()
if stats.TotalGrowths == 0 {
t.Error("Expected at least one table growth due to concurrent operations")
}
}
func TestMapParallelResize_IntersectingKeys(t *testing.T) {
testParallelResizeWithSameKeys(t, 1)
testParallelResizeWithSameKeys(t, runtime.GOMAXPROCS(0))
testParallelResizeWithSameKeys(t, 100)
}
func testParallelShrinking(t *testing.T, numGoroutines int) {
m := NewMap[int, int]()
// Fill the map to trigger resizing
const entries = 100000
for i := 0; i < entries; i++ {
m.Store(i, i)
}
// Start concurrent operations that should trigger helping behavior
var wg sync.WaitGroup
// Launch goroutines that will encounter resize operations
for g := 0; g < numGoroutines; g++ {
wg.Add(1)
go func(goroutineID int) {
defer wg.Done()
for i := 0; i < entries; i++ {
m.Delete(i)
}
}(g)
}
wg.Wait()
// Verify all entries are present
finalSize := m.Size()
if finalSize != 0 {
t.Errorf("Expected size 0, got %d", finalSize)
}
stats := m.Stats()
if stats.TotalShrinks == 0 {
t.Error("Expected at least one table shrinking due to concurrent operations")
}
}
func TestMapParallelShrinking(t *testing.T) {
testParallelShrinking(t, 1)
testParallelShrinking(t, runtime.GOMAXPROCS(0))
testParallelShrinking(t, 100)
}
func parallelSeqMapGrower(m *Map[int, int], numEntries int, positive bool, cdone chan bool) { func parallelSeqMapGrower(m *Map[int, int], numEntries int, positive bool, cdone chan bool) {
for i := 0; i < numEntries; i++ { for i := 0; i < numEntries; i++ {
if positive { if positive {
@@ -1459,7 +1598,7 @@ func BenchmarkMapRange(b *testing.B) {
} }
// Benchmarks noop performance of Compute // Benchmarks noop performance of Compute
func BenchmarkCompute(b *testing.B) { func BenchmarkMapCompute(b *testing.B) {
tests := []struct { tests := []struct {
Name string Name string
Op ComputeOp Op ComputeOp
@@ -1487,6 +1626,57 @@ func BenchmarkCompute(b *testing.B) {
} }
} }
func BenchmarkMapParallelRehashing(b *testing.B) {
tests := []struct {
name string
goroutines int
numEntries int
}{
{"1goroutine_10M", 1, 10_000_000},
{"4goroutines_10M", 4, 10_000_000},
{"8goroutines_10M", 8, 10_000_000},
}
for _, test := range tests {
b.Run(test.name, func(b *testing.B) {
for i := 0; i < b.N; i++ {
m := NewMap[int, int]()
var wg sync.WaitGroup
entriesPerGoroutine := test.numEntries / test.goroutines
start := time.Now()
for g := 0; g < test.goroutines; g++ {
wg.Add(1)
go func(goroutineID int) {
defer wg.Done()
base := goroutineID * entriesPerGoroutine
for j := 0; j < entriesPerGoroutine; j++ {
key := base + j
m.Store(key, key)
}
}(g)
}
wg.Wait()
duration := time.Since(start)
b.ReportMetric(float64(test.numEntries)/duration.Seconds(), "entries/s")
finalSize := m.Size()
if finalSize != test.numEntries {
b.Fatalf("Expected size %d, got %d", test.numEntries, finalSize)
}
stats := m.Stats()
if stats.TotalGrowths == 0 {
b.Error("Expected at least one table growth during rehashing")
}
}
})
}
}
func TestNextPowOf2(t *testing.T) { func TestNextPowOf2(t *testing.T) {
if nextPowOf2(0) != 1 { if nextPowOf2(0) != 1 {
t.Error("nextPowOf2 failed") t.Error("nextPowOf2 failed")

View File

@@ -1,17 +1,17 @@
package tls package ca
import ( import (
utls "github.com/metacubex/utls" "github.com/metacubex/tls"
) )
type ClientAuthType = utls.ClientAuthType type ClientAuthType = tls.ClientAuthType
const ( const (
NoClientCert = utls.NoClientCert NoClientCert = tls.NoClientCert
RequestClientCert = utls.RequestClientCert RequestClientCert = tls.RequestClientCert
RequireAnyClientCert = utls.RequireAnyClientCert RequireAnyClientCert = tls.RequireAnyClientCert
VerifyClientCertIfGiven = utls.VerifyClientCertIfGiven VerifyClientCertIfGiven = tls.VerifyClientCertIfGiven
RequireAndVerifyClientCert = utls.RequireAndVerifyClientCert RequireAndVerifyClientCert = tls.RequireAndVerifyClientCert
) )
func ClientAuthTypeFromString(s string) ClientAuthType { func ClientAuthTypeFromString(s string) ClientAuthType {

View File

@@ -1,7 +1,6 @@
package ca package ca
import ( import (
"crypto/tls"
"crypto/x509" "crypto/x509"
_ "embed" _ "embed"
"errors" "errors"
@@ -11,8 +10,9 @@ import (
"sync" "sync"
"github.com/metacubex/mihomo/common/once" "github.com/metacubex/mihomo/common/once"
C "github.com/metacubex/mihomo/constant"
"github.com/metacubex/mihomo/ntp" "github.com/metacubex/mihomo/ntp"
"github.com/metacubex/tls"
) )
var globalCertPool *x509.CertPool var globalCertPool *x509.CertPool
@@ -106,12 +106,13 @@ func GetTLSConfig(opt Option) (tlsConfig *tls.Config, err error) {
} }
if len(opt.Certificate) > 0 || len(opt.PrivateKey) > 0 { if len(opt.Certificate) > 0 || len(opt.PrivateKey) > 0 {
var cert tls.Certificate certLoader, err := NewTLSKeyPairLoader(opt.Certificate, opt.PrivateKey)
cert, err = LoadTLSKeyPair(opt.Certificate, opt.PrivateKey, C.Path)
if err != nil { if err != nil {
return nil, err return nil, err
} }
tlsConfig.Certificates = []tls.Certificate{cert} tlsConfig.GetClientCertificate = func(*tls.CertificateRequestInfo) (*tls.Certificate, error) {
return certLoader()
}
} }
return tlsConfig, nil return tlsConfig, nil
} }

View File

@@ -7,71 +7,85 @@ import (
"crypto/elliptic" "crypto/elliptic"
"crypto/rand" "crypto/rand"
"crypto/rsa" "crypto/rsa"
"crypto/tls"
"crypto/x509" "crypto/x509"
"encoding/pem" "encoding/pem"
"fmt" "fmt"
"math/big" "math/big"
"os" "os"
"runtime"
"sync"
"time" "time"
C "github.com/metacubex/mihomo/constant"
"github.com/metacubex/fswatch"
"github.com/metacubex/tls"
) )
type Path interface { // NewTLSKeyPairLoader creates a loader function for TLS key pairs from the provided certificate and private key data or file paths.
Resolve(path string) string
IsSafePath(path string) bool
ErrNotSafePath(path string) error
}
// LoadTLSKeyPair loads a TLS key pair from the provided certificate and private key data or file paths, supporting fallback resolution.
// Returns a tls.Certificate and an error, where the error indicates issues during parsing or file loading.
// If both certificate and privateKey are empty, generates a random TLS RSA key pair. // If both certificate and privateKey are empty, generates a random TLS RSA key pair.
// Accepts a Path interface for resolving file paths when necessary. func NewTLSKeyPairLoader(certificate, privateKey string) (func() (*tls.Certificate, error), error) {
func LoadTLSKeyPair(certificate, privateKey string, path Path) (tls.Certificate, error) {
if certificate == "" && privateKey == "" { if certificate == "" && privateKey == "" {
var err error var err error
certificate, privateKey, _, err = NewRandomTLSKeyPair(KeyPairTypeRSA) certificate, privateKey, _, err = NewRandomTLSKeyPair(KeyPairTypeRSA)
if err != nil { if err != nil {
return tls.Certificate{}, err return nil, err
} }
} }
cert, painTextErr := tls.X509KeyPair([]byte(certificate), []byte(privateKey)) cert, painTextErr := tls.X509KeyPair([]byte(certificate), []byte(privateKey))
if painTextErr == nil { if painTextErr == nil {
return cert, nil return func() (*tls.Certificate, error) {
} return &cert, nil
if path == nil { }, nil
return tls.Certificate{}, painTextErr
} }
certificate = path.Resolve(certificate) certificate = C.Path.Resolve(certificate)
privateKey = path.Resolve(privateKey) privateKey = C.Path.Resolve(privateKey)
var loadErr error var loadErr error
if !path.IsSafePath(certificate) { if !C.Path.IsSafePath(certificate) {
loadErr = path.ErrNotSafePath(certificate) loadErr = C.Path.ErrNotSafePath(certificate)
} else if !path.IsSafePath(privateKey) { } else if !C.Path.IsSafePath(privateKey) {
loadErr = path.ErrNotSafePath(privateKey) loadErr = C.Path.ErrNotSafePath(privateKey)
} else { } else {
cert, loadErr = tls.LoadX509KeyPair(certificate, privateKey) cert, loadErr = tls.LoadX509KeyPair(certificate, privateKey)
} }
if loadErr != nil { if loadErr != nil {
return tls.Certificate{}, fmt.Errorf("parse certificate failed, maybe format error:%s, or path error: %s", painTextErr.Error(), loadErr.Error()) return nil, fmt.Errorf("parse certificate failed, maybe format error:%s, or path error: %s", painTextErr.Error(), loadErr.Error())
} }
return cert, nil gcFlag := new(os.File) // tiny (on the order of 16 bytes or less) and pointer-free objects may never run the finalizer, so we choose new an os.File
updateMutex := sync.RWMutex{}
if watcher, err := fswatch.NewWatcher(fswatch.Options{Path: []string{certificate, privateKey}, Callback: func(path string) {
updateMutex.Lock()
defer updateMutex.Unlock()
if newCert, err := tls.LoadX509KeyPair(certificate, privateKey); err == nil {
cert = newCert
}
}}); err == nil {
if err = watcher.Start(); err == nil {
runtime.SetFinalizer(gcFlag, func(f *os.File) {
_ = watcher.Close()
})
}
}
return func() (*tls.Certificate, error) {
defer runtime.KeepAlive(gcFlag)
updateMutex.RLock()
defer updateMutex.RUnlock()
return &cert, nil
}, nil
} }
func LoadCertificates(certificate string, path Path) (*x509.CertPool, error) { func LoadCertificates(certificate string) (*x509.CertPool, error) {
pool := x509.NewCertPool() pool := x509.NewCertPool()
if pool.AppendCertsFromPEM([]byte(certificate)) { if pool.AppendCertsFromPEM([]byte(certificate)) {
return pool, nil return pool, nil
} }
painTextErr := fmt.Errorf("invalid certificate: %s", certificate) painTextErr := fmt.Errorf("invalid certificate: %s", certificate)
if path == nil {
return nil, painTextErr
}
certificate = path.Resolve(certificate) certificate = C.Path.Resolve(certificate)
var loadErr error var loadErr error
if !path.IsSafePath(certificate) { if !C.Path.IsSafePath(certificate) {
loadErr = path.ErrNotSafePath(certificate) loadErr = C.Path.ErrNotSafePath(certificate)
} else { } else {
certPEMBlock, err := os.ReadFile(certificate) certPEMBlock, err := os.ReadFile(certificate)
if pool.AppendCertsFromPEM(certPEMBlock) { if pool.AppendCertsFromPEM(certPEMBlock) {
@@ -82,6 +96,9 @@ func LoadCertificates(certificate string, path Path) (*x509.CertPool, error) {
if loadErr != nil { if loadErr != nil {
return nil, fmt.Errorf("parse certificate failed, maybe format error:%s, or path error: %s", painTextErr.Error(), loadErr.Error()) return nil, fmt.Errorf("parse certificate failed, maybe format error:%s, or path error: %s", painTextErr.Error(), loadErr.Error())
} }
//TODO: support dynamic update pool too
// blocked by: https://github.com/golang/go/issues/64796
// maybe we can direct add `GetRootCAs` and `GetClientCAs` to ourselves tls fork
return pool, nil return pool, nil
} }

View File

@@ -13,6 +13,7 @@ import (
"time" "time"
"github.com/metacubex/mihomo/component/keepalive" "github.com/metacubex/mihomo/component/keepalive"
"github.com/metacubex/mihomo/component/mptcp"
"github.com/metacubex/mihomo/component/resolver" "github.com/metacubex/mihomo/component/resolver"
) )
@@ -140,9 +141,7 @@ func dialContext(ctx context.Context, network string, destination netip.Addr, po
dialer := netDialer.(*net.Dialer) dialer := netDialer.(*net.Dialer)
keepalive.SetNetDialer(dialer) keepalive.SetNetDialer(dialer)
if opt.mpTcp { mptcp.SetNetDialer(dialer, opt.mpTcp)
setMultiPathTCP(dialer)
}
if DefaultSocketHook != nil { // ignore interfaceName, routingMark and tfo when DefaultSocketHook not null (in CMFA) if DefaultSocketHook != nil { // ignore interfaceName, routingMark and tfo when DefaultSocketHook not null (in CMFA)
socketHookToToDialer(dialer) socketHookToToDialer(dialer)

View File

@@ -1,12 +0,0 @@
//go:build !go1.21
package dialer
import (
"net"
)
const multipathTCPAvailable = false
func setMultiPathTCP(dialer *net.Dialer) {
}

View File

@@ -1,11 +0,0 @@
//go:build go1.21
package dialer
import "net"
const multipathTCPAvailable = true
func setMultiPathTCP(dialer *net.Dialer) {
dialer.SetMultipathTCP(true)
}

View File

@@ -5,13 +5,33 @@ import (
"fmt" "fmt"
tlsC "github.com/metacubex/mihomo/component/tls" tlsC "github.com/metacubex/mihomo/component/tls"
"github.com/metacubex/tls"
) )
type Config struct { type Config struct {
GetEncryptedClientHelloConfigList func(ctx context.Context, serverName string) ([]byte, error) GetEncryptedClientHelloConfigList func(ctx context.Context, serverName string) ([]byte, error)
} }
func (cfg *Config) ClientHandle(ctx context.Context, tlsConfig *tlsC.Config) (err error) { func (cfg *Config) ClientHandle(ctx context.Context, tlsConfig *tls.Config) (err error) {
if cfg == nil {
return nil
}
echConfigList, err := cfg.GetEncryptedClientHelloConfigList(ctx, tlsConfig.ServerName)
if err != nil {
return fmt.Errorf("resolve ECH config error: %w", err)
}
tlsConfig.EncryptedClientHelloConfigList = echConfigList
if tlsConfig.MinVersion != 0 && tlsConfig.MinVersion < tls.VersionTLS13 {
tlsConfig.MinVersion = tls.VersionTLS13
}
if tlsConfig.MaxVersion != 0 && tlsConfig.MaxVersion < tls.VersionTLS13 {
tlsConfig.MaxVersion = tls.VersionTLS13
}
return nil
}
func (cfg *Config) ClientHandleUTLS(ctx context.Context, tlsConfig *tlsC.Config) (err error) {
if cfg == nil { if cfg == nil {
return nil return nil
} }

View File

@@ -0,0 +1,147 @@
package echparser
import (
"errors"
"fmt"
"golang.org/x/crypto/cryptobyte"
)
// export from std's crypto/tls/ech.go
const extensionEncryptedClientHello = 0xfe0d
type ECHCipher struct {
KDFID uint16
AEADID uint16
}
type ECHExtension struct {
Type uint16
Data []byte
}
type ECHConfig struct {
raw []byte
Version uint16
Length uint16
ConfigID uint8
KemID uint16
PublicKey []byte
SymmetricCipherSuite []ECHCipher
MaxNameLength uint8
PublicName []byte
Extensions []ECHExtension
}
var ErrMalformedECHConfigList = errors.New("tls: malformed ECHConfigList")
type EchConfigErr struct {
field string
}
func (e *EchConfigErr) Error() string {
if e.field == "" {
return "tls: malformed ECHConfig"
}
return fmt.Sprintf("tls: malformed ECHConfig, invalid %s field", e.field)
}
func ParseECHConfig(enc []byte) (skip bool, ec ECHConfig, err error) {
s := cryptobyte.String(enc)
ec.raw = []byte(enc)
if !s.ReadUint16(&ec.Version) {
return false, ECHConfig{}, &EchConfigErr{"version"}
}
if !s.ReadUint16(&ec.Length) {
return false, ECHConfig{}, &EchConfigErr{"length"}
}
if len(ec.raw) < int(ec.Length)+4 {
return false, ECHConfig{}, &EchConfigErr{"length"}
}
ec.raw = ec.raw[:ec.Length+4]
if ec.Version != extensionEncryptedClientHello {
s.Skip(int(ec.Length))
return true, ECHConfig{}, nil
}
if !s.ReadUint8(&ec.ConfigID) {
return false, ECHConfig{}, &EchConfigErr{"config_id"}
}
if !s.ReadUint16(&ec.KemID) {
return false, ECHConfig{}, &EchConfigErr{"kem_id"}
}
if !s.ReadUint16LengthPrefixed((*cryptobyte.String)(&ec.PublicKey)) {
return false, ECHConfig{}, &EchConfigErr{"public_key"}
}
var cipherSuites cryptobyte.String
if !s.ReadUint16LengthPrefixed(&cipherSuites) {
return false, ECHConfig{}, &EchConfigErr{"cipher_suites"}
}
for !cipherSuites.Empty() {
var c ECHCipher
if !cipherSuites.ReadUint16(&c.KDFID) {
return false, ECHConfig{}, &EchConfigErr{"cipher_suites kdf_id"}
}
if !cipherSuites.ReadUint16(&c.AEADID) {
return false, ECHConfig{}, &EchConfigErr{"cipher_suites aead_id"}
}
ec.SymmetricCipherSuite = append(ec.SymmetricCipherSuite, c)
}
if !s.ReadUint8(&ec.MaxNameLength) {
return false, ECHConfig{}, &EchConfigErr{"maximum_name_length"}
}
var publicName cryptobyte.String
if !s.ReadUint8LengthPrefixed(&publicName) {
return false, ECHConfig{}, &EchConfigErr{"public_name"}
}
ec.PublicName = publicName
var extensions cryptobyte.String
if !s.ReadUint16LengthPrefixed(&extensions) {
return false, ECHConfig{}, &EchConfigErr{"extensions"}
}
for !extensions.Empty() {
var e ECHExtension
if !extensions.ReadUint16(&e.Type) {
return false, ECHConfig{}, &EchConfigErr{"extensions type"}
}
if !extensions.ReadUint16LengthPrefixed((*cryptobyte.String)(&e.Data)) {
return false, ECHConfig{}, &EchConfigErr{"extensions data"}
}
ec.Extensions = append(ec.Extensions, e)
}
return false, ec, nil
}
// ParseECHConfigList parses a draft-ietf-tls-esni-18 ECHConfigList, returning a
// slice of parsed ECHConfigs, in the same order they were parsed, or an error
// if the list is malformed.
func ParseECHConfigList(data []byte) ([]ECHConfig, error) {
s := cryptobyte.String(data)
var length uint16
if !s.ReadUint16(&length) {
return nil, ErrMalformedECHConfigList
}
if length != uint16(len(data)-2) {
return nil, ErrMalformedECHConfigList
}
var configs []ECHConfig
for len(s) > 0 {
if len(s) < 4 {
return nil, errors.New("tls: malformed ECHConfig")
}
configLen := uint16(s[2])<<8 | uint16(s[3])
skip, ec, err := ParseECHConfig(s)
if err != nil {
return nil, err
}
s = s[configLen+4:]
if !skip {
configs = append(configs, ec)
}
}
return configs, nil
}

View File

@@ -8,10 +8,13 @@ import (
"errors" "errors"
"fmt" "fmt"
"os" "os"
"runtime"
"sync"
"github.com/metacubex/mihomo/component/ca" C "github.com/metacubex/mihomo/constant"
tlsC "github.com/metacubex/mihomo/component/tls"
"github.com/metacubex/fswatch"
"github.com/metacubex/tls"
"golang.org/x/crypto/cryptobyte" "golang.org/x/crypto/cryptobyte"
) )
@@ -85,11 +88,11 @@ func GenECHConfig(publicName string) (configBase64 string, keyPem string, err er
return return
} }
func UnmarshalECHKeys(raw []byte) ([]tlsC.EncryptedClientHelloKey, error) { func UnmarshalECHKeys(raw []byte) ([]tls.EncryptedClientHelloKey, error) {
var keys []tlsC.EncryptedClientHelloKey var keys []tls.EncryptedClientHelloKey
rawString := cryptobyte.String(raw) rawString := cryptobyte.String(raw)
for !rawString.Empty() { for !rawString.Empty() {
var key tlsC.EncryptedClientHelloKey var key tls.EncryptedClientHelloKey
if !rawString.ReadUint16LengthPrefixed((*cryptobyte.String)(&key.PrivateKey)) { if !rawString.ReadUint16LengthPrefixed((*cryptobyte.String)(&key.PrivateKey)) {
return nil, errors.New("error parsing private key") return nil, errors.New("error parsing private key")
} }
@@ -104,40 +107,65 @@ func UnmarshalECHKeys(raw []byte) ([]tlsC.EncryptedClientHelloKey, error) {
return keys, nil return keys, nil
} }
func LoadECHKey(key string, tlsConfig *tlsC.Config, path ca.Path) error { func LoadECHKey(key string, tlsConfig *tls.Config) error {
if key == "" { if key == "" {
return nil return nil
} }
painTextErr := loadECHKey([]byte(key), tlsConfig) echKeys, painTextErr := loadECHKey([]byte(key))
if painTextErr == nil { if painTextErr == nil {
tlsConfig.GetEncryptedClientHelloKeys = func(info *tls.ClientHelloInfo) ([]tls.EncryptedClientHelloKey, error) {
return echKeys, nil
}
return nil return nil
} }
key = path.Resolve(key) key = C.Path.Resolve(key)
var loadErr error var loadErr error
if !path.IsSafePath(key) { if !C.Path.IsSafePath(key) {
loadErr = path.ErrNotSafePath(key) loadErr = C.Path.ErrNotSafePath(key)
} else { } else {
var echKey []byte var echKey []byte
echKey, loadErr = os.ReadFile(key) echKey, loadErr = os.ReadFile(key)
if loadErr == nil { if loadErr == nil {
loadErr = loadECHKey(echKey, tlsConfig) echKeys, loadErr = loadECHKey(echKey)
} }
} }
if loadErr != nil { if loadErr != nil {
return fmt.Errorf("parse ECH keys failed, maybe format error:%s, or path error: %s", painTextErr.Error(), loadErr.Error()) return fmt.Errorf("parse ECH keys failed, maybe format error:%s, or path error: %s", painTextErr.Error(), loadErr.Error())
} }
gcFlag := new(os.File) // tiny (on the order of 16 bytes or less) and pointer-free objects may never run the finalizer, so we choose new an os.File
updateMutex := sync.RWMutex{}
if watcher, err := fswatch.NewWatcher(fswatch.Options{Path: []string{key}, Callback: func(path string) {
updateMutex.Lock()
defer updateMutex.Unlock()
if echKey, err := os.ReadFile(key); err == nil {
if newEchKeys, err := loadECHKey(echKey); err == nil {
echKeys = newEchKeys
}
}
}}); err == nil {
if err = watcher.Start(); err == nil {
runtime.SetFinalizer(gcFlag, func(f *os.File) {
_ = watcher.Close()
})
}
}
tlsConfig.GetEncryptedClientHelloKeys = func(info *tls.ClientHelloInfo) ([]tls.EncryptedClientHelloKey, error) {
defer runtime.KeepAlive(gcFlag)
updateMutex.RLock()
defer updateMutex.RUnlock()
return echKeys, nil
}
return nil return nil
} }
func loadECHKey(echKey []byte, tlsConfig *tlsC.Config) error { func loadECHKey(echKey []byte) ([]tls.EncryptedClientHelloKey, error) {
block, rest := pem.Decode(echKey) block, rest := pem.Decode(echKey)
if block == nil || block.Type != "ECH KEYS" || len(rest) > 0 { if block == nil || block.Type != "ECH KEYS" || len(rest) > 0 {
return errors.New("invalid ECH keys pem") return nil, errors.New("invalid ECH keys pem")
} }
echKeys, err := UnmarshalECHKeys(block.Bytes) echKeys, err := UnmarshalECHKeys(block.Bytes)
if err != nil { if err != nil {
return fmt.Errorf("parse ECH keys: %w", err) return nil, fmt.Errorf("parse ECH keys: %w", err)
} }
tlsConfig.EncryptedClientHelloKeys = echKeys return echKeys, err
return nil
} }

30
component/ech/key_test.go Normal file
View File

@@ -0,0 +1,30 @@
package ech
import (
"encoding/base64"
"testing"
"github.com/metacubex/mihomo/component/ech/echparser"
)
func TestGenECHConfig(t *testing.T) {
domain := "www.example.com"
configBase64, _, err := GenECHConfig(domain)
if err != nil {
t.Error(err)
}
echConfigList, err := base64.StdEncoding.DecodeString(configBase64)
if err != nil {
t.Error(err)
}
echConfigs, err := echparser.ParseECHConfigList(echConfigList)
if err != nil {
t.Error(err)
}
if len(echConfigs) == 0 {
t.Error("no ech config")
}
if publicName := string(echConfigs[0].PublicName); publicName != domain {
t.Error("ech config domain error, expect ", domain, " got", publicName)
}
}

View File

@@ -4,13 +4,29 @@ import (
C "github.com/metacubex/mihomo/constant" C "github.com/metacubex/mihomo/constant"
) )
const (
UseFakeIP = "fake-ip"
UseRealIP = "real-ip"
)
type Skipper struct { type Skipper struct {
Host []C.DomainMatcher Rules []C.Rule
Mode C.FilterMode Host []C.DomainMatcher
Mode C.FilterMode
} }
// ShouldSkipped return if domain should be skipped // ShouldSkipped return if domain should be skipped
func (p *Skipper) ShouldSkipped(domain string) bool { func (p *Skipper) ShouldSkipped(domain string) bool {
if len(p.Rules) > 0 {
metadata := &C.Metadata{Host: domain}
for _, rule := range p.Rules {
if matched, action := rule.Match(metadata, C.RuleMatchHelper{}); matched {
return action == UseRealIP
}
}
return false
}
should := p.shouldSkipped(domain) should := p.shouldSkipped(domain)
if p.Mode == C.FilterWhiteList { if p.Mode == C.FilterWhiteList {
return !should return !should

View File

@@ -5,6 +5,7 @@ import (
"fmt" "fmt"
"github.com/metacubex/mihomo/component/ech" "github.com/metacubex/mihomo/component/ech"
"github.com/metacubex/mihomo/transport/sudoku"
"github.com/metacubex/mihomo/transport/vless/encryption" "github.com/metacubex/mihomo/transport/vless/encryption"
"github.com/gofrs/uuid/v5" "github.com/gofrs/uuid/v5"
@@ -12,7 +13,7 @@ import (
func Main(args []string) { func Main(args []string) {
if len(args) < 1 { if len(args) < 1 {
panic("Using: generate uuid/reality-keypair/wg-keypair/ech-keypair/vless-mlkem768/vless-x25519") panic("Using: generate uuid/reality-keypair/wg-keypair/ech-keypair/vless-mlkem768/vless-x25519/sudoku-keypair")
} }
switch args[0] { switch args[0] {
case "uuid": case "uuid":
@@ -69,5 +70,13 @@ func Main(args []string) {
fmt.Println("PrivateKey: " + privateKeyBase64) fmt.Println("PrivateKey: " + privateKeyBase64)
fmt.Println("Password: " + passwordBase64) fmt.Println("Password: " + passwordBase64)
fmt.Println("Hash32: " + hash32Base64) fmt.Println("Hash32: " + hash32Base64)
case "sudoku-keypair":
privateKey, publicKey, err := sudoku.GenKeyPair()
if err != nil {
panic(err)
}
// Output: Available Private Key for client, Master Public Key for server
fmt.Println("PrivateKey: " + privateKey)
fmt.Println("PublicKey: " + publicKey)
} }
} }

View File

@@ -4,7 +4,6 @@ import (
"context" "context"
"fmt" "fmt"
"io" "io"
"net/http"
"os" "os"
"sync" "sync"
"time" "time"
@@ -14,6 +13,8 @@ import (
"github.com/metacubex/mihomo/component/mmdb" "github.com/metacubex/mihomo/component/mmdb"
C "github.com/metacubex/mihomo/constant" C "github.com/metacubex/mihomo/constant"
"github.com/metacubex/mihomo/log" "github.com/metacubex/mihomo/log"
"github.com/metacubex/http"
) )
var ( var (

View File

@@ -4,7 +4,6 @@ import (
"context" "context"
"io" "io"
"net" "net"
"net/http"
URL "net/url" URL "net/url"
"runtime" "runtime"
"strings" "strings"
@@ -13,6 +12,8 @@ import (
"github.com/metacubex/mihomo/component/ca" "github.com/metacubex/mihomo/component/ca"
"github.com/metacubex/mihomo/component/dialer" "github.com/metacubex/mihomo/component/dialer"
"github.com/metacubex/mihomo/listener/inner" "github.com/metacubex/mihomo/listener/inner"
"github.com/metacubex/http"
) )
var ( var (

View File

@@ -1,9 +1,9 @@
package memory package memory
import ( import (
"syscall"
"unsafe" "unsafe"
_ "unsafe"
"github.com/ebitengine/purego"
) )
const PROC_PIDTASKINFO = 4 const PROC_PIDTASKINFO = 4
@@ -29,24 +29,12 @@ type ProcTaskInfo struct {
Priority int32 Priority int32
} }
const System = "/usr/lib/libSystem.B.dylib"
type ProcPidInfoFunc func(pid, flavor int32, arg uint64, buffer uintptr, bufferSize int32) int32
const ProcPidInfoSym = "proc_pidinfo"
func GetMemoryInfo(pid int32) (*MemoryInfoStat, error) { func GetMemoryInfo(pid int32) (*MemoryInfoStat, error) {
lib, err := purego.Dlopen(System, purego.RTLD_LAZY|purego.RTLD_GLOBAL)
if err != nil {
return nil, err
}
defer purego.Dlclose(lib)
var procPidInfo ProcPidInfoFunc
purego.RegisterLibFunc(&procPidInfo, lib, ProcPidInfoSym)
var ti ProcTaskInfo var ti ProcTaskInfo
procPidInfo(pid, PROC_PIDTASKINFO, 0, uintptr(unsafe.Pointer(&ti)), int32(unsafe.Sizeof(ti))) _, _, errno := syscall_syscall6(proc_pidinfo_trampoline_addr, uintptr(pid), PROC_PIDTASKINFO, 0, uintptr(unsafe.Pointer(&ti)), unsafe.Sizeof(ti), 0)
if errno != 0 {
return nil, errno
}
ret := &MemoryInfoStat{ ret := &MemoryInfoStat{
RSS: uint64(ti.Resident_size), RSS: uint64(ti.Resident_size),
@@ -54,3 +42,26 @@ func GetMemoryInfo(pid int32) (*MemoryInfoStat, error) {
} }
return ret, nil return ret, nil
} }
var proc_pidinfo_trampoline_addr uintptr
//go:cgo_import_dynamic proc_pidinfo proc_pidinfo "/usr/lib/libSystem.B.dylib"
// from golang.org/x/sys@v0.30.0/unix/syscall_darwin_libSystem.go
// Implemented in the runtime package (runtime/sys_darwin.go)
func syscall_syscall(fn, a1, a2, a3 uintptr) (r1, r2 uintptr, err syscall.Errno)
func syscall_syscall6(fn, a1, a2, a3, a4, a5, a6 uintptr) (r1, r2 uintptr, err syscall.Errno)
func syscall_syscall6X(fn, a1, a2, a3, a4, a5, a6 uintptr) (r1, r2 uintptr, err syscall.Errno)
func syscall_syscall9(fn, a1, a2, a3, a4, a5, a6, a7, a8, a9 uintptr) (r1, r2 uintptr, err syscall.Errno) // 32-bit only
func syscall_rawSyscall(fn, a1, a2, a3 uintptr) (r1, r2 uintptr, err syscall.Errno)
func syscall_rawSyscall6(fn, a1, a2, a3, a4, a5, a6 uintptr) (r1, r2 uintptr, err syscall.Errno)
func syscall_syscallPtr(fn, a1, a2, a3 uintptr) (r1, r2 uintptr, err syscall.Errno)
//go:linkname syscall_syscall syscall.syscall
//go:linkname syscall_syscall6 syscall.syscall6
//go:linkname syscall_syscall6X syscall.syscall6X
//go:linkname syscall_syscall9 syscall.syscall9
//go:linkname syscall_rawSyscall syscall.rawSyscall
//go:linkname syscall_rawSyscall6 syscall.rawSyscall6
//go:linkname syscall_syscallPtr syscall.syscallPtr

View File

@@ -0,0 +1,9 @@
// go run mkasm.go darwin amd64
// Code generated by the command above; DO NOT EDIT.
#include "textflag.h"
TEXT proc_pidinfo_trampoline<>(SB),NOSPLIT,$0-0
JMP proc_pidinfo(SB)
GLOBL ·proc_pidinfo_trampoline_addr(SB), RODATA, $8
DATA ·proc_pidinfo_trampoline_addr(SB)/8, $proc_pidinfo_trampoline<>(SB)

View File

@@ -0,0 +1,9 @@
// go run mkasm.go darwin arm64
// Code generated by the command above; DO NOT EDIT.
#include "textflag.h"
TEXT proc_pidinfo_trampoline<>(SB),NOSPLIT,$0-0
JMP proc_pidinfo(SB)
GLOBL ·proc_pidinfo_trampoline_addr(SB), RODATA, $8
DATA ·proc_pidinfo_trampoline_addr(SB)/8, $proc_pidinfo_trampoline<>(SB)

View File

@@ -0,0 +1,23 @@
//go:build !go1.21
package mptcp
import (
"net"
)
const MultipathTCPAvailable = false
func SetNetDialer(dialer *net.Dialer, open bool) {
}
func GetNetDialer(dialer *net.Dialer) bool {
return false
}
func SetNetListenConfig(listenConfig *net.ListenConfig, open bool) {
}
func GetNetListenConfig(listenConfig *net.ListenConfig) bool {
return false
}

View File

@@ -0,0 +1,23 @@
//go:build go1.21
package mptcp
import "net"
const MultipathTCPAvailable = true
func SetNetDialer(dialer *net.Dialer, open bool) {
dialer.SetMultipathTCP(open)
}
func GetNetDialer(dialer *net.Dialer) bool {
return dialer.MultipathTCP()
}
func SetNetListenConfig(listenConfig *net.ListenConfig, open bool) {
listenConfig.SetMultipathTCP(open)
}
func GetNetListenConfig(listenConfig *net.ListenConfig) bool {
return listenConfig.MultipathTCP()
}

View File

@@ -197,6 +197,10 @@ func newSearcher(major int) *searcher {
case 12: case 12:
fallthrough fallthrough
case 13: case 13:
fallthrough
case 14:
fallthrough
case 15:
s = &searcher{ s = &searcher{
headSize: 64, headSize: 64,
tcpItemSize: 744, tcpItemSize: 744,

View File

@@ -0,0 +1,37 @@
package proxydialer
import (
"context"
"fmt"
"net"
"net/netip"
C "github.com/metacubex/mihomo/constant"
"github.com/metacubex/mihomo/tunnel"
)
type byNameProxyDialer struct {
proxyName string
}
func (d byNameProxyDialer) DialContext(ctx context.Context, network, address string) (net.Conn, error) {
proxies := tunnel.Proxies()
proxy, ok := proxies[d.proxyName]
if !ok {
return nil, fmt.Errorf("proxyName[%s] not found", d.proxyName)
}
return New(proxy, true).DialContext(ctx, network, address)
}
func (d byNameProxyDialer) ListenPacket(ctx context.Context, network, address string, rAddrPort netip.AddrPort) (net.PacketConn, error) {
proxies := tunnel.Proxies()
proxy, ok := proxies[d.proxyName]
if !ok {
return nil, fmt.Errorf("proxyName[%s] not found", d.proxyName)
}
return New(proxy, true).ListenPacket(ctx, network, address, rAddrPort)
}
func NewByName(proxyName string) C.Dialer {
return byNameProxyDialer{proxyName: proxyName}
}

View File

@@ -2,34 +2,22 @@ package proxydialer
import ( import (
"context" "context"
"fmt"
"net" "net"
"net/netip" "net/netip"
"strings" "strings"
N "github.com/metacubex/mihomo/common/net" N "github.com/metacubex/mihomo/common/net"
"github.com/metacubex/mihomo/component/dialer"
C "github.com/metacubex/mihomo/constant" C "github.com/metacubex/mihomo/constant"
"github.com/metacubex/mihomo/tunnel"
"github.com/metacubex/mihomo/tunnel/statistic" "github.com/metacubex/mihomo/tunnel/statistic"
) )
type proxyDialer struct { type proxyDialer struct {
proxy C.ProxyAdapter proxy C.ProxyAdapter
dialer C.Dialer
statistic bool statistic bool
} }
func New(proxy C.ProxyAdapter, dialer C.Dialer, statistic bool) C.Dialer { func New(proxy C.ProxyAdapter, statistic bool) C.Dialer {
return proxyDialer{proxy: proxy, dialer: dialer, statistic: statistic} return proxyDialer{proxy: proxy, statistic: statistic}
}
func NewByName(proxyName string, dialer C.Dialer) (C.Dialer, error) {
proxies := tunnel.Proxies()
if proxy, ok := proxies[proxyName]; ok {
return New(proxy, dialer, true), nil
}
return nil, fmt.Errorf("proxyName[%s] not found", proxyName)
} }
func (p proxyDialer) DialContext(ctx context.Context, network, address string) (net.Conn, error) { func (p proxyDialer) DialContext(ctx context.Context, network, address string) (net.Conn, error) {
@@ -50,13 +38,7 @@ func (p proxyDialer) DialContext(ctx context.Context, network, address string) (
} }
return N.NewBindPacketConn(pc, currentMeta.UDPAddr()), nil return N.NewBindPacketConn(pc, currentMeta.UDPAddr()), nil
} }
var conn C.Conn conn, err := p.proxy.DialContext(ctx, currentMeta)
var err error
if _, ok := p.dialer.(dialer.Dialer); ok { // first using old function to let mux work
conn, err = p.proxy.DialContext(ctx, currentMeta)
} else {
conn, err = p.proxy.DialContextWithDialer(ctx, p.dialer, currentMeta)
}
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -72,14 +54,8 @@ func (p proxyDialer) ListenPacket(ctx context.Context, network, address string,
} }
func (p proxyDialer) listenPacket(ctx context.Context, currentMeta *C.Metadata) (C.PacketConn, error) { func (p proxyDialer) listenPacket(ctx context.Context, currentMeta *C.Metadata) (C.PacketConn, error) {
var pc C.PacketConn
var err error
currentMeta.NetWork = C.UDP currentMeta.NetWork = C.UDP
if _, ok := p.dialer.(dialer.Dialer); ok { // first using old function to let mux work pc, err := p.proxy.ListenPacketContext(ctx, currentMeta)
pc, err = p.proxy.ListenPacketContext(ctx, currentMeta)
} else {
pc, err = p.proxy.ListenPacketWithDialer(ctx, p.dialer, currentMeta)
}
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@@ -12,71 +12,22 @@ import (
type SingDialer interface { type SingDialer interface {
N.Dialer N.Dialer
SetDialer(dialer C.Dialer)
} }
type singDialer proxyDialer type singDialer struct {
cDialer C.Dialer
}
var _ N.Dialer = (*singDialer)(nil) var _ N.Dialer = (*singDialer)(nil)
func (d *singDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { func (d singDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
return (*proxyDialer)(d).DialContext(ctx, network, destination.String()) return d.cDialer.DialContext(ctx, network, destination.String())
} }
func (d *singDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { func (d singDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
return (*proxyDialer)(d).ListenPacket(ctx, "udp", "", destination.AddrPort()) return d.cDialer.ListenPacket(ctx, "udp", "", destination.AddrPort())
} }
func (d *singDialer) SetDialer(dialer C.Dialer) { func NewSingDialer(cDialer C.Dialer) SingDialer {
(*proxyDialer)(d).dialer = dialer return singDialer{cDialer: cDialer}
}
func NewSingDialer(proxy C.ProxyAdapter, dialer C.Dialer, statistic bool) SingDialer {
return (*singDialer)(&proxyDialer{
proxy: proxy,
dialer: dialer,
statistic: statistic,
})
}
type byNameSingDialer struct {
dialer C.Dialer
proxyName string
}
var _ N.Dialer = (*byNameSingDialer)(nil)
func (d *byNameSingDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
var cDialer C.Dialer = d.dialer
if len(d.proxyName) > 0 {
pd, err := NewByName(d.proxyName, d.dialer)
if err != nil {
return nil, err
}
cDialer = pd
}
return cDialer.DialContext(ctx, network, destination.String())
}
func (d *byNameSingDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
var cDialer C.Dialer = d.dialer
if len(d.proxyName) > 0 {
pd, err := NewByName(d.proxyName, d.dialer)
if err != nil {
return nil, err
}
cDialer = pd
}
return cDialer.ListenPacket(ctx, "udp", "", destination.AddrPort())
}
func (d *byNameSingDialer) SetDialer(dialer C.Dialer) {
d.dialer = dialer
}
func NewByNameSingDialer(proxyName string, dialer C.Dialer) SingDialer {
return &byNameSingDialer{
dialer: dialer,
proxyName: proxyName,
}
} }

View File

@@ -4,7 +4,6 @@ import (
"context" "context"
"errors" "errors"
"io" "io"
"net/http"
"os" "os"
"path/filepath" "path/filepath"
"time" "time"
@@ -13,6 +12,8 @@ import (
mihomoHttp "github.com/metacubex/mihomo/component/http" mihomoHttp "github.com/metacubex/mihomo/component/http"
"github.com/metacubex/mihomo/component/profile/cachefile" "github.com/metacubex/mihomo/component/profile/cachefile"
P "github.com/metacubex/mihomo/constant/provider" P "github.com/metacubex/mihomo/constant/provider"
"github.com/metacubex/http"
) )
const ( const (

View File

@@ -3,14 +3,13 @@ package tls
import ( import (
"context" "context"
"net" "net"
"net/http"
"runtime/debug" "runtime/debug"
"time" "time"
N "github.com/metacubex/mihomo/common/net" N "github.com/metacubex/mihomo/common/net"
"github.com/metacubex/mihomo/log" "github.com/metacubex/mihomo/log"
"golang.org/x/net/http2" "github.com/metacubex/http"
) )
func extractTlsHandshakeTimeoutFromServer(s *http.Server) time.Duration { func extractTlsHandshakeTimeoutFromServer(s *http.Server) time.Duration {
@@ -35,8 +34,8 @@ func extractTlsHandshakeTimeoutFromServer(s *http.Server) time.Duration {
// only do tls handshake and check NegotiatedProtocol with std's *tls.Conn // only do tls handshake and check NegotiatedProtocol with std's *tls.Conn
// so we do the same logic to let http2 (not h2c) work fine // so we do the same logic to let http2 (not h2c) work fine
func NewListenerForHttps(l net.Listener, httpServer *http.Server, tlsConfig *Config) net.Listener { func NewListenerForHttps(l net.Listener, httpServer *http.Server, tlsConfig *Config) net.Listener {
http2Server := &http2.Server{} http2Server := &http.Http2Server{}
_ = http2.ConfigureServer(httpServer, http2Server) _ = http.Http2ConfigureServer(httpServer, http2Server)
return N.NewHandleContextListener(context.Background(), l, func(ctx context.Context, conn net.Conn) (net.Conn, error) { return N.NewHandleContextListener(context.Background(), l, func(ctx context.Context, conn net.Conn) (net.Conn, error) {
c := Server(conn, tlsConfig) c := Server(conn, tlsConfig)
@@ -58,8 +57,8 @@ func NewListenerForHttps(l net.Listener, httpServer *http.Server, tlsConfig *Con
_ = conn.SetWriteDeadline(time.Time{}) _ = conn.SetWriteDeadline(time.Time{})
} }
if c.ConnectionState().NegotiatedProtocol == http2.NextProtoTLS { if c.ConnectionState().NegotiatedProtocol == http.Http2NextProtoTLS {
http2Server.ServeConn(c, &http2.ServeConnOpts{BaseConfig: httpServer}) http2Server.ServeConn(c, &http.Http2ServeConnOpts{BaseConfig: httpServer})
return nil, net.ErrClosed return nil, net.ErrClosed
} }
return c, nil return c, nil

View File

@@ -10,22 +10,21 @@ import (
"crypto/hmac" "crypto/hmac"
"crypto/sha256" "crypto/sha256"
"crypto/sha512" "crypto/sha512"
"crypto/tls"
"crypto/x509" "crypto/x509"
"encoding/binary" "encoding/binary"
"errors" "errors"
"net" "net"
"net/http"
"strings" "strings"
"time" "time"
"github.com/metacubex/mihomo/log" "github.com/metacubex/mihomo/log"
"github.com/metacubex/mihomo/ntp" "github.com/metacubex/mihomo/ntp"
"github.com/metacubex/http"
"github.com/metacubex/randv2" "github.com/metacubex/randv2"
"github.com/metacubex/tls"
utls "github.com/metacubex/utls" utls "github.com/metacubex/utls"
"golang.org/x/crypto/hkdf" "golang.org/x/crypto/hkdf"
"golang.org/x/net/http2"
) )
const RealityMaxShortIDLen = 8 const RealityMaxShortIDLen = 8
@@ -37,13 +36,14 @@ type RealityConfig struct {
SupportX25519MLKEM768 bool SupportX25519MLKEM768 bool
} }
func GetRealityConn(ctx context.Context, conn net.Conn, fingerprint UClientHelloID, tlsConfig *Config, realityConfig *RealityConfig) (net.Conn, error) { func GetRealityConn(ctx context.Context, conn net.Conn, fingerprint UClientHelloID, serverName string, realityConfig *RealityConfig) (net.Conn, error) {
for retry := 0; ; retry++ { for retry := 0; ; retry++ {
verifier := &realityVerifier{ verifier := &realityVerifier{
serverName: tlsConfig.ServerName, serverName: serverName,
} }
uConfig := &utls.Config{ uConfig := &utls.Config{
ServerName: tlsConfig.ServerName, Time: ntp.Now,
ServerName: serverName,
InsecureSkipVerify: true, InsecureSkipVerify: true,
SessionTicketsDisabled: true, SessionTicketsDisabled: true,
VerifyPeerCertificate: verifier.VerifyPeerCertificate, VerifyPeerCertificate: verifier.VerifyPeerCertificate,
@@ -132,7 +132,7 @@ func GetRealityConn(ctx context.Context, conn net.Conn, fingerprint UClientHello
func realityClientFallback(uConn net.Conn, serverName string, fingerprint utls.ClientHelloID) { func realityClientFallback(uConn net.Conn, serverName string, fingerprint utls.ClientHelloID) {
defer uConn.Close() defer uConn.Close()
client := http.Client{ client := http.Client{
Transport: &http2.Transport{ Transport: &http.Http2Transport{
DialTLSContext: func(ctx context.Context, network, addr string, config *tls.Config) (net.Conn, error) { DialTLSContext: func(ctx context.Context, network, addr string, config *tls.Config) (net.Conn, error) {
return uConn, nil return uConn, nil
}, },

View File

@@ -1,13 +1,16 @@
package tls package tls
import ( import (
"crypto/tls" "context"
"net" "net"
"reflect"
"unsafe"
"github.com/metacubex/mihomo/common/once" "github.com/metacubex/mihomo/common/once"
"github.com/metacubex/mihomo/common/utils" "github.com/metacubex/mihomo/common/utils"
"github.com/metacubex/mihomo/log" "github.com/metacubex/mihomo/log"
"github.com/metacubex/tls"
utls "github.com/metacubex/utls" utls "github.com/metacubex/utls"
"github.com/mroth/weightedrand/v2" "github.com/mroth/weightedrand/v2"
) )
@@ -126,8 +129,11 @@ type EncryptedClientHelloKey = utls.EncryptedClientHelloKey
type Config = utls.Config type Config = utls.Config
var tlsCertificateRequestInfoCtxOffset = utils.MustOK(reflect.TypeOf((*tls.CertificateRequestInfo)(nil)).Elem().FieldByName("ctx")).Offset
var tlsClientHelloInfoCtxOffset = utils.MustOK(reflect.TypeOf((*tls.ClientHelloInfo)(nil)).Elem().FieldByName("ctx")).Offset
func UConfig(config *tls.Config) *utls.Config { func UConfig(config *tls.Config) *utls.Config {
return &utls.Config{ cfg := &utls.Config{
Rand: config.Rand, Rand: config.Rand,
Time: config.Time, Time: config.Time,
Certificates: utils.Map(config.Certificates, UCertificate), Certificates: utils.Map(config.Certificates, UCertificate),
@@ -147,6 +153,52 @@ func UConfig(config *tls.Config) *utls.Config {
SessionTicketsDisabled: config.SessionTicketsDisabled, SessionTicketsDisabled: config.SessionTicketsDisabled,
Renegotiation: utls.RenegotiationSupport(config.Renegotiation), Renegotiation: utls.RenegotiationSupport(config.Renegotiation),
} }
if config.GetClientCertificate != nil {
cfg.GetClientCertificate = func(info *utls.CertificateRequestInfo) (*utls.Certificate, error) {
tlsInfo := &tls.CertificateRequestInfo{
AcceptableCAs: info.AcceptableCAs,
SignatureSchemes: utils.Map(info.SignatureSchemes, func(it utls.SignatureScheme) tls.SignatureScheme {
return tls.SignatureScheme(it)
}),
Version: info.Version,
}
*(*context.Context)(unsafe.Add(unsafe.Pointer(tlsInfo), tlsCertificateRequestInfoCtxOffset)) = info.Context() // for tlsInfo.ctx
cert, err := config.GetClientCertificate(tlsInfo)
if err != nil {
return nil, err
}
uCert := UCertificate(*cert)
return &uCert, err
}
}
if config.GetCertificate != nil {
cfg.GetCertificate = func(info *utls.ClientHelloInfo) (*utls.Certificate, error) {
tlsInfo := &tls.ClientHelloInfo{
CipherSuites: info.CipherSuites,
ServerName: info.ServerName,
SupportedCurves: utils.Map(info.SupportedCurves, func(it utls.CurveID) tls.CurveID {
return tls.CurveID(it)
}),
SupportedPoints: info.SupportedPoints,
SignatureSchemes: utils.Map(info.SignatureSchemes, func(it utls.SignatureScheme) tls.SignatureScheme {
return tls.SignatureScheme(it)
}),
SupportedProtos: info.SupportedProtos,
SupportedVersions: info.SupportedVersions,
Extensions: info.Extensions,
Conn: info.Conn,
//HelloRetryRequest: info.HelloRetryRequest,
}
*(*context.Context)(unsafe.Add(unsafe.Pointer(tlsInfo), tlsClientHelloInfoCtxOffset)) = info.Context() // for tlsInfo.ctx
cert, err := config.GetCertificate(tlsInfo)
if err != nil {
return nil, err
}
uCert := UCertificate(*cert)
return &uCert, err
}
}
return cfg
} }
// BuildWebsocketHandshakeState it will only send http/1.1 in its ALPN. // BuildWebsocketHandshakeState it will only send http/1.1 in its ALPN.

View File

@@ -6,7 +6,6 @@ import (
"context" "context"
"fmt" "fmt"
"io" "io"
"net/http"
"os" "os"
"os/exec" "os/exec"
"path/filepath" "path/filepath"
@@ -20,6 +19,8 @@ import (
C "github.com/metacubex/mihomo/constant" C "github.com/metacubex/mihomo/constant"
"github.com/metacubex/mihomo/constant/features" "github.com/metacubex/mihomo/constant/features"
"github.com/metacubex/mihomo/log" "github.com/metacubex/mihomo/log"
"github.com/metacubex/http"
) )
const ( const (

View File

@@ -3,11 +3,12 @@ package updater
import ( import (
"context" "context"
"io" "io"
"net/http"
"os" "os"
"time" "time"
mihomoHttp "github.com/metacubex/mihomo/component/http" mihomoHttp "github.com/metacubex/mihomo/component/http"
"github.com/metacubex/http"
) )
const defaultHttpTimeout = time.Second * 90 const defaultHttpTimeout = time.Second * 90

View File

@@ -162,6 +162,7 @@ type DNS struct {
FakeIPRange6 netip.Prefix FakeIPRange6 netip.Prefix
FakeIPPool6 *fakeip.Pool FakeIPPool6 *fakeip.Pool
FakeIPSkipper *fakeip.Skipper FakeIPSkipper *fakeip.Skipper
FakeIPTTL int
NameServerPolicy []dns.Policy NameServerPolicy []dns.Policy
ProxyServerNameserver []dns.NameServer ProxyServerNameserver []dns.NameServer
DirectNameServer []dns.NameServer DirectNameServer []dns.NameServer
@@ -228,6 +229,7 @@ type RawDNS struct {
FakeIPRange6 string `yaml:"fake-ip-range6" json:"fake-ip-range6"` FakeIPRange6 string `yaml:"fake-ip-range6" json:"fake-ip-range6"`
FakeIPFilter []string `yaml:"fake-ip-filter" json:"fake-ip-filter"` FakeIPFilter []string `yaml:"fake-ip-filter" json:"fake-ip-filter"`
FakeIPFilterMode C.FilterMode `yaml:"fake-ip-filter-mode" json:"fake-ip-filter-mode"` FakeIPFilterMode C.FilterMode `yaml:"fake-ip-filter-mode" json:"fake-ip-filter-mode"`
FakeIPTTL int `yaml:"fake-ip-ttl" json:"fake-ip-ttl"`
DefaultNameserver []string `yaml:"default-nameserver" json:"default-nameserver"` DefaultNameserver []string `yaml:"default-nameserver" json:"default-nameserver"`
CacheAlgorithm string `yaml:"cache-algorithm" json:"cache-algorithm"` CacheAlgorithm string `yaml:"cache-algorithm" json:"cache-algorithm"`
CacheMaxSize int `yaml:"cache-max-size" json:"cache-max-size"` CacheMaxSize int `yaml:"cache-max-size" json:"cache-max-size"`
@@ -490,6 +492,7 @@ func DefaultRawConfig() *RawConfig {
IPv6Timeout: 100, IPv6Timeout: 100,
EnhancedMode: C.DNSMapping, EnhancedMode: C.DNSMapping,
FakeIPRange: "198.18.0.1/16", FakeIPRange: "198.18.0.1/16",
FakeIPTTL: 1,
FallbackFilter: RawFallbackFilter{ FallbackFilter: RawFallbackFilter{
GeoIP: true, GeoIP: true,
GeoIPCode: "CN", GeoIPCode: "CN",
@@ -1289,7 +1292,7 @@ func parseNameServerPolicy(nsPolicy *orderedmap.OrderedMap[string, any], rulePro
} }
kLower := strings.ToLower(k) kLower := strings.ToLower(k)
if strings.Contains(kLower, ",") { if strings.Contains(kLower, ",") {
if strings.Contains(kLower, "geosite:") { if strings.HasPrefix(kLower, "geosite:") {
subkeys := strings.Split(k, ":") subkeys := strings.Split(k, ":")
subkeys = subkeys[1:] subkeys = subkeys[1:]
subkeys = strings.Split(subkeys[0], ",") subkeys = strings.Split(subkeys[0], ",")
@@ -1297,7 +1300,7 @@ func parseNameServerPolicy(nsPolicy *orderedmap.OrderedMap[string, any], rulePro
newKey := "geosite:" + subkey newKey := "geosite:" + subkey
policy = append(policy, dns.Policy{Domain: newKey, NameServers: nameservers}) policy = append(policy, dns.Policy{Domain: newKey, NameServers: nameservers})
} }
} else if strings.Contains(kLower, "rule-set:") { } else if strings.HasPrefix(kLower, "rule-set:") {
subkeys := strings.Split(k, ":") subkeys := strings.Split(k, ":")
subkeys = subkeys[1:] subkeys = subkeys[1:]
subkeys = strings.Split(subkeys[0], ",") subkeys = strings.Split(subkeys[0], ",")
@@ -1312,9 +1315,9 @@ func parseNameServerPolicy(nsPolicy *orderedmap.OrderedMap[string, any], rulePro
} }
} }
} else { } else {
if strings.Contains(kLower, "geosite:") { if strings.HasPrefix(kLower, "geosite:") {
policy = append(policy, dns.Policy{Domain: "geosite:" + k[8:], NameServers: nameservers}) policy = append(policy, dns.Policy{Domain: "geosite:" + k[8:], NameServers: nameservers})
} else if strings.Contains(kLower, "rule-set:") { } else if strings.HasPrefix(kLower, "rule-set:") {
policy = append(policy, dns.Policy{Domain: "rule-set:" + k[9:], NameServers: nameservers}) policy = append(policy, dns.Policy{Domain: "rule-set:" + k[9:], NameServers: nameservers})
} else { } else {
policy = append(policy, dns.Policy{Domain: k, NameServers: nameservers}) policy = append(policy, dns.Policy{Domain: k, NameServers: nameservers})
@@ -1447,17 +1450,24 @@ func parseDNS(rawCfg *RawConfig, ruleProviders map[string]P.RuleProvider) (*DNS,
} }
} }
// fake ip skip host filter skipper := &fakeip.Skipper{Mode: cfg.FakeIPFilterMode}
host, err := parseDomain(cfg.FakeIPFilter, fakeIPTrie, "dns.fake-ip-filter", ruleProviders)
if err != nil { if cfg.FakeIPFilterMode == C.FilterRule {
return nil, err rules, err := parseFakeIPRules(cfg.FakeIPFilter, ruleProviders)
if err != nil {
return nil, err
}
skipper.Rules = rules
} else {
host, err := parseDomain(cfg.FakeIPFilter, fakeIPTrie, "dns.fake-ip-filter", ruleProviders)
if err != nil {
return nil, err
}
skipper.Host = host
} }
skipper := &fakeip.Skipper{
Host: host,
Mode: cfg.FakeIPFilterMode,
}
dnsCfg.FakeIPSkipper = skipper dnsCfg.FakeIPSkipper = skipper
dnsCfg.FakeIPTTL = cfg.FakeIPTTL
if dnsCfg.FakeIPRange.IsValid() { if dnsCfg.FakeIPRange.IsValid() {
pool, err := fakeip.New(fakeip.Options{ pool, err := fakeip.New(fakeip.Options{
@@ -1537,6 +1547,55 @@ func parseDNS(rawCfg *RawConfig, ruleProviders map[string]P.RuleProvider) (*DNS,
return dnsCfg, nil return dnsCfg, nil
} }
func parseFakeIPRules(rawRules []string, ruleProviders map[string]P.RuleProvider) ([]C.Rule, error) {
var rules []C.Rule
for idx, line := range rawRules {
tp, payload, action, params := RC.ParseRulePayload(line, true)
action = strings.ToLower(action)
if action != fakeip.UseFakeIP && action != fakeip.UseRealIP {
return nil, fmt.Errorf("dns.fake-ip-filter[%d] [%s] error: invalid action '%s', must be 'fake-ip' or 'real-ip'", idx, line, action)
}
if tp == "RULE-SET" {
if rp, ok := ruleProviders[payload]; !ok {
return nil, fmt.Errorf("dns.fake-ip-filter[%d] [%s] error: rule-set '%s' not found", idx, line, payload)
} else {
switch rp.Behavior() {
case P.IPCIDR:
return nil, fmt.Errorf("dns.fake-ip-filter[%d] [%s] error: rule-set behavior is %s, must be domain or classical", idx, line, rp.Behavior())
case P.Classical:
log.Warnln("%s provider is %s, only matching domain rules in fake-ip-filter", rp.Name(), rp.Behavior())
default:
}
}
}
parsed, err := R.ParseRule(tp, payload, action, params, nil)
if err != nil {
return nil, fmt.Errorf("dns.fake-ip-filter[%d] [%s] error: %w", idx, line, err)
}
if !isDomainRule(parsed.RuleType()) && parsed.RuleType() != C.MATCH {
return nil, fmt.Errorf("dns.fake-ip-filter[%d] [%s] error: rule type '%s' not supported, only domain-based rules allowed", idx, line, tp)
}
rules = append(rules, parsed)
}
return rules, nil
}
func isDomainRule(rt C.RuleType) bool {
switch rt {
case C.Domain, C.DomainSuffix, C.DomainKeyword, C.DomainRegex, C.DomainWildcard, C.GEOSITE, C.RuleSet:
return true
default:
return false
}
}
func parseAuthentication(rawRecords []string) []auth.AuthUser { func parseAuthentication(rawRecords []string) []auth.AuthUser {
var users []auth.AuthUser var users []auth.AuthUser
for _, line := range rawRecords { for _, line := range rawRecords {
@@ -1708,7 +1767,7 @@ func parseSniffer(snifferRaw RawSniffer, ruleProviders map[string]P.RuleProvider
} }
snifferConfig.SkipSrcAddress = skipSrcAddress snifferConfig.SkipSrcAddress = skipSrcAddress
skipDstAddress, err := parseIPCIDR(snifferRaw.SkipDstAddress, nil, "sniffer.skip-src-address", ruleProviders) skipDstAddress, err := parseIPCIDR(snifferRaw.SkipDstAddress, nil, "sniffer.skip-dst-address", ruleProviders)
if err != nil { if err != nil {
return nil, fmt.Errorf("error in skip-dst-address, error:%w", err) return nil, fmt.Errorf("error in skip-dst-address, error:%w", err)
} }
@@ -1727,7 +1786,7 @@ func parseIPCIDR(addresses []string, cidrSet *cidr.IpCidrSet, adapterName string
var matcher C.IpMatcher var matcher C.IpMatcher
for _, ipcidr := range addresses { for _, ipcidr := range addresses {
ipcidrLower := strings.ToLower(ipcidr) ipcidrLower := strings.ToLower(ipcidr)
if strings.Contains(ipcidrLower, "geoip:") { if strings.HasPrefix(ipcidrLower, "geoip:") {
subkeys := strings.Split(ipcidr, ":") subkeys := strings.Split(ipcidr, ":")
subkeys = subkeys[1:] subkeys = subkeys[1:]
subkeys = strings.Split(subkeys[0], ",") subkeys = strings.Split(subkeys[0], ",")
@@ -1738,7 +1797,7 @@ func parseIPCIDR(addresses []string, cidrSet *cidr.IpCidrSet, adapterName string
} }
matchers = append(matchers, matcher) matchers = append(matchers, matcher)
} }
} else if strings.Contains(ipcidrLower, "rule-set:") { } else if strings.HasPrefix(ipcidrLower, "rule-set:") {
subkeys := strings.Split(ipcidr, ":") subkeys := strings.Split(ipcidr, ":")
subkeys = subkeys[1:] subkeys = subkeys[1:]
subkeys = strings.Split(subkeys[0], ",") subkeys = strings.Split(subkeys[0], ",")
@@ -1774,7 +1833,7 @@ func parseDomain(domains []string, domainTrie *trie.DomainTrie[struct{}], adapte
var matcher C.DomainMatcher var matcher C.DomainMatcher
for _, domain := range domains { for _, domain := range domains {
domainLower := strings.ToLower(domain) domainLower := strings.ToLower(domain)
if strings.Contains(domainLower, "geosite:") { if strings.HasPrefix(domainLower, "geosite:") {
subkeys := strings.Split(domain, ":") subkeys := strings.Split(domain, ":")
subkeys = subkeys[1:] subkeys = subkeys[1:]
subkeys = strings.Split(subkeys[0], ",") subkeys = strings.Split(subkeys[0], ",")
@@ -1785,7 +1844,7 @@ func parseDomain(domains []string, domainTrie *trie.DomainTrie[struct{}], adapte
} }
matchers = append(matchers, matcher) matchers = append(matchers, matcher)
} }
} else if strings.Contains(domainLower, "rule-set:") { } else if strings.HasPrefix(domainLower, "rule-set:") {
subkeys := strings.Split(domain, ":") subkeys := strings.Split(domain, ":")
subkeys = subkeys[1:] subkeys = subkeys[1:]
subkeys = strings.Split(subkeys[0], ",") subkeys = strings.Split(subkeys[0], ",")

View File

@@ -44,6 +44,7 @@ const (
Ssh Ssh
Mieru Mieru
AnyTLS AnyTLS
Sudoku
) )
const ( const (
@@ -58,6 +59,7 @@ var ErrNotSupport = errors.New("no support")
type Connection interface { type Connection interface {
Chains() Chain Chains() Chain
ProviderChains() Chain
AppendToChains(adapter ProxyAdapter) AppendToChains(adapter ProxyAdapter)
RemoteDestination() string RemoteDestination() string
} }
@@ -101,13 +103,14 @@ type Dialer interface {
} }
type ProxyInfo struct { type ProxyInfo struct {
XUDP bool XUDP bool
TFO bool TFO bool
MPTCP bool MPTCP bool
SMUX bool SMUX bool
Interface string Interface string
RoutingMark int RoutingMark int
DialerProxy string ProviderName string
DialerProxy string
} }
type ProxyAdapter interface { type ProxyAdapter interface {
@@ -120,17 +123,6 @@ type ProxyAdapter interface {
ProxyInfo() ProxyInfo ProxyInfo() ProxyInfo
MarshalJSON() ([]byte, error) MarshalJSON() ([]byte, error)
// Deprecated: use DialContextWithDialer and ListenPacketWithDialer instead.
// StreamConn wraps a protocol around net.Conn with Metadata.
//
// Examples:
// conn, _ := net.DialContext(context.Background(), "tcp", "host:port")
// conn, _ = adapter.StreamConnContext(context.Background(), conn, metadata)
//
// It returns a C.Conn with protocol which start with
// a new session (if any)
StreamConnContext(ctx context.Context, c net.Conn, metadata *Metadata) (net.Conn, error)
// DialContext return a C.Conn with protocol which // DialContext return a C.Conn with protocol which
// contains multiplexing-related reuse logic (if any) // contains multiplexing-related reuse logic (if any)
DialContext(ctx context.Context, metadata *Metadata) (Conn, error) DialContext(ctx context.Context, metadata *Metadata) (Conn, error)
@@ -139,13 +131,6 @@ type ProxyAdapter interface {
// SupportUOT return UDP over TCP support // SupportUOT return UDP over TCP support
SupportUOT() bool SupportUOT() bool
// SupportWithDialer only for deprecated relay group, the new protocol does not need to be implemented.
SupportWithDialer() NetWork
// DialContextWithDialer only for deprecated relay group, the new protocol does not need to be implemented.
DialContextWithDialer(ctx context.Context, dialer Dialer, metadata *Metadata) (Conn, error)
// ListenPacketWithDialer only for deprecated relay group, the new protocol does not need to be implemented.
ListenPacketWithDialer(ctx context.Context, dialer Dialer, metadata *Metadata) (PacketConn, error)
// IsL3Protocol return ProxyAdapter working in L3 (tell dns module not pass the domain to avoid loopback) // IsL3Protocol return ProxyAdapter working in L3 (tell dns module not pass the domain to avoid loopback)
IsL3Protocol(metadata *Metadata) bool IsL3Protocol(metadata *Metadata) bool
@@ -156,11 +141,6 @@ type ProxyAdapter interface {
Close() error Close() error
} }
type Group interface {
URLTest(ctx context.Context, url string, expectedStatus utils.IntRanges[uint16]) (mp map[string]uint16, err error)
Touch()
}
type DelayHistory struct { type DelayHistory struct {
Time time.Time `json:"time"` Time time.Time `json:"time"`
Delay uint16 `json:"delay"` Delay uint16 `json:"delay"`
@@ -230,6 +210,8 @@ func (at AdapterType) String() string {
return "Mieru" return "Mieru"
case AnyTLS: case AnyTLS:
return "AnyTLS" return "AnyTLS"
case Sudoku:
return "Sudoku"
case Relay: case Relay:
return "Relay" return "Relay"
case Selector: case Selector:
@@ -292,6 +274,10 @@ func (s *packetAdapter) Key() string {
return s.key return s.key
} }
func (s *packetAdapter) Upstream() any {
return s.UDPPacket
}
func NewPacketAdapter(packet UDPPacket, metadata *Metadata) PacketAdapter { func NewPacketAdapter(packet UDPPacket, metadata *Metadata) PacketAdapter {
return &packetAdapter{ return &packetAdapter{
packet, packet,

View File

@@ -86,18 +86,24 @@ func (d DNSPrefer) String() string {
} }
} }
func NewDNSPrefer(prefer string) DNSPrefer { func (d DNSPrefer) MarshalText() ([]byte, error) {
if p, ok := dnsPreferMap[prefer]; ok { return []byte(d.String()), nil
return p }
} else {
return DualStack func (d *DNSPrefer) UnmarshalText(data []byte) error {
p, exist := dnsPreferMap[strings.ToLower(string(data))]
if !exist {
p = DualStack
} }
*d = p
return nil
} }
// FilterModeMapping is a mapping for FilterMode enum // FilterModeMapping is a mapping for FilterMode enum
var FilterModeMapping = map[string]FilterMode{ var FilterModeMapping = map[string]FilterMode{
FilterBlackList.String(): FilterBlackList, FilterBlackList.String(): FilterBlackList,
FilterWhiteList.String(): FilterWhiteList, FilterWhiteList.String(): FilterWhiteList,
FilterRule.String(): FilterRule,
} }
type FilterMode int type FilterMode int
@@ -105,6 +111,7 @@ type FilterMode int
const ( const (
FilterBlackList FilterMode = iota FilterBlackList FilterMode = iota
FilterWhiteList FilterWhiteList
FilterRule
) )
func (e FilterMode) String() string { func (e FilterMode) String() string {
@@ -113,6 +120,8 @@ func (e FilterMode) String() string {
return "blacklist" return "blacklist"
case FilterWhiteList: case FilterWhiteList:
return "whitelist" return "whitelist"
case FilterRule:
return "rule"
default: default:
return "unknown" return "unknown"
} }

View File

@@ -39,6 +39,7 @@ const (
HYSTERIA2 HYSTERIA2
ANYTLS ANYTLS
MIERU MIERU
SUDOKU
INNER INNER
) )
@@ -112,6 +113,8 @@ func (t Type) String() string {
return "AnyTLS" return "AnyTLS"
case MIERU: case MIERU:
return "Mieru" return "Mieru"
case SUDOKU:
return "Sudoku"
case INNER: case INNER:
return "Inner" return "Inner"
default: default:
@@ -154,6 +157,8 @@ func ParseType(t string) (*Type, error) {
res = ANYTLS res = ANYTLS
case "MIERU": case "MIERU":
res = MIERU res = MIERU
case "SUDOKU":
res = SUDOKU
case "INNER": case "INNER":
res = INNER res = INNER
default: default:

View File

@@ -1,5 +1,11 @@
package constant package constant
import (
"io"
"github.com/metacubex/sing/common/exceptions"
)
// Rule Type // Rule Type
const ( const (
Domain RuleType = iota Domain RuleType = iota
@@ -129,3 +135,7 @@ type RuleGroup interface {
Rule Rule
GetRecodeSize() int GetRecodeSize() int
} }
var (
ErrResetByRule = exceptions.Cause(io.EOF, "reset by rule") // TODO: replace function from sing
)

View File

@@ -2,7 +2,6 @@ package dns
import ( import (
"context" "context"
"crypto/tls"
"fmt" "fmt"
"net" "net"
"strings" "strings"
@@ -12,6 +11,7 @@ import (
C "github.com/metacubex/mihomo/constant" C "github.com/metacubex/mihomo/constant"
"github.com/metacubex/mihomo/log" "github.com/metacubex/mihomo/log"
"github.com/metacubex/tls"
D "github.com/miekg/dns" D "github.com/miekg/dns"
) )

View File

@@ -2,13 +2,11 @@ package dns
import ( import (
"context" "context"
"crypto/tls"
"encoding/base64" "encoding/base64"
"errors" "errors"
"fmt" "fmt"
"io" "io"
"net" "net"
"net/http"
"net/url" "net/url"
"runtime" "runtime"
"strconv" "strconv"
@@ -16,15 +14,15 @@ import (
"time" "time"
"github.com/metacubex/mihomo/component/ca" "github.com/metacubex/mihomo/component/ca"
tlsC "github.com/metacubex/mihomo/component/tls"
C "github.com/metacubex/mihomo/constant" C "github.com/metacubex/mihomo/constant"
"github.com/metacubex/mihomo/log" "github.com/metacubex/mihomo/log"
"github.com/metacubex/http"
"github.com/metacubex/quic-go" "github.com/metacubex/quic-go"
"github.com/metacubex/quic-go/http3" "github.com/metacubex/quic-go/http3"
"github.com/metacubex/tls"
D "github.com/miekg/dns" D "github.com/miekg/dns"
"golang.org/x/exp/slices" "golang.org/x/exp/slices"
"golang.org/x/net/http2"
) )
// Values to configure HTTP and HTTP/2 transport. // Values to configure HTTP and HTTP/2 transport.
@@ -439,8 +437,8 @@ func (doh *dnsOverHTTPS) createTransport(ctx context.Context) (t http.RoundTripp
// Explicitly configure transport to use HTTP/2. // Explicitly configure transport to use HTTP/2.
// //
// See https://github.com/AdguardTeam/dnsproxy/issues/11. // See https://github.com/AdguardTeam/dnsproxy/issues/11.
var transportH2 *http2.Transport var transportH2 *http.Http2Transport
transportH2, err = http2.ConfigureTransports(transport) transportH2, err = http.Http2ConfigureTransports(transport)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -530,20 +528,20 @@ func (doh *dnsOverHTTPS) createTransportH3(
// Ignore the address and always connect to the one that we got // Ignore the address and always connect to the one that we got
// from the bootstrapper. // from the bootstrapper.
_ string, _ string,
tlsCfg *tlsC.Config, tlsCfg *tls.Config,
cfg *quic.Config, cfg *quic.Config,
) (c *quic.Conn, err error) { ) (c *quic.Conn, err error) {
return doh.dialQuic(ctx, addr, tlsCfg, cfg) return doh.dialQuic(ctx, addr, tlsCfg, cfg)
}, },
DisableCompression: true, DisableCompression: true,
TLSClientConfig: tlsC.UConfig(tlsConfig), TLSClientConfig: tlsConfig,
QUICConfig: doh.getQUICConfig(), QUICConfig: doh.getQUICConfig(),
} }
return &http3Transport{baseTransport: rt}, nil return &http3Transport{baseTransport: rt}, nil
} }
func (doh *dnsOverHTTPS) dialQuic(ctx context.Context, addr string, tlsCfg *tlsC.Config, cfg *quic.Config) (*quic.Conn, error) { func (doh *dnsOverHTTPS) dialQuic(ctx context.Context, addr string, tlsCfg *tls.Config, cfg *quic.Config) (*quic.Conn, error) {
ip, port, err := net.SplitHostPort(addr) ip, port, err := net.SplitHostPort(addr)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -612,7 +610,7 @@ func (doh *dnsOverHTTPS) probeH3(
// Run probeQUIC and probeTLS in parallel and see which one is faster. // Run probeQUIC and probeTLS in parallel and see which one is faster.
chQuic := make(chan error, 1) chQuic := make(chan error, 1)
chTLS := make(chan error, 1) chTLS := make(chan error, 1)
go doh.probeQUIC(ctx, addr, tlsC.UConfig(probeTLSCfg), chQuic) go doh.probeQUIC(ctx, addr, probeTLSCfg, chQuic)
go doh.probeTLS(ctx, probeTLSCfg, chTLS) go doh.probeTLS(ctx, probeTLSCfg, chTLS)
select { select {
@@ -637,7 +635,7 @@ func (doh *dnsOverHTTPS) probeH3(
// probeQUIC attempts to establish a QUIC connection to the specified address. // probeQUIC attempts to establish a QUIC connection to the specified address.
// We run probeQUIC and probeTLS in parallel and see which one is faster. // We run probeQUIC and probeTLS in parallel and see which one is faster.
func (doh *dnsOverHTTPS) probeQUIC(ctx context.Context, addr string, tlsConfig *tlsC.Config, ch chan error) { func (doh *dnsOverHTTPS) probeQUIC(ctx context.Context, addr string, tlsConfig *tls.Config, ch chan error) {
startTime := time.Now() startTime := time.Now()
conn, err := doh.dialQuic(ctx, addr, tlsConfig, doh.getQUICConfig()) conn, err := doh.dialQuic(ctx, addr, tlsConfig, doh.getQUICConfig())
if err != nil { if err != nil {

View File

@@ -2,7 +2,6 @@ package dns
import ( import (
"context" "context"
"crypto/tls"
"encoding/binary" "encoding/binary"
"errors" "errors"
"fmt" "fmt"
@@ -13,11 +12,11 @@ import (
"time" "time"
"github.com/metacubex/mihomo/component/ca" "github.com/metacubex/mihomo/component/ca"
tlsC "github.com/metacubex/mihomo/component/tls"
C "github.com/metacubex/mihomo/constant" C "github.com/metacubex/mihomo/constant"
"github.com/metacubex/mihomo/log" "github.com/metacubex/mihomo/log"
"github.com/metacubex/quic-go" "github.com/metacubex/quic-go"
"github.com/metacubex/tls"
D "github.com/miekg/dns" D "github.com/miekg/dns"
) )
@@ -348,7 +347,7 @@ func (doq *dnsOverQUIC) openConnection(ctx context.Context) (conn *quic.Conn, er
transport := quic.Transport{Conn: udp} transport := quic.Transport{Conn: udp}
transport.SetCreatedConn(true) // auto close conn transport.SetCreatedConn(true) // auto close conn
transport.SetSingleUse(true) // auto close transport transport.SetSingleUse(true) // auto close transport
conn, err = transport.Dial(ctx, &udpAddr, tlsC.UConfig(tlsConfig), doq.getQUICConfig()) conn, err = transport.Dial(ctx, &udpAddr, tlsConfig, doq.getQUICConfig())
if err != nil { if err != nil {
return nil, fmt.Errorf("opening quic connection to %s: %w", doq.addr, err) return nil, fmt.Errorf("opening quic connection to %s: %w", doq.addr, err)
} }

View File

@@ -1,6 +1,7 @@
package dns package dns
import ( import (
"errors"
"net/netip" "net/netip"
"github.com/metacubex/mihomo/common/lru" "github.com/metacubex/mihomo/common/lru"
@@ -13,6 +14,7 @@ type ResolverEnhancer struct {
fakeIPPool *fakeip.Pool fakeIPPool *fakeip.Pool
fakeIPPool6 *fakeip.Pool fakeIPPool6 *fakeip.Pool
fakeIPSkipper *fakeip.Skipper fakeIPSkipper *fakeip.Skipper
fakeIPTTL int
mapping *lru.LruCache[netip.Addr, string] mapping *lru.LruCache[netip.Addr, string]
useHosts bool useHosts bool
} }
@@ -31,11 +33,15 @@ func (h *ResolverEnhancer) IsExistFakeIP(ip netip.Addr) bool {
} }
if pool := h.fakeIPPool; pool != nil { if pool := h.fakeIPPool; pool != nil {
return pool.Exist(ip) if pool.Exist(ip) {
return true
}
} }
if pool6 := h.fakeIPPool6; pool6 != nil { if pool6 := h.fakeIPPool6; pool6 != nil {
return pool6.Exist(ip) if pool6.Exist(ip) {
return true
}
} }
return false return false
@@ -47,11 +53,15 @@ func (h *ResolverEnhancer) IsFakeIP(ip netip.Addr) bool {
} }
if pool := h.fakeIPPool; pool != nil { if pool := h.fakeIPPool; pool != nil {
return pool.IPNet().Contains(ip) && ip != pool.Gateway() && ip != pool.Broadcast() if pool.IPNet().Contains(ip) && ip != pool.Gateway() && ip != pool.Broadcast() {
return true
}
} }
if pool6 := h.fakeIPPool6; pool6 != nil { if pool6 := h.fakeIPPool6; pool6 != nil {
return pool6.IPNet().Contains(ip) && ip != pool6.Gateway() && ip != pool6.Broadcast() if pool6.IPNet().Contains(ip) && ip != pool6.Gateway() && ip != pool6.Broadcast() {
return true
}
} }
return false return false
@@ -63,11 +73,15 @@ func (h *ResolverEnhancer) IsFakeBroadcastIP(ip netip.Addr) bool {
} }
if pool := h.fakeIPPool; pool != nil { if pool := h.fakeIPPool; pool != nil {
return pool.Broadcast() == ip if pool.Broadcast() == ip {
return true
}
} }
if pool6 := h.fakeIPPool6; pool6 != nil { if pool6 := h.fakeIPPool6; pool6 != nil {
return pool6.Broadcast() == ip if pool6.Broadcast() == ip {
return true
}
} }
return false return false
@@ -102,11 +116,19 @@ func (h *ResolverEnhancer) InsertHostByIP(ip netip.Addr, host string) {
} }
func (h *ResolverEnhancer) FlushFakeIP() error { func (h *ResolverEnhancer) FlushFakeIP() error {
var errs []error
if pool := h.fakeIPPool; pool != nil { if pool := h.fakeIPPool; pool != nil {
return pool.FlushFakeIP() if err := pool.FlushFakeIP(); err != nil {
errs = append(errs, err)
}
} }
if pool6 := h.fakeIPPool6; pool6 != nil { if pool6 := h.fakeIPPool6; pool6 != nil {
return pool6.FlushFakeIP() if err := pool6.FlushFakeIP(); err != nil {
errs = append(errs, err)
}
}
if len(errs) > 0 {
return errors.Join(errs...)
} }
return nil return nil
} }
@@ -141,6 +163,7 @@ type EnhancerConfig struct {
FakeIPPool *fakeip.Pool FakeIPPool *fakeip.Pool
FakeIPPool6 *fakeip.Pool FakeIPPool6 *fakeip.Pool
FakeIPSkipper *fakeip.Skipper FakeIPSkipper *fakeip.Skipper
FakeIPTTL int
UseHosts bool UseHosts bool
} }
@@ -156,6 +179,10 @@ func NewEnhancer(cfg EnhancerConfig) *ResolverEnhancer {
e.fakeIPPool6 = cfg.FakeIPPool6 e.fakeIPPool6 = cfg.FakeIPPool6
} }
e.fakeIPSkipper = cfg.FakeIPSkipper e.fakeIPSkipper = cfg.FakeIPSkipper
e.fakeIPTTL = cfg.FakeIPTTL
if e.fakeIPTTL < 1 {
e.fakeIPTTL = 1
}
e.mapping = lru.New(lru.WithSize[netip.Addr, string](4096)) e.mapping = lru.New(lru.WithSize[netip.Addr, string](4096))
} }

View File

@@ -64,7 +64,7 @@ func withHosts(mapping *lru.LruCache[netip.Addr, string]) middleware {
if mapping != nil { if mapping != nil {
mapping.SetWithExpire(ipAddr, host, time.Now().Add(time.Second*10)) mapping.SetWithExpire(ipAddr, host, time.Now().Add(time.Second*10))
} }
} else if q.Qtype == D.TypeAAAA { } else if ipAddr.Is6() && q.Qtype == D.TypeAAAA {
rr := &D.AAAA{} rr := &D.AAAA{}
rr.Hdr = D.RR_Header{Name: q.Name, Rrtype: D.TypeAAAA, Class: D.ClassINET, Ttl: 10} rr.Hdr = D.RR_Header{Name: q.Name, Rrtype: D.TypeAAAA, Class: D.ClassINET, Ttl: 10}
rr.AAAA = ipAddr.AsSlice() rr.AAAA = ipAddr.AsSlice()
@@ -146,7 +146,7 @@ func withMapping(mapping *lru.LruCache[netip.Addr, string]) middleware {
} }
} }
func withFakeIP(skipper *fakeip.Skipper, fakePool *fakeip.Pool, fakePool6 *fakeip.Pool) middleware { func withFakeIP(skipper *fakeip.Skipper, fakePool *fakeip.Pool, fakePool6 *fakeip.Pool, fakeIPTTL int) middleware {
return func(next handler) handler { return func(next handler) handler {
return func(ctx *icontext.DNSContext, r *D.Msg) (*D.Msg, error) { return func(ctx *icontext.DNSContext, r *D.Msg) (*D.Msg, error) {
q := r.Question[0] q := r.Question[0]
@@ -186,7 +186,7 @@ func withFakeIP(skipper *fakeip.Skipper, fakePool *fakeip.Pool, fakePool6 *fakei
msg.Answer = []D.RR{rr} msg.Answer = []D.RR{rr}
ctx.SetType(icontext.DNSTypeFakeIP) ctx.SetType(icontext.DNSTypeFakeIP)
setMsgTTL(msg, 1) setMsgTTL(msg, uint32(fakeIPTTL))
msg.SetRcode(r, D.RcodeSuccess) msg.SetRcode(r, D.RcodeSuccess)
msg.Authoritative = true msg.Authoritative = true
msg.RecursionAvailable = true msg.RecursionAvailable = true
@@ -238,7 +238,7 @@ func newHandler(resolver *Resolver, mapper *ResolverEnhancer) handler {
} }
if mapper.mode == C.DNSFakeIP { if mapper.mode == C.DNSFakeIP {
middlewares = append(middlewares, withFakeIP(mapper.fakeIPSkipper, mapper.fakeIPPool, mapper.fakeIPPool6)) middlewares = append(middlewares, withFakeIP(mapper.fakeIPSkipper, mapper.fakeIPPool, mapper.fakeIPPool6, mapper.fakeIPTTL))
} }
if mapper.mode != C.DNSNormal { if mapper.mode != C.DNSNormal {

View File

@@ -165,11 +165,9 @@ func (r *Resolver) ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg, e
q := m.Question[0] q := m.Question[0]
domain := msgToDomain(m) domain := msgToDomain(m)
_, qTypeStr := msgToQtype(m)
cacheM, expireTime, hit := r.cache.GetWithExpire(q.String()) cacheM, expireTime, hit := r.cache.GetWithExpire(q.String())
if hit { if hit {
ips := msgToIP(cacheM) log.Debugln("[DNS] cache hit %s --> %s, expire at %s", domain, msgToLogString(cacheM), expireTime.Format("2006-01-02 15:04:05"))
log.Debugln("[DNS] cache hit %s --> %s %s, expire at %s", domain, ips, qTypeStr, expireTime.Format("2006-01-02 15:04:05"))
now := time.Now() now := time.Now()
msg = cacheM.Copy() msg = cacheM.Copy()
if expireTime.Before(now) { if expireTime.Before(now) {

View File

@@ -9,6 +9,7 @@ import (
"time" "time"
"github.com/metacubex/mihomo/common/picker" "github.com/metacubex/mihomo/common/picker"
"github.com/metacubex/mihomo/component/ech/echparser"
"github.com/metacubex/mihomo/component/resolver" "github.com/metacubex/mihomo/component/resolver"
"github.com/metacubex/mihomo/log" "github.com/metacubex/mihomo/log"
@@ -226,6 +227,81 @@ func msgToQtype(msg *D.Msg) (uint16, string) {
return 0, "" return 0, ""
} }
func msgToHTTPSRRInfo(msg *D.Msg) string {
var alpns []string
var publicName string
var hasIPv4, hasIPv6 bool
collect := func(rrs []D.RR) {
for _, rr := range rrs {
httpsRR, ok := rr.(*D.HTTPS)
if !ok {
continue
}
for _, kv := range httpsRR.Value {
switch v := kv.(type) {
case *D.SVCBAlpn:
if len(alpns) == 0 && len(v.Alpn) > 0 {
alpns = append(alpns, v.Alpn...)
}
case *D.SVCBIPv4Hint:
if len(v.Hint) > 0 {
hasIPv4 = true
}
case *D.SVCBIPv6Hint:
if len(v.Hint) > 0 {
hasIPv6 = true
}
case *D.SVCBECHConfig:
if publicName == "" && len(v.ECH) > 0 {
if cfgs, err := echparser.ParseECHConfigList(v.ECH); err == nil && len(cfgs) > 0 {
publicName = string(cfgs[0].PublicName)
}
}
}
}
}
}
collect(msg.Answer)
//TODO: Do we need to process the data in msg.Extra?
// If so, do we need to validate whether the domain names within it match our request?
// To simplify the problem, let's ignore it for now.
//collect(msg.Extra)
if len(alpns) == 0 && publicName == "" && !hasIPv4 && !hasIPv6 {
return ""
}
var parts []string
if len(alpns) > 0 {
parts = append(parts, "alpn:"+strings.Join(alpns, ","))
}
if publicName != "" {
parts = append(parts, "pn:"+publicName)
}
if hasIPv4 {
parts = append(parts, "ipv4hint")
}
if hasIPv6 {
parts = append(parts, "ipv6hint")
}
return strings.Join(parts, ";")
}
func msgToLogString(msg *D.Msg) string {
qType, qTypeStr := msgToQtype(msg)
switch qType {
case D.TypeHTTPS:
return fmt.Sprintf("[%s] %s", msgToHTTPSRRInfo(msg), qTypeStr)
default:
return fmt.Sprintf("%s %s", msgToIP(msg), qTypeStr)
}
}
func batchExchange(ctx context.Context, clients []dnsClient, m *D.Msg) (msg *D.Msg, cache bool, err error) { func batchExchange(ctx context.Context, clients []dnsClient, m *D.Msg) (msg *D.Msg, cache bool, err error) {
cache = true cache = true
fast, ctx := picker.WithTimeout[*D.Msg](ctx, resolver.DefaultDNSTimeout) fast, ctx := picker.WithTimeout[*D.Msg](ctx, resolver.DefaultDNSTimeout)
@@ -248,8 +324,7 @@ func batchExchange(ctx context.Context, clients []dnsClient, m *D.Msg) (msg *D.M
// so we would ignore RCode errors from RCode clients. // so we would ignore RCode errors from RCode clients.
return nil, errors.New("server failure: " + D.RcodeToString[m.Rcode]) return nil, errors.New("server failure: " + D.RcodeToString[m.Rcode])
} }
ips := msgToIP(m) log.Debugln("[DNS] %s --> %s from %s", domain, msgToLogString(m), client.Address())
log.Debugln("[DNS] %s --> %s %s from %s", domain, ips, qTypeStr, client.Address())
return m, nil return m, nil
}) })
} }

View File

@@ -13,7 +13,7 @@ case $TARGETPLATFORM in
"linux/arm/v7") "linux/arm/v7")
arch="armv7" arch="armv7"
;; ;;
"riscv64") "linux/riscv64")
arch="riscv64" arch="riscv64"
;; ;;
*) *)
@@ -22,4 +22,4 @@ case $TARGETPLATFORM in
;; ;;
esac esac
file_name="$os$arch-$(cat bin/version.txt)" file_name="$os$arch-$(cat bin/version.txt)"
echo $file_name echo $file_name

View File

@@ -272,9 +272,23 @@ dns:
- rule-set:fakeip-filter - rule-set:fakeip-filter
# fakeip-filter 为 geosite 中名为 fakeip-filter 的分类(需要自行保证该分类存在) # fakeip-filter 为 geosite 中名为 fakeip-filter 的分类(需要自行保证该分类存在)
- geosite:fakeip-filter - geosite:fakeip-filter
# 当 fake-ip-filter-mode: rule 时开启规则模式
# fake-ip 与路由 rules 匹配逻辑一致(自上而下)语法也一致支持GEOSITE、RuleSet、DOMAIN*、MATCH
- RULE-SET,reject-domain,fake-ip # 自定义 RuleSet behavior 必须为 domain/classical当为 classical 时仅会生效域名类规则
- RULE-SET,proxy-domain,fake-ip
- GEOSITE,gfw,fake-ip
- DOMAIN,www.baidu.com,real-ip
- DOMAIN-SUFFIX,qq.com,real-ip
- DOMAIN-SUFFIX,jd.com,fake-ip
- MATCH,fake-ip # 最后 fake-ip or real-ip
# 配置fake-ip-filter的匹配模式默认为blacklist即如果匹配成功不返回fake-ip # 配置fake-ip-filter的匹配模式默认为blacklist即如果匹配成功不返回fake-ip
# 可设置为whitelist即只有匹配成功才返回fake-ip # 可设置为whitelist即只有匹配成功才返回fake-ip
# 也可配置为rule规则模式语法见fake-ip-filter说明
fake-ip-filter-mode: blacklist fake-ip-filter-mode: blacklist
# 配置fakeip查询返回的TTL非必要情况下请勿修改
fake-ip-ttl: 1
# use-hosts: true # 查询 hosts # use-hosts: true # 查询 hosts
@@ -1027,7 +1041,7 @@ proxies: # socks5
server: 1.2.3.4 server: 1.2.3.4
port: 2999 port: 2999
# port-range: 2090-2099 #(不可同时填写 port 和 port-range # port-range: 2090-2099 #(不可同时填写 port 和 port-range
transport: TCP # 支持 TCP transport: TCP # 支持 TCP 或者 UDP
udp: true # 支持 UDP over TCP udp: true # 支持 UDP over TCP
username: user username: user
password: password password: password
@@ -1036,6 +1050,25 @@ proxies: # socks5
# 如果想开启 0-RTT 握手,请设置为 HANDSHAKE_NO_WAIT否则请设置为 HANDSHAKE_STANDARD。默认值为 HANDSHAKE_STANDARD # 如果想开启 0-RTT 握手,请设置为 HANDSHAKE_NO_WAIT否则请设置为 HANDSHAKE_STANDARD。默认值为 HANDSHAKE_STANDARD
# handshake-mode: HANDSHAKE_STANDARD # handshake-mode: HANDSHAKE_STANDARD
# sudoku
- name: sudoku
type: sudoku
server: server_ip/domain # 1.2.3.4 or domain
port: 443
key: "<client_key>" # 如果你使用sudoku生成的ED25519密钥对请填写密钥对中的私钥否则填入和服务端相同的uuid
aead-method: chacha20-poly1305 # 可选值chacha20-poly1305、aes-128-gcm、none 我们保证在none的情况下sudoku混淆层仍然确保安全
padding-min: 2 # 最小填充字节数
padding-max: 7 # 最大填充字节数
table-type: prefer_ascii # 可选值prefer_ascii、prefer_entropy 前者全ascii映射后者保证熵值汉明1低于3
# custom-table: xpxvvpvv # 可选自定义字节布局必须包含2个x、2个p、4个v可随意组合。启用此处则需配置`table-type`为`prefer_entropy`
# custom-tables: ["xpxvvpvv", "vxpvxvvp"] # 可选自定义字节布局列表x/v/p用于 xvp 模式轮换;非空时覆盖 custom-table
http-mask: true # 是否启用http掩码
# http-mask-mode: legacy # 可选legacy默认、stream、poll、autostream/poll/auto 支持走 CDN/反代
# http-mask-tls: true # 可选:仅在 http-mask-mode 为 stream/poll/auto 时生效true 强制 httpsfalse 强制 http不会根据端口自动推断
# http-mask-host: "" # 可选:覆盖 Host/SNI支持 example.com 或 example.com:443仅在 http-mask-mode 为 stream/poll/auto 时生效
# http-mask-strategy: random # 可选random默认、post、websocket仅 legacy 下生效
enable-pure-downlink: false # 是否启用混淆下行false的情况下能在保证数据安全的前提下极大提升下行速度与服务端端保持相同(如果此处为false则要求aead不可为none)
# anytls # anytls
- name: anytls - name: anytls
type: anytls type: anytls
@@ -1565,6 +1598,24 @@ listeners:
username1: password1 username1: password1
username2: password2 username2: password2
- name: sudoku-in-1
type: sudoku
port: 8443 # 仅支持单端口
listen: 0.0.0.0
key: "<server_key>" # 如果你使用sudoku生成的ED25519密钥对此处是密钥对中的公钥当然你也可以仅仅使用任意uuid充当key
aead-method: chacha20-poly1305 # 支持chacha20-poly1305或者aes-128-gcm以及nonesudoku的混淆层可以确保none情况下数据安全
padding-min: 1 # 填充最小长度
padding-max: 15 # 填充最大长度,均不建议过大
table-type: prefer_ascii # 可选值prefer_ascii、prefer_entropy 前者全ascii映射后者保证熵值汉明1低于3
# custom-table: xpxvvpvv # 可选自定义字节布局必须包含2个x、2个p、4个v可随意组合。启用此处则需配置`table-type`为`prefer_entropy`
# custom-tables: ["xpxvvpvv", "vxpvxvvp"] # 可选自定义字节布局列表x/v/p用于 xvp 模式轮换;非空时覆盖 custom-table
handshake-timeout: 5 # optional
enable-pure-downlink: false # 是否启用混淆下行false的情况下能在保证数据安全的前提下极大提升下行速度与客户端保持相同(如果此处为false则要求aead不可为none)
disable-http-mask: false # 可选:禁用 http 掩码/隧道(默认为 false
# http-mask-mode: legacy # 可选legacy默认、stream、poll、autostream/poll/auto 支持走 CDN/反代
- name: trojan-in-1 - name: trojan-in-1
type: trojan type: trojan
port: 10819 # 支持使用ports格式例如200,302 or 200,204,401-429,501-503 port: 10819 # 支持使用ports格式例如200,302 or 200,204,401-429,501-503

36
go.mod
View File

@@ -3,13 +3,11 @@ module github.com/metacubex/mihomo
go 1.20 go 1.20
require ( require (
filippo.io/edwards25519 v1.1.0
github.com/bahlo/generic-list-go v0.2.0 github.com/bahlo/generic-list-go v0.2.0
github.com/coreos/go-iptables v0.8.0 github.com/coreos/go-iptables v0.8.0
github.com/dlclark/regexp2 v1.11.5 github.com/dlclark/regexp2 v1.11.5
github.com/ebitengine/purego v0.9.1 github.com/enfein/mieru/v3 v3.26.0
github.com/enfein/mieru/v3 v3.22.1
github.com/go-chi/chi/v5 v5.2.3
github.com/go-chi/render v1.0.3
github.com/gobwas/ws v1.4.0 github.com/gobwas/ws v1.4.0
github.com/gofrs/uuid/v5 v5.4.0 github.com/gofrs/uuid/v5 v5.4.0
github.com/golang/snappy v1.0.0 github.com/golang/snappy v1.0.0
@@ -21,30 +19,34 @@ require (
github.com/metacubex/bbolt v0.0.0-20250725135710-010dbbbb7a5b github.com/metacubex/bbolt v0.0.0-20250725135710-010dbbbb7a5b
github.com/metacubex/blake3 v0.1.0 github.com/metacubex/blake3 v0.1.0
github.com/metacubex/chacha v0.1.5 github.com/metacubex/chacha v0.1.5
github.com/metacubex/chi v0.1.0
github.com/metacubex/cpu v0.1.0
github.com/metacubex/fswatch v0.1.1 github.com/metacubex/fswatch v0.1.1
github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759 github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759
github.com/metacubex/kcp-go v0.0.0-20251105084629-8c93f4bf37be github.com/metacubex/http v0.1.0
github.com/metacubex/quic-go v0.55.1-0.20251024060151-bd465f127128 github.com/metacubex/kcp-go v0.0.0-20251111012849-7455698490e9
github.com/metacubex/mlkem v0.1.0
github.com/metacubex/quic-go v0.58.1-0.20251222092318-72a81ab195ec
github.com/metacubex/randv2 v0.2.0 github.com/metacubex/randv2 v0.2.0
github.com/metacubex/restls-client-go v0.1.7 github.com/metacubex/restls-client-go v0.1.7
github.com/metacubex/sing v0.5.6 github.com/metacubex/sing v0.5.6
github.com/metacubex/sing-mux v0.3.4 github.com/metacubex/sing-mux v0.3.4
github.com/metacubex/sing-quic v0.0.0-20251004051927-c45ee18473bb github.com/metacubex/sing-quic v0.0.0-20251217080445-b15217cb57f3
github.com/metacubex/sing-shadowsocks v0.2.12 github.com/metacubex/sing-shadowsocks v0.2.12
github.com/metacubex/sing-shadowsocks2 v0.2.7 github.com/metacubex/sing-shadowsocks2 v0.2.7
github.com/metacubex/sing-shadowtls v0.0.0-20250503063515-5d9f966d17a2 github.com/metacubex/sing-shadowtls v0.0.0-20250503063515-5d9f966d17a2
github.com/metacubex/sing-tun v0.4.9 github.com/metacubex/sing-tun v0.4.12-0.20251231220427-f11396db2fa1
github.com/metacubex/sing-vmess v0.2.4 github.com/metacubex/sing-vmess v0.2.4
github.com/metacubex/sing-wireguard v0.0.0-20250503063753-2dc62acc626f github.com/metacubex/sing-wireguard v0.0.0-20250503063753-2dc62acc626f
github.com/metacubex/smux v0.0.0-20250922175018-15c9a6a78719 github.com/metacubex/smux v0.0.0-20251111013112-03f8d12dafc1
github.com/metacubex/tfo-go v0.0.0-20251024101424-368b42b59148 github.com/metacubex/tfo-go v0.0.0-20251130171125-413e892ac443
github.com/metacubex/tls v0.1.0
github.com/metacubex/utls v1.8.3 github.com/metacubex/utls v1.8.3
github.com/metacubex/wireguard-go v0.0.0-20250820062549-a6cecdd7f57f github.com/metacubex/wireguard-go v0.0.0-20250820062549-a6cecdd7f57f
github.com/miekg/dns v1.1.63 // lastest version compatible with golang1.20 github.com/miekg/dns v1.1.63 // lastest version compatible with golang1.20
github.com/mroth/weightedrand/v2 v2.1.0 github.com/mroth/weightedrand/v2 v2.1.0
github.com/openacid/low v0.1.21 github.com/openacid/low v0.1.21
github.com/oschwald/maxminddb-golang v1.12.0 // lastest version compatible with golang1.20 github.com/oschwald/maxminddb-golang v1.12.0 // lastest version compatible with golang1.20
github.com/sagernet/cors v1.2.1
github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a
github.com/samber/lo v1.52.0 github.com/samber/lo v1.52.0
github.com/sirupsen/logrus v1.9.3 github.com/sirupsen/logrus v1.9.3
@@ -77,12 +79,11 @@ require (
github.com/fsnotify/fsnotify v1.9.0 // indirect github.com/fsnotify/fsnotify v1.9.0 // indirect
github.com/gaukas/godicttls v0.0.4 // indirect github.com/gaukas/godicttls v0.0.4 // indirect
github.com/go-ole/go-ole v1.3.0 // indirect github.com/go-ole/go-ole v1.3.0 // indirect
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
github.com/gobwas/httphead v0.1.0 // indirect github.com/gobwas/httphead v0.1.0 // indirect
github.com/gobwas/pool v0.2.1 // indirect github.com/gobwas/pool v0.2.1 // indirect
github.com/google/btree v1.1.3 // indirect github.com/google/btree v1.1.3 // indirect
github.com/google/go-cmp v0.6.0 // indirect github.com/google/go-cmp v0.6.0 // indirect
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 // indirect github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8 // indirect
github.com/josharian/native v1.1.0 // indirect github.com/josharian/native v1.1.0 // indirect
github.com/klauspost/cpuid/v2 v2.2.6 // indirect github.com/klauspost/cpuid/v2 v2.2.6 // indirect
github.com/klauspost/reedsolomon v1.12.3 // indirect github.com/klauspost/reedsolomon v1.12.3 // indirect
@@ -90,14 +91,15 @@ require (
github.com/mailru/easyjson v0.7.7 // indirect github.com/mailru/easyjson v0.7.7 // indirect
github.com/mdlayher/socket v0.4.1 // indirect github.com/mdlayher/socket v0.4.1 // indirect
github.com/metacubex/ascon v0.1.0 // indirect github.com/metacubex/ascon v0.1.0 // indirect
github.com/metacubex/gvisor v0.0.0-20250919004547-6122b699a301 // indirect github.com/metacubex/gvisor v0.0.0-20251227095601-261ec1326fe8 // indirect
github.com/metacubex/hkdf v0.1.0 // indirect
github.com/metacubex/hpke v0.1.0 // indirect
github.com/metacubex/nftables v0.0.0-20250503052935-30a69ab87793 // indirect github.com/metacubex/nftables v0.0.0-20250503052935-30a69ab87793 // indirect
github.com/metacubex/qpack v0.6.0 // indirect
github.com/metacubex/yamux v0.0.0-20250918083631-dd5f17c0be49 // indirect github.com/metacubex/yamux v0.0.0-20250918083631-dd5f17c0be49 // indirect
github.com/oasisprotocol/deoxysii v0.0.0-20220228165953-2091330c22b7 // indirect github.com/oasisprotocol/deoxysii v0.0.0-20220228165953-2091330c22b7 // indirect
github.com/onsi/ginkgo/v2 v2.9.5 // indirect
github.com/pierrec/lz4/v4 v4.1.14 // indirect github.com/pierrec/lz4/v4 v4.1.14 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/quic-go/qpack v0.4.0 // indirect
github.com/sina-ghaderi/poly1305 v0.0.0-20220724002748-c5926b03988b // indirect github.com/sina-ghaderi/poly1305 v0.0.0-20220724002748-c5926b03988b // indirect
github.com/sina-ghaderi/rabaead v0.0.0-20220730151906-ab6e06b96e8c // indirect github.com/sina-ghaderi/rabaead v0.0.0-20220730151906-ab6e06b96e8c // indirect
github.com/sina-ghaderi/rabbitio v0.0.0-20220730151941-9ce26f4f872e // indirect github.com/sina-ghaderi/rabbitio v0.0.0-20220730151941-9ce26f4f872e // indirect
@@ -107,6 +109,6 @@ require (
gitlab.com/yawning/bsaes.git v0.0.0-20190805113838-0a714cd429ec // indirect gitlab.com/yawning/bsaes.git v0.0.0-20190805113838-0a714cd429ec // indirect
golang.org/x/mod v0.20.0 // indirect golang.org/x/mod v0.20.0 // indirect
golang.org/x/text v0.22.0 // indirect golang.org/x/text v0.22.0 // indirect
golang.org/x/time v0.7.0 // indirect golang.org/x/time v0.10.0 // indirect
golang.org/x/tools v0.24.0 // indirect golang.org/x/tools v0.24.0 // indirect
) )

80
go.sum
View File

@@ -1,3 +1,5 @@
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
github.com/RyuaNerin/go-krypto v1.3.0 h1:smavTzSMAx8iuVlGb4pEwl9MD2qicqMzuXR2QWp2/Pg= github.com/RyuaNerin/go-krypto v1.3.0 h1:smavTzSMAx8iuVlGb4pEwl9MD2qicqMzuXR2QWp2/Pg=
github.com/RyuaNerin/go-krypto v1.3.0/go.mod h1:9R9TU936laAIqAmjcHo/LsaXYOZlymudOAxjaBf62UM= github.com/RyuaNerin/go-krypto v1.3.0/go.mod h1:9R9TU936laAIqAmjcHo/LsaXYOZlymudOAxjaBf62UM=
github.com/RyuaNerin/testingutil v0.1.0 h1:IYT6JL57RV3U2ml3dLHZsVtPOP6yNK7WUVdzzlpNrss= github.com/RyuaNerin/testingutil v0.1.0 h1:IYT6JL57RV3U2ml3dLHZsVtPOP6yNK7WUVdzzlpNrss=
@@ -12,9 +14,6 @@ github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xW
github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs= github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs=
github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/coreos/go-iptables v0.8.0 h1:MPc2P89IhuVpLI7ETL/2tx3XZ61VeICZjYqDEgNsPRc= github.com/coreos/go-iptables v0.8.0 h1:MPc2P89IhuVpLI7ETL/2tx3XZ61VeICZjYqDEgNsPRc=
github.com/coreos/go-iptables v0.8.0/go.mod h1:Qe8Bv2Xik5FyTXwgIbLAnv2sWSBmvWdFETJConOQ//Q= github.com/coreos/go-iptables v0.8.0/go.mod h1:Qe8Bv2Xik5FyTXwgIbLAnv2sWSBmvWdFETJConOQ//Q=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
@@ -23,10 +22,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dlclark/regexp2 v1.11.5 h1:Q/sSnsKerHeCkc/jSTNq1oCm7KiVgUMZRDUoRu0JQZQ= github.com/dlclark/regexp2 v1.11.5 h1:Q/sSnsKerHeCkc/jSTNq1oCm7KiVgUMZRDUoRu0JQZQ=
github.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= github.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
github.com/ebitengine/purego v0.9.1 h1:a/k2f2HQU3Pi399RPW1MOaZyhKJL9w/xFpKAg4q1s0A= github.com/enfein/mieru/v3 v3.26.0 h1:ZsxCFkh3UfGSu9LL6EQ9+b97uxTJ7/AnJmLMyrbjSDI=
github.com/ebitengine/purego v0.9.1/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= github.com/enfein/mieru/v3 v3.26.0/go.mod h1:zJBUCsi5rxyvHM8fjFf+GLaEl4OEjjBXr1s5F6Qd3hM=
github.com/enfein/mieru/v3 v3.22.1 h1:/XGYYXpEhEJlxosmtbpEJkhtRLHB8IToG7LB8kU2ZDY=
github.com/enfein/mieru/v3 v3.22.1/go.mod h1:zJBUCsi5rxyvHM8fjFf+GLaEl4OEjjBXr1s5F6Qd3hM=
github.com/ericlagergren/aegis v0.0.0-20250325060835-cd0defd64358 h1:kXYqH/sL8dS/FdoFjr12ePjnLPorPo2FsnrHNuXSDyo= github.com/ericlagergren/aegis v0.0.0-20250325060835-cd0defd64358 h1:kXYqH/sL8dS/FdoFjr12ePjnLPorPo2FsnrHNuXSDyo=
github.com/ericlagergren/aegis v0.0.0-20250325060835-cd0defd64358/go.mod h1:hkIFzoiIPZYxdFOOLyDho59b7SrDfo+w3h+yWdlg45I= github.com/ericlagergren/aegis v0.0.0-20250325060835-cd0defd64358/go.mod h1:hkIFzoiIPZYxdFOOLyDho59b7SrDfo+w3h+yWdlg45I=
github.com/ericlagergren/polyval v0.0.0-20220411101811-e25bc10ba391 h1:8j2RH289RJplhA6WfdaPqzg1MjH2K8wX5e0uhAxrw2g= github.com/ericlagergren/polyval v0.0.0-20220411101811-e25bc10ba391 h1:8j2RH289RJplhA6WfdaPqzg1MjH2K8wX5e0uhAxrw2g=
@@ -40,15 +37,8 @@ github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
github.com/gaukas/godicttls v0.0.4 h1:NlRaXb3J6hAnTmWdsEKb9bcSBD6BvcIjdGdeb0zfXbk= github.com/gaukas/godicttls v0.0.4 h1:NlRaXb3J6hAnTmWdsEKb9bcSBD6BvcIjdGdeb0zfXbk=
github.com/gaukas/godicttls v0.0.4/go.mod h1:l6EenT4TLWgTdwslVb4sEMOCf7Bv0JAK67deKr9/NCI= github.com/gaukas/godicttls v0.0.4/go.mod h1:l6EenT4TLWgTdwslVb4sEMOCf7Bv0JAK67deKr9/NCI=
github.com/go-chi/chi/v5 v5.2.3 h1:WQIt9uxdsAbgIYgid+BpYc+liqQZGMHRaUwp0JUcvdE=
github.com/go-chi/chi/v5 v5.2.3/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops=
github.com/go-chi/render v1.0.3 h1:AsXqd2a1/INaIfUSKq3G5uA8weYx20FOsM7uSoCyyt4=
github.com/go-chi/render v1.0.3/go.mod h1:/gr3hVkmYR0YlEy3LxCuVRFzEu9Ruok+gFqbIofjao0=
github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU= github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU=
github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM= github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM=
github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og= github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og=
@@ -58,17 +48,15 @@ github.com/gobwas/ws v1.4.0/go.mod h1:G3gNqMNtPppf5XUz7O4shetPpcZ1VJ7zt18dlUeakr
github.com/gofrs/uuid/v5 v5.4.0 h1:EfbpCTjqMuGyq5ZJwxqzn3Cbr2d0rUZU7v5ycAk/e/0= github.com/gofrs/uuid/v5 v5.4.0 h1:EfbpCTjqMuGyq5ZJwxqzn3Cbr2d0rUZU7v5ycAk/e/0=
github.com/gofrs/uuid/v5 v5.4.0/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8= github.com/gofrs/uuid/v5 v5.4.0/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
github.com/golang/snappy v1.0.0 h1:Oy607GVXHs7RtbggtPBnr2RmDArIsAefDwvrdWvRhGs= github.com/golang/snappy v1.0.0 h1:Oy607GVXHs7RtbggtPBnr2RmDArIsAefDwvrdWvRhGs=
github.com/golang/snappy v1.0.0/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v1.0.0/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg= github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg=
github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 h1:yAJXTCF9TqKcTiHJAE8dj7HMvPfh66eeA2JYW7eFpSE= github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8 h1:FKHo8hFI3A+7w0aUQuYXQ+6EN5stWmeY/AZqtM8xk9k=
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8/go.mod h1:K1liHPHnj73Fdn/EKuT8nrFqBihUSKXoLYU0BuatOYo=
github.com/google/tink/go v1.6.1 h1:t7JHqO8Ath2w2ig5vjwQYJzhGEZymedQc90lQXUBa4I= github.com/google/tink/go v1.6.1 h1:t7JHqO8Ath2w2ig5vjwQYJzhGEZymedQc90lQXUBa4I=
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/insomniacslk/dhcp v0.0.0-20250109001534-8abf58130905 h1:q3OEI9RaN/wwcx+qgGo6ZaoJkCiDYe/gjDLfq7lQQF4= github.com/insomniacslk/dhcp v0.0.0-20250109001534-8abf58130905 h1:q3OEI9RaN/wwcx+qgGo6ZaoJkCiDYe/gjDLfq7lQQF4=
github.com/insomniacslk/dhcp v0.0.0-20250109001534-8abf58130905/go.mod h1:VvGYjkZoJyKqlmT1yzakUs4mfKMNB0XdODP0+rdml6k= github.com/insomniacslk/dhcp v0.0.0-20250109001534-8abf58130905/go.mod h1:VvGYjkZoJyKqlmT1yzakUs4mfKMNB0XdODP0+rdml6k=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
@@ -102,18 +90,32 @@ github.com/metacubex/blake3 v0.1.0 h1:KGnjh/56REO7U+cgZA8dnBhxdP7jByrG7hTP+bu6cq
github.com/metacubex/blake3 v0.1.0/go.mod h1:CCkLdzFrqf7xmxCdhQFvJsRRV2mwOLDoSPg6vUTB9Uk= github.com/metacubex/blake3 v0.1.0/go.mod h1:CCkLdzFrqf7xmxCdhQFvJsRRV2mwOLDoSPg6vUTB9Uk=
github.com/metacubex/chacha v0.1.5 h1:fKWMb/5c7ZrY8Uoqi79PPFxl+qwR7X/q0OrsAubyX2M= github.com/metacubex/chacha v0.1.5 h1:fKWMb/5c7ZrY8Uoqi79PPFxl+qwR7X/q0OrsAubyX2M=
github.com/metacubex/chacha v0.1.5/go.mod h1:Djn9bPZxLTXbJFSeyo0/qzEzQI+gUSSzttuzZM75GH8= github.com/metacubex/chacha v0.1.5/go.mod h1:Djn9bPZxLTXbJFSeyo0/qzEzQI+gUSSzttuzZM75GH8=
github.com/metacubex/chi v0.1.0 h1:rjNDyDj50nRpicG43CNkIw4ssiCbmDL8d7wJXKlUCsg=
github.com/metacubex/chi v0.1.0/go.mod h1:zM5u5oMQt8b2DjvDHvzadKrP6B2ztmasL1YHRMbVV+g=
github.com/metacubex/cpu v0.1.0 h1:8PeTdV9j6UKbN1K5Jvtbi/Jock7dknvzyYuLb8Conmk=
github.com/metacubex/cpu v0.1.0/go.mod h1:09VEt4dSRLR+bOA8l4w4NDuzGZ8n5dkMv7e8axgEeTU=
github.com/metacubex/fswatch v0.1.1 h1:jqU7C/v+g0qc2RUFgmAOPoVvfl2BXXUXEumn6oQuxhU= github.com/metacubex/fswatch v0.1.1 h1:jqU7C/v+g0qc2RUFgmAOPoVvfl2BXXUXEumn6oQuxhU=
github.com/metacubex/fswatch v0.1.1/go.mod h1:czrTT7Zlbz7vWft8RQu9Qqh+JoX+Nnb+UabuyN1YsgI= github.com/metacubex/fswatch v0.1.1/go.mod h1:czrTT7Zlbz7vWft8RQu9Qqh+JoX+Nnb+UabuyN1YsgI=
github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759 h1:cjd4biTvOzK9ubNCCkQ+ldc4YSH/rILn53l/xGBFHHI= github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759 h1:cjd4biTvOzK9ubNCCkQ+ldc4YSH/rILn53l/xGBFHHI=
github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759/go.mod h1:UHOv2xu+RIgLwpXca7TLrXleEd4oR3sPatW6IF8wU88= github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759/go.mod h1:UHOv2xu+RIgLwpXca7TLrXleEd4oR3sPatW6IF8wU88=
github.com/metacubex/gvisor v0.0.0-20250919004547-6122b699a301 h1:N5GExQJqYAH3gOCshpp2u/J3CtNYzMctmlb0xK9wtbQ= github.com/metacubex/gvisor v0.0.0-20251227095601-261ec1326fe8 h1:hUL81H0Ic/XIDkvtn9M1pmfDdfid7JzYQToY4Ps1TvQ=
github.com/metacubex/gvisor v0.0.0-20250919004547-6122b699a301/go.mod h1:8LpS0IJW1VmWzUm3ylb0e2SK5QDm5lO/2qwWLZgRpBU= github.com/metacubex/gvisor v0.0.0-20251227095601-261ec1326fe8/go.mod h1:8LpS0IJW1VmWzUm3ylb0e2SK5QDm5lO/2qwWLZgRpBU=
github.com/metacubex/kcp-go v0.0.0-20251105084629-8c93f4bf37be h1:Y7SigZIqfv/+RIA/D7R6EbB9p+brPRoGOM6zobSmRIM= github.com/metacubex/hkdf v0.1.0 h1:fPA6VzXK8cU1foc/TOmGCDmSa7pZbxlnqhl3RNsthaA=
github.com/metacubex/kcp-go v0.0.0-20251105084629-8c93f4bf37be/go.mod h1:HIJZW4QMhbBqXuqC1ly6Hn0TEYT2SzRw58ns1yGhXTs= github.com/metacubex/hkdf v0.1.0/go.mod h1:3seEfds3smgTAXqUGn+tgEJH3uXdsUjOiduG/2EtvZ4=
github.com/metacubex/hpke v0.1.0 h1:gu2jUNhraehWi0P/z5HX2md3d7L1FhPQE6/Q0E9r9xQ=
github.com/metacubex/hpke v0.1.0/go.mod h1:vfDm6gfgrwlXUxKDkWbcE44hXtmc1uxLDm2BcR11b3U=
github.com/metacubex/http v0.1.0 h1:Jcy0I9zKjYijSUaksZU34XEe2xNdoFkgUTB7z7K5q0o=
github.com/metacubex/http v0.1.0/go.mod h1:Nxx0zZAo2AhRfanyL+fmmK6ACMtVsfpwIl1aFAik2Eg=
github.com/metacubex/kcp-go v0.0.0-20251111012849-7455698490e9 h1:7m3tRPrLpKOLOvZ/Lp4XCxz0t7rg9t9K35x6TahjR8o=
github.com/metacubex/kcp-go v0.0.0-20251111012849-7455698490e9/go.mod h1:HIJZW4QMhbBqXuqC1ly6Hn0TEYT2SzRw58ns1yGhXTs=
github.com/metacubex/mlkem v0.1.0 h1:wFClitonSFcmipzzQvax75beLQU+D7JuC+VK1RzSL8I=
github.com/metacubex/mlkem v0.1.0/go.mod h1:amhaXZVeYNShuy9BILcR7P0gbeo/QLZsnqCdL8U2PDQ=
github.com/metacubex/nftables v0.0.0-20250503052935-30a69ab87793 h1:1Qpuy+sU3DmyX9HwI+CrBT/oLNJngvBorR2RbajJcqo= github.com/metacubex/nftables v0.0.0-20250503052935-30a69ab87793 h1:1Qpuy+sU3DmyX9HwI+CrBT/oLNJngvBorR2RbajJcqo=
github.com/metacubex/nftables v0.0.0-20250503052935-30a69ab87793/go.mod h1:RjRNb4G52yAgfR+Oe/kp9G4PJJ97Fnj89eY1BFO3YyA= github.com/metacubex/nftables v0.0.0-20250503052935-30a69ab87793/go.mod h1:RjRNb4G52yAgfR+Oe/kp9G4PJJ97Fnj89eY1BFO3YyA=
github.com/metacubex/quic-go v0.55.1-0.20251024060151-bd465f127128 h1:I1uvJl206/HbkzEAZpLgGkZgUveOZb+P+6oTUj7dN+o= github.com/metacubex/qpack v0.6.0 h1:YqClGIMOpiRYLjV1qOs483Od08MdPgRnHjt90FuaAKw=
github.com/metacubex/quic-go v0.55.1-0.20251024060151-bd465f127128/go.mod h1:1lktQFtCD17FZliVypbrDHwbsFSsmz2xz2TRXydvB5c= github.com/metacubex/qpack v0.6.0/go.mod h1:lKGSi7Xk94IMvHGOmxS9eIei3bvIqpOAImEBsaOwTkA=
github.com/metacubex/quic-go v0.58.1-0.20251222092318-72a81ab195ec h1:5ePGO2Xht06fpwjNIzfY5XS+82xwDHHx4xGbqgLbxjA=
github.com/metacubex/quic-go v0.58.1-0.20251222092318-72a81ab195ec/go.mod h1:oNzMrmylS897M3zSMuapIdwSwfq6F2qW01Z3NhVRJhk=
github.com/metacubex/randv2 v0.2.0 h1:uP38uBvV2SxYfLj53kuvAjbND4RUDfFJjwr4UigMiLs= github.com/metacubex/randv2 v0.2.0 h1:uP38uBvV2SxYfLj53kuvAjbND4RUDfFJjwr4UigMiLs=
github.com/metacubex/randv2 v0.2.0/go.mod h1:kFi2SzrQ5WuneuoLLCMkABtiBu6VRrMrWFqSPyj2cxY= github.com/metacubex/randv2 v0.2.0/go.mod h1:kFi2SzrQ5WuneuoLLCMkABtiBu6VRrMrWFqSPyj2cxY=
github.com/metacubex/restls-client-go v0.1.7 h1:eCwiXCTQb5WJu9IlgYvDBA1OgrINv58dEe7hcN5H15k= github.com/metacubex/restls-client-go v0.1.7 h1:eCwiXCTQb5WJu9IlgYvDBA1OgrINv58dEe7hcN5H15k=
@@ -123,24 +125,26 @@ github.com/metacubex/sing v0.5.6 h1:mEPDCadsCj3DB8gn+t/EtposlYuALEkExa/LUguw6/c=
github.com/metacubex/sing v0.5.6/go.mod h1:ypf0mjwlZm0sKdQSY+yQvmsbWa0hNPtkeqyRMGgoN+w= github.com/metacubex/sing v0.5.6/go.mod h1:ypf0mjwlZm0sKdQSY+yQvmsbWa0hNPtkeqyRMGgoN+w=
github.com/metacubex/sing-mux v0.3.4 h1:tf4r27CIkzaxq9kBlAXQkgMXq2HPp5Mta60Kb4RCZF0= github.com/metacubex/sing-mux v0.3.4 h1:tf4r27CIkzaxq9kBlAXQkgMXq2HPp5Mta60Kb4RCZF0=
github.com/metacubex/sing-mux v0.3.4/go.mod h1:SEJfAuykNj/ozbPqngEYqyggwSr81+L7Nu09NRD5mh4= github.com/metacubex/sing-mux v0.3.4/go.mod h1:SEJfAuykNj/ozbPqngEYqyggwSr81+L7Nu09NRD5mh4=
github.com/metacubex/sing-quic v0.0.0-20251004051927-c45ee18473bb h1:gxrJmnxuEAel+kh3V7ntqkHjURif0xKDu76nzr/BF5Y= github.com/metacubex/sing-quic v0.0.0-20251217080445-b15217cb57f3 h1:3LlkguIRAzyBWLxP5xrETi1AMIt3McZcDlXNgiyXMsE=
github.com/metacubex/sing-quic v0.0.0-20251004051927-c45ee18473bb/go.mod h1:JK4+PYUKps6pnlicKjsSUAjAcvIUjhorIjdNZGg930M= github.com/metacubex/sing-quic v0.0.0-20251217080445-b15217cb57f3/go.mod h1:fAyoc/8IFK1yJp8meJvPNyGk7ZnKG1vmNaTwYx6NHA4=
github.com/metacubex/sing-shadowsocks v0.2.12 h1:Wqzo8bYXrK5aWqxu/TjlTnYZzAKtKsaFQBdr6IHFaBE= github.com/metacubex/sing-shadowsocks v0.2.12 h1:Wqzo8bYXrK5aWqxu/TjlTnYZzAKtKsaFQBdr6IHFaBE=
github.com/metacubex/sing-shadowsocks v0.2.12/go.mod h1:2e5EIaw0rxKrm1YTRmiMnDulwbGxH9hAFlrwQLQMQkU= github.com/metacubex/sing-shadowsocks v0.2.12/go.mod h1:2e5EIaw0rxKrm1YTRmiMnDulwbGxH9hAFlrwQLQMQkU=
github.com/metacubex/sing-shadowsocks2 v0.2.7 h1:hSuuc0YpsfiqYqt1o+fP4m34BQz4e6wVj3PPBVhor3A= github.com/metacubex/sing-shadowsocks2 v0.2.7 h1:hSuuc0YpsfiqYqt1o+fP4m34BQz4e6wVj3PPBVhor3A=
github.com/metacubex/sing-shadowsocks2 v0.2.7/go.mod h1:vOEbfKC60txi0ca+yUlqEwOGc3Obl6cnSgx9Gf45KjE= github.com/metacubex/sing-shadowsocks2 v0.2.7/go.mod h1:vOEbfKC60txi0ca+yUlqEwOGc3Obl6cnSgx9Gf45KjE=
github.com/metacubex/sing-shadowtls v0.0.0-20250503063515-5d9f966d17a2 h1:gXU+MYPm7Wme3/OAY2FFzVq9d9GxPHOqu5AQfg/ddhI= github.com/metacubex/sing-shadowtls v0.0.0-20250503063515-5d9f966d17a2 h1:gXU+MYPm7Wme3/OAY2FFzVq9d9GxPHOqu5AQfg/ddhI=
github.com/metacubex/sing-shadowtls v0.0.0-20250503063515-5d9f966d17a2/go.mod h1:mbfboaXauKJNIHJYxQRa+NJs4JU9NZfkA+I33dS2+9E= github.com/metacubex/sing-shadowtls v0.0.0-20250503063515-5d9f966d17a2/go.mod h1:mbfboaXauKJNIHJYxQRa+NJs4JU9NZfkA+I33dS2+9E=
github.com/metacubex/sing-tun v0.4.9 h1:jY0Yyt8nnN3yQRN/jTxgqNCmGi1dsFdxdIi7pQUlVVU= github.com/metacubex/sing-tun v0.4.12-0.20251231220427-f11396db2fa1 h1:eaQCkCI2STxaGiVK35huV9TjUu3QDo3LiS/LKO5n1Z8=
github.com/metacubex/sing-tun v0.4.9/go.mod h1:L/TjQY5JEGy8nvsuYmy/XgMFMCPiF0+AWSFCYfS6r9w= github.com/metacubex/sing-tun v0.4.12-0.20251231220427-f11396db2fa1/go.mod h1:L/TjQY5JEGy8nvsuYmy/XgMFMCPiF0+AWSFCYfS6r9w=
github.com/metacubex/sing-vmess v0.2.4 h1:Tx6AGgCiEf400E/xyDuYyafsel6sGbR8oF7RkAaus6I= github.com/metacubex/sing-vmess v0.2.4 h1:Tx6AGgCiEf400E/xyDuYyafsel6sGbR8oF7RkAaus6I=
github.com/metacubex/sing-vmess v0.2.4/go.mod h1:21R5R1u90uUvBQF0owoooEu96/SAYYD56nDrwm6nFaM= github.com/metacubex/sing-vmess v0.2.4/go.mod h1:21R5R1u90uUvBQF0owoooEu96/SAYYD56nDrwm6nFaM=
github.com/metacubex/sing-wireguard v0.0.0-20250503063753-2dc62acc626f h1:Sr/DYKYofKHKc4GF3qkRGNuj6XA6c0eqPgEDN+VAsYU= github.com/metacubex/sing-wireguard v0.0.0-20250503063753-2dc62acc626f h1:Sr/DYKYofKHKc4GF3qkRGNuj6XA6c0eqPgEDN+VAsYU=
github.com/metacubex/sing-wireguard v0.0.0-20250503063753-2dc62acc626f/go.mod h1:jpAkVLPnCpGSfNyVmj6Cq4YbuZsFepm/Dc+9BAOcR80= github.com/metacubex/sing-wireguard v0.0.0-20250503063753-2dc62acc626f/go.mod h1:jpAkVLPnCpGSfNyVmj6Cq4YbuZsFepm/Dc+9BAOcR80=
github.com/metacubex/smux v0.0.0-20250922175018-15c9a6a78719 h1:T6qCCfolRDAVJKeaPW/mXwNLjnlo65AYN7WS2jrBNaM= github.com/metacubex/smux v0.0.0-20251111013112-03f8d12dafc1 h1:a6DF0ze9miXes+rdwl8a4Wkvfpe0lXYU82sPJfDzz6s=
github.com/metacubex/smux v0.0.0-20250922175018-15c9a6a78719/go.mod h1:4bPD8HWx9jPJ9aE4uadgyN7D1/Wz3KmPy+vale8sKLE= github.com/metacubex/smux v0.0.0-20251111013112-03f8d12dafc1/go.mod h1:4bPD8HWx9jPJ9aE4uadgyN7D1/Wz3KmPy+vale8sKLE=
github.com/metacubex/tfo-go v0.0.0-20251024101424-368b42b59148 h1:Zd0QqciLIhv9MKbGKTPEgN8WUFsgQGA1WJBy6spEnVU= github.com/metacubex/tfo-go v0.0.0-20251130171125-413e892ac443 h1:H6TnfM12tOoTizYE/qBHH3nEuibIelmHI+BVSxVJr8o=
github.com/metacubex/tfo-go v0.0.0-20251024101424-368b42b59148/go.mod h1:l9oLnLoEXyGZ5RVLsh7QCC5XsouTUyKk4F2nLm2DHLw= github.com/metacubex/tfo-go v0.0.0-20251130171125-413e892ac443/go.mod h1:l9oLnLoEXyGZ5RVLsh7QCC5XsouTUyKk4F2nLm2DHLw=
github.com/metacubex/tls v0.1.0 h1:1kjR/1q2uU1cZIwiHYEnWzS4L+0Cu1/X3yfIQ76BzNY=
github.com/metacubex/tls v0.1.0/go.mod h1:0XeVdL0cBw+8i5Hqy3lVeP9IyD/LFTq02ExvHM6rzEM=
github.com/metacubex/utls v1.8.3 h1:0m/yCxm3SK6kWve2lKiFb1pue1wHitJ8sQQD4Ikqde4= github.com/metacubex/utls v1.8.3 h1:0m/yCxm3SK6kWve2lKiFb1pue1wHitJ8sQQD4Ikqde4=
github.com/metacubex/utls v1.8.3/go.mod h1:kncGGVhFaoGn5M3pFe3SXhZCzsbCJayNOH4UEqTKTko= github.com/metacubex/utls v1.8.3/go.mod h1:kncGGVhFaoGn5M3pFe3SXhZCzsbCJayNOH4UEqTKTko=
github.com/metacubex/wireguard-go v0.0.0-20250820062549-a6cecdd7f57f h1:FGBPRb1zUabhPhDrlKEjQ9lgIwQ6cHL4x8M9lrERhbk= github.com/metacubex/wireguard-go v0.0.0-20250820062549-a6cecdd7f57f h1:FGBPRb1zUabhPhDrlKEjQ9lgIwQ6cHL4x8M9lrERhbk=
@@ -153,9 +157,6 @@ github.com/mroth/weightedrand/v2 v2.1.0 h1:o1ascnB1CIVzsqlfArQQjeMy1U0NcIbBO5rfd
github.com/mroth/weightedrand/v2 v2.1.0/go.mod h1:f2faGsfOGOwc1p94wzHKKZyTpcJUW7OJ/9U4yfiNAOU= github.com/mroth/weightedrand/v2 v2.1.0/go.mod h1:f2faGsfOGOwc1p94wzHKKZyTpcJUW7OJ/9U4yfiNAOU=
github.com/oasisprotocol/deoxysii v0.0.0-20220228165953-2091330c22b7 h1:1102pQc2SEPp5+xrS26wEaeb26sZy6k9/ZXlZN+eXE4= github.com/oasisprotocol/deoxysii v0.0.0-20220228165953-2091330c22b7 h1:1102pQc2SEPp5+xrS26wEaeb26sZy6k9/ZXlZN+eXE4=
github.com/oasisprotocol/deoxysii v0.0.0-20220228165953-2091330c22b7/go.mod h1:UqoUn6cHESlliMhOnKLWr+CBH+e3bazUPvFj1XZwAjs= github.com/oasisprotocol/deoxysii v0.0.0-20220228165953-2091330c22b7/go.mod h1:UqoUn6cHESlliMhOnKLWr+CBH+e3bazUPvFj1XZwAjs=
github.com/onsi/ginkgo/v2 v2.9.5 h1:+6Hr4uxzP4XIUyAkg61dWBw8lb/gc4/X5luuxN/EC+Q=
github.com/onsi/ginkgo/v2 v2.9.5/go.mod h1:tvAoo1QUJwNEU2ITftXTpR7R1RbCzoZUOs3RonqW57k=
github.com/onsi/gomega v1.27.6 h1:ENqfyGeS5AX/rlXDd/ETokDz93u0YufY1Pgxuy/PvWE=
github.com/openacid/errors v0.8.1/go.mod h1:GUQEJJOJE3W9skHm8E8Y4phdl2LLEN8iD7c5gcGgdx0= github.com/openacid/errors v0.8.1/go.mod h1:GUQEJJOJE3W9skHm8E8Y4phdl2LLEN8iD7c5gcGgdx0=
github.com/openacid/low v0.1.21 h1:Tr2GNu4N/+rGRYdOsEHOE89cxUIaDViZbVmKz29uKGo= github.com/openacid/low v0.1.21 h1:Tr2GNu4N/+rGRYdOsEHOE89cxUIaDViZbVmKz29uKGo=
github.com/openacid/low v0.1.21/go.mod h1:q+MsKI6Pz2xsCkzV4BLj7NR5M4EX0sGz5AqotpZDVh0= github.com/openacid/low v0.1.21/go.mod h1:q+MsKI6Pz2xsCkzV4BLj7NR5M4EX0sGz5AqotpZDVh0=
@@ -169,10 +170,6 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g= github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g=
github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo=
github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A=
github.com/sagernet/cors v1.2.1 h1:Cv5Z8y9YSD6Gm+qSpNrL3LO4lD3eQVvbFYJSG7JCMHQ=
github.com/sagernet/cors v1.2.1/go.mod h1:O64VyOjjhrkLmQIjF4KGRrJO/5dVXFdpEmCW/eISRAI=
github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a h1:ObwtHN2VpqE0ZNjr6sGeT00J8uU7JF4cNUdb44/Duis= github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a h1:ObwtHN2VpqE0ZNjr6sGeT00J8uU7JF4cNUdb44/Duis=
github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM= github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM=
github.com/samber/lo v1.52.0 h1:Rvi+3BFHES3A8meP33VPAxiBZX/Aws5RxrschYGjomw= github.com/samber/lo v1.52.0 h1:Rvi+3BFHES3A8meP33VPAxiBZX/Aws5RxrschYGjomw=
@@ -242,7 +239,6 @@ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5h
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190804053845-51ab0e2deafa/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190804053845-51ab0e2deafa/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20220622161953-175b2fd9d664/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220622161953-175b2fd9d664/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@@ -256,8 +252,8 @@ golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
golang.org/x/time v0.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ= golang.org/x/time v0.10.0 h1:3usCWA8tQn0L8+hFJQNgzpWbd89begxN66o1Ojdn5L4=
golang.org/x/time v0.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/time v0.10.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24= golang.org/x/tools v0.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24=
golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ= golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ=

View File

@@ -269,6 +269,7 @@ func updateDNS(c *config.DNS, generalIPv6 bool) {
FakeIPPool: c.FakeIPPool, FakeIPPool: c.FakeIPPool,
FakeIPPool6: c.FakeIPPool6, FakeIPPool6: c.FakeIPPool6,
FakeIPSkipper: c.FakeIPSkipper, FakeIPSkipper: c.FakeIPSkipper,
FakeIPTTL: c.FakeIPTTL,
UseHosts: c.UseHosts, UseHosts: c.UseHosts,
}) })
@@ -449,12 +450,7 @@ func patchSelectGroup(proxies map[string]C.Proxy) {
return return
} }
for name, proxy := range proxies { for name, outbound := range proxies {
outbound, ok := proxy.(C.Proxy)
if !ok {
continue
}
selector, ok := outbound.Adapter().(outboundgroup.SelectAble) selector, ok := outbound.Adapter().(outboundgroup.SelectAble)
if !ok { if !ok {
continue continue

View File

@@ -1,12 +1,11 @@
package route package route
import ( import (
"net/http"
"github.com/metacubex/mihomo/component/resolver" "github.com/metacubex/mihomo/component/resolver"
"github.com/go-chi/chi/v5" "github.com/metacubex/chi"
"github.com/go-chi/render" "github.com/metacubex/chi/render"
"github.com/metacubex/http"
) )
func cacheRouter() http.Handler { func cacheRouter() http.Handler {

View File

@@ -1,10 +1,20 @@
package route package route
import ( import (
"net/http" "bufio"
"encoding/binary"
"errors"
"io"
"net"
"net/url" "net/url"
"strconv"
"strings"
"time"
"github.com/go-chi/chi/v5" N "github.com/metacubex/mihomo/common/net"
"github.com/metacubex/chi"
"github.com/metacubex/http"
) )
// When name is composed of a partial escape string, Golang does not unescape it // When name is composed of a partial escape string, Golang does not unescape it
@@ -15,3 +25,151 @@ func getEscapeParam(r *http.Request, paramName string) string {
} }
return param return param
} }
// wsUpgrade upgrades http connection to the websocket connection.
//
// It hijacks net.Conn from w and returns received net.Conn and
// bufio.ReadWriter.
func wsUpgrade(r *http.Request, w http.ResponseWriter) (conn net.Conn, rw *bufio.ReadWriter, err error) {
// See https://tools.ietf.org/html/rfc6455#section-4.1
// The method of the request MUST be GET, and the HTTP version MUST be at least 1.1.
var nonce string
if r.Method != http.MethodGet {
err = errors.New("handshake error: bad HTTP request method")
body := err.Error()
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
w.Header().Set("Content-Length", strconv.Itoa(len(body)))
w.WriteHeader(http.StatusMethodNotAllowed)
w.Write([]byte(body))
return nil, nil, err
} else if r.ProtoMajor < 1 || (r.ProtoMajor == 1 && r.ProtoMinor < 1) {
err = errors.New("handshake error: bad HTTP protocol version")
body := err.Error()
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
w.Header().Set("Content-Length", strconv.Itoa(len(body)))
w.WriteHeader(http.StatusHTTPVersionNotSupported)
w.Write([]byte(body))
return nil, nil, err
} else if r.Host == "" {
err = errors.New("handshake error: bad Host header")
body := err.Error()
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
w.Header().Set("Content-Length", strconv.Itoa(len(body)))
w.WriteHeader(http.StatusBadRequest)
w.Write([]byte(body))
return nil, nil, err
} else if u := r.Header.Get("Upgrade"); u != "websocket" && !strings.EqualFold(u, "websocket") {
err = errors.New("handshake error: bad Upgrade header")
body := err.Error()
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
w.Header().Set("Content-Length", strconv.Itoa(len(body)))
w.WriteHeader(http.StatusBadRequest)
w.Write([]byte(body))
return nil, nil, err
} else if c := r.Header.Get("Connection"); c != "Upgrade" && !strings.Contains(strings.ToLower(c), "upgrade") {
err = errors.New("handshake error: bad Connection header")
body := err.Error()
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
w.Header().Set("Content-Length", strconv.Itoa(len(body)))
w.WriteHeader(http.StatusBadRequest)
w.Write([]byte(body))
return nil, nil, err
} else if nonce = r.Header.Get("Sec-WebSocket-Key"); len(nonce) != 24 {
err = errors.New("handshake error: bad Sec-WebSocket-Key header")
body := err.Error()
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
w.Header().Set("Content-Length", strconv.Itoa(len(body)))
w.WriteHeader(http.StatusBadRequest)
w.Write([]byte(body))
return nil, nil, err
} else if v := r.Header.Get("Sec-WebSocket-Version"); v != "13" {
err = errors.New("handshake error: bad Sec-WebSocket-Version header")
body := err.Error()
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
w.Header().Set("Content-Length", strconv.Itoa(len(body)))
if v != "" {
// According to RFC6455:
// If this version does not match a version understood by the server, the
// server MUST abort the WebSocket handshake described in this section and
// instead send an appropriate HTTP error code (such as 426 Upgrade Required)
// and a |Sec-WebSocket-Version| header field indicating the version(s) the
// server is capable of understanding.
w.Header().Set("Sec-WebSocket-Version", "13")
w.WriteHeader(http.StatusUpgradeRequired)
} else {
w.WriteHeader(http.StatusBadRequest)
}
w.Write([]byte(body))
return nil, nil, err
}
conn, rw, err = http.NewResponseController(w).Hijack()
if err != nil {
body := err.Error()
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
w.Header().Set("Content-Length", strconv.Itoa(len(body)))
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte(body))
return nil, nil, err
}
// Clear deadlines set by server.
conn.SetDeadline(time.Time{})
rw.Writer.WriteString("HTTP/1.1 101 Switching Protocols\r\n")
header := http.Header{}
header.Set("Upgrade", "websocket")
header.Set("Connection", "Upgrade")
header.Set("Sec-WebSocket-Accept", N.GetWebSocketSecAccept(nonce))
header.Write(rw.Writer)
rw.Writer.WriteString("\r\n")
err = rw.Writer.Flush()
return conn, rw, err
}
// wsWriteServerMessage writes message to w, considering that caller represents server side.
func wsWriteServerMessage(w io.Writer, op byte, p []byte) error {
dataLen := len(p)
// Make slice of bytes with capacity 14 that could hold any header.
bts := make([]byte, 14)
bts[0] |= 0x80 //FIN
bts[0] |= 0 << 4 //RSV
bts[0] |= op //OPCODE
var n int
switch {
case dataLen < 126:
bts[1] = byte(dataLen)
n = 2
case dataLen < 65536:
bts[1] = 126
binary.BigEndian.PutUint16(bts[2:4], uint16(dataLen))
n = 4
default:
bts[1] = 127
binary.BigEndian.PutUint64(bts[2:10], uint64(dataLen))
n = 10
}
_, err := w.Write(bts[:n])
if err != nil {
return err
}
_, err = w.Write(p)
return err
}
// wsWriteServerText is the same as wsWriteServerMessage with ws.OpText.
func wsWriteServerText(w io.Writer, p []byte) error {
const opText = 0x1
return wsWriteServerMessage(w, opText, p)
}
// wsWriteServerBinary is the same as wsWriteServerMessage with ws.OpBinary.
func wsWriteServerBinary(w io.Writer, p []byte) error {
const opBinary = 0x2
return wsWriteServerMessage(w, opBinary, p)
}

View File

@@ -1,7 +1,6 @@
package route package route
import ( import (
"net/http"
"net/netip" "net/netip"
"path/filepath" "path/filepath"
@@ -18,8 +17,9 @@ import (
"github.com/metacubex/mihomo/log" "github.com/metacubex/mihomo/log"
"github.com/metacubex/mihomo/tunnel" "github.com/metacubex/mihomo/tunnel"
"github.com/go-chi/chi/v5" "github.com/metacubex/chi"
"github.com/go-chi/render" "github.com/metacubex/chi/render"
"github.com/metacubex/http"
) )
func configRouter() http.Handler { func configRouter() http.Handler {

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