Compare commits

...

49 Commits

Author SHA1 Message Date
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
150 changed files with 4567 additions and 1927 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,17 @@ 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 - name: Remove inbound test for macOS
if: ${{ runner.os == 'macOS' }} if: ${{ runner.os == 'macOS' }}
run: | run: |

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

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

@@ -8,9 +8,7 @@ import (
"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/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"
@@ -106,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
@@ -130,20 +128,12 @@ 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: dialer} config.PacketDialer = mieruPacketDialer{Dialer: m.dialer}
config.Resolver = mieruDNSResolver{prefer: m.prefer} 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
@@ -177,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
} }

View File

@@ -17,6 +17,7 @@ type Reject struct {
} }
type RejectOption struct { type RejectOption struct {
BasicOption
Name string `proxy:"name"` Name string `proxy:"name"`
} }

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

View File

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

View File

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

@@ -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,146 +8,152 @@ 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": case "sudoku":
sudokuOption := &outbound.SudokuOption{} sudokuOption := &outbound.SudokuOption{BasicOption: basicOption}
err = decoder.Decode(mapping, sudokuOption) err = decoder.Decode(mapping, sudokuOption)
if err != nil { if err != nil {
break break
@@ -178,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

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

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

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

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

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

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

@@ -1292,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], ",")
@@ -1300,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], ",")
@@ -1315,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})
@@ -1712,7 +1712,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)
} }
@@ -1731,7 +1731,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], ",")
@@ -1742,7 +1742,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], ",")
@@ -1778,7 +1778,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], ",")
@@ -1789,7 +1789,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

@@ -59,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
} }
@@ -102,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 {
@@ -121,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)
@@ -140,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
@@ -157,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"`

View File

@@ -86,12 +86,17 @@ 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

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

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

@@ -1048,7 +1048,11 @@ proxies: # socks5
padding-min: 2 # 最小填充字节数 padding-min: 2 # 最小填充字节数
padding-max: 7 # 最大填充字节数 padding-max: 7 # 最大填充字节数
table-type: prefer_ascii # 可选值prefer_ascii、prefer_entropy 前者全ascii映射后者保证熵值汉明1低于3 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: true # 是否启用http掩码
# http-mask-strategy: random # 可选random默认、post、websocket仅在 http-mask=true 时生效
enable-pure-downlink: false # 是否启用混淆下行false的情况下能在保证数据安全的前提下极大提升下行速度与服务端端保持相同(如果此处为false则要求aead不可为none)
# anytls # anytls
- name: anytls - name: anytls
@@ -1587,9 +1591,12 @@ listeners:
aead-method: chacha20-poly1305 # 支持chacha20-poly1305或者aes-128-gcm以及nonesudoku的混淆层可以确保none情况下数据安全 aead-method: chacha20-poly1305 # 支持chacha20-poly1305或者aes-128-gcm以及nonesudoku的混淆层可以确保none情况下数据安全
padding-min: 1 # 填充最小长度 padding-min: 1 # 填充最小长度
padding-max: 15 # 填充最大长度,均不建议过大 padding-max: 15 # 填充最大长度,均不建议过大
seed: "<seed-or-key>" # 如果你不使用ED25519密钥对就请填入uuid否则仍然是公钥
table-type: prefer_ascii # 可选值prefer_ascii、prefer_entropy 前者全ascii映射后者保证熵值汉明1低于3 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 handshake-timeout: 5 # optional
enable-pure-downlink: false # 是否启用混淆下行false的情况下能在保证数据安全的前提下极大提升下行速度与客户端保持相同(如果此处为false则要求aead不可为none)
- name: trojan-in-1 - name: trojan-in-1
@@ -1740,4 +1747,3 @@ listeners:
# alpn: # alpn:
# - h3 # - h3
# max-udp-relay-packet-size: 1500 # max-udp-relay-packet-size: 1500

26
go.mod
View File

@@ -7,8 +7,6 @@ require (
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/enfein/mieru/v3 v3.26.0 github.com/enfein/mieru/v3 v3.26.0
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
@@ -20,31 +18,35 @@ 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/http v0.1.0
github.com/metacubex/kcp-go v0.0.0-20251111012849-7455698490e9 github.com/metacubex/kcp-go v0.0.0-20251111012849-7455698490e9
github.com/metacubex/quic-go v0.55.1-0.20251024060151-bd465f127128 github.com/metacubex/mlkem v0.1.0
github.com/metacubex/quic-go v0.57.1-0.20251217071004-e89f497a2e72
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.10 github.com/metacubex/sing-tun v0.4.11
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-20251111013112-03f8d12dafc1 github.com/metacubex/smux v0.0.0-20251111013112-03f8d12dafc1
github.com/metacubex/tfo-go v0.0.0-20251130171125-413e892ac443 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/saba-futai/sudoku v0.0.1-g github.com/saba-futai/sudoku v0.0.2-d
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
@@ -78,12 +80,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
@@ -92,13 +93,14 @@ require (
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-20250919004547-6122b699a301 // 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
@@ -108,6 +110,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
) )

60
go.sum
View File

@@ -14,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=
@@ -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-20250919004547-6122b699a301 h1:N5GExQJqYAH3gOCshpp2u/J3CtNYzMctmlb0xK9wtbQ=
github.com/metacubex/gvisor v0.0.0-20250919004547-6122b699a301/go.mod h1:8LpS0IJW1VmWzUm3ylb0e2SK5QDm5lO/2qwWLZgRpBU= github.com/metacubex/gvisor v0.0.0-20250919004547-6122b699a301/go.mod h1:8LpS0IJW1VmWzUm3ylb0e2SK5QDm5lO/2qwWLZgRpBU=
github.com/metacubex/hkdf v0.1.0 h1:fPA6VzXK8cU1foc/TOmGCDmSa7pZbxlnqhl3RNsthaA=
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 h1:7m3tRPrLpKOLOvZ/Lp4XCxz0t7rg9t9K35x6TahjR8o=
github.com/metacubex/kcp-go v0.0.0-20251111012849-7455698490e9/go.mod h1:HIJZW4QMhbBqXuqC1ly6Hn0TEYT2SzRw58ns1yGhXTs= 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.57.1-0.20251217071004-e89f497a2e72 h1:kNlYHZ75itJwkerDiySpixX+dKsv/K0TYQsKvuxogNM=
github.com/metacubex/quic-go v0.57.1-0.20251217071004-e89f497a2e72/go.mod h1:N071X2oW2+kIhLlHW3mfcD2QP+zWu2bEs1EEAm66bvI=
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,16 +125,16 @@ 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.10 h1:DllQTERAcqQyiEl4L/R7Ia0jCiSzZzikw2kL8N85p0E= github.com/metacubex/sing-tun v0.4.11 h1:NG5zpvYPbBXf+9GSUmDaGCDwl3hZXV677tbRAw0QtCM=
github.com/metacubex/sing-tun v0.4.10/go.mod h1:L/TjQY5JEGy8nvsuYmy/XgMFMCPiF0+AWSFCYfS6r9w= github.com/metacubex/sing-tun v0.4.11/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=
@@ -141,6 +143,8 @@ github.com/metacubex/smux v0.0.0-20251111013112-03f8d12dafc1 h1:a6DF0ze9miXes+rd
github.com/metacubex/smux v0.0.0-20251111013112-03f8d12dafc1/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-20251130171125-413e892ac443 h1:H6TnfM12tOoTizYE/qBHH3nEuibIelmHI+BVSxVJr8o= github.com/metacubex/tfo-go v0.0.0-20251130171125-413e892ac443 h1:H6TnfM12tOoTizYE/qBHH3nEuibIelmHI+BVSxVJr8o=
github.com/metacubex/tfo-go v0.0.0-20251130171125-413e892ac443/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,12 +170,8 @@ 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/saba-futai/sudoku v0.0.2-d h1:HW/gIyNUFcDchpMN+ZhluM86U/HGkWkkRV+9Km6WZM8=
github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A= github.com/saba-futai/sudoku v0.0.2-d/go.mod h1:Rvggsoprp7HQM7bMIZUd1M27bPj8THRsZdY1dGbIAvo=
github.com/saba-futai/sudoku v0.0.1-g h1:4q6OuAA6COaRW+CgoQtdim5AUPzzm0uOkvbYpJnOaBE=
github.com/saba-futai/sudoku v0.0.1-g/go.mod h1:2ZRzRwz93cS2K/o2yOG4CPJEltcvk5y6vbvUmjftGU0=
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=
@@ -244,7 +241,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=
@@ -258,8 +254,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

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

View File

@@ -3,16 +3,14 @@ package route
import ( import (
"bytes" "bytes"
"encoding/json" "encoding/json"
"net/http"
"strconv" "strconv"
"time" "time"
"github.com/metacubex/mihomo/tunnel/statistic" "github.com/metacubex/mihomo/tunnel/statistic"
"github.com/go-chi/chi/v5" "github.com/metacubex/chi"
"github.com/go-chi/render" "github.com/metacubex/chi/render"
"github.com/gobwas/ws" "github.com/metacubex/http"
"github.com/gobwas/ws/wsutil"
) )
func connectionRouter() http.Handler { func connectionRouter() http.Handler {
@@ -30,7 +28,7 @@ func getConnections(w http.ResponseWriter, r *http.Request) {
return return
} }
conn, _, _, err := ws.UpgradeHTTP(r, w) conn, _, err := wsUpgrade(r, w)
if err != nil { if err != nil {
return return
} }
@@ -56,7 +54,7 @@ func getConnections(w http.ResponseWriter, r *http.Request) {
return err return err
} }
return wsutil.WriteMessage(conn, ws.StateServerSide, ws.OpText, buf.Bytes()) return wsWriteServerText(conn, buf.Bytes())
} }
if err := sendSnapshot(); err != nil { if err := sendSnapshot(); err != nil {

View File

@@ -3,12 +3,12 @@ package route
import ( import (
"context" "context"
"math" "math"
"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"
"github.com/miekg/dns" "github.com/miekg/dns"
"github.com/samber/lo" "github.com/samber/lo"
) )

View File

@@ -4,11 +4,11 @@ import (
"context" "context"
"encoding/base64" "encoding/base64"
"io" "io"
"net/http"
"github.com/metacubex/mihomo/component/resolver" "github.com/metacubex/mihomo/component/resolver"
"github.com/go-chi/render" "github.com/metacubex/chi/render"
"github.com/metacubex/http"
) )
func dohRouter() http.Handler { func dohRouter() http.Handler {

View File

@@ -1,6 +1,6 @@
package route package route
import "github.com/go-chi/chi/v5" import "github.com/metacubex/chi"
type externalRouter func(r chi.Router) type externalRouter func(r chi.Router)

View File

@@ -2,18 +2,18 @@ package route
import ( import (
"context" "context"
"net/http"
"strconv" "strconv"
"time" "time"
"github.com/go-chi/chi/v5"
"github.com/go-chi/render"
"github.com/metacubex/mihomo/adapter/outboundgroup" "github.com/metacubex/mihomo/adapter/outboundgroup"
"github.com/metacubex/mihomo/common/utils" "github.com/metacubex/mihomo/common/utils"
"github.com/metacubex/mihomo/component/profile/cachefile" "github.com/metacubex/mihomo/component/profile/cachefile"
C "github.com/metacubex/mihomo/constant" C "github.com/metacubex/mihomo/constant"
"github.com/metacubex/mihomo/tunnel" "github.com/metacubex/mihomo/tunnel"
"github.com/metacubex/chi"
"github.com/metacubex/chi/render"
"github.com/metacubex/http"
) )
func groupRouter() http.Handler { func groupRouter() http.Handler {
@@ -31,7 +31,7 @@ func groupRouter() http.Handler {
func getGroups(w http.ResponseWriter, r *http.Request) { func getGroups(w http.ResponseWriter, r *http.Request) {
var gs []C.Proxy var gs []C.Proxy
for _, p := range tunnel.Proxies() { for _, p := range tunnel.Proxies() {
if _, ok := p.Adapter().(C.Group); ok { if _, ok := p.Adapter().(outboundgroup.ProxyGroup); ok {
gs = append(gs, p) gs = append(gs, p)
} }
} }
@@ -42,7 +42,7 @@ func getGroups(w http.ResponseWriter, r *http.Request) {
func getGroup(w http.ResponseWriter, r *http.Request) { func getGroup(w http.ResponseWriter, r *http.Request) {
proxy := r.Context().Value(CtxKeyProxy).(C.Proxy) proxy := r.Context().Value(CtxKeyProxy).(C.Proxy)
if _, ok := proxy.Adapter().(C.Group); ok { if _, ok := proxy.Adapter().(outboundgroup.ProxyGroup); ok {
render.JSON(w, r, proxy) render.JSON(w, r, proxy)
return return
} }
@@ -52,7 +52,7 @@ func getGroup(w http.ResponseWriter, r *http.Request) {
func getGroupDelay(w http.ResponseWriter, r *http.Request) { func getGroupDelay(w http.ResponseWriter, r *http.Request) {
proxy := r.Context().Value(CtxKeyProxy).(C.Proxy) proxy := r.Context().Value(CtxKeyProxy).(C.Proxy)
group, ok := proxy.Adapter().(C.Group) group, ok := proxy.Adapter().(outboundgroup.ProxyGroup)
if !ok { if !ok {
render.Status(r, http.StatusNotFound) render.Status(r, http.StatusNotFound)
render.JSON(w, r, ErrNotFound) render.JSON(w, r, ErrNotFound)

View File

@@ -2,14 +2,14 @@ package route
import ( import (
"context" "context"
"net/http"
C "github.com/metacubex/mihomo/constant" C "github.com/metacubex/mihomo/constant"
P "github.com/metacubex/mihomo/constant/provider" P "github.com/metacubex/mihomo/constant/provider"
"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"
"github.com/samber/lo" "github.com/samber/lo"
) )

View File

@@ -3,7 +3,6 @@ package route
import ( import (
"context" "context"
"fmt" "fmt"
"net/http"
"strconv" "strconv"
"time" "time"
@@ -13,8 +12,9 @@ import (
C "github.com/metacubex/mihomo/constant" C "github.com/metacubex/mihomo/constant"
"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"
) )
var ( var (

View File

@@ -2,7 +2,6 @@ package route
import ( import (
"fmt" "fmt"
"net/http"
"os" "os"
"os/exec" "os/exec"
"runtime" "runtime"
@@ -11,8 +10,9 @@ import (
"github.com/metacubex/mihomo/hub/executor" "github.com/metacubex/mihomo/hub/executor"
"github.com/metacubex/mihomo/log" "github.com/metacubex/mihomo/log"
"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 restartRouter() http.Handler { func restartRouter() http.Handler {

View File

@@ -2,12 +2,11 @@ package route
import ( import (
"github.com/metacubex/mihomo/constant" "github.com/metacubex/mihomo/constant"
"net/http"
"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 ruleRouter() http.Handler { func ruleRouter() http.Handler {

View File

@@ -5,7 +5,6 @@ import (
"crypto/subtle" "crypto/subtle"
"encoding/json" "encoding/json"
"net" "net"
"net/http"
"os" "os"
"path/filepath" "path/filepath"
"runtime/debug" "runtime/debug"
@@ -17,18 +16,17 @@ import (
"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/ech" "github.com/metacubex/mihomo/component/ech"
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/mihomo/ntp" "github.com/metacubex/mihomo/ntp"
"github.com/metacubex/mihomo/tunnel/statistic" "github.com/metacubex/mihomo/tunnel/statistic"
"github.com/go-chi/chi/v5" "github.com/metacubex/chi"
"github.com/go-chi/chi/v5/middleware" "github.com/metacubex/chi/cors"
"github.com/go-chi/render" "github.com/metacubex/chi/middleware"
"github.com/gobwas/ws" "github.com/metacubex/chi/render"
"github.com/gobwas/ws/wsutil" "github.com/metacubex/http"
"github.com/sagernet/cors" "github.com/metacubex/tls"
) )
var ( var (
@@ -47,8 +45,10 @@ func SetEmbedMode(embed bool) {
} }
type Traffic struct { type Traffic struct {
Up int64 `json:"up"` Up int64 `json:"up"`
Down int64 `json:"down"` Down int64 `json:"down"`
UpTotal int64 `json:"upTotal"`
DownTotal int64 `json:"downTotal"`
} }
type Memory struct { type Memory struct {
@@ -191,7 +191,7 @@ func startTLS(cfg *Config) {
// handle tlsAddr // handle tlsAddr
if len(cfg.TLSAddr) > 0 { if len(cfg.TLSAddr) > 0 {
cert, err := ca.LoadTLSKeyPair(cfg.Certificate, cfg.PrivateKey, C.Path) certLoader, err := ca.NewTLSKeyPairLoader(cfg.Certificate, cfg.PrivateKey)
if err != nil { if err != nil {
log.Errorln("External controller tls listen error: %s", err) log.Errorln("External controller tls listen error: %s", err)
return return
@@ -204,17 +204,19 @@ func startTLS(cfg *Config) {
} }
log.Infoln("RESTful API tls listening at: %s", l.Addr().String()) log.Infoln("RESTful API tls listening at: %s", l.Addr().String())
tlsConfig := &tlsC.Config{Time: ntp.Now} tlsConfig := &tls.Config{Time: ntp.Now}
tlsConfig.NextProtos = []string{"h2", "http/1.1"} tlsConfig.NextProtos = []string{"h2", "http/1.1"}
tlsConfig.Certificates = []tlsC.Certificate{tlsC.UCertificate(cert)} tlsConfig.GetCertificate = func(*tls.ClientHelloInfo) (*tls.Certificate, error) {
tlsConfig.ClientAuth = tlsC.ClientAuthTypeFromString(cfg.ClientAuthType) return certLoader()
}
tlsConfig.ClientAuth = ca.ClientAuthTypeFromString(cfg.ClientAuthType)
if len(cfg.ClientAuthCert) > 0 { if len(cfg.ClientAuthCert) > 0 {
if tlsConfig.ClientAuth == tlsC.NoClientCert { if tlsConfig.ClientAuth == tls.NoClientCert {
tlsConfig.ClientAuth = tlsC.RequireAndVerifyClientCert tlsConfig.ClientAuth = tls.RequireAndVerifyClientCert
} }
} }
if tlsConfig.ClientAuth == tlsC.VerifyClientCertIfGiven || tlsConfig.ClientAuth == tlsC.RequireAndVerifyClientCert { if tlsConfig.ClientAuth == tls.VerifyClientCertIfGiven || tlsConfig.ClientAuth == tls.RequireAndVerifyClientCert {
pool, err := ca.LoadCertificates(cfg.ClientAuthCert, C.Path) pool, err := ca.LoadCertificates(cfg.ClientAuthCert)
if err != nil { if err != nil {
log.Errorln("External controller tls listen error: %s", err) log.Errorln("External controller tls listen error: %s", err)
return return
@@ -223,7 +225,7 @@ func startTLS(cfg *Config) {
} }
if cfg.EchKey != "" { if cfg.EchKey != "" {
err = ech.LoadECHKey(cfg.EchKey, tlsConfig, C.Path) err = ech.LoadECHKey(cfg.EchKey, tlsConfig)
if err != nil { if err != nil {
log.Errorln("External controller tls serve error: %s", err) log.Errorln("External controller tls serve error: %s", err)
return return
@@ -233,7 +235,7 @@ func startTLS(cfg *Config) {
Handler: router(cfg.IsDebug, cfg.Secret, cfg.DohServer, cfg.Cors), Handler: router(cfg.IsDebug, cfg.Secret, cfg.DohServer, cfg.Cors),
} }
tlsServer = server tlsServer = server
if err = server.Serve(tlsC.NewListenerForHttps(l, server, tlsConfig)); err != nil { if err = server.Serve(tls.NewListener(l, tlsConfig)); err != nil {
log.Errorln("External controller tls serve error: %s", err) log.Errorln("External controller tls serve error: %s", err)
} }
} }
@@ -361,7 +363,7 @@ func traffic(w http.ResponseWriter, r *http.Request) {
var wsConn net.Conn var wsConn net.Conn
if r.Header.Get("Upgrade") == "websocket" { if r.Header.Get("Upgrade") == "websocket" {
var err error var err error
wsConn, _, _, err = ws.UpgradeHTTP(r, w) wsConn, _, err = wsUpgrade(r, w)
if err != nil { if err != nil {
return return
} }
@@ -380,9 +382,12 @@ func traffic(w http.ResponseWriter, r *http.Request) {
for range tick.C { for range tick.C {
buf.Reset() buf.Reset()
up, down := t.Now() up, down := t.Now()
upTotal, downTotal := t.Total()
if err := json.NewEncoder(buf).Encode(Traffic{ if err := json.NewEncoder(buf).Encode(Traffic{
Up: up, Up: up,
Down: down, Down: down,
UpTotal: upTotal,
DownTotal: downTotal,
}); err != nil { }); err != nil {
break break
} }
@@ -391,7 +396,7 @@ func traffic(w http.ResponseWriter, r *http.Request) {
_, err = w.Write(buf.Bytes()) _, err = w.Write(buf.Bytes())
w.(http.Flusher).Flush() w.(http.Flusher).Flush()
} else { } else {
err = wsutil.WriteMessage(wsConn, ws.StateServerSide, ws.OpText, buf.Bytes()) err = wsWriteServerText(wsConn, buf.Bytes())
} }
if err != nil { if err != nil {
@@ -404,7 +409,7 @@ func memory(w http.ResponseWriter, r *http.Request) {
var wsConn net.Conn var wsConn net.Conn
if r.Header.Get("Upgrade") == "websocket" { if r.Header.Get("Upgrade") == "websocket" {
var err error var err error
wsConn, _, _, err = ws.UpgradeHTTP(r, w) wsConn, _, err = wsUpgrade(r, w)
if err != nil { if err != nil {
return return
} }
@@ -441,7 +446,7 @@ func memory(w http.ResponseWriter, r *http.Request) {
_, err = w.Write(buf.Bytes()) _, err = w.Write(buf.Bytes())
w.(http.Flusher).Flush() w.(http.Flusher).Flush()
} else { } else {
err = wsutil.WriteMessage(wsConn, ws.StateServerSide, ws.OpText, buf.Bytes()) err = wsWriteServerText(wsConn, buf.Bytes())
} }
if err != nil { if err != nil {
@@ -487,7 +492,7 @@ func getLogs(w http.ResponseWriter, r *http.Request) {
var wsConn net.Conn var wsConn net.Conn
if r.Header.Get("Upgrade") == "websocket" { if r.Header.Get("Upgrade") == "websocket" {
var err error var err error
wsConn, _, _, err = ws.UpgradeHTTP(r, w) wsConn, _, err = wsUpgrade(r, w)
if err != nil { if err != nil {
return return
} }
@@ -546,7 +551,7 @@ func getLogs(w http.ResponseWriter, r *http.Request) {
_, err = w.Write(buf.Bytes()) _, err = w.Write(buf.Bytes())
w.(http.Flusher).Flush() w.(http.Flusher).Flush()
} else { } else {
err = wsutil.WriteMessage(wsConn, ws.StateServerSide, ws.OpText, buf.Bytes()) err = wsWriteServerText(wsConn, buf.Bytes())
} }
if err != nil { if err != nil {

View File

@@ -2,14 +2,14 @@ package route
import ( import (
"fmt" "fmt"
"net/http"
"os" "os"
"github.com/metacubex/mihomo/component/updater" "github.com/metacubex/mihomo/component/updater"
"github.com/metacubex/mihomo/log" "github.com/metacubex/mihomo/log"
"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 upgradeRouter() http.Handler { func upgradeRouter() http.Handler {

View File

@@ -13,7 +13,6 @@ import (
"github.com/metacubex/mihomo/common/buf" "github.com/metacubex/mihomo/common/buf"
"github.com/metacubex/mihomo/component/ca" "github.com/metacubex/mihomo/component/ca"
"github.com/metacubex/mihomo/component/ech" "github.com/metacubex/mihomo/component/ech"
tlsC "github.com/metacubex/mihomo/component/tls"
C "github.com/metacubex/mihomo/constant" C "github.com/metacubex/mihomo/constant"
LC "github.com/metacubex/mihomo/listener/config" LC "github.com/metacubex/mihomo/listener/config"
"github.com/metacubex/mihomo/listener/sing" "github.com/metacubex/mihomo/listener/sing"
@@ -24,13 +23,14 @@ import (
"github.com/metacubex/sing/common/auth" "github.com/metacubex/sing/common/auth"
"github.com/metacubex/sing/common/bufio" "github.com/metacubex/sing/common/bufio"
M "github.com/metacubex/sing/common/metadata" M "github.com/metacubex/sing/common/metadata"
"github.com/metacubex/tls"
) )
type Listener struct { type Listener struct {
closed bool closed bool
config LC.AnyTLSServer config LC.AnyTLSServer
listeners []net.Listener listeners []net.Listener
tlsConfig *tlsC.Config tlsConfig *tls.Config
userMap map[[32]byte]string userMap map[[32]byte]string
padding atomic.Pointer[padding.PaddingFactory] padding atomic.Pointer[padding.PaddingFactory]
} }
@@ -43,29 +43,31 @@ func New(config LC.AnyTLSServer, tunnel C.Tunnel, additions ...inbound.Addition)
} }
} }
tlsConfig := &tlsC.Config{Time: ntp.Now} tlsConfig := &tls.Config{Time: ntp.Now}
if config.Certificate != "" && config.PrivateKey != "" { if config.Certificate != "" && config.PrivateKey != "" {
cert, err := ca.LoadTLSKeyPair(config.Certificate, config.PrivateKey, C.Path) certLoader, err := ca.NewTLSKeyPairLoader(config.Certificate, config.PrivateKey)
if err != nil { if err != nil {
return nil, err return nil, err
} }
tlsConfig.Certificates = []tlsC.Certificate{tlsC.UCertificate(cert)} tlsConfig.GetCertificate = func(*tls.ClientHelloInfo) (*tls.Certificate, error) {
return certLoader()
}
if config.EchKey != "" { if config.EchKey != "" {
err = ech.LoadECHKey(config.EchKey, tlsConfig, C.Path) err = ech.LoadECHKey(config.EchKey, tlsConfig)
if err != nil { if err != nil {
return nil, err return nil, err
} }
} }
} }
tlsConfig.ClientAuth = tlsC.ClientAuthTypeFromString(config.ClientAuthType) tlsConfig.ClientAuth = ca.ClientAuthTypeFromString(config.ClientAuthType)
if len(config.ClientAuthCert) > 0 { if len(config.ClientAuthCert) > 0 {
if tlsConfig.ClientAuth == tlsC.NoClientCert { if tlsConfig.ClientAuth == tls.NoClientCert {
tlsConfig.ClientAuth = tlsC.RequireAndVerifyClientCert tlsConfig.ClientAuth = tls.RequireAndVerifyClientCert
} }
} }
if tlsConfig.ClientAuth == tlsC.VerifyClientCertIfGiven || tlsConfig.ClientAuth == tlsC.RequireAndVerifyClientCert { if tlsConfig.ClientAuth == tls.VerifyClientCertIfGiven || tlsConfig.ClientAuth == tls.RequireAndVerifyClientCert {
pool, err := ca.LoadCertificates(config.ClientAuthCert, C.Path) pool, err := ca.LoadCertificates(config.ClientAuthCert)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -108,8 +110,8 @@ func New(config LC.AnyTLSServer, tunnel C.Tunnel, additions ...inbound.Addition)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if len(tlsConfig.Certificates) > 0 { if tlsConfig.GetCertificate != nil {
l = tlsC.NewListener(l, tlsConfig) l = tls.NewListener(l, tlsConfig)
} else { } else {
return nil, errors.New("disallow using AnyTLS without certificates config") return nil, errors.New("disallow using AnyTLS without certificates config")
} }

View File

@@ -1,19 +1,28 @@
package config package config
import "encoding/json" import (
"encoding/json"
"github.com/metacubex/mihomo/listener/sing"
)
// SudokuServer describes a Sudoku inbound server configuration. // SudokuServer describes a Sudoku inbound server configuration.
// It is internal to the listener layer and mainly used for logging and wiring. // It is internal to the listener layer and mainly used for logging and wiring.
type SudokuServer struct { type SudokuServer struct {
Enable bool `json:"enable"` Enable bool `json:"enable"`
Listen string `json:"listen"` Listen string `json:"listen"`
Key string `json:"key"` Key string `json:"key"`
AEADMethod string `json:"aead-method,omitempty"` AEADMethod string `json:"aead-method,omitempty"`
PaddingMin *int `json:"padding-min,omitempty"` PaddingMin *int `json:"padding-min,omitempty"`
PaddingMax *int `json:"padding-max,omitempty"` PaddingMax *int `json:"padding-max,omitempty"`
Seed string `json:"seed,omitempty"` TableType string `json:"table-type,omitempty"`
TableType string `json:"table-type,omitempty"` HandshakeTimeoutSecond *int `json:"handshake-timeout,omitempty"`
HandshakeTimeoutSecond *int `json:"handshake-timeout,omitempty"` EnablePureDownlink *bool `json:"enable-pure-downlink,omitempty"`
CustomTable string `json:"custom-table,omitempty"`
CustomTables []string `json:"custom-tables,omitempty"`
// mihomo private extension (not the part of standard Sudoku protocol)
MuxOption sing.MuxOption `json:"mux-option,omitempty"`
} }
func (s SudokuServer) String() string { func (s SudokuServer) String() string {

View File

@@ -4,13 +4,14 @@ import (
"context" "context"
"errors" "errors"
"net" "net"
"net/http"
"time" "time"
"github.com/metacubex/mihomo/adapter/inbound" "github.com/metacubex/mihomo/adapter/inbound"
N "github.com/metacubex/mihomo/common/net" N "github.com/metacubex/mihomo/common/net"
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 newClient(srcConn net.Conn, tunnel C.Tunnel, additions []inbound.Addition) *http.Client { // additions using slice let caller can change its value (without size) after newClient return func newClient(srcConn net.Conn, tunnel C.Tunnel, additions []inbound.Addition) *http.Client { // additions using slice let caller can change its value (without size) after newClient return

View File

@@ -2,9 +2,10 @@ package http
import ( import (
"bufio" "bufio"
"net/http"
_ "unsafe" _ "unsafe"
"github.com/metacubex/http"
) )
//go:linkname ReadRequest net/http.readRequest //go:linkname ReadRequest github.com/metacubex/http.readRequest
func ReadRequest(b *bufio.Reader) (req *http.Request, err error) func ReadRequest(b *bufio.Reader) (req *http.Request, err error)

View File

@@ -5,7 +5,6 @@ import (
"fmt" "fmt"
"io" "io"
"net" "net"
"net/http"
"strings" "strings"
"sync" "sync"
@@ -14,6 +13,8 @@ import (
"github.com/metacubex/mihomo/component/auth" "github.com/metacubex/mihomo/component/auth"
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"
) )
type bodyWrapper struct { type bodyWrapper struct {

View File

@@ -7,12 +7,13 @@ import (
"github.com/metacubex/mihomo/adapter/inbound" "github.com/metacubex/mihomo/adapter/inbound"
"github.com/metacubex/mihomo/component/ca" "github.com/metacubex/mihomo/component/ca"
"github.com/metacubex/mihomo/component/ech" "github.com/metacubex/mihomo/component/ech"
tlsC "github.com/metacubex/mihomo/component/tls"
C "github.com/metacubex/mihomo/constant" C "github.com/metacubex/mihomo/constant"
authStore "github.com/metacubex/mihomo/listener/auth" authStore "github.com/metacubex/mihomo/listener/auth"
LC "github.com/metacubex/mihomo/listener/config" LC "github.com/metacubex/mihomo/listener/config"
"github.com/metacubex/mihomo/listener/reality" "github.com/metacubex/mihomo/listener/reality"
"github.com/metacubex/mihomo/ntp" "github.com/metacubex/mihomo/ntp"
"github.com/metacubex/tls"
) )
type Listener struct { type Listener struct {
@@ -66,41 +67,43 @@ func NewWithConfig(config LC.AuthServer, tunnel C.Tunnel, additions ...inbound.A
return nil, err return nil, err
} }
tlsConfig := &tlsC.Config{Time: ntp.Now} tlsConfig := &tls.Config{Time: ntp.Now}
var realityBuilder *reality.Builder var realityBuilder *reality.Builder
if config.Certificate != "" && config.PrivateKey != "" { if config.Certificate != "" && config.PrivateKey != "" {
cert, err := ca.LoadTLSKeyPair(config.Certificate, config.PrivateKey, C.Path) certLoader, err := ca.NewTLSKeyPairLoader(config.Certificate, config.PrivateKey)
if err != nil { if err != nil {
return nil, err return nil, err
} }
tlsConfig.Certificates = []tlsC.Certificate{tlsC.UCertificate(cert)} tlsConfig.GetCertificate = func(*tls.ClientHelloInfo) (*tls.Certificate, error) {
return certLoader()
}
if config.EchKey != "" { if config.EchKey != "" {
err = ech.LoadECHKey(config.EchKey, tlsConfig, C.Path) err = ech.LoadECHKey(config.EchKey, tlsConfig)
if err != nil { if err != nil {
return nil, err return nil, err
} }
} }
} }
tlsConfig.ClientAuth = tlsC.ClientAuthTypeFromString(config.ClientAuthType) tlsConfig.ClientAuth = ca.ClientAuthTypeFromString(config.ClientAuthType)
if len(config.ClientAuthCert) > 0 { if len(config.ClientAuthCert) > 0 {
if tlsConfig.ClientAuth == tlsC.NoClientCert { if tlsConfig.ClientAuth == tls.NoClientCert {
tlsConfig.ClientAuth = tlsC.RequireAndVerifyClientCert tlsConfig.ClientAuth = tls.RequireAndVerifyClientCert
} }
} }
if tlsConfig.ClientAuth == tlsC.VerifyClientCertIfGiven || tlsConfig.ClientAuth == tlsC.RequireAndVerifyClientCert { if tlsConfig.ClientAuth == tls.VerifyClientCertIfGiven || tlsConfig.ClientAuth == tls.RequireAndVerifyClientCert {
pool, err := ca.LoadCertificates(config.ClientAuthCert, C.Path) pool, err := ca.LoadCertificates(config.ClientAuthCert)
if err != nil { if err != nil {
return nil, err return nil, err
} }
tlsConfig.ClientCAs = pool tlsConfig.ClientCAs = pool
} }
if config.RealityConfig.PrivateKey != "" { if config.RealityConfig.PrivateKey != "" {
if tlsConfig.Certificates != nil { if tlsConfig.GetCertificate != nil {
return nil, errors.New("certificate is unavailable in reality") return nil, errors.New("certificate is unavailable in reality")
} }
if tlsConfig.ClientAuth != tlsC.NoClientCert { if tlsConfig.ClientAuth != tls.NoClientCert {
return nil, errors.New("client-auth is unavailable in reality") return nil, errors.New("client-auth is unavailable in reality")
} }
realityBuilder, err = config.RealityConfig.Build(tunnel) realityBuilder, err = config.RealityConfig.Build(tunnel)
@@ -111,8 +114,8 @@ func NewWithConfig(config LC.AuthServer, tunnel C.Tunnel, additions ...inbound.A
if realityBuilder != nil { if realityBuilder != nil {
l = realityBuilder.NewListener(l) l = realityBuilder.NewListener(l)
} else if len(tlsConfig.Certificates) > 0 { } else if tlsConfig.GetCertificate != nil {
l = tlsC.NewListener(l, tlsConfig) l = tls.NewListener(l, tlsConfig)
} }
hl := &Listener{ hl := &Listener{

View File

@@ -2,15 +2,16 @@ package http
import ( import (
"context" "context"
"crypto/tls"
"net" "net"
"net/http"
"strings" "strings"
"github.com/metacubex/mihomo/adapter/inbound" "github.com/metacubex/mihomo/adapter/inbound"
N "github.com/metacubex/mihomo/common/net" N "github.com/metacubex/mihomo/common/net"
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"
"github.com/metacubex/tls"
) )
func isUpgradeRequest(req *http.Request) bool { func isUpgradeRequest(req *http.Request) bool {

View File

@@ -4,9 +4,10 @@ import (
"encoding/base64" "encoding/base64"
"errors" "errors"
"net" "net"
"net/http"
"net/netip" "net/netip"
"strings" "strings"
"github.com/metacubex/http"
) )
// removeHopByHopHeaders remove Proxy-* headers // removeHopByHopHeaders remove Proxy-* headers

View File

@@ -4,12 +4,10 @@ import (
"bytes" "bytes"
"context" "context"
"crypto/rand" "crypto/rand"
"crypto/tls"
"encoding/base64" "encoding/base64"
"fmt" "fmt"
"io" "io"
"net" "net"
"net/http"
"net/netip" "net/netip"
"strconv" "strconv"
"sync" "sync"
@@ -23,13 +21,13 @@ import (
"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/generator" "github.com/metacubex/mihomo/component/generator"
tlsC "github.com/metacubex/mihomo/component/tls"
C "github.com/metacubex/mihomo/constant" C "github.com/metacubex/mihomo/constant"
"github.com/go-chi/chi/v5" "github.com/metacubex/chi"
"github.com/go-chi/render" "github.com/metacubex/chi/render"
"github.com/metacubex/http"
"github.com/metacubex/tls"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"golang.org/x/net/http2"
) )
var httpPath = "/inbound_test" var httpPath = "/inbound_test"
@@ -157,9 +155,9 @@ func NewHttpTestTunnel() *TestTunnel {
io.Copy(io.Discard, r.Body) io.Copy(io.Discard, r.Body)
render.Data(w, r, httpData[:size]) render.Data(w, r, httpData[:size])
}) })
h2Server := &http2.Server{} //h2Server := &http.Http2Server{}
server := http.Server{Handler: r} server := http.Server{Handler: r}
_ = http2.ConfigureServer(&server, h2Server) //_ = http.Http2ConfigureServer(&server, h2Server)
go server.Serve(ln) go server.Serve(ln)
testFn := func(t *testing.T, proxy C.ProxyAdapter, proto string, size int) { testFn := func(t *testing.T, proxy C.ProxyAdapter, proto string, size int) {
req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("%s://%s%s?size=%d", proto, remoteAddr, httpPath, size), bytes.NewReader(httpData[:size])) req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("%s://%s%s?size=%d", proto, remoteAddr, httpPath, size), bytes.NewReader(httpData[:size]))
@@ -216,6 +214,9 @@ func NewHttpTestTunnel() *TestTunnel {
defer resp.Body.Close() defer resp.Body.Close()
assert.Equal(t, http.StatusOK, resp.StatusCode) assert.Equal(t, http.StatusOK, resp.StatusCode)
if proto == "https" { // ensure server using http2
assert.Equal(t, 2, resp.ProtoMajor)
}
data, err := io.ReadAll(resp.Body) data, err := io.ReadAll(resp.Body)
if !assert.NoError(t, err) { if !assert.NoError(t, err) {
@@ -268,7 +269,7 @@ func NewHttpTestTunnel() *TestTunnel {
ch: make(chan struct{}), ch: make(chan struct{}),
} }
if metadata.DstPort == 443 { if metadata.DstPort == 443 {
tlsConn := tlsC.Server(c, tlsC.UConfig(tlsConfig)) tlsConn := tls.Server(c, tlsConfig)
if metadata.Host == realityDest { // ignore the tls handshake error for realityDest if metadata.Host == realityDest { // ignore the tls handshake error for realityDest
if realityRealDial { if realityRealDial {
rconn, err := dialer.DialContext(ctx, "tcp", metadata.RemoteAddress()) rconn, err := dialer.DialContext(ctx, "tcp", metadata.RemoteAddress())
@@ -284,11 +285,12 @@ func NewHttpTestTunnel() *TestTunnel {
if err := tlsConn.HandshakeContext(ctx); err != nil { if err := tlsConn.HandshakeContext(ctx); err != nil {
return return
} }
if tlsConn.ConnectionState().NegotiatedProtocol == http2.NextProtoTLS { //if tlsConn.ConnectionState().NegotiatedProtocol == http.Http2NextProtoTLS {
h2Server.ServeConn(tlsConn, &http2.ServeConnOpts{BaseConfig: &server}) // h2Server.ServeConn(tlsConn, &http.Http2ServeConnOpts{BaseConfig: &server})
} else { //} else {
ln.ch <- tlsConn // ln.ch <- tlsConn
} //}
ln.ch <- tlsConn
} else { } else {
ln.ch <- c ln.ch <- c
} }

View File

@@ -5,23 +5,26 @@ import (
"fmt" "fmt"
"strings" "strings"
"github.com/saba-futai/sudoku/apis"
C "github.com/metacubex/mihomo/constant" C "github.com/metacubex/mihomo/constant"
LC "github.com/metacubex/mihomo/listener/config" LC "github.com/metacubex/mihomo/listener/config"
sudokuListener "github.com/metacubex/mihomo/listener/sudoku" "github.com/metacubex/mihomo/listener/sudoku"
"github.com/metacubex/mihomo/log" "github.com/metacubex/mihomo/log"
) )
type SudokuOption struct { type SudokuOption struct {
BaseOption BaseOption
Key string `inbound:"key"` Key string `inbound:"key"`
AEADMethod string `inbound:"aead-method,omitempty"` AEADMethod string `inbound:"aead-method,omitempty"`
PaddingMin *int `inbound:"padding-min,omitempty"` PaddingMin *int `inbound:"padding-min,omitempty"`
PaddingMax *int `inbound:"padding-max,omitempty"` PaddingMax *int `inbound:"padding-max,omitempty"`
Seed string `inbound:"seed,omitempty"` TableType string `inbound:"table-type,omitempty"` // "prefer_ascii" or "prefer_entropy"
TableType string `inbound:"table-type,omitempty"` // "prefer_ascii" or "prefer_entropy" HandshakeTimeoutSecond *int `inbound:"handshake-timeout,omitempty"`
HandshakeTimeoutSecond *int `inbound:"handshake-timeout,omitempty"` EnablePureDownlink *bool `inbound:"enable-pure-downlink,omitempty"`
CustomTable string `inbound:"custom-table,omitempty"` // optional custom byte layout, e.g. xpxvvpvv
CustomTables []string `inbound:"custom-tables,omitempty"`
// mihomo private extension (not the part of standard Sudoku protocol)
MuxOption MuxOption `inbound:"mux-option,omitempty"`
} }
func (o SudokuOption) Equal(config C.InboundConfig) bool { func (o SudokuOption) Equal(config C.InboundConfig) bool {
@@ -31,7 +34,7 @@ func (o SudokuOption) Equal(config C.InboundConfig) bool {
type Sudoku struct { type Sudoku struct {
*Base *Base
config *SudokuOption config *SudokuOption
listeners []*sudokuListener.Listener listeners []*sudoku.Listener
serverConf LC.SudokuServer serverConf LC.SudokuServer
} }
@@ -44,25 +47,20 @@ func NewSudoku(options *SudokuOption) (*Sudoku, error) {
return nil, err return nil, err
} }
defaultConf := apis.DefaultConfig()
serverConf := LC.SudokuServer{ serverConf := LC.SudokuServer{
Enable: true, Enable: true,
Listen: base.RawAddress(), Listen: base.RawAddress(),
Key: options.Key, Key: options.Key,
AEADMethod: options.AEADMethod, AEADMethod: options.AEADMethod,
PaddingMin: options.PaddingMin, PaddingMin: options.PaddingMin,
PaddingMax: options.PaddingMax, PaddingMax: options.PaddingMax,
Seed: options.Seed, TableType: options.TableType,
TableType: options.TableType, HandshakeTimeoutSecond: options.HandshakeTimeoutSecond,
} EnablePureDownlink: options.EnablePureDownlink,
if options.HandshakeTimeoutSecond != nil { CustomTable: options.CustomTable,
serverConf.HandshakeTimeoutSecond = options.HandshakeTimeoutSecond CustomTables: options.CustomTables,
} else {
// Use Sudoku default if not specified.
v := defaultConf.HandshakeTimeoutSeconds
serverConf.HandshakeTimeoutSecond = &v
} }
serverConf.MuxOption = options.MuxOption.Build()
return &Sudoku{ return &Sudoku{
Base: base, Base: base,
@@ -96,7 +94,7 @@ func (s *Sudoku) Listen(tunnel C.Tunnel) error {
conf := s.serverConf conf := s.serverConf
conf.Listen = addr conf.Listen = addr
l, err := sudokuListener.New(conf, tunnel, s.Additions()...) l, err := sudoku.New(conf, tunnel, s.Additions()...)
if err != nil { if err != nil {
errs = append(errs, err) errs = append(errs, err)
continue continue

View File

@@ -6,9 +6,12 @@ import (
"github.com/metacubex/mihomo/adapter/outbound" "github.com/metacubex/mihomo/adapter/outbound"
"github.com/metacubex/mihomo/listener/inbound" "github.com/metacubex/mihomo/listener/inbound"
"github.com/metacubex/mihomo/transport/sudoku"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
var sudokuPrivateKey, sudokuPublicKey, _ = sudoku.GenKeyPair()
func testInboundSudoku(t *testing.T, inboundOptions inbound.SudokuOption, outboundOptions outbound.SudokuOption) { func testInboundSudoku(t *testing.T, inboundOptions inbound.SudokuOption, outboundOptions outbound.SudokuOption) {
t.Parallel() t.Parallel()
@@ -47,6 +50,8 @@ func testInboundSudoku(t *testing.T, inboundOptions inbound.SudokuOption, outbou
defer out.Close() defer out.Close()
tunnel.DoTest(t, out) tunnel.DoTest(t, out)
testSingMux(t, tunnel, out)
} }
func TestInboundSudoku_Basic(t *testing.T) { func TestInboundSudoku_Basic(t *testing.T) {
@@ -58,6 +63,14 @@ func TestInboundSudoku_Basic(t *testing.T) {
Key: key, Key: key,
} }
testInboundSudoku(t, inboundOptions, outboundOptions) testInboundSudoku(t, inboundOptions, outboundOptions)
t.Run("ed25519key", func(t *testing.T) {
inboundOptions := inboundOptions
outboundOptions := outboundOptions
inboundOptions.Key = sudokuPublicKey
outboundOptions.Key = sudokuPrivateKey
testInboundSudoku(t, inboundOptions, outboundOptions)
})
} }
func TestInboundSudoku_Entropy(t *testing.T) { func TestInboundSudoku_Entropy(t *testing.T) {
@@ -71,21 +84,83 @@ func TestInboundSudoku_Entropy(t *testing.T) {
TableType: "prefer_entropy", TableType: "prefer_entropy",
} }
testInboundSudoku(t, inboundOptions, outboundOptions) testInboundSudoku(t, inboundOptions, outboundOptions)
t.Run("ed25519key", func(t *testing.T) {
inboundOptions := inboundOptions
outboundOptions := outboundOptions
inboundOptions.Key = sudokuPublicKey
outboundOptions.Key = sudokuPrivateKey
testInboundSudoku(t, inboundOptions, outboundOptions)
})
} }
func TestInboundSudoku_Padding(t *testing.T) { func TestInboundSudoku_Padding(t *testing.T) {
key := "test_key_padding" key := "test_key_padding"
min := 10 paddingMin := 10
max := 100 paddingMax := 100
inboundOptions := inbound.SudokuOption{ inboundOptions := inbound.SudokuOption{
Key: key, Key: key,
PaddingMin: &min, PaddingMin: &paddingMin,
PaddingMax: &max, PaddingMax: &paddingMax,
} }
outboundOptions := outbound.SudokuOption{ outboundOptions := outbound.SudokuOption{
Key: key, Key: key,
PaddingMin: &min, PaddingMin: &paddingMin,
PaddingMax: &max, PaddingMax: &paddingMax,
} }
testInboundSudoku(t, inboundOptions, outboundOptions) testInboundSudoku(t, inboundOptions, outboundOptions)
t.Run("ed25519key", func(t *testing.T) {
inboundOptions := inboundOptions
outboundOptions := outboundOptions
inboundOptions.Key = sudokuPublicKey
outboundOptions.Key = sudokuPrivateKey
testInboundSudoku(t, inboundOptions, outboundOptions)
})
}
func TestInboundSudoku_PackedDownlink(t *testing.T) {
key := "test_key_packed"
enablePure := false
inboundOptions := inbound.SudokuOption{
Key: key,
EnablePureDownlink: &enablePure,
}
outboundOptions := outbound.SudokuOption{
Key: key,
EnablePureDownlink: &enablePure,
}
testInboundSudoku(t, inboundOptions, outboundOptions)
t.Run("ed25519key", func(t *testing.T) {
inboundOptions := inboundOptions
outboundOptions := outboundOptions
inboundOptions.Key = sudokuPublicKey
outboundOptions.Key = sudokuPrivateKey
testInboundSudoku(t, inboundOptions, outboundOptions)
})
}
func TestInboundSudoku_CustomTable(t *testing.T) {
key := "test_key_custom"
custom := "xpxvvpvv"
inboundOptions := inbound.SudokuOption{
Key: key,
TableType: "prefer_entropy",
CustomTable: custom,
}
outboundOptions := outbound.SudokuOption{
Key: key,
TableType: "prefer_entropy",
CustomTable: custom,
}
testInboundSudoku(t, inboundOptions, outboundOptions)
t.Run("ed25519key", func(t *testing.T) {
inboundOptions := inboundOptions
outboundOptions := outboundOptions
inboundOptions.Key = sudokuPublicKey
outboundOptions.Key = sudokuPrivateKey
testInboundSudoku(t, inboundOptions, outboundOptions)
})
} }

View File

@@ -9,7 +9,6 @@ import (
"github.com/metacubex/mihomo/component/auth" "github.com/metacubex/mihomo/component/auth"
"github.com/metacubex/mihomo/component/ca" "github.com/metacubex/mihomo/component/ca"
"github.com/metacubex/mihomo/component/ech" "github.com/metacubex/mihomo/component/ech"
tlsC "github.com/metacubex/mihomo/component/tls"
C "github.com/metacubex/mihomo/constant" C "github.com/metacubex/mihomo/constant"
authStore "github.com/metacubex/mihomo/listener/auth" authStore "github.com/metacubex/mihomo/listener/auth"
LC "github.com/metacubex/mihomo/listener/config" LC "github.com/metacubex/mihomo/listener/config"
@@ -19,6 +18,8 @@ import (
"github.com/metacubex/mihomo/ntp" "github.com/metacubex/mihomo/ntp"
"github.com/metacubex/mihomo/transport/socks4" "github.com/metacubex/mihomo/transport/socks4"
"github.com/metacubex/mihomo/transport/socks5" "github.com/metacubex/mihomo/transport/socks5"
"github.com/metacubex/tls"
) )
type Listener struct { type Listener struct {
@@ -62,41 +63,43 @@ func NewWithConfig(config LC.AuthServer, tunnel C.Tunnel, additions ...inbound.A
return nil, err return nil, err
} }
tlsConfig := &tlsC.Config{Time: ntp.Now} tlsConfig := &tls.Config{Time: ntp.Now}
var realityBuilder *reality.Builder var realityBuilder *reality.Builder
if config.Certificate != "" && config.PrivateKey != "" { if config.Certificate != "" && config.PrivateKey != "" {
cert, err := ca.LoadTLSKeyPair(config.Certificate, config.PrivateKey, C.Path) certLoader, err := ca.NewTLSKeyPairLoader(config.Certificate, config.PrivateKey)
if err != nil { if err != nil {
return nil, err return nil, err
} }
tlsConfig.Certificates = []tlsC.Certificate{tlsC.UCertificate(cert)} tlsConfig.GetCertificate = func(*tls.ClientHelloInfo) (*tls.Certificate, error) {
return certLoader()
}
if config.EchKey != "" { if config.EchKey != "" {
err = ech.LoadECHKey(config.EchKey, tlsConfig, C.Path) err = ech.LoadECHKey(config.EchKey, tlsConfig)
if err != nil { if err != nil {
return nil, err return nil, err
} }
} }
} }
tlsConfig.ClientAuth = tlsC.ClientAuthTypeFromString(config.ClientAuthType) tlsConfig.ClientAuth = ca.ClientAuthTypeFromString(config.ClientAuthType)
if len(config.ClientAuthCert) > 0 { if len(config.ClientAuthCert) > 0 {
if tlsConfig.ClientAuth == tlsC.NoClientCert { if tlsConfig.ClientAuth == tls.NoClientCert {
tlsConfig.ClientAuth = tlsC.RequireAndVerifyClientCert tlsConfig.ClientAuth = tls.RequireAndVerifyClientCert
} }
} }
if tlsConfig.ClientAuth == tlsC.VerifyClientCertIfGiven || tlsConfig.ClientAuth == tlsC.RequireAndVerifyClientCert { if tlsConfig.ClientAuth == tls.VerifyClientCertIfGiven || tlsConfig.ClientAuth == tls.RequireAndVerifyClientCert {
pool, err := ca.LoadCertificates(config.ClientAuthCert, C.Path) pool, err := ca.LoadCertificates(config.ClientAuthCert)
if err != nil { if err != nil {
return nil, err return nil, err
} }
tlsConfig.ClientCAs = pool tlsConfig.ClientCAs = pool
} }
if config.RealityConfig.PrivateKey != "" { if config.RealityConfig.PrivateKey != "" {
if tlsConfig.Certificates != nil { if tlsConfig.GetCertificate != nil {
return nil, errors.New("certificate is unavailable in reality") return nil, errors.New("certificate is unavailable in reality")
} }
if tlsConfig.ClientAuth != tlsC.NoClientCert { if tlsConfig.ClientAuth != tls.NoClientCert {
return nil, errors.New("client-auth is unavailable in reality") return nil, errors.New("client-auth is unavailable in reality")
} }
realityBuilder, err = config.RealityConfig.Build(tunnel) realityBuilder, err = config.RealityConfig.Build(tunnel)
@@ -107,8 +110,8 @@ func NewWithConfig(config LC.AuthServer, tunnel C.Tunnel, additions ...inbound.A
if realityBuilder != nil { if realityBuilder != nil {
l = realityBuilder.NewListener(l) l = realityBuilder.NewListener(l)
} else if len(tlsConfig.Certificates) > 0 { } else if tlsConfig.GetCertificate != nil {
l = tlsC.NewListener(l, tlsConfig) l = tls.NewListener(l, tlsConfig)
} }
ml := &Listener{ ml := &Listener{

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