mirror of
https://github.com/MetaCubeX/mihomo.git
synced 2026-02-28 17:49:55 +00:00
Compare commits
127 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0128a0bb1f | ||
|
|
c830b8aaf7 | ||
|
|
1db3e4583b | ||
|
|
4f8a5a5f54 | ||
|
|
303f6e4567 | ||
|
|
0d90a93645 | ||
|
|
40c9829328 | ||
|
|
4051ea522a | ||
|
|
cc7823dad8 | ||
|
|
d6a1af23a7 | ||
|
|
313493cc94 | ||
|
|
4b9fdacbad | ||
|
|
13b7ab8da3 | ||
|
|
de61e81ff7 | ||
|
|
4eb13a73bf | ||
|
|
fd5b537ab1 | ||
|
|
28794c62c4 | ||
|
|
e263518f01 | ||
|
|
345061a7cc | ||
|
|
a05016a5da | ||
|
|
9e3589d638 | ||
|
|
fc03bd2f0d | ||
|
|
4277dc6eab | ||
|
|
0e228765fc | ||
|
|
f45ccc0761 | ||
|
|
9f4cd646c2 | ||
|
|
a9ecc627e6 | ||
|
|
50286678bf | ||
|
|
917c5fdd80 | ||
|
|
1457f83530 | ||
|
|
5ab8154e7e | ||
|
|
09be5cbc99 | ||
|
|
0738e18100 | ||
|
|
40f40f6d24 | ||
|
|
ad5bc51c77 | ||
|
|
75c16f9b87 | ||
|
|
d96d7651ca | ||
|
|
a5f25a2246 | ||
|
|
2b4741fbc7 | ||
|
|
f317baa8de | ||
|
|
5678131591 | ||
|
|
10f8ba4434 | ||
|
|
cacfefad4b | ||
|
|
0d4e57cb21 | ||
|
|
063836fe5d | ||
|
|
7b3c9e94e6 | ||
|
|
be3d121ec6 | ||
|
|
59fd3cffe3 | ||
|
|
39eda257a7 | ||
|
|
d3fea909e9 | ||
|
|
7eb70aeb4d | ||
|
|
846bdfa812 | ||
|
|
0b6ae6ffb8 | ||
|
|
71922dd0b1 | ||
|
|
5eb8958ff2 | ||
|
|
ac2506154f | ||
|
|
43bdc76f87 | ||
|
|
3195c678c7 | ||
|
|
bd43eca09d | ||
|
|
c504985b99 | ||
|
|
4243a74284 | ||
|
|
48e481d0a2 | ||
|
|
e749c7e492 | ||
|
|
c3ee921d30 | ||
|
|
df69a31e62 | ||
|
|
b7c02a5923 | ||
|
|
bfb6caeeaf | ||
|
|
30a913aad6 | ||
|
|
00e361c5ac | ||
|
|
56edd8f671 | ||
|
|
2b52809d2c | ||
|
|
5c3a9b1dfc | ||
|
|
fe88f0e437 | ||
|
|
87877d1b80 | ||
|
|
1bc3c16b59 | ||
|
|
ed1e7e32c7 | ||
|
|
5da9ccaa98 | ||
|
|
fd7ecc004f | ||
|
|
3ae4014b39 | ||
|
|
a50339bd5f | ||
|
|
7df1c26942 | ||
|
|
fc82a32a48 | ||
|
|
adf0ff588f | ||
|
|
b840eae4c6 | ||
|
|
619f34119e | ||
|
|
6d1c62bbf0 | ||
|
|
5dd883e790 | ||
|
|
a2b43faa0b | ||
|
|
8861eaf903 | ||
|
|
107e3e7630 | ||
|
|
81947304bc | ||
|
|
314c0bb34b | ||
|
|
89a097faa8 | ||
|
|
df01582996 | ||
|
|
8ff56b5bb8 | ||
|
|
2f8f139f7c | ||
|
|
b2280c85b7 | ||
|
|
002b8af94a | ||
|
|
99b274acbc | ||
|
|
d4ececae20 | ||
|
|
ff2071c1da | ||
|
|
189b7b9c5f | ||
|
|
a878254662 | ||
|
|
3566542d0e | ||
|
|
ca84ab1a94 | ||
|
|
d84f88b50f | ||
|
|
e3b69b8ae2 | ||
|
|
91a7ffaad2 | ||
|
|
16fadd2441 | ||
|
|
cff7df164f | ||
|
|
de38fa882c | ||
|
|
bd703b8ff2 | ||
|
|
72df27be44 | ||
|
|
f3e23b1128 | ||
|
|
19f7220c0b | ||
|
|
3249572dc1 | ||
|
|
ba09139bd7 | ||
|
|
90bf158e9f | ||
|
|
d1539e6c07 | ||
|
|
c893e3c462 | ||
|
|
b56e73a02a | ||
|
|
40f5c5b987 | ||
|
|
d48517b29d | ||
|
|
3b472f786e | ||
|
|
f3743fc7f9 | ||
|
|
56ed9019a6 | ||
|
|
3e0bd65135 |
4
.github/mihomo.service
vendored
4
.github/mihomo.service
vendored
@@ -6,8 +6,8 @@ After=network.target NetworkManager.service systemd-networkd.service iwd.service
|
||||
Type=simple
|
||||
LimitNPROC=500
|
||||
LimitNOFILE=1000000
|
||||
CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_RAW CAP_NET_BIND_SERVICE CAP_SYS_TIME CAP_SYS_PTRACE CAP_DAC_READ_SEARCH
|
||||
AmbientCapabilities=CAP_NET_ADMIN CAP_NET_RAW CAP_NET_BIND_SERVICE CAP_SYS_TIME CAP_SYS_PTRACE CAP_DAC_READ_SEARCH
|
||||
CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_RAW CAP_NET_BIND_SERVICE CAP_SYS_TIME CAP_SYS_PTRACE CAP_DAC_READ_SEARCH CAP_DAC_OVERRIDE
|
||||
AmbientCapabilities=CAP_NET_ADMIN CAP_NET_RAW CAP_NET_BIND_SERVICE CAP_SYS_TIME CAP_SYS_PTRACE CAP_DAC_READ_SEARCH CAP_DAC_OVERRIDE
|
||||
Restart=always
|
||||
ExecStartPre=/usr/bin/sleep 2s
|
||||
ExecStart=/usr/bin/mihomo -d /etc/mihomo
|
||||
|
||||
54
.github/patch_go122/48042aa09c2f878c4faa576948b07fe625c4707a.diff
vendored
Normal file
54
.github/patch_go122/48042aa09c2f878c4faa576948b07fe625c4707a.diff
vendored
Normal file
@@ -0,0 +1,54 @@
|
||||
diff --git a/src/syscall/exec_windows.go b/src/syscall/exec_windows.go
|
||||
index 06e684c7116b4..b311a5c74684b 100644
|
||||
--- a/src/syscall/exec_windows.go
|
||||
+++ b/src/syscall/exec_windows.go
|
||||
@@ -319,17 +319,6 @@ func StartProcess(argv0 string, argv []string, attr *ProcAttr) (pid int, handle
|
||||
}
|
||||
}
|
||||
|
||||
- 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 {
|
||||
@@ -338,15 +327,7 @@ func StartProcess(argv0 string, argv []string, attr *ProcAttr) (pid int, handle
|
||||
fd := make([]Handle, len(attr.Files))
|
||||
for i := range attr.Files {
|
||||
if attr.Files[i] > 0 {
|
||||
- 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)
|
||||
+ err := DuplicateHandle(p, Handle(attr.Files[i]), parentProcess, &fd[i], 0, true, DUPLICATE_SAME_ACCESS)
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
@@ -377,14 +358,6 @@ func StartProcess(argv0 string, argv []string, attr *ProcAttr) (pid int, handle
|
||||
|
||||
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
|
||||
158
.github/patch_go122/693def151adff1af707d82d28f55dba81ceb08e1.diff
vendored
Normal file
158
.github/patch_go122/693def151adff1af707d82d28f55dba81ceb08e1.diff
vendored
Normal file
@@ -0,0 +1,158 @@
|
||||
diff --git a/src/crypto/rand/rand.go b/src/crypto/rand/rand.go
|
||||
index 62738e2cb1a7d..d0dcc7cc71fc0 100644
|
||||
--- a/src/crypto/rand/rand.go
|
||||
+++ b/src/crypto/rand/rand.go
|
||||
@@ -15,7 +15,7 @@ import "io"
|
||||
// available, /dev/urandom otherwise.
|
||||
// On OpenBSD and macOS, Reader uses getentropy(2).
|
||||
// On other Unix-like systems, Reader reads from /dev/urandom.
|
||||
-// On Windows systems, Reader uses the RtlGenRandom API.
|
||||
+// On Windows systems, Reader uses the ProcessPrng API.
|
||||
// On JS/Wasm, Reader uses the Web Crypto API.
|
||||
// On WASIP1/Wasm, Reader uses random_get from wasi_snapshot_preview1.
|
||||
var Reader io.Reader
|
||||
diff --git a/src/crypto/rand/rand_windows.go b/src/crypto/rand/rand_windows.go
|
||||
index 6c0655c72b692..7380f1f0f1e6e 100644
|
||||
--- a/src/crypto/rand/rand_windows.go
|
||||
+++ b/src/crypto/rand/rand_windows.go
|
||||
@@ -15,11 +15,8 @@ func init() { Reader = &rngReader{} }
|
||||
|
||||
type rngReader struct{}
|
||||
|
||||
-func (r *rngReader) Read(b []byte) (n int, err error) {
|
||||
- // RtlGenRandom only returns 1<<32-1 bytes at a time. We only read at
|
||||
- // most 1<<31-1 bytes at a time so that this works the same on 32-bit
|
||||
- // and 64-bit systems.
|
||||
- if err := batched(windows.RtlGenRandom, 1<<31-1)(b); err != nil {
|
||||
+func (r *rngReader) Read(b []byte) (int, error) {
|
||||
+ if err := windows.ProcessPrng(b); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return len(b), nil
|
||||
diff --git a/src/internal/syscall/windows/syscall_windows.go b/src/internal/syscall/windows/syscall_windows.go
|
||||
index ab4ad2ec64108..5854ca60b5cef 100644
|
||||
--- a/src/internal/syscall/windows/syscall_windows.go
|
||||
+++ b/src/internal/syscall/windows/syscall_windows.go
|
||||
@@ -373,7 +373,7 @@ func ErrorLoadingGetTempPath2() error {
|
||||
//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 RtlGenRandom(buf []byte) (err error) = advapi32.SystemFunction036
|
||||
+//sys ProcessPrng(buf []byte) (err error) = bcryptprimitives.ProcessPrng
|
||||
|
||||
type FILE_ID_BOTH_DIR_INFO struct {
|
||||
NextEntryOffset uint32
|
||||
diff --git a/src/internal/syscall/windows/zsyscall_windows.go b/src/internal/syscall/windows/zsyscall_windows.go
|
||||
index e3f6d8d2a2208..5a587ad4f146c 100644
|
||||
--- a/src/internal/syscall/windows/zsyscall_windows.go
|
||||
+++ b/src/internal/syscall/windows/zsyscall_windows.go
|
||||
@@ -37,13 +37,14 @@ func errnoErr(e syscall.Errno) error {
|
||||
}
|
||||
|
||||
var (
|
||||
- modadvapi32 = syscall.NewLazyDLL(sysdll.Add("advapi32.dll"))
|
||||
- modiphlpapi = syscall.NewLazyDLL(sysdll.Add("iphlpapi.dll"))
|
||||
- modkernel32 = syscall.NewLazyDLL(sysdll.Add("kernel32.dll"))
|
||||
- modnetapi32 = syscall.NewLazyDLL(sysdll.Add("netapi32.dll"))
|
||||
- modpsapi = syscall.NewLazyDLL(sysdll.Add("psapi.dll"))
|
||||
- moduserenv = syscall.NewLazyDLL(sysdll.Add("userenv.dll"))
|
||||
- modws2_32 = syscall.NewLazyDLL(sysdll.Add("ws2_32.dll"))
|
||||
+ modadvapi32 = syscall.NewLazyDLL(sysdll.Add("advapi32.dll"))
|
||||
+ modbcryptprimitives = syscall.NewLazyDLL(sysdll.Add("bcryptprimitives.dll"))
|
||||
+ modiphlpapi = syscall.NewLazyDLL(sysdll.Add("iphlpapi.dll"))
|
||||
+ modkernel32 = syscall.NewLazyDLL(sysdll.Add("kernel32.dll"))
|
||||
+ modnetapi32 = syscall.NewLazyDLL(sysdll.Add("netapi32.dll"))
|
||||
+ modpsapi = syscall.NewLazyDLL(sysdll.Add("psapi.dll"))
|
||||
+ moduserenv = syscall.NewLazyDLL(sysdll.Add("userenv.dll"))
|
||||
+ modws2_32 = syscall.NewLazyDLL(sysdll.Add("ws2_32.dll"))
|
||||
|
||||
procAdjustTokenPrivileges = modadvapi32.NewProc("AdjustTokenPrivileges")
|
||||
procDuplicateTokenEx = modadvapi32.NewProc("DuplicateTokenEx")
|
||||
@@ -55,7 +56,7 @@ var (
|
||||
procQueryServiceStatus = modadvapi32.NewProc("QueryServiceStatus")
|
||||
procRevertToSelf = modadvapi32.NewProc("RevertToSelf")
|
||||
procSetTokenInformation = modadvapi32.NewProc("SetTokenInformation")
|
||||
- procSystemFunction036 = modadvapi32.NewProc("SystemFunction036")
|
||||
+ procProcessPrng = modbcryptprimitives.NewProc("ProcessPrng")
|
||||
procGetAdaptersAddresses = modiphlpapi.NewProc("GetAdaptersAddresses")
|
||||
procCreateEventW = modkernel32.NewProc("CreateEventW")
|
||||
procGetACP = modkernel32.NewProc("GetACP")
|
||||
@@ -179,12 +180,12 @@ func SetTokenInformation(tokenHandle syscall.Token, tokenInformationClass uint32
|
||||
return
|
||||
}
|
||||
|
||||
-func RtlGenRandom(buf []byte) (err error) {
|
||||
+func ProcessPrng(buf []byte) (err error) {
|
||||
var _p0 *byte
|
||||
if len(buf) > 0 {
|
||||
_p0 = &buf[0]
|
||||
}
|
||||
- r1, _, e1 := syscall.Syscall(procSystemFunction036.Addr(), 2, uintptr(unsafe.Pointer(_p0)), uintptr(len(buf)), 0)
|
||||
+ r1, _, e1 := syscall.Syscall(procProcessPrng.Addr(), 2, uintptr(unsafe.Pointer(_p0)), uintptr(len(buf)), 0)
|
||||
if r1 == 0 {
|
||||
err = errnoErr(e1)
|
||||
}
|
||||
diff --git a/src/runtime/os_windows.go b/src/runtime/os_windows.go
|
||||
index 8ca8d7790909e..3772a864b2ff4 100644
|
||||
--- a/src/runtime/os_windows.go
|
||||
+++ b/src/runtime/os_windows.go
|
||||
@@ -127,15 +127,8 @@ var (
|
||||
_WriteFile,
|
||||
_ 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
|
||||
+ // Use ProcessPrng to generate cryptographically random data.
|
||||
+ _ProcessPrng stdFunction
|
||||
|
||||
// Load ntdll.dll manually during startup, otherwise Mingw
|
||||
// links wrong printf function to cgo executable (see issue
|
||||
@@ -151,11 +144,11 @@ var (
|
||||
)
|
||||
|
||||
var (
|
||||
- advapi32dll = [...]uint16{'a', 'd', 'v', 'a', 'p', 'i', '3', '2', '.', 'd', 'l', 'l', 0}
|
||||
- ntdlldll = [...]uint16{'n', 't', 'd', 'l', 'l', '.', 'd', 'l', 'l', 0}
|
||||
- powrprofdll = [...]uint16{'p', 'o', 'w', 'r', 'p', 'r', 'o', 'f', '.', 'd', 'l', 'l', 0}
|
||||
- winmmdll = [...]uint16{'w', 'i', 'n', 'm', 'm', '.', 'd', 'l', 'l', 0}
|
||||
- ws2_32dll = [...]uint16{'w', 's', '2', '_', '3', '2', '.', 'd', 'l', 'l', 0}
|
||||
+ bcryptprimitivesdll = [...]uint16{'b', 'c', 'r', 'y', 'p', 't', 'p', 'r', 'i', 'm', 'i', 't', 'i', 'v', 'e', 's', '.', 'd', 'l', 'l', 0}
|
||||
+ ntdlldll = [...]uint16{'n', 't', 'd', 'l', 'l', '.', 'd', 'l', 'l', 0}
|
||||
+ powrprofdll = [...]uint16{'p', 'o', 'w', 'r', 'p', 'r', 'o', 'f', '.', 'd', 'l', 'l', 0}
|
||||
+ winmmdll = [...]uint16{'w', 'i', 'n', 'm', 'm', '.', 'd', 'l', 'l', 0}
|
||||
+ ws2_32dll = [...]uint16{'w', 's', '2', '_', '3', '2', '.', 'd', 'l', 'l', 0}
|
||||
)
|
||||
|
||||
// Function to be called by windows CreateThread
|
||||
@@ -251,11 +244,11 @@ func windowsLoadSystemLib(name []uint16) uintptr {
|
||||
}
|
||||
|
||||
func loadOptionalSyscalls() {
|
||||
- a32 := windowsLoadSystemLib(advapi32dll[:])
|
||||
- if a32 == 0 {
|
||||
- throw("advapi32.dll not found")
|
||||
+ bcryptPrimitives := windowsLoadSystemLib(bcryptprimitivesdll[:])
|
||||
+ if bcryptPrimitives == 0 {
|
||||
+ throw("bcryptprimitives.dll not found")
|
||||
}
|
||||
- _RtlGenRandom = windowsFindfunc(a32, []byte("SystemFunction036\000"))
|
||||
+ _ProcessPrng = windowsFindfunc(bcryptPrimitives, []byte("ProcessPrng\000"))
|
||||
|
||||
n32 := windowsLoadSystemLib(ntdlldll[:])
|
||||
if n32 == 0 {
|
||||
@@ -531,7 +524,7 @@ func osinit() {
|
||||
//go:nosplit
|
||||
func readRandom(r []byte) int {
|
||||
n := 0
|
||||
- if stdcall2(_RtlGenRandom, uintptr(unsafe.Pointer(&r[0])), uintptr(len(r)))&0xff != 0 {
|
||||
+ if stdcall2(_ProcessPrng, uintptr(unsafe.Pointer(&r[0])), uintptr(len(r)))&0xff != 0 {
|
||||
n = len(r)
|
||||
}
|
||||
return n
|
||||
162
.github/patch_go122/7c1157f9544922e96945196b47b95664b1e39108.diff
vendored
Normal file
162
.github/patch_go122/7c1157f9544922e96945196b47b95664b1e39108.diff
vendored
Normal file
@@ -0,0 +1,162 @@
|
||||
diff --git a/src/net/hook_windows.go b/src/net/hook_windows.go
|
||||
index ab8656cbbf343..28c49cc6de7e7 100644
|
||||
--- a/src/net/hook_windows.go
|
||||
+++ b/src/net/hook_windows.go
|
||||
@@ -14,7 +14,6 @@ var (
|
||||
testHookDialChannel = func() { time.Sleep(time.Millisecond) } // see golang.org/issue/5349
|
||||
|
||||
// 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
|
||||
diff --git a/src/net/internal/socktest/main_test.go b/src/net/internal/socktest/main_test.go
|
||||
index 0197feb3f199a..967ce6795aedb 100644
|
||||
--- a/src/net/internal/socktest/main_test.go
|
||||
+++ b/src/net/internal/socktest/main_test.go
|
||||
@@ -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
|
||||
+//go:build !js && !plan9 && !wasip1 && !windows
|
||||
|
||||
package socktest_test
|
||||
|
||||
diff --git a/src/net/internal/socktest/main_windows_test.go b/src/net/internal/socktest/main_windows_test.go
|
||||
deleted file mode 100644
|
||||
index df1cb97784b51..0000000000000
|
||||
--- a/src/net/internal/socktest/main_windows_test.go
|
||||
+++ /dev/null
|
||||
@@ -1,22 +0,0 @@
|
||||
-// 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
|
||||
-}
|
||||
diff --git a/src/net/internal/socktest/sys_windows.go b/src/net/internal/socktest/sys_windows.go
|
||||
index 8c1c862f33c9b..1c42e5c7f34b7 100644
|
||||
--- a/src/net/internal/socktest/sys_windows.go
|
||||
+++ b/src/net/internal/socktest/sys_windows.go
|
||||
@@ -9,38 +9,6 @@ import (
|
||||
"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)
|
||||
diff --git a/src/net/main_windows_test.go b/src/net/main_windows_test.go
|
||||
index 07f21b72eb1fc..bc024c0bbd82d 100644
|
||||
--- a/src/net/main_windows_test.go
|
||||
+++ b/src/net/main_windows_test.go
|
||||
@@ -8,7 +8,6 @@ import "internal/poll"
|
||||
|
||||
var (
|
||||
// Placeholders for saving original socket system calls.
|
||||
- origSocket = socketFunc
|
||||
origWSASocket = wsaSocketFunc
|
||||
origClosesocket = poll.CloseFunc
|
||||
origConnect = connectFunc
|
||||
@@ -18,7 +17,6 @@ var (
|
||||
)
|
||||
|
||||
func installTestHooks() {
|
||||
- socketFunc = sw.Socket
|
||||
wsaSocketFunc = sw.WSASocket
|
||||
poll.CloseFunc = sw.Closesocket
|
||||
connectFunc = sw.Connect
|
||||
@@ -28,7 +26,6 @@ func installTestHooks() {
|
||||
}
|
||||
|
||||
func uninstallTestHooks() {
|
||||
- socketFunc = origSocket
|
||||
wsaSocketFunc = origWSASocket
|
||||
poll.CloseFunc = origClosesocket
|
||||
connectFunc = origConnect
|
||||
diff --git a/src/net/sock_windows.go b/src/net/sock_windows.go
|
||||
index fa11c7af2e727..5540135a2c43e 100644
|
||||
--- a/src/net/sock_windows.go
|
||||
+++ b/src/net/sock_windows.go
|
||||
@@ -19,21 +19,6 @@ func maxListenerBacklog() int {
|
||||
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)
|
||||
}
|
||||
diff --git a/src/syscall/exec_windows.go b/src/syscall/exec_windows.go
|
||||
index 0a93bc0a80d4e..06e684c7116b4 100644
|
||||
--- a/src/syscall/exec_windows.go
|
||||
+++ b/src/syscall/exec_windows.go
|
||||
@@ -14,6 +14,7 @@ import (
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
+// ForkLock is not used on Windows.
|
||||
var ForkLock sync.RWMutex
|
||||
|
||||
// EscapeArg rewrites command line argument s as prescribed
|
||||
201
.github/workflows/build.yml
vendored
201
.github/workflows/build.yml
vendored
@@ -1,6 +1,10 @@
|
||||
name: Build
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
version:
|
||||
description: "Tag version to release"
|
||||
required: true
|
||||
push:
|
||||
paths-ignore:
|
||||
- "docs/**"
|
||||
@@ -13,9 +17,8 @@ on:
|
||||
pull_request_target:
|
||||
branches:
|
||||
- Alpha
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
group: "${{ github.workflow }}-${{ github.ref }}"
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
@@ -34,6 +37,8 @@ jobs:
|
||||
- { goos: linux, goarch: amd64, goamd64: v1, output: amd64-compatible, test: test }
|
||||
- { goos: linux, goarch: amd64, goamd64: v3, output: amd64 }
|
||||
- { goos: linux, goarch: arm64, output: arm64 }
|
||||
- { goos: linux, goarch: arm, goarm: '5', output: armv5 }
|
||||
- { goos: linux, goarch: arm, goarm: '6', output: armv6 }
|
||||
- { goos: linux, goarch: arm, goarm: '7', output: armv7 }
|
||||
- { goos: linux, goarch: mips, mips: hardfloat, output: mips-hardfloat }
|
||||
- { goos: linux, goarch: mips, mips: softfloat, output: mips-softfloat }
|
||||
@@ -62,6 +67,13 @@ jobs:
|
||||
- { goos: android, goarch: arm, ndk: armv7a-linux-androideabi34, output: armv7 }
|
||||
- { goos: android, goarch: arm64, ndk: aarch64-linux-android34, output: arm64-v8 }
|
||||
|
||||
# Go 1.21 can revert commit `9e4385` to work on Windows 7
|
||||
# https://github.com/golang/go/issues/64622#issuecomment-1847475161
|
||||
# (OR we can just use golang1.21.4 which unneeded any patch)
|
||||
- { goos: windows, goarch: '386', output: '386-go121', goversion: '1.21' }
|
||||
- { goos: windows, goarch: amd64, goamd64: v1, output: amd64-compatible-go121, goversion: '1.21' }
|
||||
- { goos: windows, goarch: amd64, goamd64: v3, output: amd64-go121, goversion: '1.21' }
|
||||
|
||||
# Go 1.20 is the last release that will run on any release of Windows 7, 8, Server 2008 and Server 2012. Go 1.21 will require at least Windows 10 or Server 2016.
|
||||
- { goos: windows, goarch: '386', output: '386-go120', goversion: '1.20' }
|
||||
- { goos: windows, goarch: amd64, goamd64: v1, output: amd64-compatible-go120, goversion: '1.20' }
|
||||
@@ -92,28 +104,50 @@ jobs:
|
||||
with:
|
||||
go-version: ${{ matrix.jobs.goversion }}
|
||||
|
||||
- name: Set up Go1.21 loongarch abi1
|
||||
- name: Set up Go1.22 loongarch abi1
|
||||
if: ${{ matrix.jobs.goarch == 'loong64' && matrix.jobs.abi == '1' }}
|
||||
run: |
|
||||
wget -q https://github.com/xishang0128/loongarch64-golang/releases/download/1.21.5/go1.21.5.linux-amd64-abi1.tar.gz
|
||||
sudo tar zxf go1.21.5.linux-amd64-abi1.tar.gz -C /usr/local
|
||||
wget -q https://github.com/xishang0128/loongarch64-golang/releases/download/1.22.0/go1.22.0.linux-amd64-abi1.tar.gz
|
||||
sudo tar zxf go1.22.0.linux-amd64-abi1.tar.gz -C /usr/local
|
||||
echo "/usr/local/go/bin" >> $GITHUB_PATH
|
||||
|
||||
- name: Set up Go1.21 loongarch abi2
|
||||
- name: Set up Go1.22 loongarch abi2
|
||||
if: ${{ matrix.jobs.goarch == 'loong64' && matrix.jobs.abi == '2' }}
|
||||
run: |
|
||||
wget -q https://github.com/xishang0128/loongarch64-golang/releases/download/1.21.5/go1.21.5.linux-amd64-abi2.tar.gz
|
||||
sudo tar zxf go1.21.5.linux-amd64-abi2.tar.gz -C /usr/local
|
||||
wget -q https://github.com/xishang0128/loongarch64-golang/releases/download/1.22.0/go1.22.0.linux-amd64-abi2.tar.gz
|
||||
sudo tar zxf go1.22.0.linux-amd64-abi2.tar.gz -C /usr/local
|
||||
echo "/usr/local/go/bin" >> $GITHUB_PATH
|
||||
|
||||
# modify from https://github.com/restic/restic/issues/4636#issuecomment-1896455557
|
||||
# this patch file only works on golang1.22.x
|
||||
# that means after golang1.23 release it must be changed
|
||||
# revert:
|
||||
# 693def151adff1af707d82d28f55dba81ceb08e1: "crypto/rand,runtime: switch RtlGenRandom for ProcessPrng"
|
||||
# 7c1157f9544922e96945196b47b95664b1e39108: "net: remove sysSocket fallback for Windows 7"
|
||||
# 48042aa09c2f878c4faa576948b07fe625c4707a: "syscall: remove Windows 7 console handle workaround"
|
||||
- name: Revert Golang1.22 commit for Windows7/8
|
||||
if: ${{ matrix.jobs.goos == 'windows' && matrix.jobs.goversion == '' }}
|
||||
run: |
|
||||
cd $(go env GOROOT)
|
||||
patch --verbose -R -p 1 < $GITHUB_WORKSPACE/.github/patch_go122/693def151adff1af707d82d28f55dba81ceb08e1.diff
|
||||
patch --verbose -R -p 1 < $GITHUB_WORKSPACE/.github/patch_go122/7c1157f9544922e96945196b47b95664b1e39108.diff
|
||||
patch --verbose -R -p 1 < $GITHUB_WORKSPACE/.github/patch_go122/48042aa09c2f878c4faa576948b07fe625c4707a.diff
|
||||
|
||||
# modify from https://github.com/restic/restic/issues/4636#issuecomment-1896455557
|
||||
- name: Revert Golang1.21 commit for Windows7/8
|
||||
if: ${{ matrix.jobs.goos == 'windows' && matrix.jobs.goversion == '1.21' }}
|
||||
run: |
|
||||
cd $(go env GOROOT)
|
||||
curl https://github.com/golang/go/commit/9e43850a3298a9b8b1162ba0033d4c53f8637571.diff | patch --verbose -R -p 1
|
||||
|
||||
- name: Set variables
|
||||
if: ${{github.ref_name=='Alpha'}}
|
||||
run: echo "VERSION=alpha-$(git rev-parse --short HEAD)" >> $GITHUB_ENV
|
||||
if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.version != '' }}
|
||||
run: echo "VERSION=${{ github.event.inputs.version }}" >> $GITHUB_ENV
|
||||
shell: bash
|
||||
|
||||
- name: Set variables
|
||||
if: ${{github.ref_name=='' || github.ref_type=='tag'}}
|
||||
run: echo "VERSION=$(git describe --tags)" >> $GITHUB_ENV
|
||||
if: ${{ github.event_name != 'workflow_dispatch' && github.ref_name == 'Alpha' }}
|
||||
run: echo "VERSION=alpha-$(git rev-parse --short HEAD)" >> $GITHUB_ENV
|
||||
shell: bash
|
||||
|
||||
- name: Set Time Variable
|
||||
@@ -141,7 +175,7 @@ jobs:
|
||||
run: |
|
||||
go test ./...
|
||||
|
||||
- name: Update UA
|
||||
- name: Update CA
|
||||
run: |
|
||||
sudo apt-get install ca-certificates
|
||||
sudo update-ca-certificates
|
||||
@@ -172,9 +206,18 @@ jobs:
|
||||
sudo apt-get install dpkg
|
||||
if [ "${{matrix.jobs.abi}}" = "1" ]; then
|
||||
ARCH=loongarch64
|
||||
elif [ "${{matrix.jobs.goarm}}" = "7" ]; then
|
||||
ARCH=armhf
|
||||
elif [ "${{matrix.jobs.goarch}}" = "arm" ]; then
|
||||
ARCH=armel
|
||||
else
|
||||
ARCH=${{matrix.jobs.goarch}}
|
||||
fi
|
||||
PackageVersion=$(curl -s "https://api.github.com/repos/MetaCubeX/mihomo/releases/latest" | grep -o '"tag_name": "[^"]*' | grep -o '[^"]*$' | sed 's/v//g' )
|
||||
if [ $(git branch | awk -F ' ' '{print $2}') = "Alpha" ]; then
|
||||
PackageVersion="$(echo "${PackageVersion}" | awk -F '.' '{$NF = $NF + 1; print}' OFS='.')-${VERSION}"
|
||||
fi
|
||||
|
||||
mkdir -p mihomo-${{matrix.jobs.goos}}-${{matrix.jobs.output}}-${VERSION}/DEBIAN
|
||||
mkdir -p mihomo-${{matrix.jobs.goos}}-${{matrix.jobs.output}}-${VERSION}/usr/bin
|
||||
mkdir -p mihomo-${{matrix.jobs.goos}}-${{matrix.jobs.output}}-${VERSION}/etc/mihomo
|
||||
@@ -192,7 +235,7 @@ jobs:
|
||||
|
||||
cat > mihomo-${{matrix.jobs.goos}}-${{matrix.jobs.output}}-${VERSION}/DEBIAN/control <<EOF
|
||||
Package: mihomo
|
||||
Version: 1.18.2-${VERSION}
|
||||
Version: ${PackageVersion}
|
||||
Section:
|
||||
Priority: extra
|
||||
Architecture: ${ARCH}
|
||||
@@ -231,7 +274,7 @@ jobs:
|
||||
- name: Archive production artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ matrix.jobs.goos }}-${{ matrix.jobs.output }}
|
||||
name: "${{ matrix.jobs.goos }}-${{ matrix.jobs.output }}"
|
||||
path: |
|
||||
mihomo*.gz
|
||||
mihomo*.deb
|
||||
@@ -241,7 +284,7 @@ jobs:
|
||||
|
||||
Upload-Prerelease:
|
||||
permissions: write-all
|
||||
if: ${{ github.ref_type == 'branch' && !startsWith(github.event_name, 'pull_request') }}
|
||||
if: ${{ github.event_name != 'workflow_dispatch' && github.ref_type == 'branch' && !startsWith(github.event_name, 'pull_request') }}
|
||||
needs: [build]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
@@ -292,44 +335,62 @@ jobs:
|
||||
|
||||
Upload-Release:
|
||||
permissions: write-all
|
||||
if: ${{ github.ref_type=='tag' }}
|
||||
if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.version != '' }}
|
||||
needs: [build]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: Meta
|
||||
fetch-depth: '0'
|
||||
fetch-tags: 'true'
|
||||
|
||||
- name: Get tags
|
||||
run: |
|
||||
echo "CURRENTVERSION=${GITHUB_REF#refs/tags/}" >> $GITHUB_ENV
|
||||
git fetch --tags
|
||||
echo "PREVERSION=$(git describe --tags --abbrev=0 HEAD^)" >> $GITHUB_ENV
|
||||
- name: Get tags
|
||||
run: |
|
||||
echo "CURRENTVERSION=${{ github.event.inputs.version }}" >> $GITHUB_ENV
|
||||
git fetch --tags
|
||||
echo "PREVERSION=$(git describe --tags --abbrev=0 HEAD)" >> $GITHUB_ENV
|
||||
|
||||
- name: Generate release notes
|
||||
run: |
|
||||
cp ./.github/genReleaseNote.sh ./
|
||||
bash ./genReleaseNote.sh -v ${PREVERSION}...${CURRENTVERSION}
|
||||
rm ./genReleaseNote.sh
|
||||
- name: Merge Alpha branch into Meta
|
||||
run: |
|
||||
git config --global user.email "github-actions[bot]@users.noreply.github.com"
|
||||
git config --global user.name "github-actions[bot]"
|
||||
git fetch origin Alpha:Alpha
|
||||
git merge Alpha
|
||||
git push origin Meta
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- uses: actions/download-artifact@v4
|
||||
with:
|
||||
path: bin/
|
||||
merge-multiple: true
|
||||
- name: Tag the commit
|
||||
run: |
|
||||
git tag ${{ github.event.inputs.version }}
|
||||
git push origin ${{ github.event.inputs.version }}
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Display structure of downloaded files
|
||||
run: ls -R
|
||||
working-directory: bin
|
||||
|
||||
- name: Upload Release
|
||||
uses: softprops/action-gh-release@v1
|
||||
if: ${{ success() }}
|
||||
with:
|
||||
tag_name: ${{ github.ref_name }}
|
||||
files: bin/*
|
||||
generate_release_notes: true
|
||||
body_path: release.md
|
||||
- name: Generate release notes
|
||||
run: |
|
||||
cp ./.github/genReleaseNote.sh ./
|
||||
bash ./genReleaseNote.sh -v ${PREVERSION}...${CURRENTVERSION}
|
||||
rm ./genReleaseNote.sh
|
||||
|
||||
- uses: actions/download-artifact@v4
|
||||
with:
|
||||
path: bin/
|
||||
merge-multiple: true
|
||||
|
||||
- name: Display structure of downloaded files
|
||||
run: ls -R
|
||||
working-directory: bin
|
||||
|
||||
- name: Upload Release
|
||||
uses: softprops/action-gh-release@v2
|
||||
if: ${{ success() }}
|
||||
with:
|
||||
tag_name: ${{ github.event.inputs.version }}
|
||||
files: bin/*
|
||||
body_path: release.md
|
||||
|
||||
Docker:
|
||||
if: ${{ !startsWith(github.event_name, 'pull_request') }}
|
||||
@@ -358,20 +419,35 @@ jobs:
|
||||
uses: docker/setup-buildx-action@v3
|
||||
with:
|
||||
version: latest
|
||||
|
||||
|
||||
# Extract metadata (tags, labels) for Docker
|
||||
# https://github.com/docker/metadata-action
|
||||
- name: Extract Docker metadata
|
||||
id: meta
|
||||
if: ${{ github.event_name != 'workflow_dispatch' }}
|
||||
id: meta_alpha
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: ${{ env.REGISTRY }}/${{ github.repository }}
|
||||
|
||||
images: '${{ env.REGISTRY }}/${{ github.repository }}'
|
||||
|
||||
# Extract metadata (tags, labels) for Docker
|
||||
# https://github.com/docker/metadata-action
|
||||
- name: Extract Docker metadata
|
||||
if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.version != '' }}
|
||||
id: meta_release
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: '${{ env.REGISTRY }}/${{ github.repository }}'
|
||||
tags: |
|
||||
${{ github.event.inputs.version }}
|
||||
flavor: |
|
||||
latest=true
|
||||
labels: org.opencontainers.image.version=${{ github.event.inputs.version }}
|
||||
|
||||
- name: Show files
|
||||
run: |
|
||||
ls .
|
||||
ls bin/
|
||||
|
||||
|
||||
- name: login to docker REGISTRY
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
@@ -382,7 +458,7 @@ jobs:
|
||||
# Build and push Docker image with Buildx (don't push on PR)
|
||||
# https://github.com/docker/build-push-action
|
||||
- name: Build and push Docker image
|
||||
id: build-and-push
|
||||
if: ${{ github.event_name != 'workflow_dispatch' }}
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: .
|
||||
@@ -393,5 +469,20 @@ jobs:
|
||||
linux/amd64
|
||||
linux/arm64
|
||||
linux/arm/v7
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
tags: ${{ steps.meta_alpha.outputs.tags }}
|
||||
labels: ${{ steps.meta_alpha.outputs.labels }}
|
||||
|
||||
- name: Build and push Docker image
|
||||
if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.version != '' }}
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: .
|
||||
file: ./Dockerfile
|
||||
push: ${{ github.event_name != 'pull_request' }}
|
||||
platforms: |
|
||||
linux/386
|
||||
linux/amd64
|
||||
linux/arm64
|
||||
linux/arm/v7
|
||||
tags: ${{ steps.meta_release.outputs.tags }}
|
||||
labels: ${{ steps.meta_release.outputs.labels }}
|
||||
@@ -98,4 +98,3 @@ API.
|
||||
|
||||
This software is released under the GPL-3.0 license.
|
||||
|
||||
[](https://app.fossa.io/projects/git%2Bgithub.com%2FMetaCubeX%2Fmihomo?ref=badge_large)
|
||||
|
||||
@@ -2,6 +2,7 @@ package adapter
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net"
|
||||
@@ -14,6 +15,7 @@ import (
|
||||
"github.com/metacubex/mihomo/common/atomic"
|
||||
"github.com/metacubex/mihomo/common/queue"
|
||||
"github.com/metacubex/mihomo/common/utils"
|
||||
"github.com/metacubex/mihomo/component/ca"
|
||||
"github.com/metacubex/mihomo/component/dialer"
|
||||
C "github.com/metacubex/mihomo/constant"
|
||||
"github.com/puzpuzpuz/xsync/v3"
|
||||
@@ -230,6 +232,7 @@ func (p *Proxy) URLTest(ctx context.Context, url string, expectedStatus utils.In
|
||||
IdleConnTimeout: 90 * time.Second,
|
||||
TLSHandshakeTimeout: 10 * time.Second,
|
||||
ExpectContinueTimeout: 1 * time.Second,
|
||||
TLSClientConfig: ca.GetGlobalTLSConfig(&tls.Config{}),
|
||||
}
|
||||
|
||||
client := http.Client{
|
||||
|
||||
@@ -47,7 +47,7 @@ func WithDstAddr(addr net.Addr) Addition {
|
||||
func WithSrcAddr(addr net.Addr) Addition {
|
||||
return func(metadata *C.Metadata) {
|
||||
m := C.Metadata{}
|
||||
if err := m.SetRemoteAddr(addr);err ==nil{
|
||||
if err := m.SetRemoteAddr(addr); err == nil {
|
||||
metadata.SrcIP = m.DstIP
|
||||
metadata.SrcPort = m.DstPort
|
||||
}
|
||||
@@ -57,7 +57,7 @@ func WithSrcAddr(addr net.Addr) Addition {
|
||||
func WithInAddr(addr net.Addr) Addition {
|
||||
return func(metadata *C.Metadata) {
|
||||
m := C.Metadata{}
|
||||
if err := m.SetRemoteAddr(addr);err ==nil{
|
||||
if err := m.SetRemoteAddr(addr); err == nil {
|
||||
metadata.InIP = m.DstIP
|
||||
metadata.InPort = m.DstPort
|
||||
}
|
||||
@@ -69,3 +69,5 @@ func WithDSCP(dscp uint8) Addition {
|
||||
metadata.DSCP = dscp
|
||||
}
|
||||
}
|
||||
|
||||
func Placeholder(metadata *C.Metadata) {}
|
||||
|
||||
@@ -14,7 +14,7 @@ func NewHTTP(target socks5.Addr, srcConn net.Conn, conn net.Conn, additions ...A
|
||||
metadata.Type = C.HTTP
|
||||
metadata.RawSrcAddr = srcConn.RemoteAddr()
|
||||
metadata.RawDstAddr = srcConn.LocalAddr()
|
||||
ApplyAdditions(metadata, WithSrcAddr(srcConn.RemoteAddr()), WithInAddr(conn.LocalAddr()))
|
||||
ApplyAdditions(metadata, WithSrcAddr(srcConn.RemoteAddr()), WithInAddr(srcConn.LocalAddr()))
|
||||
ApplyAdditions(metadata, additions...)
|
||||
return conn, metadata
|
||||
}
|
||||
|
||||
@@ -3,22 +3,10 @@ package inbound
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
|
||||
"github.com/metacubex/tfo-go"
|
||||
)
|
||||
|
||||
var (
|
||||
lc = tfo.ListenConfig{
|
||||
DisableTFO: true,
|
||||
}
|
||||
)
|
||||
|
||||
func SetTfo(open bool) {
|
||||
lc.DisableTFO = !open
|
||||
}
|
||||
|
||||
func SetMPTCP(open bool) {
|
||||
setMultiPathTCP(&lc.ListenConfig, open)
|
||||
setMultiPathTCP(getListenConfig(), open)
|
||||
}
|
||||
|
||||
func ListenContext(ctx context.Context, network, address string) (net.Listener, error) {
|
||||
|
||||
23
adapter/inbound/listen_unix.go
Normal file
23
adapter/inbound/listen_unix.go
Normal file
@@ -0,0 +1,23 @@
|
||||
//go:build unix
|
||||
|
||||
package inbound
|
||||
|
||||
import (
|
||||
"net"
|
||||
|
||||
"github.com/metacubex/tfo-go"
|
||||
)
|
||||
|
||||
var (
|
||||
lc = tfo.ListenConfig{
|
||||
DisableTFO: true,
|
||||
}
|
||||
)
|
||||
|
||||
func SetTfo(open bool) {
|
||||
lc.DisableTFO = !open
|
||||
}
|
||||
|
||||
func getListenConfig() *net.ListenConfig {
|
||||
return &lc.ListenConfig
|
||||
}
|
||||
15
adapter/inbound/listen_windows.go
Normal file
15
adapter/inbound/listen_windows.go
Normal file
@@ -0,0 +1,15 @@
|
||||
package inbound
|
||||
|
||||
import (
|
||||
"net"
|
||||
)
|
||||
|
||||
var (
|
||||
lc = net.ListenConfig{}
|
||||
)
|
||||
|
||||
func SetTfo(open bool) {}
|
||||
|
||||
func getListenConfig() *net.ListenConfig {
|
||||
return &lc
|
||||
}
|
||||
@@ -3,18 +3,22 @@ package outbound
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/netip"
|
||||
"os"
|
||||
"strconv"
|
||||
|
||||
N "github.com/metacubex/mihomo/common/net"
|
||||
"github.com/metacubex/mihomo/component/dialer"
|
||||
"github.com/metacubex/mihomo/component/loopback"
|
||||
"github.com/metacubex/mihomo/component/resolver"
|
||||
C "github.com/metacubex/mihomo/constant"
|
||||
"github.com/metacubex/mihomo/constant/features"
|
||||
)
|
||||
|
||||
var DisableLoopBackDetector, _ = strconv.ParseBool(os.Getenv("DISABLE_LOOPBACK_DETECTOR"))
|
||||
|
||||
type Direct struct {
|
||||
*Base
|
||||
loopBack *loopBackDetector
|
||||
loopBack *loopback.Detector
|
||||
}
|
||||
|
||||
type DirectOption struct {
|
||||
@@ -24,8 +28,10 @@ type DirectOption struct {
|
||||
|
||||
// DialContext implements C.ProxyAdapter
|
||||
func (d *Direct) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.Conn, error) {
|
||||
if d.loopBack.CheckConn(metadata.SourceAddrPort()) {
|
||||
return nil, fmt.Errorf("reject loopback connection to: %s", metadata.RemoteAddress())
|
||||
if !features.CMFA && !DisableLoopBackDetector {
|
||||
if err := d.loopBack.CheckConn(metadata); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
opts = append(opts, dialer.WithResolver(resolver.DefaultResolver))
|
||||
c, err := dialer.DialContext(ctx, "tcp", metadata.RemoteAddress(), d.Base.DialOptions(opts...)...)
|
||||
@@ -38,8 +44,10 @@ func (d *Direct) DialContext(ctx context.Context, metadata *C.Metadata, opts ...
|
||||
|
||||
// ListenPacketContext implements C.ProxyAdapter
|
||||
func (d *Direct) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) {
|
||||
if d.loopBack.CheckPacketConn(metadata.SourceAddrPort()) {
|
||||
return nil, fmt.Errorf("reject loopback connection to: %s", metadata.RemoteAddress())
|
||||
if !features.CMFA && !DisableLoopBackDetector {
|
||||
if err := d.loopBack.CheckPacketConn(metadata); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
// net.UDPConn.WriteTo only working with *net.UDPAddr, so we need a net.UDPAddr
|
||||
if !metadata.Resolved() {
|
||||
@@ -49,13 +57,17 @@ func (d *Direct) ListenPacketContext(ctx context.Context, metadata *C.Metadata,
|
||||
}
|
||||
metadata.DstIP = ip
|
||||
}
|
||||
pc, err := dialer.NewDialer(d.Base.DialOptions(opts...)...).ListenPacket(ctx, "udp", "", netip.AddrPortFrom(metadata.DstIP, metadata.DstPort))
|
||||
pc, err := dialer.NewDialer(d.Base.DialOptions(opts...)...).ListenPacket(ctx, "udp", "", metadata.AddrPort())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return d.loopBack.NewPacketConn(newPacketConn(pc, d)), nil
|
||||
}
|
||||
|
||||
func (d *Direct) IsL3Protocol(metadata *C.Metadata) bool {
|
||||
return true // tell DNSDialer don't send domain to DialContext, avoid lookback to DefaultResolver
|
||||
}
|
||||
|
||||
func NewDirectWithOption(option DirectOption) *Direct {
|
||||
return &Direct{
|
||||
Base: &Base{
|
||||
@@ -68,7 +80,7 @@ func NewDirectWithOption(option DirectOption) *Direct {
|
||||
rmark: option.RoutingMark,
|
||||
prefer: C.NewDNSPrefer(option.IPVersion),
|
||||
},
|
||||
loopBack: newLoopBackDetector(),
|
||||
loopBack: loopback.NewDetector(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,7 +92,7 @@ func NewDirect() *Direct {
|
||||
udp: true,
|
||||
prefer: C.DualStack,
|
||||
},
|
||||
loopBack: newLoopBackDetector(),
|
||||
loopBack: loopback.NewDetector(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -92,6 +104,6 @@ func NewCompatible() *Direct {
|
||||
udp: true,
|
||||
prefer: C.DualStack,
|
||||
},
|
||||
loopBack: newLoopBackDetector(),
|
||||
loopBack: loopback.NewDetector(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,68 +0,0 @@
|
||||
package outbound
|
||||
|
||||
import (
|
||||
"net/netip"
|
||||
|
||||
"github.com/metacubex/mihomo/common/callback"
|
||||
C "github.com/metacubex/mihomo/constant"
|
||||
|
||||
"github.com/puzpuzpuz/xsync/v3"
|
||||
)
|
||||
|
||||
type loopBackDetector struct {
|
||||
connMap *xsync.MapOf[netip.AddrPort, struct{}]
|
||||
packetConnMap *xsync.MapOf[netip.AddrPort, struct{}]
|
||||
}
|
||||
|
||||
func newLoopBackDetector() *loopBackDetector {
|
||||
return &loopBackDetector{
|
||||
connMap: xsync.NewMapOf[netip.AddrPort, struct{}](),
|
||||
packetConnMap: xsync.NewMapOf[netip.AddrPort, struct{}](),
|
||||
}
|
||||
}
|
||||
|
||||
func (l *loopBackDetector) NewConn(conn C.Conn) C.Conn {
|
||||
metadata := C.Metadata{}
|
||||
if metadata.SetRemoteAddr(conn.LocalAddr()) != nil {
|
||||
return conn
|
||||
}
|
||||
connAddr := metadata.AddrPort()
|
||||
if !connAddr.IsValid() {
|
||||
return conn
|
||||
}
|
||||
l.connMap.Store(connAddr, struct{}{})
|
||||
return callback.NewCloseCallbackConn(conn, func() {
|
||||
l.connMap.Delete(connAddr)
|
||||
})
|
||||
}
|
||||
|
||||
func (l *loopBackDetector) NewPacketConn(conn C.PacketConn) C.PacketConn {
|
||||
metadata := C.Metadata{}
|
||||
if metadata.SetRemoteAddr(conn.LocalAddr()) != nil {
|
||||
return conn
|
||||
}
|
||||
connAddr := metadata.AddrPort()
|
||||
if !connAddr.IsValid() {
|
||||
return conn
|
||||
}
|
||||
l.packetConnMap.Store(connAddr, struct{}{})
|
||||
return callback.NewCloseCallbackPacketConn(conn, func() {
|
||||
l.packetConnMap.Delete(connAddr)
|
||||
})
|
||||
}
|
||||
|
||||
func (l *loopBackDetector) CheckConn(connAddr netip.AddrPort) bool {
|
||||
if !connAddr.IsValid() {
|
||||
return false
|
||||
}
|
||||
_, ok := l.connMap.Load(connAddr)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (l *loopBackDetector) CheckPacketConn(connAddr netip.AddrPort) bool {
|
||||
if !connAddr.IsValid() {
|
||||
return false
|
||||
}
|
||||
_, ok := l.packetConnMap.Load(connAddr)
|
||||
return ok
|
||||
}
|
||||
@@ -89,14 +89,14 @@ func (d *dnsPacketConn) WriteTo(p []byte, addr net.Addr) (n int, err error) {
|
||||
return len(p), nil
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(d.ctx, resolver.DefaultDnsRelayTimeout)
|
||||
defer cancel()
|
||||
|
||||
buf := pool.Get(resolver.SafeDnsPacketSize)
|
||||
put := func() { _ = pool.Put(buf) }
|
||||
copy(buf, p) // avoid p be changed after WriteTo returned
|
||||
|
||||
go func() { // don't block the WriteTo function
|
||||
ctx, cancel := context.WithTimeout(d.ctx, resolver.DefaultDnsRelayTimeout)
|
||||
defer cancel()
|
||||
|
||||
buf, err = resolver.RelayDnsPacket(ctx, buf[:len(p)], buf)
|
||||
if err != nil {
|
||||
put()
|
||||
|
||||
@@ -21,8 +21,8 @@ import (
|
||||
|
||||
"github.com/metacubex/sing-quic/hysteria2"
|
||||
|
||||
"github.com/metacubex/randv2"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
"github.com/zhangyunhao116/fastrand"
|
||||
)
|
||||
|
||||
func init() {
|
||||
@@ -165,7 +165,7 @@ func NewHysteria2(option Hysteria2Option) (*Hysteria2, error) {
|
||||
})
|
||||
if len(serverAddress) > 0 {
|
||||
clientOptions.ServerAddress = func(ctx context.Context) (*net.UDPAddr, error) {
|
||||
return resolveUDPAddrWithPrefer(ctx, "udp", serverAddress[fastrand.Intn(len(serverAddress))], C.NewDNSPrefer(option.IPVersion))
|
||||
return resolveUDPAddrWithPrefer(ctx, "udp", serverAddress[randv2.IntN(len(serverAddress))], C.NewDNSPrefer(option.IPVersion))
|
||||
}
|
||||
|
||||
if option.HopInterval == 0 {
|
||||
|
||||
@@ -166,12 +166,6 @@ func (ss *ShadowSocks) ListenPacketContext(ctx context.Context, metadata *C.Meta
|
||||
|
||||
// ListenPacketWithDialer implements C.ProxyAdapter
|
||||
func (ss *ShadowSocks) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, metadata *C.Metadata) (_ C.PacketConn, err error) {
|
||||
if len(ss.option.DialerProxy) > 0 {
|
||||
dialer, err = proxydialer.NewByName(ss.option.DialerProxy, dialer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if ss.option.UDPOverTCP {
|
||||
tcpConn, err := ss.DialContextWithDialer(ctx, dialer, metadata)
|
||||
if err != nil {
|
||||
@@ -179,6 +173,12 @@ func (ss *ShadowSocks) ListenPacketWithDialer(ctx context.Context, dialer C.Dial
|
||||
}
|
||||
return ss.ListenPacketOnStreamConn(ctx, tcpConn, metadata)
|
||||
}
|
||||
if len(ss.option.DialerProxy) > 0 {
|
||||
dialer, err = proxydialer.NewByName(ss.option.DialerProxy, dialer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
addr, err := resolveUDPAddrWithPrefer(ctx, "udp", ss.addr, ss.prefer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -273,6 +273,7 @@ func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) {
|
||||
if opts.TLS {
|
||||
v2rayOption.TLS = true
|
||||
v2rayOption.SkipCertVerify = opts.SkipCertVerify
|
||||
v2rayOption.Fingerprint = opts.Fingerprint
|
||||
}
|
||||
} else if option.Plugin == shadowtls.Mode {
|
||||
obfsMode = shadowtls.Mode
|
||||
|
||||
@@ -17,7 +17,7 @@ import (
|
||||
"github.com/metacubex/mihomo/component/proxydialer"
|
||||
C "github.com/metacubex/mihomo/constant"
|
||||
|
||||
"github.com/zhangyunhao116/fastrand"
|
||||
"github.com/metacubex/randv2"
|
||||
"golang.org/x/crypto/ssh"
|
||||
)
|
||||
|
||||
@@ -180,10 +180,10 @@ func NewSsh(option SshOption) (*Ssh, error) {
|
||||
}
|
||||
|
||||
version := "SSH-2.0-OpenSSH_"
|
||||
if fastrand.Intn(2) == 0 {
|
||||
version += "7." + strconv.Itoa(fastrand.Intn(10))
|
||||
if randv2.IntN(2) == 0 {
|
||||
version += "7." + strconv.Itoa(randv2.IntN(10))
|
||||
} else {
|
||||
version += "8." + strconv.Itoa(fastrand.Intn(9))
|
||||
version += "8." + strconv.Itoa(randv2.IntN(9))
|
||||
}
|
||||
config.ClientVersion = version
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ package outbound
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
@@ -15,6 +16,7 @@ import (
|
||||
tlsC "github.com/metacubex/mihomo/component/tls"
|
||||
C "github.com/metacubex/mihomo/constant"
|
||||
"github.com/metacubex/mihomo/transport/gun"
|
||||
"github.com/metacubex/mihomo/transport/shadowsocks/core"
|
||||
"github.com/metacubex/mihomo/transport/trojan"
|
||||
)
|
||||
|
||||
@@ -29,6 +31,8 @@ type Trojan struct {
|
||||
transport *gun.TransportWrap
|
||||
|
||||
realityConfig *tlsC.RealityConfig
|
||||
|
||||
ssCipher core.Cipher
|
||||
}
|
||||
|
||||
type TrojanOption struct {
|
||||
@@ -46,9 +50,17 @@ type TrojanOption struct {
|
||||
RealityOpts RealityOptions `proxy:"reality-opts,omitempty"`
|
||||
GrpcOpts GrpcOptions `proxy:"grpc-opts,omitempty"`
|
||||
WSOpts WSOptions `proxy:"ws-opts,omitempty"`
|
||||
SSOpts TrojanSSOption `proxy:"ss-opts,omitempty"`
|
||||
ClientFingerprint string `proxy:"client-fingerprint,omitempty"`
|
||||
}
|
||||
|
||||
// TrojanSSOption from https://github.com/p4gefau1t/trojan-go/blob/v0.10.6/tunnel/shadowsocks/config.go#L5
|
||||
type TrojanSSOption struct {
|
||||
Enabled bool `proxy:"enabled,omitempty"`
|
||||
Method string `proxy:"method,omitempty"`
|
||||
Password string `proxy:"password,omitempty"`
|
||||
}
|
||||
|
||||
func (t *Trojan) plainStream(ctx context.Context, c net.Conn) (net.Conn, error) {
|
||||
if t.option.Network == "ws" {
|
||||
host, port, _ := net.SplitHostPort(t.addr)
|
||||
@@ -95,6 +107,10 @@ func (t *Trojan) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.
|
||||
return nil, fmt.Errorf("%s connect error: %w", t.addr, err)
|
||||
}
|
||||
|
||||
if t.ssCipher != nil {
|
||||
c = t.ssCipher.StreamConn(c)
|
||||
}
|
||||
|
||||
if metadata.NetWork == C.UDP {
|
||||
err = t.instance.WriteHeader(c, trojan.CommandUDP, serializesSocksAddr(metadata))
|
||||
return c, err
|
||||
@@ -112,6 +128,10 @@ func (t *Trojan) DialContext(ctx context.Context, metadata *C.Metadata, opts ...
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if t.ssCipher != nil {
|
||||
c = t.ssCipher.StreamConn(c)
|
||||
}
|
||||
|
||||
if err = t.instance.WriteHeader(c, trojan.CommandTCP, serializesSocksAddr(metadata)); err != nil {
|
||||
c.Close()
|
||||
return nil, err
|
||||
@@ -161,6 +181,11 @@ func (t *Trojan) ListenPacketContext(ctx context.Context, metadata *C.Metadata,
|
||||
defer func(c net.Conn) {
|
||||
safeConnClose(c, err)
|
||||
}(c)
|
||||
|
||||
if t.ssCipher != nil {
|
||||
c = t.ssCipher.StreamConn(c)
|
||||
}
|
||||
|
||||
err = t.instance.WriteHeader(c, trojan.CommandUDP, serializesSocksAddr(metadata))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -193,6 +218,10 @@ func (t *Trojan) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, me
|
||||
return nil, fmt.Errorf("%s connect error: %w", t.addr, err)
|
||||
}
|
||||
|
||||
if t.ssCipher != nil {
|
||||
c = t.ssCipher.StreamConn(c)
|
||||
}
|
||||
|
||||
err = t.instance.WriteHeader(c, trojan.CommandUDP, serializesSocksAddr(metadata))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -257,6 +286,20 @@ func NewTrojan(option TrojanOption) (*Trojan, error) {
|
||||
}
|
||||
tOption.Reality = t.realityConfig
|
||||
|
||||
if option.SSOpts.Enabled {
|
||||
if option.SSOpts.Password == "" {
|
||||
return nil, errors.New("empty password")
|
||||
}
|
||||
if option.SSOpts.Method == "" {
|
||||
option.SSOpts.Method = "AES-128-GCM"
|
||||
}
|
||||
ciph, err := core.PickCipher(option.SSOpts.Method, nil, option.SSOpts.Password)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
t.ssCipher = ciph
|
||||
}
|
||||
|
||||
if option.Network == "grpc" {
|
||||
dialFn := func(network, addr string) (net.Conn, error) {
|
||||
var err error
|
||||
|
||||
@@ -179,6 +179,7 @@ func (v *Vmess) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.M
|
||||
tlsOpts := mihomoVMess.TLSConfig{
|
||||
Host: host,
|
||||
SkipCertVerify: v.option.SkipCertVerify,
|
||||
FingerPrint: v.option.Fingerprint,
|
||||
NextProtos: []string{"h2"},
|
||||
ClientFingerprint: v.option.ClientFingerprint,
|
||||
Reality: v.realityConfig,
|
||||
@@ -208,6 +209,7 @@ func (v *Vmess) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.M
|
||||
tlsOpts := &mihomoVMess.TLSConfig{
|
||||
Host: host,
|
||||
SkipCertVerify: v.option.SkipCertVerify,
|
||||
FingerPrint: v.option.Fingerprint,
|
||||
ClientFingerprint: v.option.ClientFingerprint,
|
||||
Reality: v.realityConfig,
|
||||
NextProtos: v.option.ALPN,
|
||||
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/metacubex/mihomo/common/atomic"
|
||||
CN "github.com/metacubex/mihomo/common/net"
|
||||
@@ -38,9 +39,21 @@ type WireGuard struct {
|
||||
device *device.Device
|
||||
tunDevice wireguard.Device
|
||||
dialer proxydialer.SingDialer
|
||||
init func(ctx context.Context) error
|
||||
resolver *dns.Resolver
|
||||
refP *refProxyAdapter
|
||||
|
||||
initOk atomic.Bool
|
||||
initMutex sync.Mutex
|
||||
initErr error
|
||||
option WireGuardOption
|
||||
connectAddr M.Socksaddr
|
||||
localPrefixes []netip.Prefix
|
||||
|
||||
serverAddrMap map[M.Socksaddr]netip.AddrPort
|
||||
serverAddrTime atomic.TypedValue[time.Time]
|
||||
serverAddrMutex sync.Mutex
|
||||
|
||||
closeCh chan struct{} // for test
|
||||
}
|
||||
|
||||
type WireGuardOption struct {
|
||||
@@ -59,6 +72,8 @@ type WireGuardOption struct {
|
||||
|
||||
RemoteDnsResolve bool `proxy:"remote-dns-resolve,omitempty"`
|
||||
Dns []string `proxy:"dns,omitempty"`
|
||||
|
||||
RefreshServerIPInterval int `proxy:"refresh-server-ip-interval,omitempty"`
|
||||
}
|
||||
|
||||
type WireGuardPeerOption struct {
|
||||
@@ -141,19 +156,6 @@ func NewWireGuard(option WireGuardOption) (*WireGuard, error) {
|
||||
}
|
||||
runtime.SetFinalizer(outbound, closeWireGuard)
|
||||
|
||||
resolv := func(ctx context.Context, address M.Socksaddr) (netip.AddrPort, error) {
|
||||
if address.Addr.IsValid() {
|
||||
return address.AddrPort(), nil
|
||||
}
|
||||
udpAddr, err := resolveUDPAddrWithPrefer(ctx, "udp", address.String(), outbound.prefer)
|
||||
if err != nil {
|
||||
return netip.AddrPort{}, err
|
||||
}
|
||||
// net.ResolveUDPAddr maybe return 4in6 address, so unmap at here
|
||||
addrPort := udpAddr.AddrPort()
|
||||
return netip.AddrPortFrom(addrPort.Addr().Unmap(), addrPort.Port()), nil
|
||||
}
|
||||
|
||||
var reserved [3]uint8
|
||||
if len(option.Reserved) > 0 {
|
||||
if len(option.Reserved) != 3 {
|
||||
@@ -162,29 +164,28 @@ func NewWireGuard(option WireGuardOption) (*WireGuard, error) {
|
||||
copy(reserved[:], option.Reserved)
|
||||
}
|
||||
var isConnect bool
|
||||
var connectAddr M.Socksaddr
|
||||
if len(option.Peers) < 2 {
|
||||
isConnect = true
|
||||
if len(option.Peers) == 1 {
|
||||
connectAddr = option.Peers[0].Addr()
|
||||
outbound.connectAddr = option.Peers[0].Addr()
|
||||
} else {
|
||||
connectAddr = option.Addr()
|
||||
outbound.connectAddr = option.Addr()
|
||||
}
|
||||
}
|
||||
outbound.bind = wireguard.NewClientBind(context.Background(), wgSingErrorHandler{outbound.Name()}, outbound.dialer, isConnect, connectAddr.AddrPort(), reserved)
|
||||
outbound.bind = wireguard.NewClientBind(context.Background(), wgSingErrorHandler{outbound.Name()}, outbound.dialer, isConnect, outbound.connectAddr.AddrPort(), reserved)
|
||||
|
||||
localPrefixes, err := option.Prefixes()
|
||||
var err error
|
||||
outbound.localPrefixes, err = option.Prefixes()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var privateKey string
|
||||
{
|
||||
bytes, err := base64.StdEncoding.DecodeString(option.PrivateKey)
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "decode private key")
|
||||
}
|
||||
privateKey = hex.EncodeToString(bytes)
|
||||
option.PrivateKey = hex.EncodeToString(bytes)
|
||||
}
|
||||
|
||||
if len(option.Peers) > 0 {
|
||||
@@ -230,110 +231,16 @@ func NewWireGuard(option WireGuardOption) (*WireGuard, error) {
|
||||
option.PreSharedKey = hex.EncodeToString(bytes)
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
initOk atomic.Bool
|
||||
initMutex sync.Mutex
|
||||
initErr error
|
||||
)
|
||||
|
||||
outbound.init = func(ctx context.Context) error {
|
||||
if initOk.Load() {
|
||||
return nil
|
||||
}
|
||||
initMutex.Lock()
|
||||
defer initMutex.Unlock()
|
||||
// double check like sync.Once
|
||||
if initOk.Load() {
|
||||
return nil
|
||||
}
|
||||
if initErr != nil {
|
||||
return initErr
|
||||
}
|
||||
|
||||
outbound.bind.ResetReservedForEndpoint()
|
||||
ipcConf := "private_key=" + privateKey
|
||||
if len(option.Peers) > 0 {
|
||||
for i, peer := range option.Peers {
|
||||
destination, err := resolv(ctx, peer.Addr())
|
||||
if err != nil {
|
||||
// !!! do not set initErr here !!!
|
||||
// let us can retry domain resolve in next time
|
||||
return E.Cause(err, "resolve endpoint domain for peer ", i)
|
||||
}
|
||||
ipcConf += "\npublic_key=" + peer.PublicKey
|
||||
ipcConf += "\nendpoint=" + destination.String()
|
||||
if peer.PreSharedKey != "" {
|
||||
ipcConf += "\npreshared_key=" + peer.PreSharedKey
|
||||
}
|
||||
for _, allowedIP := range peer.AllowedIPs {
|
||||
ipcConf += "\nallowed_ip=" + allowedIP
|
||||
}
|
||||
if len(peer.Reserved) > 0 {
|
||||
copy(reserved[:], option.Reserved)
|
||||
outbound.bind.SetReservedForEndpoint(destination, reserved)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ipcConf += "\npublic_key=" + option.PublicKey
|
||||
destination, err := resolv(ctx, connectAddr)
|
||||
if err != nil {
|
||||
// !!! do not set initErr here !!!
|
||||
// let us can retry domain resolve in next time
|
||||
return E.Cause(err, "resolve endpoint domain")
|
||||
}
|
||||
outbound.bind.SetConnectAddr(destination)
|
||||
ipcConf += "\nendpoint=" + destination.String()
|
||||
if option.PreSharedKey != "" {
|
||||
ipcConf += "\npreshared_key=" + option.PreSharedKey
|
||||
}
|
||||
var has4, has6 bool
|
||||
for _, address := range localPrefixes {
|
||||
if address.Addr().Is4() {
|
||||
has4 = true
|
||||
} else {
|
||||
has6 = true
|
||||
}
|
||||
}
|
||||
if has4 {
|
||||
ipcConf += "\nallowed_ip=0.0.0.0/0"
|
||||
}
|
||||
if has6 {
|
||||
ipcConf += "\nallowed_ip=::/0"
|
||||
}
|
||||
}
|
||||
|
||||
if option.PersistentKeepalive != 0 {
|
||||
ipcConf += fmt.Sprintf("\npersistent_keepalive_interval=%d", option.PersistentKeepalive)
|
||||
}
|
||||
|
||||
if debug.Enabled {
|
||||
log.SingLogger.Trace(fmt.Sprintf("[WG](%s) created wireguard ipc conf: \n %s", option.Name, ipcConf))
|
||||
}
|
||||
err = outbound.device.IpcSet(ipcConf)
|
||||
if err != nil {
|
||||
initErr = E.Cause(err, "setup wireguard")
|
||||
return initErr
|
||||
}
|
||||
|
||||
err = outbound.tunDevice.Start()
|
||||
if err != nil {
|
||||
initErr = err
|
||||
return initErr
|
||||
}
|
||||
|
||||
initOk.Store(true)
|
||||
return nil
|
||||
}
|
||||
outbound.option = option
|
||||
|
||||
mtu := option.MTU
|
||||
if mtu == 0 {
|
||||
mtu = 1408
|
||||
}
|
||||
if len(localPrefixes) == 0 {
|
||||
if len(outbound.localPrefixes) == 0 {
|
||||
return nil, E.New("missing local address")
|
||||
}
|
||||
outbound.tunDevice, err = wireguard.NewStackDevice(localPrefixes, uint32(mtu))
|
||||
outbound.tunDevice, err = wireguard.NewStackDevice(outbound.localPrefixes, uint32(mtu))
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "create WireGuard device")
|
||||
}
|
||||
@@ -347,7 +254,7 @@ func NewWireGuard(option WireGuardOption) (*WireGuard, error) {
|
||||
}, option.Workers)
|
||||
|
||||
var has6 bool
|
||||
for _, address := range localPrefixes {
|
||||
for _, address := range outbound.localPrefixes {
|
||||
if !address.Addr().Unmap().Is4() {
|
||||
has6 = true
|
||||
break
|
||||
@@ -373,11 +280,186 @@ func NewWireGuard(option WireGuardOption) (*WireGuard, error) {
|
||||
return outbound, nil
|
||||
}
|
||||
|
||||
func (w *WireGuard) resolve(ctx context.Context, address M.Socksaddr) (netip.AddrPort, error) {
|
||||
if address.Addr.IsValid() {
|
||||
return address.AddrPort(), nil
|
||||
}
|
||||
udpAddr, err := resolveUDPAddrWithPrefer(ctx, "udp", address.String(), w.prefer)
|
||||
if err != nil {
|
||||
return netip.AddrPort{}, err
|
||||
}
|
||||
// net.ResolveUDPAddr maybe return 4in6 address, so unmap at here
|
||||
addrPort := udpAddr.AddrPort()
|
||||
return netip.AddrPortFrom(addrPort.Addr().Unmap(), addrPort.Port()), nil
|
||||
}
|
||||
|
||||
func (w *WireGuard) init(ctx context.Context) error {
|
||||
err := w.init0(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
w.updateServerAddr(ctx)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *WireGuard) init0(ctx context.Context) error {
|
||||
if w.initOk.Load() {
|
||||
return nil
|
||||
}
|
||||
w.initMutex.Lock()
|
||||
defer w.initMutex.Unlock()
|
||||
// double check like sync.Once
|
||||
if w.initOk.Load() {
|
||||
return nil
|
||||
}
|
||||
if w.initErr != nil {
|
||||
return w.initErr
|
||||
}
|
||||
|
||||
w.bind.ResetReservedForEndpoint()
|
||||
w.serverAddrMap = make(map[M.Socksaddr]netip.AddrPort)
|
||||
ipcConf, err := w.genIpcConf(ctx, false)
|
||||
if err != nil {
|
||||
// !!! do not set initErr here !!!
|
||||
// let us can retry domain resolve in next time
|
||||
return err
|
||||
}
|
||||
|
||||
if debug.Enabled {
|
||||
log.SingLogger.Trace(fmt.Sprintf("[WG](%s) created wireguard ipc conf: \n %s", w.option.Name, ipcConf))
|
||||
}
|
||||
err = w.device.IpcSet(ipcConf)
|
||||
if err != nil {
|
||||
w.initErr = E.Cause(err, "setup wireguard")
|
||||
return w.initErr
|
||||
}
|
||||
w.serverAddrTime.Store(time.Now())
|
||||
|
||||
err = w.tunDevice.Start()
|
||||
if err != nil {
|
||||
w.initErr = err
|
||||
return w.initErr
|
||||
}
|
||||
|
||||
w.initOk.Store(true)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *WireGuard) updateServerAddr(ctx context.Context) {
|
||||
if w.option.RefreshServerIPInterval != 0 && time.Since(w.serverAddrTime.Load()) > time.Second*time.Duration(w.option.RefreshServerIPInterval) {
|
||||
if w.serverAddrMutex.TryLock() {
|
||||
defer w.serverAddrMutex.Unlock()
|
||||
ipcConf, err := w.genIpcConf(ctx, true)
|
||||
if err != nil {
|
||||
log.Warnln("[WG](%s)UpdateServerAddr failed to generate wireguard ipc conf: %s", w.option.Name, err)
|
||||
return
|
||||
}
|
||||
err = w.device.IpcSet(ipcConf)
|
||||
if err != nil {
|
||||
log.Warnln("[WG](%s)UpdateServerAddr failed to update wireguard ipc conf: %s", w.option.Name, err)
|
||||
return
|
||||
}
|
||||
w.serverAddrTime.Store(time.Now())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (w *WireGuard) genIpcConf(ctx context.Context, updateOnly bool) (string, error) {
|
||||
ipcConf := ""
|
||||
if !updateOnly {
|
||||
ipcConf += "private_key=" + w.option.PrivateKey + "\n"
|
||||
}
|
||||
if len(w.option.Peers) > 0 {
|
||||
for i, peer := range w.option.Peers {
|
||||
peerAddr := peer.Addr()
|
||||
destination, err := w.resolve(ctx, peerAddr)
|
||||
if err != nil {
|
||||
return "", E.Cause(err, "resolve endpoint domain for peer ", i)
|
||||
}
|
||||
if w.serverAddrMap[peerAddr] != destination {
|
||||
w.serverAddrMap[peerAddr] = destination
|
||||
} else if updateOnly {
|
||||
continue
|
||||
}
|
||||
|
||||
if len(w.option.Peers) == 1 { // must call SetConnectAddr if isConnect == true
|
||||
w.bind.SetConnectAddr(destination)
|
||||
}
|
||||
ipcConf += "public_key=" + peer.PublicKey + "\n"
|
||||
if updateOnly {
|
||||
ipcConf += "update_only=true\n"
|
||||
}
|
||||
ipcConf += "endpoint=" + destination.String() + "\n"
|
||||
if len(peer.Reserved) > 0 {
|
||||
var reserved [3]uint8
|
||||
copy(reserved[:], w.option.Reserved)
|
||||
w.bind.SetReservedForEndpoint(destination, reserved)
|
||||
}
|
||||
if updateOnly {
|
||||
continue
|
||||
}
|
||||
if peer.PreSharedKey != "" {
|
||||
ipcConf += "preshared_key=" + peer.PreSharedKey + "\n"
|
||||
}
|
||||
for _, allowedIP := range peer.AllowedIPs {
|
||||
ipcConf += "allowed_ip=" + allowedIP + "\n"
|
||||
}
|
||||
if w.option.PersistentKeepalive != 0 {
|
||||
ipcConf += fmt.Sprintf("persistent_keepalive_interval=%d\n", w.option.PersistentKeepalive)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
destination, err := w.resolve(ctx, w.connectAddr)
|
||||
if err != nil {
|
||||
return "", E.Cause(err, "resolve endpoint domain")
|
||||
}
|
||||
if w.serverAddrMap[w.connectAddr] != destination {
|
||||
w.serverAddrMap[w.connectAddr] = destination
|
||||
} else if updateOnly {
|
||||
return "", nil
|
||||
}
|
||||
w.bind.SetConnectAddr(destination) // must call SetConnectAddr if isConnect == true
|
||||
ipcConf += "public_key=" + w.option.PublicKey + "\n"
|
||||
if updateOnly {
|
||||
ipcConf += "update_only=true\n"
|
||||
}
|
||||
ipcConf += "endpoint=" + destination.String() + "\n"
|
||||
if updateOnly {
|
||||
return ipcConf, nil
|
||||
}
|
||||
if w.option.PreSharedKey != "" {
|
||||
ipcConf += "preshared_key=" + w.option.PreSharedKey + "\n"
|
||||
}
|
||||
var has4, has6 bool
|
||||
for _, address := range w.localPrefixes {
|
||||
if address.Addr().Is4() {
|
||||
has4 = true
|
||||
} else {
|
||||
has6 = true
|
||||
}
|
||||
}
|
||||
if has4 {
|
||||
ipcConf += "allowed_ip=0.0.0.0/0\n"
|
||||
}
|
||||
if has6 {
|
||||
ipcConf += "allowed_ip=::/0\n"
|
||||
}
|
||||
|
||||
if w.option.PersistentKeepalive != 0 {
|
||||
ipcConf += fmt.Sprintf("persistent_keepalive_interval=%d\n", w.option.PersistentKeepalive)
|
||||
}
|
||||
}
|
||||
return ipcConf, nil
|
||||
}
|
||||
|
||||
func closeWireGuard(w *WireGuard) {
|
||||
if w.device != nil {
|
||||
w.device.Close()
|
||||
}
|
||||
_ = common.Close(w.tunDevice)
|
||||
if w.closeCh != nil {
|
||||
close(w.closeCh)
|
||||
}
|
||||
}
|
||||
|
||||
func (w *WireGuard) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) {
|
||||
@@ -416,9 +498,6 @@ func (w *WireGuard) ListenPacketContext(ctx context.Context, metadata *C.Metadat
|
||||
if err = w.init(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if (!metadata.Resolved() || w.resolver != nil) && metadata.Host != "" {
|
||||
r := resolver.DefaultResolver
|
||||
if w.resolver != nil {
|
||||
|
||||
44
adapter/outbound/wireguard_test.go
Normal file
44
adapter/outbound/wireguard_test.go
Normal file
@@ -0,0 +1,44 @@
|
||||
//go:build with_gvisor
|
||||
|
||||
package outbound
|
||||
|
||||
import (
|
||||
"context"
|
||||
"runtime"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestWireGuardGC(t *testing.T) {
|
||||
option := WireGuardOption{}
|
||||
option.Server = "162.159.192.1"
|
||||
option.Port = 2408
|
||||
option.PrivateKey = "iOx7749AdqH3IqluG7+0YbGKd0m1mcEXAfGRzpy9rG8="
|
||||
option.PublicKey = "bmXOC+F1FxEMF9dyiK2H5/1SUtzH0JuVo51h2wPfgyo="
|
||||
option.Ip = "172.16.0.2"
|
||||
option.Ipv6 = "2606:4700:110:8d29:be92:3a6a:f4:c437"
|
||||
option.Reserved = []uint8{51, 69, 125}
|
||||
wg, err := NewWireGuard(option)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
closeCh := make(chan struct{})
|
||||
wg.closeCh = closeCh
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
|
||||
defer cancel()
|
||||
err = wg.init(ctx)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
// must do a small sleep before test GC
|
||||
// because it maybe deadlocks if w.device.Close call too fast after w.device.Start
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
wg = nil
|
||||
runtime.GC()
|
||||
select {
|
||||
case <-closeCh:
|
||||
return
|
||||
case <-ctx.Done():
|
||||
t.Error("timeout not GC")
|
||||
}
|
||||
}
|
||||
@@ -48,7 +48,7 @@ type GroupBaseOption struct {
|
||||
func NewGroupBase(opt GroupBaseOption) *GroupBase {
|
||||
var excludeFilterReg *regexp2.Regexp
|
||||
if opt.excludeFilter != "" {
|
||||
excludeFilterReg = regexp2.MustCompile(opt.excludeFilter, 0)
|
||||
excludeFilterReg = regexp2.MustCompile(opt.excludeFilter, regexp2.None)
|
||||
}
|
||||
var excludeTypeArray []string
|
||||
if opt.excludeType != "" {
|
||||
@@ -58,7 +58,7 @@ func NewGroupBase(opt GroupBaseOption) *GroupBase {
|
||||
var filterRegs []*regexp2.Regexp
|
||||
if opt.filter != "" {
|
||||
for _, filter := range strings.Split(opt.filter, "`") {
|
||||
filterReg := regexp2.MustCompile(filter, 0)
|
||||
filterReg := regexp2.MustCompile(filter, regexp2.None)
|
||||
filterRegs = append(filterRegs, filterReg)
|
||||
}
|
||||
}
|
||||
@@ -126,7 +126,7 @@ func (gb *GroupBase) GetProxies(touch bool) []C.Proxy {
|
||||
for _, filterReg := range gb.filterRegs {
|
||||
for _, p := range proxies {
|
||||
name := p.Name()
|
||||
if mat, _ := filterReg.FindStringMatch(name); mat != nil {
|
||||
if mat, _ := filterReg.MatchString(name); mat {
|
||||
if _, ok := proxiesSet[name]; !ok {
|
||||
proxiesSet[name] = struct{}{}
|
||||
newProxies = append(newProxies, p)
|
||||
@@ -150,7 +150,7 @@ func (gb *GroupBase) GetProxies(touch bool) []C.Proxy {
|
||||
for _, filterReg := range gb.filterRegs {
|
||||
for _, p := range proxies {
|
||||
name := p.Name()
|
||||
if mat, _ := filterReg.FindStringMatch(name); mat != nil {
|
||||
if mat, _ := filterReg.MatchString(name); mat {
|
||||
if _, ok := proxiesSet[name]; !ok {
|
||||
proxiesSet[name] = struct{}{}
|
||||
newProxies = append(newProxies, p)
|
||||
@@ -191,7 +191,7 @@ func (gb *GroupBase) GetProxies(touch bool) []C.Proxy {
|
||||
var newProxies []C.Proxy
|
||||
for _, p := range proxies {
|
||||
name := p.Name()
|
||||
if mat, _ := gb.excludeFilterReg.FindStringMatch(name); mat != nil {
|
||||
if mat, _ := gb.excludeFilterReg.MatchString(name); mat {
|
||||
continue
|
||||
}
|
||||
newProxies = append(newProxies, p)
|
||||
|
||||
@@ -205,7 +205,6 @@ func strategyStickySessions(url string) strategyFn {
|
||||
proxy := proxies[nowIdx]
|
||||
if proxy.AliveForTestUrl(url) {
|
||||
if nowIdx != idx {
|
||||
lruCache.Delete(key)
|
||||
lruCache.Set(key, nowIdx)
|
||||
}
|
||||
|
||||
@@ -215,7 +214,6 @@ func strategyStickySessions(url string) strategyFn {
|
||||
}
|
||||
}
|
||||
|
||||
lruCache.Delete(key)
|
||||
lruCache.Set(key, 0)
|
||||
return proxies[0]
|
||||
}
|
||||
|
||||
@@ -5,6 +5,8 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/dlclark/regexp2"
|
||||
|
||||
"github.com/metacubex/mihomo/adapter/outbound"
|
||||
"github.com/metacubex/mihomo/adapter/provider"
|
||||
"github.com/metacubex/mihomo/common/structure"
|
||||
@@ -67,10 +69,25 @@ func ParseProxyGroup(config map[string]any, proxyMap map[string]C.Proxy, provide
|
||||
}
|
||||
|
||||
if groupOption.IncludeAllProviders {
|
||||
groupOption.Use = append(groupOption.Use, AllProviders...)
|
||||
groupOption.Use = AllProviders
|
||||
}
|
||||
if groupOption.IncludeAllProxies {
|
||||
groupOption.Proxies = append(groupOption.Proxies, AllProxies...)
|
||||
if groupOption.Filter != "" {
|
||||
var filterRegs []*regexp2.Regexp
|
||||
for _, filter := range strings.Split(groupOption.Filter, "`") {
|
||||
filterReg := regexp2.MustCompile(filter, regexp2.None)
|
||||
filterRegs = append(filterRegs, filterReg)
|
||||
}
|
||||
for _, p := range AllProxies {
|
||||
for _, filterReg := range filterRegs {
|
||||
if mat, _ := filterReg.MatchString(p); mat {
|
||||
groupOption.Proxies = append(groupOption.Proxies, p)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
groupOption.Proxies = append(groupOption.Proxies, AllProxies...)
|
||||
}
|
||||
}
|
||||
|
||||
if len(groupOption.Proxies) == 0 && len(groupOption.Use) == 0 {
|
||||
@@ -88,6 +105,29 @@ func ParseProxyGroup(config map[string]any, proxyMap map[string]C.Proxy, provide
|
||||
}
|
||||
groupOption.ExpectedStatus = status
|
||||
|
||||
if len(groupOption.Use) != 0 {
|
||||
PDs, err := getProviders(providersMap, groupOption.Use)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s: %w", groupName, err)
|
||||
}
|
||||
|
||||
// if test URL is empty, use the first health check URL of providers
|
||||
if groupOption.URL == "" {
|
||||
for _, pd := range PDs {
|
||||
if pd.HealthCheckURL() != "" {
|
||||
groupOption.URL = pd.HealthCheckURL()
|
||||
break
|
||||
}
|
||||
}
|
||||
if groupOption.URL == "" {
|
||||
groupOption.URL = C.DefaultTestURL
|
||||
}
|
||||
} else {
|
||||
addTestUrlToProviders(PDs, groupOption.URL, expectedStatus, groupOption.Filter, uint(groupOption.Interval))
|
||||
}
|
||||
providers = append(providers, PDs...)
|
||||
}
|
||||
|
||||
if len(groupOption.Proxies) != 0 {
|
||||
ps, err := getProxies(proxyMap, groupOption.Proxies)
|
||||
if err != nil {
|
||||
@@ -98,14 +138,15 @@ func ParseProxyGroup(config map[string]any, proxyMap map[string]C.Proxy, provide
|
||||
return nil, fmt.Errorf("%s: %w", groupName, errDuplicateProvider)
|
||||
}
|
||||
|
||||
// select don't need health check
|
||||
if groupOption.URL == "" {
|
||||
groupOption.URL = C.DefaultTestURL
|
||||
}
|
||||
|
||||
// select don't need auto health check
|
||||
if groupOption.Type != "select" && groupOption.Type != "relay" {
|
||||
if groupOption.Interval == 0 {
|
||||
groupOption.Interval = 300
|
||||
}
|
||||
if groupOption.URL == "" {
|
||||
groupOption.URL = C.DefaultTestURL
|
||||
}
|
||||
}
|
||||
|
||||
hc := provider.NewHealthCheck(ps, groupOption.URL, uint(groupOption.TestTimeout), uint(groupOption.Interval), groupOption.Lazy, expectedStatus)
|
||||
@@ -115,34 +156,10 @@ func ParseProxyGroup(config map[string]any, proxyMap map[string]C.Proxy, provide
|
||||
return nil, fmt.Errorf("%s: %w", groupName, err)
|
||||
}
|
||||
|
||||
providers = append(providers, pd)
|
||||
providers = append([]types.ProxyProvider{pd}, providers...)
|
||||
providersMap[groupName] = pd
|
||||
}
|
||||
|
||||
if len(groupOption.Use) != 0 {
|
||||
list, err := getProviders(providersMap, groupOption.Use)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s: %w", groupName, err)
|
||||
}
|
||||
|
||||
if groupOption.URL == "" {
|
||||
for _, p := range list {
|
||||
if p.HealthCheckURL() != "" {
|
||||
groupOption.URL = p.HealthCheckURL()
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
if groupOption.URL == "" {
|
||||
groupOption.URL = C.DefaultTestURL
|
||||
}
|
||||
}
|
||||
|
||||
// different proxy groups use different test URL
|
||||
addTestUrlToProviders(list, groupOption.URL, expectedStatus, groupOption.Filter, uint(groupOption.Interval))
|
||||
providers = append(providers, list...)
|
||||
}
|
||||
|
||||
var group C.ProxyAdapter
|
||||
switch groupOption.Type {
|
||||
case "url-test":
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"github.com/metacubex/mihomo/component/proxydialer"
|
||||
C "github.com/metacubex/mihomo/constant"
|
||||
"github.com/metacubex/mihomo/constant/provider"
|
||||
"github.com/metacubex/mihomo/log"
|
||||
)
|
||||
|
||||
type Relay struct {
|
||||
@@ -149,6 +150,7 @@ func (r *Relay) Addr() string {
|
||||
}
|
||||
|
||||
func NewRelay(option *GroupCommonOption, providers []provider.ProxyProvider) *Relay {
|
||||
log.Warnln("The group [%s] with relay type is deprecated, please using dialer-proxy instead", option.Name)
|
||||
return &Relay{
|
||||
GroupBase: NewGroupBase(GroupBaseOption{
|
||||
outbound.BaseOption{
|
||||
|
||||
@@ -181,14 +181,14 @@ func (hc *HealthCheck) execute(b *batch.Batch[bool], url, uid string, option *ex
|
||||
filters = append(filters, filter)
|
||||
}
|
||||
|
||||
filterReg = regexp2.MustCompile(strings.Join(filters, "|"), 0)
|
||||
filterReg = regexp2.MustCompile(strings.Join(filters, "|"), regexp2.None)
|
||||
}
|
||||
}
|
||||
|
||||
for _, proxy := range hc.proxies {
|
||||
// skip proxies that do not require health check
|
||||
if filterReg != nil {
|
||||
if match, _ := filterReg.FindStringMatch(proxy.Name()); match == nil {
|
||||
if match, _ := filterReg.MatchString(proxy.Name()); !match {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,7 +28,10 @@ type healthCheckSchema struct {
|
||||
}
|
||||
|
||||
type OverrideSchema struct {
|
||||
TFO *bool `provider:"tfo,omitempty"`
|
||||
MPTcp *bool `provider:"mptcp,omitempty"`
|
||||
UDP *bool `provider:"udp,omitempty"`
|
||||
UDPOverTCP *bool `provider:"udp-over-tcp,omitempty"`
|
||||
Up *string `provider:"up,omitempty"`
|
||||
Down *string `provider:"down,omitempty"`
|
||||
DialerProxy *string `provider:"dialer-proxy,omitempty"`
|
||||
@@ -44,14 +47,16 @@ type proxyProviderSchema struct {
|
||||
Type string `provider:"type"`
|
||||
Path string `provider:"path,omitempty"`
|
||||
URL string `provider:"url,omitempty"`
|
||||
Proxy string `provider:"proxy,omitempty"`
|
||||
Interval int `provider:"interval,omitempty"`
|
||||
Filter string `provider:"filter,omitempty"`
|
||||
ExcludeFilter string `provider:"exclude-filter,omitempty"`
|
||||
ExcludeType string `provider:"exclude-type,omitempty"`
|
||||
DialerProxy string `provider:"dialer-proxy,omitempty"`
|
||||
|
||||
HealthCheck healthCheckSchema `provider:"health-check,omitempty"`
|
||||
Override OverrideSchema `provider:"override,omitempty"`
|
||||
HealthCheck healthCheckSchema `provider:"health-check,omitempty"`
|
||||
Override OverrideSchema `provider:"override,omitempty"`
|
||||
Header map[string][]string `provider:"header,omitempty"`
|
||||
}
|
||||
|
||||
func ParseProxyProvider(name string, mapping map[string]any) (types.ProxyProvider, error) {
|
||||
@@ -86,16 +91,14 @@ func ParseProxyProvider(name string, mapping map[string]any) (types.ProxyProvide
|
||||
path := C.Path.Resolve(schema.Path)
|
||||
vehicle = resource.NewFileVehicle(path)
|
||||
case "http":
|
||||
path := C.Path.GetPathByHash("proxies", schema.URL)
|
||||
if schema.Path != "" {
|
||||
path := C.Path.Resolve(schema.Path)
|
||||
path = C.Path.Resolve(schema.Path)
|
||||
if !features.CMFA && !C.Path.IsSafePath(path) {
|
||||
return nil, fmt.Errorf("%w: %s", errSubPath, path)
|
||||
}
|
||||
vehicle = resource.NewHTTPVehicle(schema.URL, path)
|
||||
} else {
|
||||
path := C.Path.GetPathByHash("proxies", schema.URL)
|
||||
vehicle = resource.NewHTTPVehicle(schema.URL, path)
|
||||
}
|
||||
vehicle = resource.NewHTTPVehicle(schema.URL, path, schema.Proxy, schema.Header)
|
||||
default:
|
||||
return nil, fmt.Errorf("%w: %s", errVehicleType, schema.Type)
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -124,8 +125,8 @@ func (pp *proxySetProvider) getSubscriptionInfo() {
|
||||
go func() {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*90)
|
||||
defer cancel()
|
||||
resp, err := mihomoHttp.HttpRequest(ctx, pp.Vehicle().(*resource.HTTPVehicle).Url(),
|
||||
http.MethodGet, http.Header{"User-Agent": {C.UA}}, nil)
|
||||
resp, err := mihomoHttp.HttpRequestWithProxy(ctx, pp.Vehicle().(*resource.HTTPVehicle).Url(),
|
||||
http.MethodGet, http.Header{"User-Agent": {C.UA}}, nil, pp.Vehicle().Proxy())
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -133,8 +134,8 @@ func (pp *proxySetProvider) getSubscriptionInfo() {
|
||||
|
||||
userInfoStr := strings.TrimSpace(resp.Header.Get("subscription-userinfo"))
|
||||
if userInfoStr == "" {
|
||||
resp2, err := mihomoHttp.HttpRequest(ctx, pp.Vehicle().(*resource.HTTPVehicle).Url(),
|
||||
http.MethodGet, http.Header{"User-Agent": {"Quantumultx"}}, nil)
|
||||
resp2, err := mihomoHttp.HttpRequestWithProxy(ctx, pp.Vehicle().(*resource.HTTPVehicle).Url(),
|
||||
http.MethodGet, http.Header{"User-Agent": {"Quantumultx"}}, nil, pp.Vehicle().Proxy())
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -169,7 +170,7 @@ func stopProxyProvider(pd *ProxySetProvider) {
|
||||
}
|
||||
|
||||
func NewProxySetProvider(name string, interval time.Duration, filter string, excludeFilter string, excludeType string, dialerProxy string, override OverrideSchema, vehicle types.Vehicle, hc *HealthCheck) (*ProxySetProvider, error) {
|
||||
excludeFilterReg, err := regexp2.Compile(excludeFilter, 0)
|
||||
excludeFilterReg, err := regexp2.Compile(excludeFilter, regexp2.None)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid excludeFilter regex: %w", err)
|
||||
}
|
||||
@@ -180,7 +181,7 @@ func NewProxySetProvider(name string, interval time.Duration, filter string, exc
|
||||
|
||||
var filterRegs []*regexp2.Regexp
|
||||
for _, filter := range strings.Split(filter, "`") {
|
||||
filterReg, err := regexp2.Compile(filter, 0)
|
||||
filterReg, err := regexp2.Compile(filter, regexp2.None)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid filter regex: %w", err)
|
||||
}
|
||||
@@ -356,12 +357,12 @@ func proxiesParseAndFilter(filter string, excludeFilter string, excludeTypeArray
|
||||
continue
|
||||
}
|
||||
if len(excludeFilter) > 0 {
|
||||
if mat, _ := excludeFilterReg.FindStringMatch(name); mat != nil {
|
||||
if mat, _ := excludeFilterReg.MatchString(name); mat {
|
||||
continue
|
||||
}
|
||||
}
|
||||
if len(filter) > 0 {
|
||||
if mat, _ := filterReg.FindStringMatch(name); mat == nil {
|
||||
if mat, _ := filterReg.MatchString(name); !mat {
|
||||
continue
|
||||
}
|
||||
}
|
||||
@@ -373,37 +374,23 @@ func proxiesParseAndFilter(filter string, excludeFilter string, excludeTypeArray
|
||||
mapping["dialer-proxy"] = dialerProxy
|
||||
}
|
||||
|
||||
if override.UDP != nil {
|
||||
mapping["udp"] = *override.UDP
|
||||
}
|
||||
if override.Up != nil {
|
||||
mapping["up"] = *override.Up
|
||||
}
|
||||
if override.Down != nil {
|
||||
mapping["down"] = *override.Down
|
||||
}
|
||||
if override.DialerProxy != nil {
|
||||
mapping["dialer-proxy"] = *override.DialerProxy
|
||||
}
|
||||
if override.SkipCertVerify != nil {
|
||||
mapping["skip-cert-verify"] = *override.SkipCertVerify
|
||||
}
|
||||
if override.Interface != nil {
|
||||
mapping["interface-name"] = *override.Interface
|
||||
}
|
||||
if override.RoutingMark != nil {
|
||||
mapping["routing-mark"] = *override.RoutingMark
|
||||
}
|
||||
if override.IPVersion != nil {
|
||||
mapping["ip-version"] = *override.IPVersion
|
||||
}
|
||||
if override.AdditionalPrefix != nil {
|
||||
name := mapping["name"].(string)
|
||||
mapping["name"] = *override.AdditionalPrefix + name
|
||||
}
|
||||
if override.AdditionalSuffix != nil {
|
||||
name := mapping["name"].(string)
|
||||
mapping["name"] = name + *override.AdditionalSuffix
|
||||
val := reflect.ValueOf(override)
|
||||
for i := 0; i < val.NumField(); i++ {
|
||||
field := val.Field(i)
|
||||
if field.IsNil() {
|
||||
continue
|
||||
}
|
||||
fieldName := strings.Split(val.Type().Field(i).Tag.Get("provider"), ",")[0]
|
||||
switch fieldName {
|
||||
case "additional-prefix":
|
||||
name := mapping["name"].(string)
|
||||
mapping["name"] = *field.Interface().(*string) + name
|
||||
case "additional-suffix":
|
||||
name := mapping["name"].(string)
|
||||
mapping["name"] = name + *field.Interface().(*string)
|
||||
default:
|
||||
mapping[fieldName] = field.Elem().Interface()
|
||||
}
|
||||
}
|
||||
|
||||
proxy, err := adapter.ParseProxy(mapping)
|
||||
|
||||
@@ -330,15 +330,38 @@ func ConvertsV2Ray(buf []byte) ([]map[string]any, error) {
|
||||
|
||||
vmess["h2-opts"] = h2Opts
|
||||
|
||||
case "ws":
|
||||
case "ws", "httpupgrade":
|
||||
headers := make(map[string]any)
|
||||
wsOpts := make(map[string]any)
|
||||
wsOpts["path"] = []string{"/"}
|
||||
wsOpts["path"] = "/"
|
||||
if host, ok := values["host"]; ok && host != "" {
|
||||
headers["Host"] = host.(string)
|
||||
}
|
||||
if path, ok := values["path"]; ok && path != "" {
|
||||
wsOpts["path"] = path.(string)
|
||||
path := path.(string)
|
||||
pathURL, err := url.Parse(path)
|
||||
if err == nil {
|
||||
query := pathURL.Query()
|
||||
if earlyData := query.Get("ed"); earlyData != "" {
|
||||
med, err := strconv.Atoi(earlyData)
|
||||
if err == nil {
|
||||
switch network {
|
||||
case "ws":
|
||||
wsOpts["max-early-data"] = med
|
||||
wsOpts["early-data-header-name"] = "Sec-WebSocket-Protocol"
|
||||
case "httpupgrade":
|
||||
wsOpts["v2ray-http-upgrade-fast-open"] = true
|
||||
}
|
||||
query.Del("ed")
|
||||
pathURL.RawQuery = query.Encode()
|
||||
path = pathURL.String()
|
||||
}
|
||||
}
|
||||
if earlyDataHeader := query.Get("eh"); earlyDataHeader != "" {
|
||||
wsOpts["early-data-header-name"] = earlyDataHeader
|
||||
}
|
||||
}
|
||||
wsOpts["path"] = path
|
||||
}
|
||||
wsOpts["headers"] = headers
|
||||
vmess["ws-opts"] = wsOpts
|
||||
|
||||
@@ -8,8 +8,8 @@ import (
|
||||
|
||||
"github.com/metacubex/mihomo/common/utils"
|
||||
|
||||
"github.com/metacubex/randv2"
|
||||
"github.com/metacubex/sing-shadowsocks/shadowimpl"
|
||||
"github.com/zhangyunhao116/fastrand"
|
||||
)
|
||||
|
||||
var hostsSuffix = []string{
|
||||
@@ -302,11 +302,11 @@ func RandHost() string {
|
||||
prefix += string(buf[6:8]) + "-"
|
||||
prefix += string(buf[len(buf)-8:])
|
||||
|
||||
return prefix + hostsSuffix[fastrand.Intn(hostsLen)]
|
||||
return prefix + hostsSuffix[randv2.IntN(hostsLen)]
|
||||
}
|
||||
|
||||
func RandUserAgent() string {
|
||||
return userAgents[fastrand.Intn(uaLen)]
|
||||
return userAgents[randv2.IntN(uaLen)]
|
||||
}
|
||||
|
||||
func SetUserAgent(header http.Header) {
|
||||
|
||||
@@ -100,7 +100,7 @@ func handleVShareLink(names map[string]int, url *url.URL, scheme string, proxy m
|
||||
h2Opts["headers"] = headers
|
||||
proxy["h2-opts"] = h2Opts
|
||||
|
||||
case "ws":
|
||||
case "ws", "httpupgrade":
|
||||
headers := make(map[string]any)
|
||||
wsOpts := make(map[string]any)
|
||||
headers["User-Agent"] = RandUserAgent()
|
||||
@@ -113,7 +113,13 @@ func handleVShareLink(names map[string]int, url *url.URL, scheme string, proxy m
|
||||
if err != nil {
|
||||
return fmt.Errorf("bad WebSocket max early data size: %v", err)
|
||||
}
|
||||
wsOpts["max-early-data"] = med
|
||||
switch network {
|
||||
case "ws":
|
||||
wsOpts["max-early-data"] = med
|
||||
wsOpts["early-data-header-name"] = "Sec-WebSocket-Protocol"
|
||||
case "httpupgrade":
|
||||
wsOpts["v2ray-http-upgrade-fast-open"] = true
|
||||
}
|
||||
}
|
||||
if earlyDataHeader := query.Get("eh"); earlyDataHeader != "" {
|
||||
wsOpts["early-data-header-name"] = earlyDataHeader
|
||||
|
||||
@@ -223,6 +223,10 @@ func (c *LruCache[K, V]) Delete(key K) {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
|
||||
c.delete(key)
|
||||
}
|
||||
|
||||
func (c *LruCache[K, V]) delete(key K) {
|
||||
if le, ok := c.cache[key]; ok {
|
||||
c.deleteElement(le)
|
||||
}
|
||||
@@ -255,6 +259,34 @@ func (c *LruCache[K, V]) Clear() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Compute either sets the computed new value for the key or deletes
|
||||
// the value for the key. When the delete result of the valueFn function
|
||||
// is set to true, the value will be deleted, if it exists. When delete
|
||||
// is set to false, the value is updated to the newValue.
|
||||
// The ok result indicates whether value was computed and stored, thus, is
|
||||
// present in the map. The actual result contains the new value in cases where
|
||||
// the value was computed and stored.
|
||||
func (c *LruCache[K, V]) Compute(
|
||||
key K,
|
||||
valueFn func(oldValue V, loaded bool) (newValue V, delete bool),
|
||||
) (actual V, ok bool) {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
|
||||
if el := c.get(key); el != nil {
|
||||
actual, ok = el.value, true
|
||||
}
|
||||
if newValue, del := valueFn(actual, ok); del {
|
||||
if ok { // data not in cache, so needn't delete
|
||||
c.delete(key)
|
||||
}
|
||||
return lo.Empty[V](), false
|
||||
} else {
|
||||
c.set(key, newValue)
|
||||
return newValue, true
|
||||
}
|
||||
}
|
||||
|
||||
type entry[K comparable, V any] struct {
|
||||
key K
|
||||
value V
|
||||
|
||||
@@ -3,8 +3,8 @@ package pool
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/metacubex/randv2"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/zhangyunhao116/fastrand"
|
||||
)
|
||||
|
||||
func TestAllocGet(t *testing.T) {
|
||||
@@ -43,6 +43,6 @@ func TestAllocPutThenGet(t *testing.T) {
|
||||
|
||||
func BenchmarkMSB(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
msb(fastrand.Int())
|
||||
msb(randv2.Int())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,8 +59,8 @@ func (q *Queue[T]) Copy() []T {
|
||||
|
||||
// Len returns the number of items in this queue.
|
||||
func (q *Queue[T]) Len() int64 {
|
||||
q.lock.Lock()
|
||||
defer q.lock.Unlock()
|
||||
q.lock.RLock()
|
||||
defer q.lock.RUnlock()
|
||||
|
||||
return int64(len(q.items))
|
||||
}
|
||||
|
||||
50
common/utils/callback.go
Normal file
50
common/utils/callback.go
Normal file
@@ -0,0 +1,50 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"io"
|
||||
"sync"
|
||||
|
||||
list "github.com/bahlo/generic-list-go"
|
||||
)
|
||||
|
||||
type Callback[T any] struct {
|
||||
list list.List[func(T)]
|
||||
mutex sync.RWMutex
|
||||
}
|
||||
|
||||
func NewCallback[T any]() *Callback[T] {
|
||||
return &Callback[T]{}
|
||||
}
|
||||
|
||||
func (c *Callback[T]) Register(item func(T)) io.Closer {
|
||||
c.mutex.Lock()
|
||||
defer c.mutex.Unlock()
|
||||
element := c.list.PushBack(item)
|
||||
return &callbackCloser[T]{
|
||||
element: element,
|
||||
callback: c,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Callback[T]) Emit(item T) {
|
||||
c.mutex.RLock()
|
||||
defer c.mutex.RUnlock()
|
||||
for element := c.list.Front(); element != nil; element = element.Next() {
|
||||
go element.Value(item)
|
||||
}
|
||||
}
|
||||
|
||||
type callbackCloser[T any] struct {
|
||||
element *list.Element[func(T)]
|
||||
callback *Callback[T]
|
||||
once sync.Once
|
||||
}
|
||||
|
||||
func (c *callbackCloser[T]) Close() error {
|
||||
c.once.Do(func() {
|
||||
c.callback.mutex.Lock()
|
||||
defer c.callback.mutex.Unlock()
|
||||
c.callback.list.Remove(c.element)
|
||||
})
|
||||
return nil
|
||||
}
|
||||
@@ -2,19 +2,39 @@ package utils
|
||||
|
||||
import (
|
||||
"github.com/gofrs/uuid/v5"
|
||||
"github.com/zhangyunhao116/fastrand"
|
||||
"github.com/metacubex/randv2"
|
||||
)
|
||||
|
||||
type fastRandReader struct{}
|
||||
type unsafeRandReader struct{}
|
||||
|
||||
func (r fastRandReader) Read(p []byte) (int, error) {
|
||||
return fastrand.Read(p)
|
||||
func (r unsafeRandReader) Read(p []byte) (n int, err error) {
|
||||
// modify from https://github.com/golang/go/blob/587c3847da81aa7cfc3b3db2677c8586c94df13a/src/runtime/rand.go#L70-L89
|
||||
// Inspired by wyrand.
|
||||
n = len(p)
|
||||
v := randv2.Uint64()
|
||||
for len(p) > 0 {
|
||||
v ^= 0xa0761d6478bd642f
|
||||
v *= 0xe7037ed1a0b428db
|
||||
size := 8
|
||||
if len(p) < 8 {
|
||||
size = len(p)
|
||||
}
|
||||
for i := 0; i < size; i++ {
|
||||
p[i] ^= byte(v >> (8 * i))
|
||||
}
|
||||
p = p[size:]
|
||||
v = v>>32 | v<<32
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
var UnsafeUUIDGenerator = uuid.NewGenWithOptions(uuid.WithRandomReader(fastRandReader{}))
|
||||
var UnsafeRandReader = unsafeRandReader{}
|
||||
|
||||
var UnsafeUUIDGenerator = uuid.NewGenWithOptions(uuid.WithRandomReader(UnsafeRandReader))
|
||||
|
||||
func NewUUIDV1() uuid.UUID {
|
||||
u, _ := UnsafeUUIDGenerator.NewV1() // fastrand.Read wouldn't cause error, so ignore err is safe
|
||||
u, _ := UnsafeUUIDGenerator.NewV1() // unsafeRandReader wouldn't cause error, so ignore err is safe
|
||||
return u
|
||||
}
|
||||
|
||||
@@ -23,7 +43,7 @@ func NewUUIDV3(ns uuid.UUID, name string) uuid.UUID {
|
||||
}
|
||||
|
||||
func NewUUIDV4() uuid.UUID {
|
||||
u, _ := UnsafeUUIDGenerator.NewV4() // fastrand.Read wouldn't cause error, so ignore err is safe
|
||||
u, _ := UnsafeUUIDGenerator.NewV4() // unsafeRandReader wouldn't cause error, so ignore err is safe
|
||||
return u
|
||||
}
|
||||
|
||||
@@ -32,12 +52,12 @@ func NewUUIDV5(ns uuid.UUID, name string) uuid.UUID {
|
||||
}
|
||||
|
||||
func NewUUIDV6() uuid.UUID {
|
||||
u, _ := UnsafeUUIDGenerator.NewV6() // fastrand.Read wouldn't cause error, so ignore err is safe
|
||||
u, _ := UnsafeUUIDGenerator.NewV6() // unsafeRandReader wouldn't cause error, so ignore err is safe
|
||||
return u
|
||||
}
|
||||
|
||||
func NewUUIDV7() uuid.UUID {
|
||||
u, _ := UnsafeUUIDGenerator.NewV7() // fastrand.Read wouldn't cause error, so ignore err is safe
|
||||
u, _ := UnsafeUUIDGenerator.NewV7() // unsafeRandReader wouldn't cause error, so ignore err is safe
|
||||
return u
|
||||
}
|
||||
|
||||
|
||||
@@ -13,6 +13,8 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
C "github.com/metacubex/mihomo/constant"
|
||||
)
|
||||
|
||||
var trustCerts []*x509.Certificate
|
||||
@@ -65,9 +67,6 @@ func ResetCertificate() {
|
||||
}
|
||||
|
||||
func getCertPool() *x509.CertPool {
|
||||
if len(trustCerts) == 0 {
|
||||
return nil
|
||||
}
|
||||
if globalCertPool == nil {
|
||||
mutex.Lock()
|
||||
defer mutex.Unlock()
|
||||
@@ -117,7 +116,7 @@ func GetTLSConfig(tlsConfig *tls.Config, fingerprint string, customCA string, cu
|
||||
var certificate []byte
|
||||
var err error
|
||||
if len(customCA) > 0 {
|
||||
certificate, err = os.ReadFile(customCA)
|
||||
certificate, err = os.ReadFile(C.Path.Resolve(customCA))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("load ca error: %w", err)
|
||||
}
|
||||
|
||||
14
component/ca/fix_windows.go
Normal file
14
component/ca/fix_windows.go
Normal file
@@ -0,0 +1,14 @@
|
||||
package ca
|
||||
|
||||
import (
|
||||
"github.com/metacubex/mihomo/constant/features"
|
||||
)
|
||||
|
||||
func init() {
|
||||
// crypto/x509: certificate validation in Windows fails to validate IP in SAN
|
||||
// https://github.com/golang/go/issues/37176
|
||||
// As far as I can tell this is still the case on most older versions of Windows (but seems to be fixed in 10)
|
||||
if features.WindowsMajorVersion < 10 && len(_CaCertificates) > 0 {
|
||||
DisableSystemCa = true
|
||||
}
|
||||
}
|
||||
@@ -43,12 +43,12 @@ func (set *IpCidrSet) IsContainForString(ipString string) bool {
|
||||
}
|
||||
|
||||
func (set *IpCidrSet) IsContain(ip netip.Addr) bool {
|
||||
return set.toIPSet().Contains(ip.WithZone(""))
|
||||
return set.ToIPSet().Contains(ip.WithZone(""))
|
||||
}
|
||||
|
||||
func (set *IpCidrSet) Merge() error {
|
||||
var b netipx.IPSetBuilder
|
||||
b.AddSet(set.toIPSet())
|
||||
b.AddSet(set.ToIPSet())
|
||||
i, err := b.IPSet()
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -57,7 +57,19 @@ func (set *IpCidrSet) Merge() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (set *IpCidrSet) toIPSet() *netipx.IPSet {
|
||||
func (set *IpCidrSet) Foreach(f func(prefix netip.Prefix) bool) {
|
||||
for _, r := range set.rr {
|
||||
for _, prefix := range r.Prefixes() {
|
||||
if !f(prefix) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ToIPSet not safe convert to *netipx.IPSet
|
||||
// be careful, must be used after Merge
|
||||
func (set *IpCidrSet) ToIPSet() *netipx.IPSet {
|
||||
return (*netipx.IPSet)(unsafe.Pointer(set))
|
||||
}
|
||||
|
||||
|
||||
77
component/cidr/ipcidr_set_bin.go
Normal file
77
component/cidr/ipcidr_set_bin.go
Normal file
@@ -0,0 +1,77 @@
|
||||
package cidr
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"io"
|
||||
"net/netip"
|
||||
|
||||
"go4.org/netipx"
|
||||
)
|
||||
|
||||
func (ss *IpCidrSet) WriteBin(w io.Writer) (err error) {
|
||||
// version
|
||||
_, err = w.Write([]byte{1})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// rr
|
||||
err = binary.Write(w, binary.BigEndian, int64(len(ss.rr)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, r := range ss.rr {
|
||||
err = binary.Write(w, binary.BigEndian, r.From().As16())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = binary.Write(w, binary.BigEndian, r.To().As16())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func ReadIpCidrSet(r io.Reader) (ss *IpCidrSet, err error) {
|
||||
// version
|
||||
version := make([]byte, 1)
|
||||
_, err = io.ReadFull(r, version)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if version[0] != 1 {
|
||||
return nil, errors.New("version is invalid")
|
||||
}
|
||||
|
||||
ss = NewIpCidrSet()
|
||||
var length int64
|
||||
|
||||
// rr
|
||||
err = binary.Read(r, binary.BigEndian, &length)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if length < 1 {
|
||||
return nil, errors.New("length is invalid")
|
||||
}
|
||||
ss.rr = make([]netipx.IPRange, length)
|
||||
for i := int64(0); i < length; i++ {
|
||||
var a16 [16]byte
|
||||
err = binary.Read(r, binary.BigEndian, &a16)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
from := netip.AddrFrom16(a16).Unmap()
|
||||
err = binary.Read(r, binary.BigEndian, &a16)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
to := netip.AddrFrom16(a16).Unmap()
|
||||
ss.rr[i] = netipx.IPRangeFrom(from, to)
|
||||
}
|
||||
|
||||
return ss, nil
|
||||
}
|
||||
@@ -3,6 +3,7 @@ package dhcp
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"net/netip"
|
||||
"runtime"
|
||||
|
||||
"github.com/metacubex/mihomo/component/dialer"
|
||||
@@ -24,5 +25,5 @@ func ListenDHCPClient(ctx context.Context, ifaceName string) (net.PacketConn, er
|
||||
options = append(options, dialer.WithFallbackBind(true))
|
||||
}
|
||||
|
||||
return dialer.ListenPacket(ctx, "udp4", listenAddr, options...)
|
||||
return dialer.ListenPacket(ctx, "udp4", listenAddr, netip.AddrPortFrom(netip.AddrFrom4([4]byte{255, 255, 255, 255}), 67), options...)
|
||||
}
|
||||
|
||||
@@ -75,7 +75,7 @@ func fallbackBindIfaceToDialer(ifaceName string, dialer *net.Dialer, network str
|
||||
return nil
|
||||
}
|
||||
|
||||
func fallbackBindIfaceToListenConfig(ifaceName string, _ *net.ListenConfig, network, address string) (string, error) {
|
||||
func fallbackBindIfaceToListenConfig(ifaceName string, _ *net.ListenConfig, network, address string, rAddrPort netip.AddrPort) (string, error) {
|
||||
_, port, err := net.SplitHostPort(address)
|
||||
if err != nil {
|
||||
port = "0"
|
||||
|
||||
@@ -46,7 +46,7 @@ func bindIfaceToDialer(ifaceName string, dialer *net.Dialer, _ string, _ netip.A
|
||||
return nil
|
||||
}
|
||||
|
||||
func bindIfaceToListenConfig(ifaceName string, lc *net.ListenConfig, _, address string) (string, error) {
|
||||
func bindIfaceToListenConfig(ifaceName string, lc *net.ListenConfig, _, address string, rAddrPort netip.AddrPort) (string, error) {
|
||||
ifaceObj, err := iface.ResolveInterface(ifaceName)
|
||||
if err != nil {
|
||||
return "", err
|
||||
|
||||
@@ -35,7 +35,7 @@ func bindIfaceToDialer(ifaceName string, dialer *net.Dialer, _ string, _ netip.A
|
||||
return nil
|
||||
}
|
||||
|
||||
func bindIfaceToListenConfig(ifaceName string, lc *net.ListenConfig, _, address string) (string, error) {
|
||||
func bindIfaceToListenConfig(ifaceName string, lc *net.ListenConfig, _, address string, rAddrPort netip.AddrPort) (string, error) {
|
||||
addControlToListenConfig(lc, bindControl(ifaceName))
|
||||
|
||||
return address, nil
|
||||
|
||||
@@ -11,8 +11,8 @@ func bindIfaceToDialer(ifaceName string, dialer *net.Dialer, network string, des
|
||||
return fallbackBindIfaceToDialer(ifaceName, dialer, network, destination)
|
||||
}
|
||||
|
||||
func bindIfaceToListenConfig(ifaceName string, lc *net.ListenConfig, network, address string) (string, error) {
|
||||
return fallbackBindIfaceToListenConfig(ifaceName, lc, network, address)
|
||||
func bindIfaceToListenConfig(ifaceName string, lc *net.ListenConfig, network, address string, rAddrPort netip.AddrPort) (string, error) {
|
||||
return fallbackBindIfaceToListenConfig(ifaceName, lc, network, address, rAddrPort)
|
||||
}
|
||||
|
||||
func ParseNetwork(network string, addr netip.Addr) string {
|
||||
|
||||
@@ -36,7 +36,7 @@ func bind6(handle syscall.Handle, ifaceIdx int) error {
|
||||
return err
|
||||
}
|
||||
|
||||
func bindControl(ifaceIdx int) controlFn {
|
||||
func bindControl(ifaceIdx int, rAddrPort netip.AddrPort) controlFn {
|
||||
return func(ctx context.Context, network, address string, c syscall.RawConn) (err error) {
|
||||
addrPort, err := netip.ParseAddrPort(address)
|
||||
if err == nil && !addrPort.Addr().IsGlobalUnicast() {
|
||||
@@ -55,7 +55,7 @@ func bindControl(ifaceIdx int) controlFn {
|
||||
innerErr = bind4err
|
||||
case "udp6":
|
||||
// golang will set network to udp6 when listenUDP on wildcard ip (eg: ":0", "")
|
||||
if (!addrPort.Addr().IsValid() || addrPort.Addr().IsUnspecified()) && bind6err != nil {
|
||||
if (!addrPort.Addr().IsValid() || addrPort.Addr().IsUnspecified()) && bind6err != nil && rAddrPort.Addr().Unmap().Is4() {
|
||||
// try bind ipv6, if failed, ignore. it's a workaround for windows disable interface ipv6
|
||||
if bind4err != nil {
|
||||
innerErr = fmt.Errorf("%w (%s)", bind6err, bind4err)
|
||||
@@ -76,23 +76,23 @@ func bindControl(ifaceIdx int) controlFn {
|
||||
}
|
||||
}
|
||||
|
||||
func bindIfaceToDialer(ifaceName string, dialer *net.Dialer, _ string, _ netip.Addr) error {
|
||||
func bindIfaceToDialer(ifaceName string, dialer *net.Dialer, _ string, destination netip.Addr) error {
|
||||
ifaceObj, err := iface.ResolveInterface(ifaceName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
addControlToDialer(dialer, bindControl(ifaceObj.Index))
|
||||
addControlToDialer(dialer, bindControl(ifaceObj.Index, netip.AddrPortFrom(destination, 0)))
|
||||
return nil
|
||||
}
|
||||
|
||||
func bindIfaceToListenConfig(ifaceName string, lc *net.ListenConfig, _, address string) (string, error) {
|
||||
func bindIfaceToListenConfig(ifaceName string, lc *net.ListenConfig, _, address string, rAddrPort netip.AddrPort) (string, error) {
|
||||
ifaceObj, err := iface.ResolveInterface(ifaceName)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
addControlToListenConfig(lc, bindControl(ifaceObj.Index))
|
||||
addControlToListenConfig(lc, bindControl(ifaceObj.Index, rAddrPort))
|
||||
return address, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -78,7 +78,7 @@ func DialContext(ctx context.Context, network, address string, options ...Option
|
||||
}
|
||||
}
|
||||
|
||||
func ListenPacket(ctx context.Context, network, address string, options ...Option) (net.PacketConn, error) {
|
||||
func ListenPacket(ctx context.Context, network, address string, rAddrPort netip.AddrPort, options ...Option) (net.PacketConn, error) {
|
||||
if features.CMFA && DefaultSocketHook != nil {
|
||||
return listenPacketHooked(ctx, network, address)
|
||||
}
|
||||
@@ -91,7 +91,7 @@ func ListenPacket(ctx context.Context, network, address string, options ...Optio
|
||||
if cfg.fallbackBind {
|
||||
bind = fallbackBindIfaceToListenConfig
|
||||
}
|
||||
addr, err := bind(cfg.interfaceName, lc, network, address)
|
||||
addr, err := bind(cfg.interfaceName, lc, network, address, rAddrPort)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -133,11 +133,9 @@ func dialContext(ctx context.Context, network string, destination netip.Addr, po
|
||||
|
||||
var address string
|
||||
if IP4PEnable {
|
||||
NewDestination, NewPort := lookupIP4P(destination.String(), port)
|
||||
address = net.JoinHostPort(NewDestination, NewPort)
|
||||
} else {
|
||||
address = net.JoinHostPort(destination.String(), port)
|
||||
destination, port = lookupIP4P(destination, port)
|
||||
}
|
||||
address = net.JoinHostPort(destination.String(), port)
|
||||
|
||||
netDialer := opt.netDialer
|
||||
switch netDialer.(type) {
|
||||
@@ -166,7 +164,7 @@ func dialContext(ctx context.Context, network string, destination netip.Addr, po
|
||||
if opt.mpTcp {
|
||||
setMultiPathTCP(dialer)
|
||||
}
|
||||
if opt.tfo {
|
||||
if opt.tfo && !DisableTFO {
|
||||
return dialTFO(ctx, *dialer, network, address)
|
||||
}
|
||||
return dialer.DialContext(ctx, network, address)
|
||||
@@ -380,12 +378,12 @@ func (d Dialer) DialContext(ctx context.Context, network, address string) (net.C
|
||||
}
|
||||
|
||||
func (d Dialer) ListenPacket(ctx context.Context, network, address string, rAddrPort netip.AddrPort) (net.PacketConn, error) {
|
||||
opt := WithOption(d.Opt)
|
||||
opt := d.Opt // make a copy
|
||||
if rAddrPort.Addr().Unmap().IsLoopback() {
|
||||
// avoid "The requested address is not valid in its context."
|
||||
opt = WithInterface("")
|
||||
WithInterface("")(&opt)
|
||||
}
|
||||
return ListenPacket(ctx, ParseNetwork(network, rAddrPort.Addr()), address, opt)
|
||||
return ListenPacket(ctx, ParseNetwork(network, rAddrPort.Addr()), address, rAddrPort, WithOption(opt))
|
||||
}
|
||||
|
||||
func NewDialer(options ...Option) Dialer {
|
||||
@@ -399,13 +397,13 @@ func GetIP4PEnable(enableIP4PConvert bool) {
|
||||
|
||||
// kanged from https://github.com/heiher/frp/blob/ip4p/client/ip4p.go
|
||||
|
||||
func lookupIP4P(addr string, port string) (string, string) {
|
||||
ip := net.ParseIP(addr)
|
||||
func lookupIP4P(addr netip.Addr, port string) (netip.Addr, string) {
|
||||
ip := addr.AsSlice()
|
||||
if ip[0] == 0x20 && ip[1] == 0x01 &&
|
||||
ip[2] == 0x00 && ip[3] == 0x00 {
|
||||
addr = net.IPv4(ip[12], ip[13], ip[14], ip[15]).String()
|
||||
addr = netip.AddrFrom4([4]byte{ip[12], ip[13], ip[14], ip[15]})
|
||||
port = strconv.Itoa(int(ip[10])<<8 + int(ip[11]))
|
||||
log.Debugln("Convert IP4P address %s to %s", ip, net.JoinHostPort(addr, port))
|
||||
log.Debugln("Convert IP4P address %s to %s", ip, net.JoinHostPort(addr.String(), port))
|
||||
return addr, port
|
||||
}
|
||||
return addr, port
|
||||
|
||||
@@ -5,8 +5,6 @@ import (
|
||||
"io"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/metacubex/tfo-go"
|
||||
)
|
||||
|
||||
type tfoConn struct {
|
||||
@@ -122,16 +120,3 @@ func (c *tfoConn) ReaderReplaceable() bool {
|
||||
func (c *tfoConn) WriterReplaceable() bool {
|
||||
return c.Conn != nil
|
||||
}
|
||||
|
||||
func dialTFO(ctx context.Context, netDialer net.Dialer, network, address string) (net.Conn, error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), DefaultTCPTimeout)
|
||||
dialer := tfo.Dialer{Dialer: netDialer, DisableTFO: false}
|
||||
return &tfoConn{
|
||||
dialed: make(chan bool, 1),
|
||||
cancel: cancel,
|
||||
ctx: ctx,
|
||||
dialFn: func(ctx context.Context, earlyData []byte) (net.Conn, error) {
|
||||
return dialer.DialContext(ctx, network, address, earlyData)
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
25
component/dialer/tfo_unix.go
Normal file
25
component/dialer/tfo_unix.go
Normal file
@@ -0,0 +1,25 @@
|
||||
//go:build unix
|
||||
|
||||
package dialer
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
|
||||
"github.com/metacubex/tfo-go"
|
||||
)
|
||||
|
||||
const DisableTFO = false
|
||||
|
||||
func dialTFO(ctx context.Context, netDialer net.Dialer, network, address string) (net.Conn, error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), DefaultTCPTimeout)
|
||||
dialer := tfo.Dialer{Dialer: netDialer, DisableTFO: false}
|
||||
return &tfoConn{
|
||||
dialed: make(chan bool, 1),
|
||||
cancel: cancel,
|
||||
ctx: ctx,
|
||||
dialFn: func(ctx context.Context, earlyData []byte) (net.Conn, error) {
|
||||
return dialer.DialContext(ctx, network, address, earlyData)
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
12
component/dialer/tfo_windows.go
Normal file
12
component/dialer/tfo_windows.go
Normal file
@@ -0,0 +1,12 @@
|
||||
package dialer
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
)
|
||||
|
||||
const DisableTFO = true
|
||||
|
||||
func dialTFO(ctx context.Context, netDialer net.Dialer, network, address string) (net.Conn, error) {
|
||||
return netDialer.DialContext(ctx, network, address)
|
||||
}
|
||||
@@ -17,7 +17,10 @@ import (
|
||||
)
|
||||
|
||||
func HttpRequest(ctx context.Context, url, method string, header map[string][]string, body io.Reader) (*http.Response, error) {
|
||||
UA := C.UA
|
||||
return HttpRequestWithProxy(ctx, url, method, header, body, "")
|
||||
}
|
||||
|
||||
func HttpRequestWithProxy(ctx context.Context, url, method string, header map[string][]string, body io.Reader, specialProxy string) (*http.Response, error) {
|
||||
method = strings.ToUpper(method)
|
||||
urlRes, err := URL.Parse(url)
|
||||
if err != nil {
|
||||
@@ -32,7 +35,7 @@ func HttpRequest(ctx context.Context, url, method string, header map[string][]st
|
||||
}
|
||||
|
||||
if _, ok := header["User-Agent"]; !ok {
|
||||
req.Header.Set("User-Agent", UA)
|
||||
req.Header.Set("User-Agent", C.UA)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
@@ -54,7 +57,7 @@ func HttpRequest(ctx context.Context, url, method string, header map[string][]st
|
||||
TLSHandshakeTimeout: 10 * time.Second,
|
||||
ExpectContinueTimeout: 1 * time.Second,
|
||||
DialContext: func(ctx context.Context, network, address string) (net.Conn, error) {
|
||||
if conn, err := inner.HandleTcp(address); err == nil {
|
||||
if conn, err := inner.HandleTcp(address, specialProxy); err == nil {
|
||||
return conn, nil
|
||||
} else {
|
||||
d := net.Dialer{}
|
||||
@@ -66,5 +69,4 @@ func HttpRequest(ctx context.Context, url, method string, header map[string][]st
|
||||
|
||||
client := http.Client{Transport: transport}
|
||||
return client.Do(req)
|
||||
|
||||
}
|
||||
|
||||
@@ -11,8 +11,9 @@ import (
|
||||
|
||||
type Interface struct {
|
||||
Index int
|
||||
MTU int
|
||||
Name string
|
||||
Addrs []netip.Prefix
|
||||
Addresses []netip.Prefix
|
||||
HardwareAddr net.HardwareAddr
|
||||
}
|
||||
|
||||
@@ -23,7 +24,7 @@ var (
|
||||
|
||||
var interfaces = singledo.NewSingle[map[string]*Interface](time.Second * 20)
|
||||
|
||||
func ResolveInterface(name string) (*Interface, error) {
|
||||
func Interfaces() (map[string]*Interface, error) {
|
||||
value, err, _ := interfaces.Do(func() (map[string]*Interface, error) {
|
||||
ifaces, err := net.Interfaces()
|
||||
if err != nil {
|
||||
@@ -61,19 +62,24 @@ func ResolveInterface(name string) (*Interface, error) {
|
||||
|
||||
r[iface.Name] = &Interface{
|
||||
Index: iface.Index,
|
||||
MTU: iface.MTU,
|
||||
Name: iface.Name,
|
||||
Addrs: ipNets,
|
||||
Addresses: ipNets,
|
||||
HardwareAddr: iface.HardwareAddr,
|
||||
}
|
||||
}
|
||||
|
||||
return r, nil
|
||||
})
|
||||
return value, err
|
||||
}
|
||||
|
||||
func ResolveInterface(name string) (*Interface, error) {
|
||||
ifaces, err := Interfaces()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ifaces := value
|
||||
iface, ok := ifaces[name]
|
||||
if !ok {
|
||||
return nil, ErrIfaceNotFound
|
||||
@@ -82,6 +88,21 @@ func ResolveInterface(name string) (*Interface, error) {
|
||||
return iface, nil
|
||||
}
|
||||
|
||||
func IsLocalIp(ip netip.Addr) (bool, error) {
|
||||
ifaces, err := Interfaces()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
for _, iface := range ifaces {
|
||||
for _, addr := range iface.Addresses {
|
||||
if addr.Contains(ip) {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func FlushCache() {
|
||||
interfaces.Reset()
|
||||
}
|
||||
@@ -101,7 +122,7 @@ func (iface *Interface) PickIPv6Addr(destination netip.Addr) (netip.Prefix, erro
|
||||
func (iface *Interface) pickIPAddr(destination netip.Addr, accept func(addr netip.Prefix) bool) (netip.Prefix, error) {
|
||||
var fallback netip.Prefix
|
||||
|
||||
for _, addr := range iface.Addrs {
|
||||
for _, addr := range iface.Addresses {
|
||||
if !accept(addr) {
|
||||
continue
|
||||
}
|
||||
|
||||
89
component/loopback/detector.go
Normal file
89
component/loopback/detector.go
Normal file
@@ -0,0 +1,89 @@
|
||||
package loopback
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/netip"
|
||||
|
||||
"github.com/metacubex/mihomo/common/callback"
|
||||
"github.com/metacubex/mihomo/component/iface"
|
||||
C "github.com/metacubex/mihomo/constant"
|
||||
|
||||
"github.com/puzpuzpuz/xsync/v3"
|
||||
)
|
||||
|
||||
var ErrReject = errors.New("reject loopback connection")
|
||||
|
||||
type Detector struct {
|
||||
connMap *xsync.MapOf[netip.AddrPort, struct{}]
|
||||
packetConnMap *xsync.MapOf[uint16, struct{}]
|
||||
}
|
||||
|
||||
func NewDetector() *Detector {
|
||||
return &Detector{
|
||||
connMap: xsync.NewMapOf[netip.AddrPort, struct{}](),
|
||||
packetConnMap: xsync.NewMapOf[uint16, struct{}](),
|
||||
}
|
||||
}
|
||||
|
||||
func (l *Detector) NewConn(conn C.Conn) C.Conn {
|
||||
metadata := C.Metadata{}
|
||||
if metadata.SetRemoteAddr(conn.LocalAddr()) != nil {
|
||||
return conn
|
||||
}
|
||||
connAddr := metadata.AddrPort()
|
||||
if !connAddr.IsValid() {
|
||||
return conn
|
||||
}
|
||||
l.connMap.Store(connAddr, struct{}{})
|
||||
return callback.NewCloseCallbackConn(conn, func() {
|
||||
l.connMap.Delete(connAddr)
|
||||
})
|
||||
}
|
||||
|
||||
func (l *Detector) NewPacketConn(conn C.PacketConn) C.PacketConn {
|
||||
metadata := C.Metadata{}
|
||||
if metadata.SetRemoteAddr(conn.LocalAddr()) != nil {
|
||||
return conn
|
||||
}
|
||||
connAddr := metadata.AddrPort()
|
||||
if !connAddr.IsValid() {
|
||||
return conn
|
||||
}
|
||||
port := connAddr.Port()
|
||||
l.packetConnMap.Store(port, struct{}{})
|
||||
return callback.NewCloseCallbackPacketConn(conn, func() {
|
||||
l.packetConnMap.Delete(port)
|
||||
})
|
||||
}
|
||||
|
||||
func (l *Detector) CheckConn(metadata *C.Metadata) error {
|
||||
connAddr := metadata.SourceAddrPort()
|
||||
if !connAddr.IsValid() {
|
||||
return nil
|
||||
}
|
||||
if _, ok := l.connMap.Load(connAddr); ok {
|
||||
return fmt.Errorf("%w to: %s", ErrReject, metadata.RemoteAddress())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *Detector) CheckPacketConn(metadata *C.Metadata) error {
|
||||
connAddr := metadata.SourceAddrPort()
|
||||
if !connAddr.IsValid() {
|
||||
return nil
|
||||
}
|
||||
|
||||
isLocalIp, err := iface.IsLocalIp(connAddr.Addr())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !isLocalIp && !connAddr.Addr().IsLoopback() {
|
||||
return nil
|
||||
}
|
||||
|
||||
if _, ok := l.packetConnMap.Load(connAddr.Port()); ok {
|
||||
return fmt.Errorf("%w to: %s", ErrReject, metadata.RemoteAddress())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -3,6 +3,8 @@ package process
|
||||
import (
|
||||
"errors"
|
||||
"net/netip"
|
||||
|
||||
C "github.com/metacubex/mihomo/constant"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -19,3 +21,18 @@ const (
|
||||
func FindProcessName(network string, srcIP netip.Addr, srcPort int) (uint32, string, error) {
|
||||
return findProcessName(network, srcIP, srcPort)
|
||||
}
|
||||
|
||||
// PackageNameResolver
|
||||
// never change type traits because it's used in CFMA
|
||||
type PackageNameResolver func(metadata *C.Metadata) (string, error)
|
||||
|
||||
// DefaultPackageNameResolver
|
||||
// never change type traits because it's used in CFMA
|
||||
var DefaultPackageNameResolver PackageNameResolver
|
||||
|
||||
func FindPackageName(metadata *C.Metadata) (string, error) {
|
||||
if resolver := DefaultPackageNameResolver; resolver != nil {
|
||||
return resolver(metadata)
|
||||
}
|
||||
return "", ErrPlatformNotSupport
|
||||
}
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
//go:build android && cmfa
|
||||
|
||||
package process
|
||||
|
||||
import "github.com/metacubex/mihomo/constant"
|
||||
|
||||
type PackageNameResolver func(metadata *constant.Metadata) (string, error)
|
||||
|
||||
var DefaultPackageNameResolver PackageNameResolver
|
||||
|
||||
func FindPackageName(metadata *constant.Metadata) (string, error) {
|
||||
if resolver := DefaultPackageNameResolver; resolver != nil {
|
||||
return resolver(metadata)
|
||||
}
|
||||
return "", ErrPlatformNotSupport
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
//go:build !(android && cmfa)
|
||||
|
||||
package process
|
||||
|
||||
import "github.com/metacubex/mihomo/constant"
|
||||
|
||||
func FindPackageName(metadata *constant.Metadata) (string, error) {
|
||||
return "", nil
|
||||
}
|
||||
@@ -46,12 +46,12 @@ func findProcessName(network string, ip netip.Addr, port int) (uint32, string, e
|
||||
|
||||
isIPv4 := ip.Is4()
|
||||
|
||||
value, err := syscall.Sysctl(spath)
|
||||
value, err := unix.SysctlRaw(spath)
|
||||
if err != nil {
|
||||
return 0, "", err
|
||||
}
|
||||
|
||||
buf := []byte(value)
|
||||
buf := value
|
||||
itemSize := structSize
|
||||
if network == TCP {
|
||||
// rup8(sizeof(xtcpcb_n))
|
||||
|
||||
@@ -64,7 +64,6 @@ func findProcessName(network string, ip netip.Addr, srcPort int) (uint32, string
|
||||
if err != nil {
|
||||
return 0, "", err
|
||||
}
|
||||
|
||||
pp, err := resolveProcessNameByProcSearch(inode, uid)
|
||||
return uid, pp, err
|
||||
}
|
||||
@@ -160,6 +159,7 @@ func resolveProcessNameByProcSearch(inode, uid uint32) (string, error) {
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if runtime.GOOS == "android" {
|
||||
if bytes.Equal(buffer[:n], socket) {
|
||||
cmdline, err := os.ReadFile(path.Join(processPath, "cmdline"))
|
||||
@@ -174,7 +174,6 @@ func resolveProcessNameByProcSearch(inode, uid uint32) (string, error) {
|
||||
return os.Readlink(filepath.Join(processPath, "exe"))
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -185,7 +184,7 @@ func splitCmdline(cmdline []byte) string {
|
||||
cmdline = bytes.Trim(cmdline, " ")
|
||||
|
||||
idx := bytes.IndexFunc(cmdline, func(r rune) bool {
|
||||
return unicode.IsControl(r) || unicode.IsSpace(r) || r == ':'
|
||||
return unicode.IsControl(r) || unicode.IsSpace(r)
|
||||
})
|
||||
|
||||
if idx == -1 {
|
||||
|
||||
@@ -9,11 +9,15 @@ import (
|
||||
_ "unsafe"
|
||||
|
||||
"github.com/metacubex/mihomo/common/utils"
|
||||
"github.com/metacubex/mihomo/component/resolver/hosts"
|
||||
"github.com/metacubex/mihomo/component/trie"
|
||||
"github.com/zhangyunhao116/fastrand"
|
||||
"github.com/metacubex/randv2"
|
||||
)
|
||||
|
||||
var DisableSystemHosts, _ = strconv.ParseBool(os.Getenv("DISABLE_SYSTEM_HOSTS"))
|
||||
var (
|
||||
DisableSystemHosts, _ = strconv.ParseBool(os.Getenv("DISABLE_SYSTEM_HOSTS"))
|
||||
UseSystemHosts bool
|
||||
)
|
||||
|
||||
type Hosts struct {
|
||||
*trie.DomainTrie[HostValue]
|
||||
@@ -25,11 +29,6 @@ func NewHosts(hosts *trie.DomainTrie[HostValue]) Hosts {
|
||||
}
|
||||
}
|
||||
|
||||
// lookupStaticHost looks up the addresses and the canonical name for the given host from /etc/hosts.
|
||||
//
|
||||
//go:linkname lookupStaticHost net.lookupStaticHost
|
||||
func lookupStaticHost(host string) ([]string, string)
|
||||
|
||||
// Return the search result and whether to match the parameter `isDomain`
|
||||
func (h *Hosts) Search(domain string, isDomain bool) (*HostValue, bool) {
|
||||
if value := h.DomainTrie.Search(domain); value != nil {
|
||||
@@ -51,8 +50,9 @@ func (h *Hosts) Search(domain string, isDomain bool) (*HostValue, bool) {
|
||||
|
||||
return &hostValue, false
|
||||
}
|
||||
if !isDomain && !DisableSystemHosts {
|
||||
addr, _ := lookupStaticHost(domain)
|
||||
|
||||
if !isDomain && !DisableSystemHosts && UseSystemHosts {
|
||||
addr, _ := hosts.LookupStaticHost(domain)
|
||||
if hostValue, err := NewHostValue(addr); err == nil {
|
||||
return &hostValue, true
|
||||
}
|
||||
@@ -125,5 +125,5 @@ func (hv HostValue) RandIP() (netip.Addr, error) {
|
||||
if hv.IsDomain {
|
||||
return netip.Addr{}, errors.New("value type is error")
|
||||
}
|
||||
return hv.IPs[fastrand.Intn(len(hv.IPs))], nil
|
||||
return hv.IPs[randv2.IntN(len(hv.IPs))], nil
|
||||
}
|
||||
|
||||
309
component/resolver/hosts/hosts.go
Normal file
309
component/resolver/hosts/hosts.go
Normal file
@@ -0,0 +1,309 @@
|
||||
package hosts
|
||||
|
||||
// this file copy and modify from golang's std net/hosts.go
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"io/fs"
|
||||
"net/netip"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
var hostsFilePath = "/etc/hosts"
|
||||
|
||||
const cacheMaxAge = 5 * time.Second
|
||||
|
||||
func parseLiteralIP(addr string) string {
|
||||
ip, err := netip.ParseAddr(addr)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return ip.String()
|
||||
}
|
||||
|
||||
type byName struct {
|
||||
addrs []string
|
||||
canonicalName string
|
||||
}
|
||||
|
||||
// hosts contains known host entries.
|
||||
var hosts struct {
|
||||
sync.Mutex
|
||||
|
||||
// Key for the list of literal IP addresses must be a host
|
||||
// name. It would be part of DNS labels, a FQDN or an absolute
|
||||
// FQDN.
|
||||
// For now the key is converted to lower case for convenience.
|
||||
byName map[string]byName
|
||||
|
||||
// Key for the list of host names must be a literal IP address
|
||||
// including IPv6 address with zone identifier.
|
||||
// We don't support old-classful IP address notation.
|
||||
byAddr map[string][]string
|
||||
|
||||
expire time.Time
|
||||
path string
|
||||
mtime time.Time
|
||||
size int64
|
||||
}
|
||||
|
||||
func readHosts() {
|
||||
now := time.Now()
|
||||
hp := hostsFilePath
|
||||
|
||||
if now.Before(hosts.expire) && hosts.path == hp && len(hosts.byName) > 0 {
|
||||
return
|
||||
}
|
||||
mtime, size, err := stat(hp)
|
||||
if err == nil && hosts.path == hp && hosts.mtime.Equal(mtime) && hosts.size == size {
|
||||
hosts.expire = now.Add(cacheMaxAge)
|
||||
return
|
||||
}
|
||||
|
||||
hs := make(map[string]byName)
|
||||
is := make(map[string][]string)
|
||||
|
||||
file, err := open(hp)
|
||||
if err != nil {
|
||||
if !errors.Is(err, fs.ErrNotExist) && !errors.Is(err, fs.ErrPermission) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if file != nil {
|
||||
defer file.close()
|
||||
for line, ok := file.readLine(); ok; line, ok = file.readLine() {
|
||||
if i := strings.IndexByte(line, '#'); i >= 0 {
|
||||
// Discard comments.
|
||||
line = line[0:i]
|
||||
}
|
||||
f := getFields(line)
|
||||
if len(f) < 2 {
|
||||
continue
|
||||
}
|
||||
addr := parseLiteralIP(f[0])
|
||||
if addr == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
var canonical string
|
||||
for i := 1; i < len(f); i++ {
|
||||
name := absDomainName(f[i])
|
||||
h := []byte(f[i])
|
||||
lowerASCIIBytes(h)
|
||||
key := absDomainName(string(h))
|
||||
|
||||
if i == 1 {
|
||||
canonical = key
|
||||
}
|
||||
|
||||
is[addr] = append(is[addr], name)
|
||||
|
||||
if v, ok := hs[key]; ok {
|
||||
hs[key] = byName{
|
||||
addrs: append(v.addrs, addr),
|
||||
canonicalName: v.canonicalName,
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
hs[key] = byName{
|
||||
addrs: []string{addr},
|
||||
canonicalName: canonical,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Update the data cache.
|
||||
hosts.expire = now.Add(cacheMaxAge)
|
||||
hosts.path = hp
|
||||
hosts.byName = hs
|
||||
hosts.byAddr = is
|
||||
hosts.mtime = mtime
|
||||
hosts.size = size
|
||||
}
|
||||
|
||||
// LookupStaticHost looks up the addresses and the canonical name for the given host from /etc/hosts.
|
||||
func LookupStaticHost(host string) ([]string, string) {
|
||||
hosts.Lock()
|
||||
defer hosts.Unlock()
|
||||
readHosts()
|
||||
if len(hosts.byName) != 0 {
|
||||
if hasUpperCase(host) {
|
||||
lowerHost := []byte(host)
|
||||
lowerASCIIBytes(lowerHost)
|
||||
host = string(lowerHost)
|
||||
}
|
||||
if byName, ok := hosts.byName[absDomainName(host)]; ok {
|
||||
ipsCp := make([]string, len(byName.addrs))
|
||||
copy(ipsCp, byName.addrs)
|
||||
return ipsCp, byName.canonicalName
|
||||
}
|
||||
}
|
||||
return nil, ""
|
||||
}
|
||||
|
||||
// LookupStaticAddr looks up the hosts for the given address from /etc/hosts.
|
||||
func LookupStaticAddr(addr string) []string {
|
||||
hosts.Lock()
|
||||
defer hosts.Unlock()
|
||||
readHosts()
|
||||
addr = parseLiteralIP(addr)
|
||||
if addr == "" {
|
||||
return nil
|
||||
}
|
||||
if len(hosts.byAddr) != 0 {
|
||||
if hosts, ok := hosts.byAddr[addr]; ok {
|
||||
hostsCp := make([]string, len(hosts))
|
||||
copy(hostsCp, hosts)
|
||||
return hostsCp
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func stat(name string) (mtime time.Time, size int64, err error) {
|
||||
st, err := os.Stat(name)
|
||||
if err != nil {
|
||||
return time.Time{}, 0, err
|
||||
}
|
||||
return st.ModTime(), st.Size(), nil
|
||||
}
|
||||
|
||||
type file struct {
|
||||
file *os.File
|
||||
data []byte
|
||||
atEOF bool
|
||||
}
|
||||
|
||||
func (f *file) close() { f.file.Close() }
|
||||
|
||||
func (f *file) getLineFromData() (s string, ok bool) {
|
||||
data := f.data
|
||||
i := 0
|
||||
for i = 0; i < len(data); i++ {
|
||||
if data[i] == '\n' {
|
||||
s = string(data[0:i])
|
||||
ok = true
|
||||
// move data
|
||||
i++
|
||||
n := len(data) - i
|
||||
copy(data[0:], data[i:])
|
||||
f.data = data[0:n]
|
||||
return
|
||||
}
|
||||
}
|
||||
if f.atEOF && len(f.data) > 0 {
|
||||
// EOF, return all we have
|
||||
s = string(data)
|
||||
f.data = f.data[0:0]
|
||||
ok = true
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (f *file) readLine() (s string, ok bool) {
|
||||
if s, ok = f.getLineFromData(); ok {
|
||||
return
|
||||
}
|
||||
if len(f.data) < cap(f.data) {
|
||||
ln := len(f.data)
|
||||
n, err := io.ReadFull(f.file, f.data[ln:cap(f.data)])
|
||||
if n >= 0 {
|
||||
f.data = f.data[0 : ln+n]
|
||||
}
|
||||
if err == io.EOF || err == io.ErrUnexpectedEOF {
|
||||
f.atEOF = true
|
||||
}
|
||||
}
|
||||
s, ok = f.getLineFromData()
|
||||
return
|
||||
}
|
||||
|
||||
func (f *file) stat() (mtime time.Time, size int64, err error) {
|
||||
st, err := f.file.Stat()
|
||||
if err != nil {
|
||||
return time.Time{}, 0, err
|
||||
}
|
||||
return st.ModTime(), st.Size(), nil
|
||||
}
|
||||
|
||||
func open(name string) (*file, error) {
|
||||
fd, err := os.Open(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &file{fd, make([]byte, 0, 64*1024), false}, nil
|
||||
}
|
||||
|
||||
func getFields(s string) []string { return splitAtBytes(s, " \r\t\n") }
|
||||
|
||||
// Count occurrences in s of any bytes in t.
|
||||
func countAnyByte(s string, t string) int {
|
||||
n := 0
|
||||
for i := 0; i < len(s); i++ {
|
||||
if strings.IndexByte(t, s[i]) >= 0 {
|
||||
n++
|
||||
}
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
// Split s at any bytes in t.
|
||||
func splitAtBytes(s string, t string) []string {
|
||||
a := make([]string, 1+countAnyByte(s, t))
|
||||
n := 0
|
||||
last := 0
|
||||
for i := 0; i < len(s); i++ {
|
||||
if strings.IndexByte(t, s[i]) >= 0 {
|
||||
if last < i {
|
||||
a[n] = s[last:i]
|
||||
n++
|
||||
}
|
||||
last = i + 1
|
||||
}
|
||||
}
|
||||
if last < len(s) {
|
||||
a[n] = s[last:]
|
||||
n++
|
||||
}
|
||||
return a[0:n]
|
||||
}
|
||||
|
||||
// lowerASCIIBytes makes x ASCII lowercase in-place.
|
||||
func lowerASCIIBytes(x []byte) {
|
||||
for i, b := range x {
|
||||
if 'A' <= b && b <= 'Z' {
|
||||
x[i] += 'a' - 'A'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// hasUpperCase tells whether the given string contains at least one upper-case.
|
||||
func hasUpperCase(s string) bool {
|
||||
for i := range s {
|
||||
if 'A' <= s[i] && s[i] <= 'Z' {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// absDomainName returns an absolute domain name which ends with a
|
||||
// trailing dot to match pure Go reverse resolver and all other lookup
|
||||
// routines.
|
||||
// See golang.org/issue/12189.
|
||||
// But we don't want to add dots for local names from /etc/hosts.
|
||||
// It's hard to tell so we settle on the heuristic that names without dots
|
||||
// (like "localhost" or "myhost") do not get trailing dots, but any other
|
||||
// names do.
|
||||
func absDomainName(s string) string {
|
||||
if strings.IndexByte(s, '.') != -1 && s[len(s)-1] != '.' {
|
||||
s += "."
|
||||
}
|
||||
return s
|
||||
}
|
||||
13
component/resolver/hosts/hosts_windows.go
Normal file
13
component/resolver/hosts/hosts_windows.go
Normal file
@@ -0,0 +1,13 @@
|
||||
package hosts
|
||||
|
||||
// this file copy and modify from golang's std net/hook_windows.go
|
||||
|
||||
import (
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
func init() {
|
||||
if dir, err := windows.GetSystemDirectory(); err == nil {
|
||||
hostsFilePath = dir + "/Drivers/etc/hosts"
|
||||
}
|
||||
}
|
||||
@@ -46,7 +46,7 @@ func RelayDnsConn(ctx context.Context, conn net.Conn, readTimeout time.Duration)
|
||||
ctx, cancel := context.WithTimeout(ctx, DefaultDnsRelayTimeout)
|
||||
defer cancel()
|
||||
inData := buff[:n]
|
||||
msg, err := RelayDnsPacket(ctx, inData, buff)
|
||||
msg, err := relayDnsPacket(ctx, inData, buff, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -69,7 +69,7 @@ func RelayDnsConn(ctx context.Context, conn net.Conn, readTimeout time.Duration)
|
||||
return nil
|
||||
}
|
||||
|
||||
func RelayDnsPacket(ctx context.Context, payload []byte, target []byte) ([]byte, error) {
|
||||
func relayDnsPacket(ctx context.Context, payload []byte, target []byte, maxSize int) ([]byte, error) {
|
||||
msg := &D.Msg{}
|
||||
if err := msg.Unpack(payload); err != nil {
|
||||
return nil, err
|
||||
@@ -83,6 +83,14 @@ func RelayDnsPacket(ctx context.Context, payload []byte, target []byte) ([]byte,
|
||||
}
|
||||
|
||||
r.SetRcode(msg, r.Rcode)
|
||||
if maxSize > 0 {
|
||||
r.Truncate(maxSize)
|
||||
}
|
||||
r.Compress = true
|
||||
return r.PackBuffer(target)
|
||||
}
|
||||
|
||||
// RelayDnsPacket will truncate udp message up to SafeDnsPacketSize
|
||||
func RelayDnsPacket(ctx context.Context, payload []byte, target []byte) ([]byte, error) {
|
||||
return relayDnsPacket(ctx, payload, target, SafeDnsPacketSize)
|
||||
}
|
||||
|
||||
@@ -12,8 +12,8 @@ import (
|
||||
"github.com/metacubex/mihomo/common/utils"
|
||||
"github.com/metacubex/mihomo/component/trie"
|
||||
|
||||
"github.com/metacubex/randv2"
|
||||
"github.com/miekg/dns"
|
||||
"github.com/zhangyunhao116/fastrand"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -93,7 +93,7 @@ func ResolveIPv4WithResolver(ctx context.Context, host string, r Resolver) (neti
|
||||
} else if len(ips) == 0 {
|
||||
return netip.Addr{}, fmt.Errorf("%w: %s", ErrIPNotFound, host)
|
||||
}
|
||||
return ips[fastrand.Intn(len(ips))], nil
|
||||
return ips[randv2.IntN(len(ips))], nil
|
||||
}
|
||||
|
||||
// ResolveIPv4 with a host, return ipv4
|
||||
@@ -149,7 +149,7 @@ func ResolveIPv6WithResolver(ctx context.Context, host string, r Resolver) (neti
|
||||
} else if len(ips) == 0 {
|
||||
return netip.Addr{}, fmt.Errorf("%w: %s", ErrIPNotFound, host)
|
||||
}
|
||||
return ips[fastrand.Intn(len(ips))], nil
|
||||
return ips[randv2.IntN(len(ips))], nil
|
||||
}
|
||||
|
||||
func ResolveIPv6(ctx context.Context, host string) (netip.Addr, error) {
|
||||
@@ -200,9 +200,9 @@ func ResolveIPWithResolver(ctx context.Context, host string, r Resolver) (netip.
|
||||
}
|
||||
ipv4s, ipv6s := SortationAddr(ips)
|
||||
if len(ipv4s) > 0 {
|
||||
return ipv4s[fastrand.Intn(len(ipv4s))], nil
|
||||
return ipv4s[randv2.IntN(len(ipv4s))], nil
|
||||
}
|
||||
return ipv6s[fastrand.Intn(len(ipv6s))], nil
|
||||
return ipv6s[randv2.IntN(len(ipv6s))], nil
|
||||
}
|
||||
|
||||
// ResolveIP with a host, return ip and priority return TypeA
|
||||
|
||||
39
component/resolver/system.go
Normal file
39
component/resolver/system.go
Normal file
@@ -0,0 +1,39 @@
|
||||
package resolver
|
||||
|
||||
import "sync"
|
||||
|
||||
var blacklist struct {
|
||||
Map map[string]struct{}
|
||||
Mutex sync.Mutex
|
||||
}
|
||||
|
||||
func init() {
|
||||
blacklist.Map = make(map[string]struct{})
|
||||
}
|
||||
|
||||
func AddSystemDnsBlacklist(names ...string) {
|
||||
blacklist.Mutex.Lock()
|
||||
defer blacklist.Mutex.Unlock()
|
||||
for _, name := range names {
|
||||
blacklist.Map[name] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
func RemoveSystemDnsBlacklist(names ...string) {
|
||||
blacklist.Mutex.Lock()
|
||||
defer blacklist.Mutex.Unlock()
|
||||
for _, name := range names {
|
||||
delete(blacklist.Map, name)
|
||||
}
|
||||
}
|
||||
|
||||
func IsSystemDnsBlacklisted(names ...string) bool {
|
||||
blacklist.Mutex.Lock()
|
||||
defer blacklist.Mutex.Unlock()
|
||||
for _, name := range names {
|
||||
if _, ok := blacklist.Map[name]; ok {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
types "github.com/metacubex/mihomo/constant/provider"
|
||||
"github.com/metacubex/mihomo/log"
|
||||
|
||||
"github.com/sagernet/fswatch"
|
||||
"github.com/samber/lo"
|
||||
)
|
||||
|
||||
@@ -30,6 +31,7 @@ type Fetcher[V any] struct {
|
||||
parser Parser[V]
|
||||
interval time.Duration
|
||||
OnUpdate func(V)
|
||||
watcher *fswatch.Watcher
|
||||
}
|
||||
|
||||
func (f *Fetcher[V]) Name() string {
|
||||
@@ -113,7 +115,20 @@ func (f *Fetcher[V]) Initial() (V, error) {
|
||||
f.hash = md5.Sum(buf)
|
||||
|
||||
// pull contents automatically
|
||||
if f.interval > 0 {
|
||||
if f.vehicle.Type() == types.File {
|
||||
f.watcher, err = fswatch.NewWatcher(fswatch.Options{
|
||||
Path: []string{f.vehicle.Path()},
|
||||
Direct: true,
|
||||
Callback: f.update,
|
||||
})
|
||||
if err != nil {
|
||||
return lo.Empty[V](), err
|
||||
}
|
||||
err = f.watcher.Start()
|
||||
if err != nil {
|
||||
return lo.Empty[V](), err
|
||||
}
|
||||
} else if f.interval > 0 {
|
||||
go f.pullLoop()
|
||||
}
|
||||
|
||||
@@ -155,6 +170,9 @@ func (f *Fetcher[V]) Destroy() error {
|
||||
if f.interval > 0 {
|
||||
f.done <- struct{}{}
|
||||
}
|
||||
if f.watcher != nil {
|
||||
_ = f.watcher.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -170,27 +188,31 @@ func (f *Fetcher[V]) pullLoop() {
|
||||
select {
|
||||
case <-timer.C:
|
||||
timer.Reset(f.interval)
|
||||
elm, same, err := f.Update()
|
||||
if err != nil {
|
||||
log.Errorln("[Provider] %s pull error: %s", f.Name(), err.Error())
|
||||
continue
|
||||
}
|
||||
|
||||
if same {
|
||||
log.Debugln("[Provider] %s's content doesn't change", f.Name())
|
||||
continue
|
||||
}
|
||||
|
||||
log.Infoln("[Provider] %s's content update", f.Name())
|
||||
if f.OnUpdate != nil {
|
||||
f.OnUpdate(elm)
|
||||
}
|
||||
f.update(f.vehicle.Path())
|
||||
case <-f.done:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (f *Fetcher[V]) update(path string) {
|
||||
elm, same, err := f.Update()
|
||||
if err != nil {
|
||||
log.Errorln("[Provider] %s pull error: %s", f.Name(), err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if same {
|
||||
log.Debugln("[Provider] %s's content doesn't change", f.Name())
|
||||
return
|
||||
}
|
||||
|
||||
log.Infoln("[Provider] %s's content update", f.Name())
|
||||
if f.OnUpdate != nil {
|
||||
f.OnUpdate(elm)
|
||||
}
|
||||
}
|
||||
|
||||
func safeWrite(path string, buf []byte) error {
|
||||
dir := filepath.Dir(path)
|
||||
|
||||
|
||||
@@ -28,13 +28,19 @@ func (f *FileVehicle) Read() ([]byte, error) {
|
||||
return os.ReadFile(f.path)
|
||||
}
|
||||
|
||||
func (f *FileVehicle) Proxy() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func NewFileVehicle(path string) *FileVehicle {
|
||||
return &FileVehicle{path: path}
|
||||
}
|
||||
|
||||
type HTTPVehicle struct {
|
||||
url string
|
||||
path string
|
||||
url string
|
||||
path string
|
||||
proxy string
|
||||
header http.Header
|
||||
}
|
||||
|
||||
func (h *HTTPVehicle) Url() string {
|
||||
@@ -49,10 +55,14 @@ func (h *HTTPVehicle) Path() string {
|
||||
return h.path
|
||||
}
|
||||
|
||||
func (h *HTTPVehicle) Proxy() string {
|
||||
return h.proxy
|
||||
}
|
||||
|
||||
func (h *HTTPVehicle) Read() ([]byte, error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*20)
|
||||
defer cancel()
|
||||
resp, err := mihomoHttp.HttpRequest(ctx, h.url, http.MethodGet, nil, nil)
|
||||
resp, err := mihomoHttp.HttpRequestWithProxy(ctx, h.url, http.MethodGet, h.header, nil, h.proxy)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -67,6 +77,6 @@ func (h *HTTPVehicle) Read() ([]byte, error) {
|
||||
return buf, nil
|
||||
}
|
||||
|
||||
func NewHTTPVehicle(url string, path string) *HTTPVehicle {
|
||||
return &HTTPVehicle{url, path}
|
||||
func NewHTTPVehicle(url string, path string, proxy string, header http.Header) *HTTPVehicle {
|
||||
return &HTTPVehicle{url, path, proxy, header}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ import (
|
||||
"fmt"
|
||||
"net"
|
||||
"net/netip"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/metacubex/mihomo/common/lru"
|
||||
@@ -30,7 +29,6 @@ type SnifferDispatcher struct {
|
||||
forceDomain *trie.DomainSet
|
||||
skipSNI *trie.DomainSet
|
||||
skipList *lru.LruCache[string, uint8]
|
||||
rwMux sync.RWMutex
|
||||
forceDnsMapping bool
|
||||
parsePureIp bool
|
||||
}
|
||||
@@ -85,14 +83,11 @@ func (sd *SnifferDispatcher) TCPSniff(conn *N.BufferedConn, metadata *C.Metadata
|
||||
return false
|
||||
}
|
||||
|
||||
sd.rwMux.RLock()
|
||||
dst := fmt.Sprintf("%s:%d", metadata.DstIP, metadata.DstPort)
|
||||
if count, ok := sd.skipList.Get(dst); ok && count > 5 {
|
||||
log.Debugln("[Sniffer] Skip sniffing[%s] due to multiple failures", dst)
|
||||
defer sd.rwMux.RUnlock()
|
||||
return false
|
||||
}
|
||||
sd.rwMux.RUnlock()
|
||||
|
||||
if host, err := sd.sniffDomain(conn, metadata); err != nil {
|
||||
sd.cacheSniffFailed(metadata)
|
||||
@@ -104,9 +99,7 @@ func (sd *SnifferDispatcher) TCPSniff(conn *N.BufferedConn, metadata *C.Metadata
|
||||
return false
|
||||
}
|
||||
|
||||
sd.rwMux.RLock()
|
||||
sd.skipList.Delete(dst)
|
||||
sd.rwMux.RUnlock()
|
||||
|
||||
sd.replaceDomain(metadata, host, overrideDest)
|
||||
return true
|
||||
@@ -116,14 +109,13 @@ func (sd *SnifferDispatcher) TCPSniff(conn *N.BufferedConn, metadata *C.Metadata
|
||||
}
|
||||
|
||||
func (sd *SnifferDispatcher) replaceDomain(metadata *C.Metadata, host string, overrideDest bool) {
|
||||
// show log early, since the following code may mutate `metadata.Host`
|
||||
log.Debugln("[Sniffer] Sniff %s [%s]-->[%s] success, replace domain [%s]-->[%s]",
|
||||
metadata.NetWork,
|
||||
metadata.SourceDetail(),
|
||||
metadata.RemoteAddress(),
|
||||
metadata.Host, host)
|
||||
metadata.SniffHost = host
|
||||
if overrideDest {
|
||||
log.Debugln("[Sniffer] Sniff %s [%s]-->[%s] success, replace domain [%s]-->[%s]",
|
||||
metadata.NetWork,
|
||||
metadata.SourceDetail(),
|
||||
metadata.RemoteAddress(),
|
||||
metadata.Host, host)
|
||||
metadata.Host = host
|
||||
}
|
||||
metadata.DNSMode = C.DNSNormal
|
||||
@@ -177,14 +169,13 @@ func (sd *SnifferDispatcher) sniffDomain(conn *N.BufferedConn, metadata *C.Metad
|
||||
}
|
||||
|
||||
func (sd *SnifferDispatcher) cacheSniffFailed(metadata *C.Metadata) {
|
||||
sd.rwMux.Lock()
|
||||
dst := fmt.Sprintf("%s:%d", metadata.DstIP, metadata.DstPort)
|
||||
count, _ := sd.skipList.Get(dst)
|
||||
if count <= 5 {
|
||||
count++
|
||||
}
|
||||
sd.skipList.Set(dst, count)
|
||||
sd.rwMux.Unlock()
|
||||
sd.skipList.Compute(dst, func(oldValue uint8, loaded bool) (newValue uint8, delete bool) {
|
||||
if oldValue <= 5 {
|
||||
oldValue++
|
||||
}
|
||||
return oldValue, false
|
||||
})
|
||||
}
|
||||
|
||||
func NewCloseSnifferDispatcher() (*SnifferDispatcher, error) {
|
||||
|
||||
@@ -16,17 +16,14 @@ import (
|
||||
"errors"
|
||||
"net"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"strings"
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
"github.com/metacubex/mihomo/common/utils"
|
||||
"github.com/metacubex/mihomo/log"
|
||||
"github.com/metacubex/mihomo/ntp"
|
||||
|
||||
utls "github.com/sagernet/utls"
|
||||
"github.com/zhangyunhao116/fastrand"
|
||||
"github.com/metacubex/randv2"
|
||||
utls "github.com/metacubex/utls"
|
||||
"golang.org/x/crypto/chacha20poly1305"
|
||||
"golang.org/x/crypto/hkdf"
|
||||
"golang.org/x/net/http2"
|
||||
@@ -39,9 +36,6 @@ type RealityConfig struct {
|
||||
ShortID [RealityMaxShortIDLen]byte
|
||||
}
|
||||
|
||||
//go:linkname aesgcmPreferred crypto/tls.aesgcmPreferred
|
||||
func aesgcmPreferred(ciphers []uint16) bool
|
||||
|
||||
func GetRealityConn(ctx context.Context, conn net.Conn, ClientFingerprint string, tlsConfig *tls.Config, realityConfig *RealityConfig) (net.Conn, error) {
|
||||
retry := 0
|
||||
for fingerprint, exists := GetFingerprint(ClientFingerprint); exists; retry++ {
|
||||
@@ -102,7 +96,7 @@ func GetRealityConn(ctx context.Context, conn net.Conn, ClientFingerprint string
|
||||
return nil, err
|
||||
}
|
||||
var aeadCipher cipher.AEAD
|
||||
if aesgcmPreferred(hello.CipherSuites) {
|
||||
if utls.AesgcmPreferred(hello.CipherSuites) {
|
||||
aesBlock, _ := aes.NewCipher(authKey)
|
||||
aeadCipher, _ = cipher.NewGCM(aesBlock)
|
||||
} else {
|
||||
@@ -139,15 +133,18 @@ func realityClientFallback(uConn net.Conn, serverName string, fingerprint utls.C
|
||||
},
|
||||
},
|
||||
}
|
||||
request, _ := http.NewRequest("GET", "https://"+serverName, nil)
|
||||
request, err := http.NewRequest("GET", "https://"+serverName, nil)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
request.Header.Set("User-Agent", fingerprint.Client)
|
||||
request.AddCookie(&http.Cookie{Name: "padding", Value: strings.Repeat("0", fastrand.Intn(32)+30)})
|
||||
request.AddCookie(&http.Cookie{Name: "padding", Value: strings.Repeat("0", randv2.IntN(32)+30)})
|
||||
response, err := client.Do(request)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
//_, _ = io.Copy(io.Discard, response.Body)
|
||||
time.Sleep(time.Duration(5+fastrand.Int63n(10)) * time.Second)
|
||||
time.Sleep(time.Duration(5+randv2.IntN(10)) * time.Second)
|
||||
response.Body.Close()
|
||||
client.CloseIdleConnections()
|
||||
}
|
||||
@@ -159,11 +156,12 @@ type realityVerifier struct {
|
||||
verified bool
|
||||
}
|
||||
|
||||
var pOffset = utils.MustOK(reflect.TypeOf((*utls.Conn)(nil)).Elem().FieldByName("peerCertificates")).Offset
|
||||
//var pOffset = utils.MustOK(reflect.TypeOf((*utls.Conn)(nil)).Elem().FieldByName("peerCertificates")).Offset
|
||||
|
||||
func (c *realityVerifier) VerifyPeerCertificate(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {
|
||||
//p, _ := reflect.TypeOf(c.Conn).Elem().FieldByName("peerCertificates")
|
||||
certs := *(*[]*x509.Certificate)(unsafe.Add(unsafe.Pointer(c.Conn), pOffset))
|
||||
//certs := *(*[]*x509.Certificate)(unsafe.Add(unsafe.Pointer(c.Conn), pOffset))
|
||||
certs := c.Conn.PeerCertificates()
|
||||
if pub, ok := certs[0].PublicKey.(ed25519.PublicKey); ok {
|
||||
h := hmac.New(sha512.New, c.authKey)
|
||||
h.Write(pub)
|
||||
|
||||
@@ -6,8 +6,8 @@ import (
|
||||
|
||||
"github.com/metacubex/mihomo/log"
|
||||
|
||||
utls "github.com/metacubex/utls"
|
||||
"github.com/mroth/weightedrand/v2"
|
||||
utls "github.com/sagernet/utls"
|
||||
)
|
||||
|
||||
type UConn struct {
|
||||
|
||||
@@ -123,16 +123,18 @@ func (t *DomainTrie[T]) Optimize() {
|
||||
t.root.optimize()
|
||||
}
|
||||
|
||||
func (t *DomainTrie[T]) Foreach(print func(domain string, data T)) {
|
||||
func (t *DomainTrie[T]) Foreach(fn func(domain string, data T) bool) {
|
||||
for key, data := range t.root.getChildren() {
|
||||
recursion([]string{key}, data, print)
|
||||
recursion([]string{key}, data, fn)
|
||||
if data != nil && data.inited {
|
||||
print(joinDomain([]string{key}), data.data)
|
||||
if !fn(joinDomain([]string{key}), data.data) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func recursion[T any](items []string, node *Node[T], fn func(domain string, data T)) {
|
||||
func recursion[T any](items []string, node *Node[T], fn func(domain string, data T) bool) bool {
|
||||
for key, data := range node.getChildren() {
|
||||
newItems := append([]string{key}, items...)
|
||||
if data != nil && data.inited {
|
||||
@@ -140,10 +142,15 @@ func recursion[T any](items []string, node *Node[T], fn func(domain string, data
|
||||
if domain[0] == domainStepByte {
|
||||
domain = complexWildcard + domain
|
||||
}
|
||||
fn(domain, data.Data())
|
||||
if !fn(domain, data.Data()) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
if !recursion(newItems, data, fn) {
|
||||
return false
|
||||
}
|
||||
recursion(newItems, data, fn)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func joinDomain(items []string) string {
|
||||
|
||||
@@ -28,8 +28,9 @@ type qElt struct{ s, e, col int }
|
||||
// NewDomainSet creates a new *DomainSet struct, from a DomainTrie.
|
||||
func (t *DomainTrie[T]) NewDomainSet() *DomainSet {
|
||||
reserveDomains := make([]string, 0)
|
||||
t.Foreach(func(domain string, data T) {
|
||||
t.Foreach(func(domain string, data T) bool {
|
||||
reserveDomains = append(reserveDomains, utils.Reverse(domain))
|
||||
return true
|
||||
})
|
||||
// ensure that the same prefix is continuous
|
||||
// and according to the ascending sequence of length
|
||||
@@ -136,6 +137,41 @@ func (ss *DomainSet) Has(key string) bool {
|
||||
|
||||
}
|
||||
|
||||
func (ss *DomainSet) keys(f func(key string) bool) {
|
||||
var currentKey []byte
|
||||
var traverse func(int, int) bool
|
||||
traverse = func(nodeId, bmIdx int) bool {
|
||||
if getBit(ss.leaves, nodeId) != 0 {
|
||||
if !f(string(currentKey)) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
for ; ; bmIdx++ {
|
||||
if getBit(ss.labelBitmap, bmIdx) != 0 {
|
||||
return true
|
||||
}
|
||||
nextLabel := ss.labels[bmIdx-nodeId]
|
||||
currentKey = append(currentKey, nextLabel)
|
||||
nextNodeId := countZeros(ss.labelBitmap, ss.ranks, bmIdx+1)
|
||||
nextBmIdx := selectIthOne(ss.labelBitmap, ss.ranks, ss.selects, nextNodeId-1) + 1
|
||||
if !traverse(nextNodeId, nextBmIdx) {
|
||||
return false
|
||||
}
|
||||
currentKey = currentKey[:len(currentKey)-1]
|
||||
}
|
||||
}
|
||||
|
||||
traverse(0, 0)
|
||||
return
|
||||
}
|
||||
|
||||
func (ss *DomainSet) Foreach(f func(key string) bool) {
|
||||
ss.keys(func(key string) bool {
|
||||
return f(utils.Reverse(key))
|
||||
})
|
||||
}
|
||||
|
||||
func setBit(bm *[]uint64, i int, v int) {
|
||||
for i>>6 >= len(*bm) {
|
||||
*bm = append(*bm, 0)
|
||||
|
||||
115
component/trie/domain_set_bin.go
Normal file
115
component/trie/domain_set_bin.go
Normal file
@@ -0,0 +1,115 @@
|
||||
package trie
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"io"
|
||||
)
|
||||
|
||||
func (ss *DomainSet) WriteBin(w io.Writer) (err error) {
|
||||
// version
|
||||
_, err = w.Write([]byte{1})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// leaves
|
||||
err = binary.Write(w, binary.BigEndian, int64(len(ss.leaves)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, d := range ss.leaves {
|
||||
err = binary.Write(w, binary.BigEndian, d)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// labelBitmap
|
||||
err = binary.Write(w, binary.BigEndian, int64(len(ss.labelBitmap)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, d := range ss.labelBitmap {
|
||||
err = binary.Write(w, binary.BigEndian, d)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// labels
|
||||
err = binary.Write(w, binary.BigEndian, int64(len(ss.labels)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = w.Write(ss.labels)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func ReadDomainSetBin(r io.Reader) (ds *DomainSet, err error) {
|
||||
// version
|
||||
version := make([]byte, 1)
|
||||
_, err = io.ReadFull(r, version)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if version[0] != 1 {
|
||||
return nil, errors.New("version is invalid")
|
||||
}
|
||||
|
||||
ds = &DomainSet{}
|
||||
var length int64
|
||||
|
||||
// leaves
|
||||
err = binary.Read(r, binary.BigEndian, &length)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if length < 1 {
|
||||
return nil, errors.New("length is invalid")
|
||||
}
|
||||
ds.leaves = make([]uint64, length)
|
||||
for i := int64(0); i < length; i++ {
|
||||
err = binary.Read(r, binary.BigEndian, &ds.leaves[i])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// labelBitmap
|
||||
err = binary.Read(r, binary.BigEndian, &length)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if length < 1 {
|
||||
return nil, errors.New("length is invalid")
|
||||
}
|
||||
ds.labelBitmap = make([]uint64, length)
|
||||
for i := int64(0); i < length; i++ {
|
||||
err = binary.Read(r, binary.BigEndian, &ds.labelBitmap[i])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// labels
|
||||
err = binary.Read(r, binary.BigEndian, &length)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if length < 1 {
|
||||
return nil, errors.New("length is invalid")
|
||||
}
|
||||
ds.labels = make([]byte, length)
|
||||
_, err = io.ReadFull(r, ds.labels)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ds.init()
|
||||
return ds, nil
|
||||
}
|
||||
@@ -1,12 +1,29 @@
|
||||
package trie_test
|
||||
|
||||
import (
|
||||
"golang.org/x/exp/slices"
|
||||
"testing"
|
||||
|
||||
"github.com/metacubex/mihomo/component/trie"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func testDump(t *testing.T, tree *trie.DomainTrie[struct{}], set *trie.DomainSet) {
|
||||
var dataSrc []string
|
||||
tree.Foreach(func(domain string, data struct{}) bool {
|
||||
dataSrc = append(dataSrc, domain)
|
||||
return true
|
||||
})
|
||||
slices.Sort(dataSrc)
|
||||
var dataSet []string
|
||||
set.Foreach(func(key string) bool {
|
||||
dataSet = append(dataSet, key)
|
||||
return true
|
||||
})
|
||||
slices.Sort(dataSet)
|
||||
assert.Equal(t, dataSrc, dataSet)
|
||||
}
|
||||
|
||||
func TestDomainSet(t *testing.T) {
|
||||
tree := trie.New[struct{}]()
|
||||
domainSet := []string{
|
||||
@@ -33,6 +50,7 @@ func TestDomainSet(t *testing.T) {
|
||||
assert.True(t, set.Has("google.com"))
|
||||
assert.False(t, set.Has("qq.com"))
|
||||
assert.False(t, set.Has("www.baidu.com"))
|
||||
testDump(t, tree, set)
|
||||
}
|
||||
|
||||
func TestDomainSetComplexWildcard(t *testing.T) {
|
||||
@@ -55,6 +73,7 @@ func TestDomainSetComplexWildcard(t *testing.T) {
|
||||
assert.False(t, set.Has("google.com"))
|
||||
assert.True(t, set.Has("www.baidu.com"))
|
||||
assert.True(t, set.Has("test.test.baidu.com"))
|
||||
testDump(t, tree, set)
|
||||
}
|
||||
|
||||
func TestDomainSetWildcard(t *testing.T) {
|
||||
@@ -82,4 +101,5 @@ func TestDomainSetWildcard(t *testing.T) {
|
||||
assert.False(t, set.Has("a.www.google.com"))
|
||||
assert.False(t, set.Has("test.qq.com"))
|
||||
assert.False(t, set.Has("test.test.test.qq.com"))
|
||||
testDump(t, tree, set)
|
||||
}
|
||||
|
||||
@@ -121,8 +121,9 @@ func TestTrie_Foreach(t *testing.T) {
|
||||
assert.NoError(t, tree.Insert(domain, localIP))
|
||||
}
|
||||
count := 0
|
||||
tree.Foreach(func(domain string, data netip.Addr) {
|
||||
tree.Foreach(func(domain string, data netip.Addr) bool {
|
||||
count++
|
||||
return true
|
||||
})
|
||||
assert.Equal(t, 7, count)
|
||||
}
|
||||
|
||||
@@ -16,7 +16,6 @@ import (
|
||||
"time"
|
||||
|
||||
mihomoHttp "github.com/metacubex/mihomo/component/http"
|
||||
"github.com/metacubex/mihomo/constant"
|
||||
C "github.com/metacubex/mihomo/constant"
|
||||
"github.com/metacubex/mihomo/log"
|
||||
|
||||
@@ -52,6 +51,10 @@ func init() {
|
||||
if runtime.GOARCH == "amd64" && cpuid.CPU.X64Level() < 3 {
|
||||
amd64Compatible = "-compatible"
|
||||
}
|
||||
if !strings.HasPrefix(C.Version, "alpha") {
|
||||
baseURL = "https://github.com/MetaCubeX/mihomo/releases/latest/download/mihomo"
|
||||
versionURL = "https://github.com/MetaCubeX/mihomo/releases/latest/download/version.txt"
|
||||
}
|
||||
}
|
||||
|
||||
type updateError struct {
|
||||
@@ -64,7 +67,7 @@ func (e *updateError) Error() string {
|
||||
|
||||
// Update performs the auto-updater. It returns an error if the updater failed.
|
||||
// If firstRun is true, it assumes the configuration file doesn't exist.
|
||||
func Update(execPath string) (err error) {
|
||||
func UpdateCore(execPath string) (err error) {
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
|
||||
@@ -73,9 +76,9 @@ func Update(execPath string) (err error) {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Infoln("current version %s, latest version %s", constant.Version, latestVersion)
|
||||
log.Infoln("current version %s, latest version %s", C.Version, latestVersion)
|
||||
|
||||
if latestVersion == constant.Version {
|
||||
if latestVersion == C.Version {
|
||||
err := &updateError{Message: "already using latest version"}
|
||||
return err
|
||||
}
|
||||
@@ -1,18 +1,27 @@
|
||||
package config
|
||||
package updater
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"runtime"
|
||||
"time"
|
||||
|
||||
"github.com/metacubex/mihomo/common/atomic"
|
||||
"github.com/metacubex/mihomo/component/geodata"
|
||||
_ "github.com/metacubex/mihomo/component/geodata/standard"
|
||||
"github.com/metacubex/mihomo/component/mmdb"
|
||||
C "github.com/metacubex/mihomo/constant"
|
||||
"github.com/metacubex/mihomo/log"
|
||||
|
||||
"github.com/oschwald/maxminddb-golang"
|
||||
)
|
||||
|
||||
func UpdateGeoDatabases() error {
|
||||
var (
|
||||
UpdatingGeo atomic.Bool
|
||||
)
|
||||
|
||||
func updateGeoDatabases() error {
|
||||
defer runtime.GC()
|
||||
geoLoader, err := geodata.GetGeoDataLoader("standard")
|
||||
if err != nil {
|
||||
@@ -88,3 +97,80 @@ func UpdateGeoDatabases() error {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
var ErrGetDatabaseUpdateSkip = errors.New("GEO database is updating, skip")
|
||||
|
||||
func UpdateGeoDatabases() error {
|
||||
log.Infoln("[GEO] Start updating GEO database")
|
||||
|
||||
if UpdatingGeo.Load() {
|
||||
return ErrGetDatabaseUpdateSkip
|
||||
}
|
||||
|
||||
UpdatingGeo.Store(true)
|
||||
defer UpdatingGeo.Store(false)
|
||||
|
||||
log.Infoln("[GEO] Updating GEO database")
|
||||
|
||||
if err := updateGeoDatabases(); err != nil {
|
||||
log.Errorln("[GEO] update GEO database error: %s", err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func getUpdateTime() (err error, time time.Time) {
|
||||
var fileInfo os.FileInfo
|
||||
if C.GeodataMode {
|
||||
fileInfo, err = os.Stat(C.Path.GeoIP())
|
||||
if err != nil {
|
||||
return err, time
|
||||
}
|
||||
} else {
|
||||
fileInfo, err = os.Stat(C.Path.MMDB())
|
||||
if err != nil {
|
||||
return err, time
|
||||
}
|
||||
}
|
||||
|
||||
return nil, fileInfo.ModTime()
|
||||
}
|
||||
|
||||
func RegisterGeoUpdater(onSuccess func()) {
|
||||
if C.GeoUpdateInterval <= 0 {
|
||||
log.Errorln("[GEO] Invalid update interval: %d", C.GeoUpdateInterval)
|
||||
return
|
||||
}
|
||||
|
||||
go func() {
|
||||
ticker := time.NewTicker(time.Duration(C.GeoUpdateInterval) * time.Hour)
|
||||
defer ticker.Stop()
|
||||
|
||||
err, lastUpdate := getUpdateTime()
|
||||
if err != nil {
|
||||
log.Errorln("[GEO] Get GEO database update time error: %s", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
log.Infoln("[GEO] last update time %s", lastUpdate)
|
||||
if lastUpdate.Add(time.Duration(C.GeoUpdateInterval) * time.Hour).Before(time.Now()) {
|
||||
log.Infoln("[GEO] Database has not been updated for %v, update now", time.Duration(C.GeoUpdateInterval)*time.Hour)
|
||||
if err := UpdateGeoDatabases(); err != nil {
|
||||
log.Errorln("[GEO] Failed to update GEO database: %s", err.Error())
|
||||
return
|
||||
} else {
|
||||
onSuccess()
|
||||
}
|
||||
}
|
||||
|
||||
for range ticker.C {
|
||||
log.Infoln("[GEO] updating database every %d hours", C.GeoUpdateInterval)
|
||||
if err := UpdateGeoDatabases(); err != nil {
|
||||
log.Errorln("[GEO] Failed to update GEO database: %s", err.Error())
|
||||
} else {
|
||||
onSuccess()
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package config
|
||||
package updater
|
||||
|
||||
import (
|
||||
"archive/zip"
|
||||
@@ -29,7 +29,7 @@ func UpdateUI() error {
|
||||
xdMutex.Lock()
|
||||
defer xdMutex.Unlock()
|
||||
|
||||
err := prepare()
|
||||
err := prepare_ui()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -64,7 +64,7 @@ func UpdateUI() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func prepare() error {
|
||||
func prepare_ui() error {
|
||||
if ExternalUIPath == "" || ExternalUIURL == "" {
|
||||
return ErrIncompleteConf
|
||||
}
|
||||
@@ -1,12 +1,35 @@
|
||||
package updater
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
mihomoHttp "github.com/metacubex/mihomo/component/http"
|
||||
C "github.com/metacubex/mihomo/constant"
|
||||
|
||||
"golang.org/x/exp/constraints"
|
||||
)
|
||||
|
||||
func downloadForBytes(url string) ([]byte, error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*90)
|
||||
defer cancel()
|
||||
resp, err := mihomoHttp.HttpRequest(ctx, url, http.MethodGet, http.Header{"User-Agent": {C.UA}}, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
return io.ReadAll(resp.Body)
|
||||
}
|
||||
|
||||
func saveFile(bytes []byte, path string) error {
|
||||
return os.WriteFile(path, bytes, 0o644)
|
||||
}
|
||||
|
||||
// LimitReachedError records the limit and the operation that caused it.
|
||||
type LimitReachedError struct {
|
||||
Limit int64
|
||||
264
config/config.go
264
config/config.go
@@ -28,6 +28,7 @@ import (
|
||||
SNIFF "github.com/metacubex/mihomo/component/sniffer"
|
||||
tlsC "github.com/metacubex/mihomo/component/tls"
|
||||
"github.com/metacubex/mihomo/component/trie"
|
||||
"github.com/metacubex/mihomo/component/updater"
|
||||
C "github.com/metacubex/mihomo/constant"
|
||||
"github.com/metacubex/mihomo/constant/features"
|
||||
providerTypes "github.com/metacubex/mihomo/constant/provider"
|
||||
@@ -41,6 +42,7 @@ import (
|
||||
T "github.com/metacubex/mihomo/tunnel"
|
||||
|
||||
orderedmap "github.com/wk8/go-ordered-map/v2"
|
||||
"golang.org/x/exp/slices"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
@@ -91,10 +93,12 @@ type Inbound struct {
|
||||
|
||||
// Controller config
|
||||
type Controller struct {
|
||||
ExternalController string `json:"-"`
|
||||
ExternalControllerTLS string `json:"-"`
|
||||
ExternalUI string `json:"-"`
|
||||
Secret string `json:"-"`
|
||||
ExternalController string `json:"-"`
|
||||
ExternalControllerTLS string `json:"-"`
|
||||
ExternalControllerUnix string `json:"-"`
|
||||
ExternalUI string `json:"-"`
|
||||
ExternalDohServer string `json:"-"`
|
||||
Secret string `json:"-"`
|
||||
}
|
||||
|
||||
// NTP config
|
||||
@@ -113,6 +117,7 @@ type DNS struct {
|
||||
PreferH3 bool `yaml:"prefer-h3"`
|
||||
IPv6 bool `yaml:"ipv6"`
|
||||
IPv6Timeout uint `yaml:"ipv6-timeout"`
|
||||
UseSystemHosts bool `yaml:"use-system-hosts"`
|
||||
NameServer []dns.NameServer `yaml:"nameserver"`
|
||||
Fallback []dns.NameServer `yaml:"fallback"`
|
||||
FallbackFilter FallbackFilter `yaml:"fallback-filter"`
|
||||
@@ -208,6 +213,8 @@ type RawDNS struct {
|
||||
IPv6 bool `yaml:"ipv6" json:"ipv6"`
|
||||
IPv6Timeout uint `yaml:"ipv6-timeout" json:"ipv6-timeout"`
|
||||
UseHosts bool `yaml:"use-hosts" json:"use-hosts"`
|
||||
UseSystemHosts bool `yaml:"use-system-hosts" json:"use-system-hosts"`
|
||||
RespectRules bool `yaml:"respect-rules" json:"respect-rules"`
|
||||
NameServer []string `yaml:"nameserver" json:"nameserver"`
|
||||
Fallback []string `yaml:"fallback" json:"fallback"`
|
||||
FallbackFilter RawFallbackFilter `yaml:"fallback-filter" json:"fallback-filter"`
|
||||
@@ -241,31 +248,39 @@ type RawTun struct {
|
||||
DNSHijack []string `yaml:"dns-hijack" json:"dns-hijack"`
|
||||
AutoRoute bool `yaml:"auto-route" json:"auto-route"`
|
||||
AutoDetectInterface bool `yaml:"auto-detect-interface"`
|
||||
RedirectToTun []string `yaml:"-" json:"-"`
|
||||
|
||||
MTU uint32 `yaml:"mtu" json:"mtu,omitempty"`
|
||||
GSO bool `yaml:"gso" json:"gso,omitempty"`
|
||||
GSOMaxSize uint32 `yaml:"gso-max-size" json:"gso-max-size,omitempty"`
|
||||
//Inet4Address []netip.Prefix `yaml:"inet4-address" json:"inet4_address,omitempty"`
|
||||
Inet6Address []netip.Prefix `yaml:"inet6-address" json:"inet6_address,omitempty"`
|
||||
StrictRoute bool `yaml:"strict-route" json:"strict_route,omitempty"`
|
||||
Inet6Address []netip.Prefix `yaml:"inet6-address" json:"inet6_address,omitempty"`
|
||||
IPRoute2TableIndex int `yaml:"iproute2-table-index" json:"iproute2_table_index,omitempty"`
|
||||
IPRoute2RuleIndex int `yaml:"iproute2-rule-index" json:"iproute2_rule_index,omitempty"`
|
||||
AutoRedirect bool `yaml:"auto-redirect" json:"auto_redirect,omitempty"`
|
||||
AutoRedirectInputMark uint32 `yaml:"auto-redirect-input-mark" json:"auto_redirect_input_mark,omitempty"`
|
||||
AutoRedirectOutputMark uint32 `yaml:"auto-redirect-output-mark" json:"auto_redirect_output_mark,omitempty"`
|
||||
StrictRoute bool `yaml:"strict-route" json:"strict_route,omitempty"`
|
||||
RouteAddress []netip.Prefix `yaml:"route-address" json:"route_address,omitempty"`
|
||||
RouteAddressSet []string `yaml:"route-address-set" json:"route_address_set,omitempty"`
|
||||
RouteExcludeAddress []netip.Prefix `yaml:"route-exclude-address" json:"route_exclude_address,omitempty"`
|
||||
RouteExcludeAddressSet []string `yaml:"route-exclude-address-set" json:"route_exclude_address_set,omitempty"`
|
||||
IncludeInterface []string `yaml:"include-interface" json:"include-interface,omitempty"`
|
||||
ExcludeInterface []string `yaml:"exclude-interface" json:"exclude-interface,omitempty"`
|
||||
IncludeUID []uint32 `yaml:"include-uid" json:"include_uid,omitempty"`
|
||||
IncludeUIDRange []string `yaml:"include-uid-range" json:"include_uid_range,omitempty"`
|
||||
ExcludeUID []uint32 `yaml:"exclude-uid" json:"exclude_uid,omitempty"`
|
||||
ExcludeUIDRange []string `yaml:"exclude-uid-range" json:"exclude_uid_range,omitempty"`
|
||||
IncludeAndroidUser []int `yaml:"include-android-user" json:"include_android_user,omitempty"`
|
||||
IncludePackage []string `yaml:"include-package" json:"include_package,omitempty"`
|
||||
ExcludePackage []string `yaml:"exclude-package" json:"exclude_package,omitempty"`
|
||||
EndpointIndependentNat bool `yaml:"endpoint-independent-nat" json:"endpoint_independent_nat,omitempty"`
|
||||
UDPTimeout int64 `yaml:"udp-timeout" json:"udp_timeout,omitempty"`
|
||||
FileDescriptor int `yaml:"file-descriptor" json:"file-descriptor"`
|
||||
|
||||
Inet4RouteAddress []netip.Prefix `yaml:"inet4-route-address" json:"inet4_route_address,omitempty"`
|
||||
Inet6RouteAddress []netip.Prefix `yaml:"inet6-route-address" json:"inet6_route_address,omitempty"`
|
||||
Inet4RouteExcludeAddress []netip.Prefix `yaml:"inet4-route-exclude-address" json:"inet4_route_exclude_address,omitempty"`
|
||||
Inet6RouteExcludeAddress []netip.Prefix `yaml:"inet6-route-exclude-address" json:"inet6_route_exclude_address,omitempty"`
|
||||
IncludeInterface []string `yaml:"include-interface" json:"include-interface,omitempty"`
|
||||
ExcludeInterface []string `yaml:"exclude-interface" json:"exclude-interface,omitempty"`
|
||||
IncludeUID []uint32 `yaml:"include-uid" json:"include_uid,omitempty"`
|
||||
IncludeUIDRange []string `yaml:"include-uid-range" json:"include_uid_range,omitempty"`
|
||||
ExcludeUID []uint32 `yaml:"exclude-uid" json:"exclude_uid,omitempty"`
|
||||
ExcludeUIDRange []string `yaml:"exclude-uid-range" json:"exclude_uid_range,omitempty"`
|
||||
IncludeAndroidUser []int `yaml:"include-android-user" json:"include_android_user,omitempty"`
|
||||
IncludePackage []string `yaml:"include-package" json:"include_package,omitempty"`
|
||||
ExcludePackage []string `yaml:"exclude-package" json:"exclude_package,omitempty"`
|
||||
EndpointIndependentNat bool `yaml:"endpoint-independent-nat" json:"endpoint_independent_nat,omitempty"`
|
||||
UDPTimeout int64 `yaml:"udp-timeout" json:"udp_timeout,omitempty"`
|
||||
FileDescriptor int `yaml:"file-descriptor" json:"file-descriptor"`
|
||||
TableIndex int `yaml:"table-index" json:"table-index"`
|
||||
}
|
||||
|
||||
type RawTuicServer struct {
|
||||
@@ -304,10 +319,12 @@ type RawConfig struct {
|
||||
LogLevel log.LogLevel `yaml:"log-level" json:"log-level"`
|
||||
IPv6 bool `yaml:"ipv6" json:"ipv6"`
|
||||
ExternalController string `yaml:"external-controller"`
|
||||
ExternalControllerUnix string `yaml:"external-controller-unix"`
|
||||
ExternalControllerTLS string `yaml:"external-controller-tls"`
|
||||
ExternalUI string `yaml:"external-ui"`
|
||||
ExternalUIURL string `yaml:"external-ui-url" json:"external-ui-url"`
|
||||
ExternalUIName string `yaml:"external-ui-name" json:"external-ui-name"`
|
||||
ExternalDohServer string `yaml:"external-doh-server"`
|
||||
Secret string `yaml:"secret"`
|
||||
Interface string `yaml:"interface-name"`
|
||||
RoutingMark int `yaml:"routing-mark"`
|
||||
@@ -413,7 +430,7 @@ func UnmarshalRawConfig(buf []byte) (*RawConfig, error) {
|
||||
ProxyGroup: []map[string]any{},
|
||||
TCPConcurrent: false,
|
||||
FindProcessMode: P.FindProcessStrict,
|
||||
GlobalUA: "clash.meta",
|
||||
GlobalUA: "clash.meta/" + C.Version,
|
||||
Tun: RawTun{
|
||||
Enable: false,
|
||||
Device: "",
|
||||
@@ -454,12 +471,13 @@ func UnmarshalRawConfig(buf []byte) (*RawConfig, error) {
|
||||
Interval: 30,
|
||||
},
|
||||
DNS: RawDNS{
|
||||
Enable: false,
|
||||
IPv6: false,
|
||||
UseHosts: true,
|
||||
IPv6Timeout: 100,
|
||||
EnhancedMode: C.DNSMapping,
|
||||
FakeIPRange: "198.18.0.1/16",
|
||||
Enable: false,
|
||||
IPv6: false,
|
||||
UseHosts: true,
|
||||
UseSystemHosts: true,
|
||||
IPv6Timeout: 100,
|
||||
EnhancedMode: C.DNSMapping,
|
||||
FakeIPRange: "198.18.0.1/16",
|
||||
FallbackFilter: RawFallbackFilter{
|
||||
GeoIP: true,
|
||||
GeoIPCode: "CN",
|
||||
@@ -489,7 +507,7 @@ func UnmarshalRawConfig(buf []byte) (*RawConfig, error) {
|
||||
},
|
||||
Sniffer: RawSniffer{
|
||||
Enable: false,
|
||||
Sniffing: []string{},
|
||||
Sniff: map[string]RawSniffingConfig{},
|
||||
ForceDomain: []string{},
|
||||
SkipDomain: []string{},
|
||||
Ports: []string{},
|
||||
@@ -557,13 +575,13 @@ func ParseRawConfig(rawCfg *RawConfig) (*Config, error) {
|
||||
}
|
||||
config.RuleProviders = ruleProviders
|
||||
|
||||
subRules, err := parseSubRules(rawCfg, proxies)
|
||||
subRules, err := parseSubRules(rawCfg, proxies, ruleProviders)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
config.SubRules = subRules
|
||||
|
||||
rules, err := parseRules(rawCfg.Rule, proxies, subRules, "rules")
|
||||
rules, err := parseRules(rawCfg.Rule, proxies, ruleProviders, subRules, "rules")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -635,31 +653,30 @@ func parseGeneral(cfg *RawConfig) (*General, error) {
|
||||
N.KeepAliveInterval = time.Duration(cfg.KeepAliveInterval) * time.Second
|
||||
}
|
||||
|
||||
ExternalUIPath = cfg.ExternalUI
|
||||
updater.ExternalUIPath = cfg.ExternalUI
|
||||
// checkout externalUI exist
|
||||
if ExternalUIPath != "" {
|
||||
ExternalUIPath = C.Path.Resolve(ExternalUIPath)
|
||||
if _, err := os.Stat(ExternalUIPath); os.IsNotExist(err) {
|
||||
if updater.ExternalUIPath != "" {
|
||||
updater.ExternalUIPath = C.Path.Resolve(updater.ExternalUIPath)
|
||||
if _, err := os.Stat(updater.ExternalUIPath); os.IsNotExist(err) {
|
||||
defaultUIpath := path.Join(C.Path.HomeDir(), "ui")
|
||||
log.Warnln("external-ui: %s does not exist, creating folder in %s", ExternalUIPath, defaultUIpath)
|
||||
log.Warnln("external-ui: %s does not exist, creating folder in %s", updater.ExternalUIPath, defaultUIpath)
|
||||
if err := os.MkdirAll(defaultUIpath, os.ModePerm); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ExternalUIPath = defaultUIpath
|
||||
updater.ExternalUIPath = defaultUIpath
|
||||
cfg.ExternalUI = defaultUIpath
|
||||
}
|
||||
}
|
||||
// checkout UIpath/name exist
|
||||
if cfg.ExternalUIName != "" {
|
||||
ExternalUIName = cfg.ExternalUIName
|
||||
updater.ExternalUIName = cfg.ExternalUIName
|
||||
} else {
|
||||
ExternalUIFolder = ExternalUIPath
|
||||
updater.ExternalUIFolder = updater.ExternalUIPath
|
||||
}
|
||||
if cfg.ExternalUIURL != "" {
|
||||
ExternalUIURL = cfg.ExternalUIURL
|
||||
updater.ExternalUIURL = cfg.ExternalUIURL
|
||||
}
|
||||
|
||||
cfg.Tun.RedirectToTun = cfg.EBpf.RedirectToTun
|
||||
return &General{
|
||||
Inbound: Inbound{
|
||||
Port: cfg.Port,
|
||||
@@ -678,10 +695,12 @@ func parseGeneral(cfg *RawConfig) (*General, error) {
|
||||
InboundMPTCP: cfg.InboundMPTCP,
|
||||
},
|
||||
Controller: Controller{
|
||||
ExternalController: cfg.ExternalController,
|
||||
ExternalUI: cfg.ExternalUI,
|
||||
Secret: cfg.Secret,
|
||||
ExternalControllerTLS: cfg.ExternalControllerTLS,
|
||||
ExternalController: cfg.ExternalController,
|
||||
ExternalUI: cfg.ExternalUI,
|
||||
Secret: cfg.Secret,
|
||||
ExternalControllerUnix: cfg.ExternalControllerUnix,
|
||||
ExternalControllerTLS: cfg.ExternalControllerTLS,
|
||||
ExternalDohServer: cfg.ExternalDohServer,
|
||||
},
|
||||
UnifiedDelay: cfg.UnifiedDelay,
|
||||
Mode: cfg.Mode,
|
||||
@@ -709,8 +728,11 @@ func parseProxies(cfg *RawConfig) (proxies map[string]C.Proxy, providersMap map[
|
||||
groupsConfig := cfg.ProxyGroup
|
||||
providersConfig := cfg.ProxyProvider
|
||||
|
||||
var proxyList []string
|
||||
var AllProxies []string
|
||||
var (
|
||||
proxyList []string
|
||||
AllProxies []string
|
||||
hasGlobal bool
|
||||
)
|
||||
proxiesList := list.New()
|
||||
groupsList := list.New()
|
||||
|
||||
@@ -743,6 +765,9 @@ func parseProxies(cfg *RawConfig) (proxies map[string]C.Proxy, providersMap map[
|
||||
if !existName {
|
||||
return nil, nil, fmt.Errorf("proxy group %d: missing name", idx)
|
||||
}
|
||||
if groupName == "GLOBAL" {
|
||||
hasGlobal = true
|
||||
}
|
||||
proxyList = append(proxyList, groupName)
|
||||
groupsList.PushBack(mapping)
|
||||
}
|
||||
@@ -768,6 +793,9 @@ func parseProxies(cfg *RawConfig) (proxies map[string]C.Proxy, providersMap map[
|
||||
AllProviders = append(AllProviders, name)
|
||||
}
|
||||
|
||||
slices.Sort(AllProxies)
|
||||
slices.Sort(AllProviders)
|
||||
|
||||
// parse proxy group
|
||||
for idx, mapping := range groupsConfig {
|
||||
group, err := outboundgroup.ParseProxyGroup(mapping, proxies, providersMap, AllProxies, AllProviders)
|
||||
@@ -794,13 +822,15 @@ func parseProxies(cfg *RawConfig) (proxies map[string]C.Proxy, providersMap map[
|
||||
pd, _ := provider.NewCompatibleProvider(provider.ReservedName, ps, hc)
|
||||
providersMap[provider.ReservedName] = pd
|
||||
|
||||
global := outboundgroup.NewSelector(
|
||||
&outboundgroup.GroupCommonOption{
|
||||
Name: "GLOBAL",
|
||||
},
|
||||
[]providerTypes.ProxyProvider{pd},
|
||||
)
|
||||
proxies["GLOBAL"] = adapter.NewProxy(global)
|
||||
if !hasGlobal {
|
||||
global := outboundgroup.NewSelector(
|
||||
&outboundgroup.GroupCommonOption{
|
||||
Name: "GLOBAL",
|
||||
},
|
||||
[]providerTypes.ProxyProvider{pd},
|
||||
)
|
||||
proxies["GLOBAL"] = adapter.NewProxy(global)
|
||||
}
|
||||
ProxiesList = proxiesList
|
||||
GroupsList = groupsList
|
||||
if ParsingProxiesCallback != nil {
|
||||
@@ -829,6 +859,7 @@ func parseListeners(cfg *RawConfig) (listeners map[string]C.InboundListener, err
|
||||
}
|
||||
|
||||
func parseRuleProviders(cfg *RawConfig) (ruleProviders map[string]providerTypes.RuleProvider, err error) {
|
||||
RP.SetTunnel(T.Tunnel)
|
||||
ruleProviders = map[string]providerTypes.RuleProvider{}
|
||||
// parse rule provider
|
||||
for name, mapping := range cfg.RuleProvider {
|
||||
@@ -838,12 +869,11 @@ func parseRuleProviders(cfg *RawConfig) (ruleProviders map[string]providerTypes.
|
||||
}
|
||||
|
||||
ruleProviders[name] = rp
|
||||
RP.SetRuleProvider(rp)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func parseSubRules(cfg *RawConfig, proxies map[string]C.Proxy) (subRules map[string][]C.Rule, err error) {
|
||||
func parseSubRules(cfg *RawConfig, proxies map[string]C.Proxy, ruleProviders map[string]providerTypes.RuleProvider) (subRules map[string][]C.Rule, err error) {
|
||||
subRules = map[string][]C.Rule{}
|
||||
for name := range cfg.SubRules {
|
||||
subRules[name] = make([]C.Rule, 0)
|
||||
@@ -853,7 +883,7 @@ func parseSubRules(cfg *RawConfig, proxies map[string]C.Proxy) (subRules map[str
|
||||
return nil, fmt.Errorf("sub-rule name is empty")
|
||||
}
|
||||
var rules []C.Rule
|
||||
rules, err = parseRules(rawRules, proxies, subRules, fmt.Sprintf("sub-rules[%s]", name))
|
||||
rules, err = parseRules(rawRules, proxies, ruleProviders, subRules, fmt.Sprintf("sub-rules[%s]", name))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -906,7 +936,7 @@ func verifySubRuleCircularReferences(n string, subRules map[string][]C.Rule, arr
|
||||
return nil
|
||||
}
|
||||
|
||||
func parseRules(rulesConfig []string, proxies map[string]C.Proxy, subRules map[string][]C.Rule, format string) ([]C.Rule, error) {
|
||||
func parseRules(rulesConfig []string, proxies map[string]C.Proxy, ruleProviders map[string]providerTypes.RuleProvider, subRules map[string][]C.Rule, format string) ([]C.Rule, error) {
|
||||
var rules []C.Rule
|
||||
|
||||
// parse rules
|
||||
@@ -921,7 +951,7 @@ func parseRules(rulesConfig []string, proxies map[string]C.Proxy, subRules map[s
|
||||
|
||||
l := len(rule)
|
||||
|
||||
if ruleName == "NOT" || ruleName == "OR" || ruleName == "AND" || ruleName == "SUB-RULE" || ruleName == "DOMAIN-REGEX" {
|
||||
if ruleName == "NOT" || ruleName == "OR" || ruleName == "AND" || ruleName == "SUB-RULE" || ruleName == "DOMAIN-REGEX" || ruleName == "PROCESS-NAME-REGEX" || ruleName == "PROCESS-PATH-REGEX" {
|
||||
target = rule[l-1]
|
||||
payload = strings.Join(rule[1:l-1], ",")
|
||||
} else {
|
||||
@@ -955,6 +985,12 @@ func parseRules(rulesConfig []string, proxies map[string]C.Proxy, subRules map[s
|
||||
return nil, fmt.Errorf("%s[%d] [%s] error: %s", format, idx, line, parseErr.Error())
|
||||
}
|
||||
|
||||
for _, name := range parsed.ProviderNames() {
|
||||
if _, ok := ruleProviders[name]; !ok {
|
||||
return nil, fmt.Errorf("%s[%d] [%s] error: rule set [%s] not found", format, idx, line, name)
|
||||
}
|
||||
}
|
||||
|
||||
rules = append(rules, parsed)
|
||||
}
|
||||
|
||||
@@ -1024,10 +1060,20 @@ func hostWithDefaultPort(host string, defPort string) (string, error) {
|
||||
return net.JoinHostPort(hostname, port), nil
|
||||
}
|
||||
|
||||
func parseNameServer(servers []string, preferH3 bool) ([]dns.NameServer, error) {
|
||||
func parseNameServer(servers []string, respectRules bool, preferH3 bool) ([]dns.NameServer, error) {
|
||||
var nameservers []dns.NameServer
|
||||
|
||||
for idx, server := range servers {
|
||||
if strings.HasPrefix(server, "dhcp://") {
|
||||
nameservers = append(
|
||||
nameservers,
|
||||
dns.NameServer{
|
||||
Net: "dhcp",
|
||||
Addr: server[len("dhcp://"):],
|
||||
},
|
||||
)
|
||||
continue
|
||||
}
|
||||
server = parsePureDNSServer(server)
|
||||
u, err := url.Parse(server)
|
||||
if err != nil {
|
||||
@@ -1048,13 +1094,16 @@ func parseNameServer(servers []string, preferH3 bool) ([]dns.NameServer, error)
|
||||
case "tls":
|
||||
addr, err = hostWithDefaultPort(u.Host, "853")
|
||||
dnsNetType = "tcp-tls" // DNS over TLS
|
||||
case "https":
|
||||
case "http", "https":
|
||||
addr, err = hostWithDefaultPort(u.Host, "443")
|
||||
dnsNetType = "https" // DNS over HTTPS
|
||||
if u.Scheme == "http" {
|
||||
addr, err = hostWithDefaultPort(u.Host, "80")
|
||||
}
|
||||
if err == nil {
|
||||
proxyName = ""
|
||||
clearURL := url.URL{Scheme: "https", Host: addr, Path: u.Path, User: u.User}
|
||||
clearURL := url.URL{Scheme: u.Scheme, Host: addr, Path: u.Path, User: u.User}
|
||||
addr = clearURL.String()
|
||||
dnsNetType = "https" // DNS over HTTPS
|
||||
if len(u.Fragment) != 0 {
|
||||
for _, s := range strings.Split(u.Fragment, "&") {
|
||||
arr := strings.Split(s, "=")
|
||||
@@ -1070,9 +1119,6 @@ func parseNameServer(servers []string, preferH3 bool) ([]dns.NameServer, error)
|
||||
}
|
||||
}
|
||||
}
|
||||
case "dhcp":
|
||||
addr = u.Host
|
||||
dnsNetType = "dhcp" // UDP from DHCP
|
||||
case "quic":
|
||||
addr, err = hostWithDefaultPort(u.Host, "853")
|
||||
dnsNetType = "quic" // DNS over QUIC
|
||||
@@ -1099,6 +1145,10 @@ func parseNameServer(servers []string, preferH3 bool) ([]dns.NameServer, error)
|
||||
return nil, fmt.Errorf("DNS NameServer[%d] format error: %s", idx, err.Error())
|
||||
}
|
||||
|
||||
if respectRules && len(proxyName) == 0 {
|
||||
proxyName = dns.RespectRules
|
||||
}
|
||||
|
||||
nameservers = append(
|
||||
nameservers,
|
||||
dns.NameServer{
|
||||
@@ -1115,7 +1165,7 @@ func parseNameServer(servers []string, preferH3 bool) ([]dns.NameServer, error)
|
||||
|
||||
func init() {
|
||||
dns.ParseNameServer = func(servers []string) ([]dns.NameServer, error) { // using by wireguard
|
||||
return parseNameServer(servers, false)
|
||||
return parseNameServer(servers, false, false)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1141,7 +1191,8 @@ func parsePureDNSServer(server string) string {
|
||||
}
|
||||
}
|
||||
}
|
||||
func parseNameServerPolicy(nsPolicy *orderedmap.OrderedMap[string, any], ruleProviders map[string]providerTypes.RuleProvider, preferH3 bool) (*orderedmap.OrderedMap[string, []dns.NameServer], error) {
|
||||
|
||||
func parseNameServerPolicy(nsPolicy *orderedmap.OrderedMap[string, any], ruleProviders map[string]providerTypes.RuleProvider, respectRules bool, preferH3 bool) (*orderedmap.OrderedMap[string, []dns.NameServer], error) {
|
||||
policy := orderedmap.New[string, []dns.NameServer]()
|
||||
updatedPolicy := orderedmap.New[string, any]()
|
||||
re := regexp.MustCompile(`[a-zA-Z0-9\-]+\.[a-zA-Z]{2,}(\.[a-zA-Z]{2,})?`)
|
||||
@@ -1187,7 +1238,7 @@ func parseNameServerPolicy(nsPolicy *orderedmap.OrderedMap[string, any], rulePro
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
nameservers, err := parseNameServer(servers, preferH3)
|
||||
nameservers, err := parseNameServer(servers, respectRules, preferH3)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -1281,39 +1332,44 @@ func parseDNS(rawCfg *RawConfig, hosts *trie.DomainTrie[resolver.HostValue], rul
|
||||
return nil, fmt.Errorf("if DNS configuration is turned on, NameServer cannot be empty")
|
||||
}
|
||||
|
||||
if cfg.RespectRules && len(cfg.ProxyServerNameserver) == 0 {
|
||||
return nil, fmt.Errorf("if “respect-rules” is turned on, “proxy-server-nameserver” cannot be empty")
|
||||
}
|
||||
|
||||
dnsCfg := &DNS{
|
||||
Enable: cfg.Enable,
|
||||
Listen: cfg.Listen,
|
||||
PreferH3: cfg.PreferH3,
|
||||
IPv6Timeout: cfg.IPv6Timeout,
|
||||
IPv6: cfg.IPv6,
|
||||
EnhancedMode: cfg.EnhancedMode,
|
||||
Enable: cfg.Enable,
|
||||
Listen: cfg.Listen,
|
||||
PreferH3: cfg.PreferH3,
|
||||
IPv6Timeout: cfg.IPv6Timeout,
|
||||
IPv6: cfg.IPv6,
|
||||
UseSystemHosts: cfg.UseSystemHosts,
|
||||
EnhancedMode: cfg.EnhancedMode,
|
||||
FallbackFilter: FallbackFilter{
|
||||
IPCIDR: []netip.Prefix{},
|
||||
GeoSite: []router.DomainMatcher{},
|
||||
},
|
||||
}
|
||||
var err error
|
||||
if dnsCfg.NameServer, err = parseNameServer(cfg.NameServer, cfg.PreferH3); err != nil {
|
||||
if dnsCfg.NameServer, err = parseNameServer(cfg.NameServer, cfg.RespectRules, cfg.PreferH3); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if dnsCfg.Fallback, err = parseNameServer(cfg.Fallback, cfg.PreferH3); err != nil {
|
||||
if dnsCfg.Fallback, err = parseNameServer(cfg.Fallback, cfg.RespectRules, cfg.PreferH3); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if dnsCfg.NameServerPolicy, err = parseNameServerPolicy(cfg.NameServerPolicy, ruleProviders, cfg.PreferH3); err != nil {
|
||||
if dnsCfg.NameServerPolicy, err = parseNameServerPolicy(cfg.NameServerPolicy, ruleProviders, cfg.RespectRules, cfg.PreferH3); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if dnsCfg.ProxyServerNameserver, err = parseNameServer(cfg.ProxyServerNameserver, cfg.PreferH3); err != nil {
|
||||
if dnsCfg.ProxyServerNameserver, err = parseNameServer(cfg.ProxyServerNameserver, false, cfg.PreferH3); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(cfg.DefaultNameserver) == 0 {
|
||||
return nil, errors.New("default nameserver should have at least one nameserver")
|
||||
}
|
||||
if dnsCfg.DefaultNameserver, err = parseNameServer(cfg.DefaultNameserver, cfg.PreferH3); err != nil {
|
||||
if dnsCfg.DefaultNameserver, err = parseNameServer(cfg.DefaultNameserver, false, cfg.PreferH3); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// check default nameserver is pure ip addr
|
||||
@@ -1430,31 +1486,39 @@ func parseTun(rawTun RawTun, general *General) error {
|
||||
DNSHijack: rawTun.DNSHijack,
|
||||
AutoRoute: rawTun.AutoRoute,
|
||||
AutoDetectInterface: rawTun.AutoDetectInterface,
|
||||
RedirectToTun: rawTun.RedirectToTun,
|
||||
|
||||
MTU: rawTun.MTU,
|
||||
GSO: rawTun.GSO,
|
||||
GSOMaxSize: rawTun.GSOMaxSize,
|
||||
Inet4Address: []netip.Prefix{tunAddressPrefix},
|
||||
Inet6Address: rawTun.Inet6Address,
|
||||
StrictRoute: rawTun.StrictRoute,
|
||||
MTU: rawTun.MTU,
|
||||
GSO: rawTun.GSO,
|
||||
GSOMaxSize: rawTun.GSOMaxSize,
|
||||
Inet4Address: []netip.Prefix{tunAddressPrefix},
|
||||
Inet6Address: rawTun.Inet6Address,
|
||||
IPRoute2TableIndex: rawTun.IPRoute2TableIndex,
|
||||
IPRoute2RuleIndex: rawTun.IPRoute2RuleIndex,
|
||||
AutoRedirect: rawTun.AutoRedirect,
|
||||
AutoRedirectInputMark: rawTun.AutoRedirectInputMark,
|
||||
AutoRedirectOutputMark: rawTun.AutoRedirectOutputMark,
|
||||
StrictRoute: rawTun.StrictRoute,
|
||||
RouteAddress: rawTun.RouteAddress,
|
||||
RouteAddressSet: rawTun.RouteAddressSet,
|
||||
RouteExcludeAddress: rawTun.RouteExcludeAddress,
|
||||
RouteExcludeAddressSet: rawTun.RouteExcludeAddressSet,
|
||||
IncludeInterface: rawTun.IncludeInterface,
|
||||
ExcludeInterface: rawTun.ExcludeInterface,
|
||||
IncludeUID: rawTun.IncludeUID,
|
||||
IncludeUIDRange: rawTun.IncludeUIDRange,
|
||||
ExcludeUID: rawTun.ExcludeUID,
|
||||
ExcludeUIDRange: rawTun.ExcludeUIDRange,
|
||||
IncludeAndroidUser: rawTun.IncludeAndroidUser,
|
||||
IncludePackage: rawTun.IncludePackage,
|
||||
ExcludePackage: rawTun.ExcludePackage,
|
||||
EndpointIndependentNat: rawTun.EndpointIndependentNat,
|
||||
UDPTimeout: rawTun.UDPTimeout,
|
||||
FileDescriptor: rawTun.FileDescriptor,
|
||||
|
||||
Inet4RouteAddress: rawTun.Inet4RouteAddress,
|
||||
Inet6RouteAddress: rawTun.Inet6RouteAddress,
|
||||
Inet4RouteExcludeAddress: rawTun.Inet4RouteExcludeAddress,
|
||||
Inet6RouteExcludeAddress: rawTun.Inet6RouteExcludeAddress,
|
||||
IncludeInterface: rawTun.IncludeInterface,
|
||||
ExcludeInterface: rawTun.ExcludeInterface,
|
||||
IncludeUID: rawTun.IncludeUID,
|
||||
IncludeUIDRange: rawTun.IncludeUIDRange,
|
||||
ExcludeUID: rawTun.ExcludeUID,
|
||||
ExcludeUIDRange: rawTun.ExcludeUIDRange,
|
||||
IncludeAndroidUser: rawTun.IncludeAndroidUser,
|
||||
IncludePackage: rawTun.IncludePackage,
|
||||
ExcludePackage: rawTun.ExcludePackage,
|
||||
EndpointIndependentNat: rawTun.EndpointIndependentNat,
|
||||
UDPTimeout: rawTun.UDPTimeout,
|
||||
FileDescriptor: rawTun.FileDescriptor,
|
||||
TableIndex: rawTun.TableIndex,
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -1512,7 +1576,7 @@ func parseSniffer(snifferRaw RawSniffer) (*Sniffer, error) {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if sniffer.Enable {
|
||||
if sniffer.Enable && len(snifferRaw.Sniffing) != 0 {
|
||||
// Deprecated: Use Sniff instead
|
||||
log.Warnln("Deprecated: Use Sniff instead")
|
||||
}
|
||||
|
||||
@@ -1,38 +1,15 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/netip"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/metacubex/mihomo/adapter/outboundgroup"
|
||||
"github.com/metacubex/mihomo/common/structure"
|
||||
mihomoHttp "github.com/metacubex/mihomo/component/http"
|
||||
C "github.com/metacubex/mihomo/constant"
|
||||
)
|
||||
|
||||
func downloadForBytes(url string) ([]byte, error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*90)
|
||||
defer cancel()
|
||||
resp, err := mihomoHttp.HttpRequest(ctx, url, http.MethodGet, http.Header{"User-Agent": {C.UA}}, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
return io.ReadAll(resp.Body)
|
||||
}
|
||||
|
||||
func saveFile(bytes []byte, path string) error {
|
||||
return os.WriteFile(path, bytes, 0o644)
|
||||
}
|
||||
|
||||
func trimArr(arr []string) (r []string) {
|
||||
for _, e := range arr {
|
||||
r = append(r, strings.Trim(e, " "))
|
||||
|
||||
5
constant/features/version.go
Normal file
5
constant/features/version.go
Normal file
@@ -0,0 +1,5 @@
|
||||
package features
|
||||
|
||||
var WindowsMajorVersion uint32
|
||||
var WindowsMinorVersion uint32
|
||||
var WindowsBuildNumber uint32
|
||||
10
constant/features/version_windows.go
Normal file
10
constant/features/version_windows.go
Normal file
@@ -0,0 +1,10 @@
|
||||
package features
|
||||
|
||||
import "golang.org/x/sys/windows"
|
||||
|
||||
func init() {
|
||||
version := windows.RtlGetVersion()
|
||||
WindowsMajorVersion = version.MajorVersion
|
||||
WindowsMinorVersion = version.MinorVersion
|
||||
WindowsBuildNumber = version.BuildNumber
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
package provider
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/metacubex/mihomo/common/utils"
|
||||
"github.com/metacubex/mihomo/constant"
|
||||
)
|
||||
@@ -31,6 +33,7 @@ func (v VehicleType) String() string {
|
||||
type Vehicle interface {
|
||||
Read() ([]byte, error)
|
||||
Path() string
|
||||
Proxy() string
|
||||
Type() VehicleType
|
||||
}
|
||||
|
||||
@@ -83,7 +86,7 @@ type RuleProvider interface {
|
||||
Match(*constant.Metadata) bool
|
||||
ShouldResolveIP() bool
|
||||
ShouldFindProcess() bool
|
||||
AsRule(adaptor string) constant.Rule
|
||||
Strategy() any
|
||||
}
|
||||
|
||||
// Rule Behavior
|
||||
@@ -109,9 +112,37 @@ func (rt RuleBehavior) String() string {
|
||||
}
|
||||
}
|
||||
|
||||
func (rt RuleBehavior) Byte() byte {
|
||||
switch rt {
|
||||
case Domain:
|
||||
return 0
|
||||
case IPCIDR:
|
||||
return 1
|
||||
case Classical:
|
||||
return 2
|
||||
default:
|
||||
return 255
|
||||
}
|
||||
}
|
||||
|
||||
func ParseBehavior(s string) (behavior RuleBehavior, err error) {
|
||||
switch s {
|
||||
case "domain":
|
||||
behavior = Domain
|
||||
case "ipcidr":
|
||||
behavior = IPCIDR
|
||||
case "classical":
|
||||
behavior = Classical
|
||||
default:
|
||||
err = fmt.Errorf("unsupported behavior type: %s", s)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
const (
|
||||
YamlRule RuleFormat = iota
|
||||
TextRule
|
||||
MrsRule
|
||||
)
|
||||
|
||||
type RuleFormat int
|
||||
@@ -122,7 +153,29 @@ func (rf RuleFormat) String() string {
|
||||
return "YamlRule"
|
||||
case TextRule:
|
||||
return "TextRule"
|
||||
case MrsRule:
|
||||
return "MrsRule"
|
||||
default:
|
||||
return "Unknown"
|
||||
}
|
||||
}
|
||||
|
||||
func ParseRuleFormat(s string) (format RuleFormat, err error) {
|
||||
switch s {
|
||||
case "", "yaml":
|
||||
format = YamlRule
|
||||
case "text":
|
||||
format = TextRule
|
||||
case "mrs":
|
||||
format = MrsRule
|
||||
default:
|
||||
err = fmt.Errorf("unsupported format type: %s", s)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
type Tunnel interface {
|
||||
Providers() map[string]ProxyProvider
|
||||
RuleProviders() map[string]RuleProvider
|
||||
RuleUpdateCallback() *utils.Callback[RuleProvider]
|
||||
}
|
||||
|
||||
@@ -8,8 +8,10 @@ const (
|
||||
DomainRegex
|
||||
GEOSITE
|
||||
GEOIP
|
||||
IPCIDR
|
||||
SrcGEOIP
|
||||
IPASN
|
||||
SrcIPASN
|
||||
IPCIDR
|
||||
SrcIPCIDR
|
||||
IPSuffix
|
||||
SrcIPSuffix
|
||||
@@ -20,8 +22,10 @@ const (
|
||||
InUser
|
||||
InName
|
||||
InType
|
||||
Process
|
||||
ProcessName
|
||||
ProcessPath
|
||||
ProcessNameRegex
|
||||
ProcessPathRegex
|
||||
RuleSet
|
||||
Network
|
||||
Uid
|
||||
@@ -48,10 +52,14 @@ func (rt RuleType) String() string {
|
||||
return "GeoSite"
|
||||
case GEOIP:
|
||||
return "GeoIP"
|
||||
case IPCIDR:
|
||||
return "IPCIDR"
|
||||
case SrcGEOIP:
|
||||
return "SrcGeoIP"
|
||||
case IPASN:
|
||||
return "IPASN"
|
||||
case SrcIPASN:
|
||||
return "SrcIPASN"
|
||||
case IPCIDR:
|
||||
return "IPCIDR"
|
||||
case SrcIPCIDR:
|
||||
return "SrcIPCIDR"
|
||||
case IPSuffix:
|
||||
@@ -70,10 +78,14 @@ func (rt RuleType) String() string {
|
||||
return "InName"
|
||||
case InType:
|
||||
return "InType"
|
||||
case Process:
|
||||
return "Process"
|
||||
case ProcessName:
|
||||
return "ProcessName"
|
||||
case ProcessPath:
|
||||
return "ProcessPath"
|
||||
case ProcessNameRegex:
|
||||
return "ProcessNameRegex"
|
||||
case ProcessPathRegex:
|
||||
return "ProcessPathRegex"
|
||||
case MATCH:
|
||||
return "Match"
|
||||
case RuleSet:
|
||||
@@ -104,4 +116,5 @@ type Rule interface {
|
||||
Payload() string
|
||||
ShouldResolveIP() bool
|
||||
ShouldFindProcess() bool
|
||||
ProviderNames() []string
|
||||
}
|
||||
|
||||
@@ -5,28 +5,20 @@ import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/netip"
|
||||
"strings"
|
||||
|
||||
"github.com/metacubex/mihomo/component/ca"
|
||||
"github.com/metacubex/mihomo/component/dialer"
|
||||
"github.com/metacubex/mihomo/component/resolver"
|
||||
C "github.com/metacubex/mihomo/constant"
|
||||
"github.com/metacubex/mihomo/log"
|
||||
|
||||
D "github.com/miekg/dns"
|
||||
"github.com/zhangyunhao116/fastrand"
|
||||
)
|
||||
|
||||
type client struct {
|
||||
*D.Client
|
||||
r *Resolver
|
||||
port string
|
||||
host string
|
||||
iface string
|
||||
proxyAdapter C.ProxyAdapter
|
||||
proxyName string
|
||||
addr string
|
||||
port string
|
||||
host string
|
||||
dialer *dnsDialer
|
||||
addr string
|
||||
}
|
||||
|
||||
var _ dnsClient = (*client)(nil)
|
||||
@@ -49,38 +41,13 @@ func (c *client) Address() string {
|
||||
}
|
||||
|
||||
func (c *client) ExchangeContext(ctx context.Context, m *D.Msg) (*D.Msg, error) {
|
||||
var (
|
||||
ip netip.Addr
|
||||
err error
|
||||
)
|
||||
if c.r == nil {
|
||||
// a default ip dns
|
||||
if ip, err = netip.ParseAddr(c.host); err != nil {
|
||||
return nil, fmt.Errorf("dns %s not a valid ip", c.host)
|
||||
}
|
||||
} else {
|
||||
ips, err := resolver.LookupIPWithResolver(ctx, c.host, c.r)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("use default dns resolve failed: %w", err)
|
||||
} else if len(ips) == 0 {
|
||||
return nil, fmt.Errorf("%w: %s", resolver.ErrIPNotFound, c.host)
|
||||
}
|
||||
ip = ips[fastrand.Intn(len(ips))]
|
||||
}
|
||||
|
||||
network := "udp"
|
||||
if strings.HasPrefix(c.Client.Net, "tcp") {
|
||||
network = "tcp"
|
||||
}
|
||||
|
||||
var options []dialer.Option
|
||||
if c.iface != "" {
|
||||
options = append(options, dialer.WithInterface(c.iface))
|
||||
}
|
||||
|
||||
dialHandler := getDialHandler(c.r, c.proxyAdapter, c.proxyName, options...)
|
||||
addr := net.JoinHostPort(ip.String(), c.port)
|
||||
conn, err := dialHandler(ctx, network, addr)
|
||||
addr := net.JoinHostPort(c.host, c.port)
|
||||
conn, err := c.dialer.DialContext(ctx, network, addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -115,7 +82,7 @@ func (c *client) ExchangeContext(ctx context.Context, m *D.Msg) (*D.Msg, error)
|
||||
tcpClient.Net = "tcp"
|
||||
network = "tcp"
|
||||
log.Debugln("[DNS] Truncated reply from %s:%s for %s over UDP, retrying over TCP", c.host, c.port, m.Question[0].String())
|
||||
dConn.Conn, err = dialHandler(ctx, network, addr)
|
||||
dConn.Conn, err = c.dialer.DialContext(ctx, network, addr)
|
||||
if err != nil {
|
||||
ch <- result{msg, err}
|
||||
return
|
||||
|
||||
11
dns/dialer.go
Normal file
11
dns/dialer.go
Normal file
@@ -0,0 +1,11 @@
|
||||
package dns
|
||||
|
||||
// export functions from tunnel module
|
||||
|
||||
import "github.com/metacubex/mihomo/tunnel"
|
||||
|
||||
const RespectRules = tunnel.DnsRespectRules
|
||||
|
||||
type dnsDialer = tunnel.DNSDialer
|
||||
|
||||
var newDNSDialer = tunnel.NewDNSDialer
|
||||
109
dns/doh.go
109
dns/doh.go
@@ -61,12 +61,12 @@ type dnsOverHTTPS struct {
|
||||
// for this upstream.
|
||||
quicConfig *quic.Config
|
||||
quicConfigGuard sync.Mutex
|
||||
url *url.URL
|
||||
r *Resolver
|
||||
httpVersions []C.HTTPVersion
|
||||
proxyAdapter C.ProxyAdapter
|
||||
proxyName string
|
||||
addr string
|
||||
|
||||
url *url.URL
|
||||
httpVersions []C.HTTPVersion
|
||||
dialer *dnsDialer
|
||||
addr string
|
||||
skipCertVerify bool
|
||||
}
|
||||
|
||||
// type check
|
||||
@@ -85,11 +85,9 @@ func newDoHClient(urlString string, r *Resolver, preferH3 bool, params map[strin
|
||||
}
|
||||
|
||||
doh := &dnsOverHTTPS{
|
||||
url: u,
|
||||
addr: u.String(),
|
||||
r: r,
|
||||
proxyAdapter: proxyAdapter,
|
||||
proxyName: proxyName,
|
||||
url: u,
|
||||
addr: u.String(),
|
||||
dialer: newDNSDialer(r, proxyAdapter, proxyName),
|
||||
quicConfig: &quic.Config{
|
||||
KeepAlivePeriod: QUICKeepAlivePeriod,
|
||||
TokenStore: newQUICTokenStore(),
|
||||
@@ -97,6 +95,10 @@ func newDoHClient(urlString string, r *Resolver, preferH3 bool, params map[strin
|
||||
httpVersions: httpVersions,
|
||||
}
|
||||
|
||||
if params["skip-cert-verify"] == "true" {
|
||||
doh.skipCertVerify = true
|
||||
}
|
||||
|
||||
runtime.SetFinalizer(doh, (*dnsOverHTTPS).Close)
|
||||
|
||||
return doh
|
||||
@@ -106,6 +108,7 @@ func newDoHClient(urlString string, r *Resolver, preferH3 bool, params map[strin
|
||||
func (doh *dnsOverHTTPS) Address() string {
|
||||
return doh.addr
|
||||
}
|
||||
|
||||
func (doh *dnsOverHTTPS) ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg, err error) {
|
||||
// Quote from https://www.rfc-editor.org/rfc/rfc8484.html:
|
||||
// In order to maximize HTTP cache friendliness, DoH clients using media
|
||||
@@ -182,19 +185,9 @@ func (doh *dnsOverHTTPS) closeClient(client *http.Client) (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// exchangeHTTPS logs the request and its result and calls exchangeHTTPSClient.
|
||||
func (doh *dnsOverHTTPS) exchangeHTTPS(ctx context.Context, client *http.Client, req *D.Msg) (resp *D.Msg, err error) {
|
||||
resp, err = doh.exchangeHTTPSClient(ctx, client, req)
|
||||
return resp, err
|
||||
}
|
||||
|
||||
// exchangeHTTPSClient sends the DNS query to a DoH resolver using the specified
|
||||
// exchangeHTTPS sends the DNS query to a DoH resolver using the specified
|
||||
// http.Client instance.
|
||||
func (doh *dnsOverHTTPS) exchangeHTTPSClient(
|
||||
ctx context.Context,
|
||||
client *http.Client,
|
||||
req *D.Msg,
|
||||
) (resp *D.Msg, err error) {
|
||||
func (doh *dnsOverHTTPS) exchangeHTTPS(ctx context.Context, client *http.Client, req *D.Msg) (resp *D.Msg, err error) {
|
||||
buf, err := req.Pack()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("packing message: %w", err)
|
||||
@@ -208,24 +201,24 @@ func (doh *dnsOverHTTPS) exchangeHTTPSClient(
|
||||
method = http3.MethodGet0RTT
|
||||
}
|
||||
|
||||
url := doh.url
|
||||
url.RawQuery = fmt.Sprintf("dns=%s", base64.RawURLEncoding.EncodeToString(buf))
|
||||
httpReq, err := http.NewRequestWithContext(ctx, method, url.String(), nil)
|
||||
requestUrl := *doh.url // don't modify origin url
|
||||
requestUrl.RawQuery = fmt.Sprintf("dns=%s", base64.RawURLEncoding.EncodeToString(buf))
|
||||
httpReq, err := http.NewRequestWithContext(ctx, method, requestUrl.String(), nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("creating http request to %s: %w", url, err)
|
||||
return nil, fmt.Errorf("creating http request to %s: %w", doh.url, err)
|
||||
}
|
||||
|
||||
httpReq.Header.Set("Accept", "application/dns-message")
|
||||
httpReq.Header.Set("User-Agent", "")
|
||||
httpResp, err := client.Do(httpReq)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("requesting %s: %w", url, err)
|
||||
return nil, fmt.Errorf("requesting %s: %w", doh.url, err)
|
||||
}
|
||||
defer httpResp.Body.Close()
|
||||
|
||||
body, err := io.ReadAll(httpResp.Body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("reading %s: %w", url, err)
|
||||
return nil, fmt.Errorf("reading %s: %w", doh.url, err)
|
||||
}
|
||||
|
||||
if httpResp.StatusCode != http.StatusOK {
|
||||
@@ -234,7 +227,7 @@ func (doh *dnsOverHTTPS) exchangeHTTPSClient(
|
||||
"expected status %d, got %d from %s",
|
||||
http.StatusOK,
|
||||
httpResp.StatusCode,
|
||||
url,
|
||||
doh.url,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -243,7 +236,7 @@ func (doh *dnsOverHTTPS) exchangeHTTPSClient(
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(
|
||||
"unpacking response from %s: body is %s: %w",
|
||||
url,
|
||||
doh.url,
|
||||
body,
|
||||
err,
|
||||
)
|
||||
@@ -377,9 +370,21 @@ func (doh *dnsOverHTTPS) createClient(ctx context.Context) (*http.Client, error)
|
||||
// HTTP3 is enabled in the upstream options). If this attempt is successful,
|
||||
// it returns an HTTP3 transport, otherwise it returns the H1/H2 transport.
|
||||
func (doh *dnsOverHTTPS) createTransport(ctx context.Context) (t http.RoundTripper, err error) {
|
||||
transport := &http.Transport{
|
||||
DisableCompression: true,
|
||||
DialContext: doh.dialer.DialContext,
|
||||
IdleConnTimeout: transportDefaultIdleConnTimeout,
|
||||
MaxConnsPerHost: dohMaxConnsPerHost,
|
||||
MaxIdleConns: dohMaxIdleConns,
|
||||
}
|
||||
|
||||
if doh.url.Scheme == "http" {
|
||||
return transport, nil
|
||||
}
|
||||
|
||||
tlsConfig := ca.GetGlobalTLSConfig(
|
||||
&tls.Config{
|
||||
InsecureSkipVerify: false,
|
||||
InsecureSkipVerify: doh.skipCertVerify,
|
||||
MinVersion: tls.VersionTLS12,
|
||||
SessionTicketsDisabled: false,
|
||||
})
|
||||
@@ -388,13 +393,13 @@ func (doh *dnsOverHTTPS) createTransport(ctx context.Context) (t http.RoundTripp
|
||||
nextProtos = append(nextProtos, string(v))
|
||||
}
|
||||
tlsConfig.NextProtos = nextProtos
|
||||
dialContext := getDialHandler(doh.r, doh.proxyAdapter, doh.proxyName)
|
||||
transport.TLSClientConfig = tlsConfig
|
||||
|
||||
if slices.Contains(doh.httpVersions, C.HTTPVersion3) {
|
||||
// First, we attempt to create an HTTP3 transport. If the probe QUIC
|
||||
// connection is established successfully, we'll be using HTTP3 for this
|
||||
// upstream.
|
||||
transportH3, err := doh.createTransportH3(ctx, tlsConfig, dialContext)
|
||||
transportH3, err := doh.createTransportH3(ctx, tlsConfig)
|
||||
if err == nil {
|
||||
log.Debugln("[%s] using HTTP/3 for this upstream: QUIC was faster", doh.url.String())
|
||||
return transportH3, nil
|
||||
@@ -407,18 +412,10 @@ func (doh *dnsOverHTTPS) createTransport(ctx context.Context) (t http.RoundTripp
|
||||
return nil, errors.New("HTTP1/1 and HTTP2 are not supported by this upstream")
|
||||
}
|
||||
|
||||
transport := &http.Transport{
|
||||
TLSClientConfig: tlsConfig,
|
||||
DisableCompression: true,
|
||||
DialContext: dialContext,
|
||||
IdleConnTimeout: transportDefaultIdleConnTimeout,
|
||||
MaxConnsPerHost: dohMaxConnsPerHost,
|
||||
MaxIdleConns: dohMaxIdleConns,
|
||||
// Since we have a custom DialContext, we need to use this field to
|
||||
// make golang http.Client attempt to use HTTP/2. Otherwise, it would
|
||||
// only be used when negotiated on the TLS level.
|
||||
ForceAttemptHTTP2: true,
|
||||
}
|
||||
// Since we have a custom DialContext, we need to use this field to
|
||||
// make golang http.Client attempt to use HTTP/2. Otherwise, it would
|
||||
// only be used when negotiated on the TLS level.
|
||||
transport.ForceAttemptHTTP2 = true
|
||||
|
||||
// Explicitly configure transport to use HTTP/2.
|
||||
//
|
||||
@@ -490,13 +487,12 @@ func (h *http3Transport) Close() (err error) {
|
||||
func (doh *dnsOverHTTPS) createTransportH3(
|
||||
ctx context.Context,
|
||||
tlsConfig *tls.Config,
|
||||
dialContext dialHandler,
|
||||
) (roundTripper http.RoundTripper, err error) {
|
||||
if !doh.supportsH3() {
|
||||
return nil, errors.New("HTTP3 support is not enabled")
|
||||
}
|
||||
|
||||
addr, err := doh.probeH3(ctx, tlsConfig, dialContext)
|
||||
addr, err := doh.probeH3(ctx, tlsConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -515,7 +511,7 @@ func (doh *dnsOverHTTPS) createTransportH3(
|
||||
},
|
||||
DisableCompression: true,
|
||||
TLSClientConfig: tlsConfig,
|
||||
QuicConfig: doh.getQUICConfig(),
|
||||
QUICConfig: doh.getQUICConfig(),
|
||||
}
|
||||
|
||||
return &http3Transport{baseTransport: rt}, nil
|
||||
@@ -534,7 +530,7 @@ func (doh *dnsOverHTTPS) dialQuic(ctx context.Context, addr string, tlsCfg *tls.
|
||||
IP: net.ParseIP(ip),
|
||||
Port: portInt,
|
||||
}
|
||||
conn, err := listenPacket(ctx, doh.proxyAdapter, doh.proxyName, "udp", addr, doh.r)
|
||||
conn, err := doh.dialer.ListenPacket(ctx, "udp", addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -557,12 +553,11 @@ func (doh *dnsOverHTTPS) dialQuic(ctx context.Context, addr string, tlsCfg *tls.
|
||||
func (doh *dnsOverHTTPS) probeH3(
|
||||
ctx context.Context,
|
||||
tlsConfig *tls.Config,
|
||||
dialContext dialHandler,
|
||||
) (addr string, err error) {
|
||||
// We're using bootstrapped address instead of what's passed to the function
|
||||
// it does not create an actual connection, but it helps us determine
|
||||
// what IP is actually reachable (when there are v4/v6 addresses).
|
||||
rawConn, err := dialContext(ctx, "udp", doh.url.Host)
|
||||
rawConn, err := doh.dialer.DialContext(ctx, "udp", doh.url.Host)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to dial: %w", err)
|
||||
}
|
||||
@@ -592,7 +587,7 @@ func (doh *dnsOverHTTPS) probeH3(
|
||||
chQuic := make(chan error, 1)
|
||||
chTLS := make(chan error, 1)
|
||||
go doh.probeQUIC(ctx, addr, probeTLSCfg, chQuic)
|
||||
go doh.probeTLS(ctx, dialContext, probeTLSCfg, chTLS)
|
||||
go doh.probeTLS(ctx, probeTLSCfg, chTLS)
|
||||
|
||||
select {
|
||||
case quicErr := <-chQuic:
|
||||
@@ -635,10 +630,10 @@ func (doh *dnsOverHTTPS) probeQUIC(ctx context.Context, addr string, tlsConfig *
|
||||
|
||||
// probeTLS attempts to establish a TLS connection to the specified address. We
|
||||
// run probeQUIC and probeTLS in parallel and see which one is faster.
|
||||
func (doh *dnsOverHTTPS) probeTLS(ctx context.Context, dialContext dialHandler, tlsConfig *tls.Config, ch chan error) {
|
||||
func (doh *dnsOverHTTPS) probeTLS(ctx context.Context, tlsConfig *tls.Config, ch chan error) {
|
||||
startTime := time.Now()
|
||||
|
||||
conn, err := doh.tlsDial(ctx, dialContext, "tcp", tlsConfig)
|
||||
conn, err := doh.tlsDial(ctx, "tcp", tlsConfig)
|
||||
if err != nil {
|
||||
ch <- fmt.Errorf("opening TLS connection: %w", err)
|
||||
return
|
||||
@@ -694,10 +689,10 @@ func isHTTP3(client *http.Client) (ok bool) {
|
||||
|
||||
// tlsDial is basically the same as tls.DialWithDialer, but we will call our own
|
||||
// dialContext function to get connection.
|
||||
func (doh *dnsOverHTTPS) tlsDial(ctx context.Context, dialContext dialHandler, network string, config *tls.Config) (*tls.Conn, error) {
|
||||
func (doh *dnsOverHTTPS) tlsDial(ctx context.Context, network string, config *tls.Config) (*tls.Conn, error) {
|
||||
// We're using bootstrapped address instead of what's passed
|
||||
// to the function.
|
||||
rawConn, err := dialContext(ctx, network, doh.url.Host)
|
||||
rawConn, err := doh.dialer.DialContext(ctx, network, doh.url.Host)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
16
dns/doq.go
16
dns/doq.go
@@ -60,10 +60,8 @@ type dnsOverQUIC struct {
|
||||
bytesPool *sync.Pool
|
||||
bytesPoolGuard sync.Mutex
|
||||
|
||||
addr string
|
||||
proxyAdapter C.ProxyAdapter
|
||||
proxyName string
|
||||
r *Resolver
|
||||
addr string
|
||||
dialer *dnsDialer
|
||||
}
|
||||
|
||||
// type check
|
||||
@@ -72,10 +70,8 @@ var _ dnsClient = (*dnsOverQUIC)(nil)
|
||||
// newDoQ returns the DNS-over-QUIC Upstream.
|
||||
func newDoQ(resolver *Resolver, addr string, proxyAdapter C.ProxyAdapter, proxyName string) (dnsClient, error) {
|
||||
doq := &dnsOverQUIC{
|
||||
addr: addr,
|
||||
proxyAdapter: proxyAdapter,
|
||||
proxyName: proxyName,
|
||||
r: resolver,
|
||||
addr: addr,
|
||||
dialer: newDNSDialer(resolver, proxyAdapter, proxyName),
|
||||
quicConfig: &quic.Config{
|
||||
KeepAlivePeriod: QUICKeepAlivePeriod,
|
||||
TokenStore: newQUICTokenStore(),
|
||||
@@ -300,7 +296,7 @@ func (doq *dnsOverQUIC) openConnection(ctx context.Context) (conn quic.Connectio
|
||||
// we're using bootstrapped address instead of what's passed to the function
|
||||
// it does not create an actual connection, but it helps us determine
|
||||
// what IP is actually reachable (when there're v4/v6 addresses).
|
||||
rawConn, err := getDialHandler(doq.r, doq.proxyAdapter, doq.proxyName)(ctx, "udp", doq.addr)
|
||||
rawConn, err := doq.dialer.DialContext(ctx, "udp", doq.addr)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to open a QUIC connection: %w", err)
|
||||
}
|
||||
@@ -315,7 +311,7 @@ func (doq *dnsOverQUIC) openConnection(ctx context.Context) (conn quic.Connectio
|
||||
|
||||
p, err := strconv.Atoi(port)
|
||||
udpAddr := net.UDPAddr{IP: net.ParseIP(ip), Port: p}
|
||||
udp, err := listenPacket(ctx, doq.proxyAdapter, doq.proxyName, "udp", addr, doq.r)
|
||||
udp, err := doq.dialer.ListenPacket(ctx, "udp", addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -37,14 +37,17 @@ func (p geositePolicy) Match(domain string) []dnsClient {
|
||||
}
|
||||
|
||||
type domainSetPolicy struct {
|
||||
domainSetProvider provider.RuleProvider
|
||||
dnsClients []dnsClient
|
||||
tunnel provider.Tunnel
|
||||
name string
|
||||
dnsClients []dnsClient
|
||||
}
|
||||
|
||||
func (p domainSetPolicy) Match(domain string) []dnsClient {
|
||||
metadata := &C.Metadata{Host: domain}
|
||||
if ok := p.domainSetProvider.Match(metadata); ok {
|
||||
return p.dnsClients
|
||||
if ruleProvider, ok := p.tunnel.RuleProviders()[p.name]; ok {
|
||||
metadata := &C.Metadata{Host: domain}
|
||||
if ok := ruleProvider.Match(metadata); ok {
|
||||
return p.dnsClients
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -146,9 +146,12 @@ func (r *Resolver) ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg, e
|
||||
}()
|
||||
|
||||
q := m.Question[0]
|
||||
domain := msgToDomain(m)
|
||||
_, qTypeStr := msgToQtype(m)
|
||||
cacheM, expireTime, hit := r.cache.GetWithExpire(q.String())
|
||||
if hit {
|
||||
log.Debugln("[DNS] cache hit for %s, expire at %s", q.Name, expireTime.Format("2006-01-02 15:04:05"))
|
||||
ips := msgToIP(cacheM)
|
||||
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()
|
||||
msg = cacheM.Copy()
|
||||
if expireTime.Before(now) {
|
||||
@@ -414,7 +417,7 @@ type Config struct {
|
||||
Pool *fakeip.Pool
|
||||
Hosts *trie.DomainTrie[resolver.HostValue]
|
||||
Policy *orderedmap.OrderedMap[string, []NameServer]
|
||||
RuleProviders map[string]provider.RuleProvider
|
||||
Tunnel provider.Tunnel
|
||||
CacheAlgorithm string
|
||||
}
|
||||
|
||||
@@ -502,11 +505,12 @@ func NewResolver(config Config) *Resolver {
|
||||
key := temp[1]
|
||||
switch prefix {
|
||||
case "rule-set":
|
||||
if p, ok := config.RuleProviders[key]; ok {
|
||||
if _, ok := config.Tunnel.RuleProviders()[key]; ok {
|
||||
log.Debugln("Adding rule-set policy: %s ", key)
|
||||
insertPolicy(domainSetPolicy{
|
||||
domainSetProvider: p,
|
||||
dnsClients: cacheTransform(nameserver),
|
||||
tunnel: config.Tunnel,
|
||||
name: key,
|
||||
dnsClients: cacheTransform(nameserver),
|
||||
})
|
||||
continue
|
||||
} else {
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/metacubex/mihomo/component/resolver"
|
||||
"github.com/metacubex/mihomo/log"
|
||||
|
||||
D "github.com/miekg/dns"
|
||||
@@ -39,6 +40,9 @@ func (c *systemClient) getDnsClients() ([]dnsClient, error) {
|
||||
if nameservers, err = dnsReadConfig(); err == nil {
|
||||
log.Debugln("[DNS] system dns update to %s", nameservers)
|
||||
for _, addr := range nameservers {
|
||||
if resolver.IsSystemDnsBlacklisted(addr) {
|
||||
continue
|
||||
}
|
||||
if _, ok := c.dnsClients[addr]; !ok {
|
||||
clients := transform(
|
||||
[]NameServer{{
|
||||
|
||||
163
dns/util.go
163
dns/util.go
@@ -7,18 +7,14 @@ import (
|
||||
"fmt"
|
||||
"net"
|
||||
"net/netip"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
N "github.com/metacubex/mihomo/common/net"
|
||||
"github.com/metacubex/mihomo/common/nnip"
|
||||
"github.com/metacubex/mihomo/common/picker"
|
||||
"github.com/metacubex/mihomo/component/dialer"
|
||||
"github.com/metacubex/mihomo/component/resolver"
|
||||
C "github.com/metacubex/mihomo/constant"
|
||||
"github.com/metacubex/mihomo/log"
|
||||
"github.com/metacubex/mihomo/tunnel"
|
||||
|
||||
D "github.com/miekg/dns"
|
||||
"github.com/samber/lo"
|
||||
@@ -120,6 +116,11 @@ func transform(servers []NameServer, resolver *Resolver) []dnsClient {
|
||||
continue
|
||||
}
|
||||
|
||||
var options []dialer.Option
|
||||
if s.Interface != "" {
|
||||
options = append(options, dialer.WithInterface(s.Interface))
|
||||
}
|
||||
|
||||
host, port, _ := net.SplitHostPort(s.Addr)
|
||||
ret = append(ret, &client{
|
||||
Client: &D.Client{
|
||||
@@ -130,12 +131,9 @@ func transform(servers []NameServer, resolver *Resolver) []dnsClient {
|
||||
UDPSize: 4096,
|
||||
Timeout: 5 * time.Second,
|
||||
},
|
||||
port: port,
|
||||
host: host,
|
||||
iface: s.Interface,
|
||||
r: resolver,
|
||||
proxyAdapter: s.ProxyAdapter,
|
||||
proxyName: s.ProxyName,
|
||||
port: port,
|
||||
host: host,
|
||||
dialer: newDNSDialer(resolver, s.ProxyAdapter, s.ProxyName, options...),
|
||||
})
|
||||
}
|
||||
return ret
|
||||
@@ -175,118 +173,12 @@ func msgToDomain(msg *D.Msg) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
type dialHandler func(ctx context.Context, network, addr string) (net.Conn, error)
|
||||
|
||||
func getDialHandler(r *Resolver, proxyAdapter C.ProxyAdapter, proxyName string, opts ...dialer.Option) dialHandler {
|
||||
return func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||
if len(proxyName) == 0 && proxyAdapter == nil {
|
||||
opts = append(opts, dialer.WithResolver(r))
|
||||
return dialer.DialContext(ctx, network, addr, opts...)
|
||||
} else {
|
||||
host, port, err := net.SplitHostPort(addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
uintPort, err := strconv.ParseUint(port, 10, 16)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if proxyAdapter == nil {
|
||||
var ok bool
|
||||
proxyAdapter, ok = tunnel.Proxies()[proxyName]
|
||||
if !ok {
|
||||
opts = append(opts, dialer.WithInterface(proxyName))
|
||||
}
|
||||
}
|
||||
|
||||
if strings.Contains(network, "tcp") {
|
||||
// tcp can resolve host by remote
|
||||
metadata := &C.Metadata{
|
||||
NetWork: C.TCP,
|
||||
Host: host,
|
||||
DstPort: uint16(uintPort),
|
||||
}
|
||||
if proxyAdapter != nil {
|
||||
if proxyAdapter.IsL3Protocol(metadata) { // L3 proxy should resolve domain before to avoid loopback
|
||||
dstIP, err := resolver.ResolveIPWithResolver(ctx, host, r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
metadata.Host = ""
|
||||
metadata.DstIP = dstIP
|
||||
}
|
||||
return proxyAdapter.DialContext(ctx, metadata, opts...)
|
||||
}
|
||||
opts = append(opts, dialer.WithResolver(r))
|
||||
return dialer.DialContext(ctx, network, addr, opts...)
|
||||
} else {
|
||||
// udp must resolve host first
|
||||
dstIP, err := resolver.ResolveIPWithResolver(ctx, host, r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
metadata := &C.Metadata{
|
||||
NetWork: C.UDP,
|
||||
Host: "",
|
||||
DstIP: dstIP,
|
||||
DstPort: uint16(uintPort),
|
||||
}
|
||||
if proxyAdapter == nil {
|
||||
return dialer.DialContext(ctx, network, addr, opts...)
|
||||
}
|
||||
|
||||
if !proxyAdapter.SupportUDP() {
|
||||
return nil, fmt.Errorf("proxy adapter [%s] UDP is not supported", proxyAdapter)
|
||||
}
|
||||
|
||||
packetConn, err := proxyAdapter.ListenPacketContext(ctx, metadata, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return N.NewBindPacketConn(packetConn, metadata.UDPAddr()), nil
|
||||
}
|
||||
}
|
||||
func msgToQtype(msg *D.Msg) (uint16, string) {
|
||||
if len(msg.Question) > 0 {
|
||||
qType := msg.Question[0].Qtype
|
||||
return qType, D.Type(qType).String()
|
||||
}
|
||||
}
|
||||
|
||||
func listenPacket(ctx context.Context, proxyAdapter C.ProxyAdapter, proxyName string, network string, addr string, r *Resolver, opts ...dialer.Option) (net.PacketConn, error) {
|
||||
host, port, err := net.SplitHostPort(addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
uintPort, err := strconv.ParseUint(port, 10, 16)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if proxyAdapter == nil {
|
||||
var ok bool
|
||||
proxyAdapter, ok = tunnel.Proxies()[proxyName]
|
||||
if !ok {
|
||||
opts = append(opts, dialer.WithInterface(proxyName))
|
||||
}
|
||||
}
|
||||
|
||||
// udp must resolve host first
|
||||
dstIP, err := resolver.ResolveIPWithResolver(ctx, host, r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
metadata := &C.Metadata{
|
||||
NetWork: C.UDP,
|
||||
Host: "",
|
||||
DstIP: dstIP,
|
||||
DstPort: uint16(uintPort),
|
||||
}
|
||||
if proxyAdapter == nil {
|
||||
return dialer.NewDialer(opts...).ListenPacket(ctx, network, "", netip.AddrPortFrom(metadata.DstIP, metadata.DstPort))
|
||||
}
|
||||
|
||||
if !proxyAdapter.SupportUDP() {
|
||||
return nil, fmt.Errorf("proxy adapter [%s] UDP is not supported", proxyAdapter)
|
||||
}
|
||||
|
||||
return proxyAdapter.ListenPacketContext(ctx, metadata, opts...)
|
||||
return 0, ""
|
||||
}
|
||||
|
||||
func batchExchange(ctx context.Context, clients []dnsClient, m *D.Msg) (msg *D.Msg, cache bool, err error) {
|
||||
@@ -294,6 +186,7 @@ func batchExchange(ctx context.Context, clients []dnsClient, m *D.Msg) (msg *D.M
|
||||
fast, ctx := picker.WithTimeout[*D.Msg](ctx, resolver.DefaultDNSTimeout)
|
||||
defer fast.Close()
|
||||
domain := msgToDomain(m)
|
||||
qType, qTypeStr := msgToQtype(m)
|
||||
var noIpMsg *D.Msg
|
||||
for _, client := range clients {
|
||||
if _, isRCodeClient := client.(rcodeClient); isRCodeClient {
|
||||
@@ -302,7 +195,7 @@ func batchExchange(ctx context.Context, clients []dnsClient, m *D.Msg) (msg *D.M
|
||||
}
|
||||
client := client // shadow define client to ensure the value captured by the closure will not be changed in the next loop
|
||||
fast.Go(func() (*D.Msg, error) {
|
||||
log.Debugln("[DNS] resolve %s from %s", domain, client.Address())
|
||||
log.Debugln("[DNS] resolve %s %s from %s", domain, qTypeStr, client.Address())
|
||||
m, err := client.ExchangeContext(ctx, m)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -311,20 +204,18 @@ func batchExchange(ctx context.Context, clients []dnsClient, m *D.Msg) (msg *D.M
|
||||
// so we would ignore RCode errors from RCode clients.
|
||||
return nil, errors.New("server failure: " + D.RcodeToString[m.Rcode])
|
||||
}
|
||||
if ips := msgToIP(m); len(m.Question) > 0 {
|
||||
qType := m.Question[0].Qtype
|
||||
log.Debugln("[DNS] %s --> %s %s from %s", domain, ips, D.Type(qType), client.Address())
|
||||
switch qType {
|
||||
case D.TypeAAAA:
|
||||
if len(ips) == 0 {
|
||||
noIpMsg = m
|
||||
return nil, resolver.ErrIPNotFound
|
||||
}
|
||||
case D.TypeA:
|
||||
if len(ips) == 0 {
|
||||
noIpMsg = m
|
||||
return nil, resolver.ErrIPNotFound
|
||||
}
|
||||
ips := msgToIP(m)
|
||||
log.Debugln("[DNS] %s --> %s %s from %s", domain, ips, qTypeStr, client.Address())
|
||||
switch qType {
|
||||
case D.TypeAAAA:
|
||||
if len(ips) == 0 {
|
||||
noIpMsg = m
|
||||
return nil, resolver.ErrIPNotFound
|
||||
}
|
||||
case D.TypeA:
|
||||
if len(ips) == 0 {
|
||||
noIpMsg = m
|
||||
return nil, resolver.ErrIPNotFound
|
||||
}
|
||||
}
|
||||
return m, nil
|
||||
|
||||
187
docs/config.yaml
187
docs/config.yaml
@@ -8,15 +8,15 @@ mixed-port: 10801 # HTTP(S) 和 SOCKS 代理混合端口
|
||||
|
||||
allow-lan: true # 允许局域网连接
|
||||
bind-address: "*" # 绑定 IP 地址,仅作用于 allow-lan 为 true,'*'表示所有地址
|
||||
authentication: # http,socks入口的验证用户名,密码
|
||||
authentication: # http,socks 入口的验证用户名,密码
|
||||
- "username:password"
|
||||
skip-auth-prefixes: # 设置跳过验证的IP段
|
||||
skip-auth-prefixes: # 设置跳过验证的 IP 段
|
||||
- 127.0.0.1/8
|
||||
- ::1/128
|
||||
lan-allowed-ips: # 允许连接的 IP 地址段,仅作用于 allow-lan 为 true, 默认值为0.0.0.0/0和::/0
|
||||
lan-allowed-ips: # 允许连接的 IP 地址段,仅作用于 allow-lan 为 true, 默认值为 0.0.0.0/0 和::/0
|
||||
- 0.0.0.0/0
|
||||
- ::/0
|
||||
lan-disallowed-ips: # 禁止连接的 IP 地址段, 黑名单优先级高于白名单, 默认值为空
|
||||
lan-disallowed-ips: # 禁止连接的 IP 地址段,黑名单优先级高于白名单,默认值为空
|
||||
- 192.168.0.3/32
|
||||
|
||||
# find-process-mode has 3 values:always, strict, off
|
||||
@@ -58,6 +58,11 @@ external-controller: 0.0.0.0:9093 # RESTful API 监听地址
|
||||
external-controller-tls: 0.0.0.0:9443 # RESTful API HTTPS 监听地址,需要配置 tls 部分配置文件
|
||||
# secret: "123456" # `Authorization:Bearer ${secret}`
|
||||
|
||||
# RESTful API Unix socket 监听地址( windows版本大于17063也可以使用,即大于等于1803/RS4版本即可使用 )
|
||||
# !!!注意: 从Unix socket访问api接口不会验证secret, 如果开启请自行保证安全问题 !!!
|
||||
# 测试方法: curl -v --unix-socket "mihomo.sock" http://localhost/
|
||||
external-controller-unix: mihomo.sock
|
||||
|
||||
# tcp-concurrent: true # TCP 并发连接所有 IP, 将使用最快握手的 TCP
|
||||
|
||||
# 配置 WEB UI 目录,使用 http://{{external-controller}}/ui 访问
|
||||
@@ -65,6 +70,10 @@ external-ui: /path/to/ui/folder/
|
||||
external-ui-name: xd
|
||||
external-ui-url: "https://github.com/MetaCubeX/metacubexd/archive/refs/heads/gh-pages.zip"
|
||||
|
||||
# 在RESTful API端口上开启DOH服务器
|
||||
# !!!该URL不会验证secret, 如果开启请自行保证安全问题 !!!
|
||||
external-doh-server: /dns-query
|
||||
|
||||
# interface-name: en0 # 设置出口网卡
|
||||
|
||||
# 全局 TLS 指纹,优先低于 proxy 内的 client-fingerprint
|
||||
@@ -109,19 +118,31 @@ tun:
|
||||
# auto-detect-interface: true # 自动识别出口网卡
|
||||
# auto-route: true # 配置路由表
|
||||
# mtu: 9000 # 最大传输单元
|
||||
# gso: false # 启用通用分段卸载, 仅支持 Linux
|
||||
# gso: false # 启用通用分段卸载,仅支持 Linux
|
||||
# gso-max-size: 65536 # 通用分段卸载包的最大大小
|
||||
# strict-route: true # 将所有连接路由到tun来防止泄漏,但你的设备将无法其他设备被访问
|
||||
inet4-route-address: # 启用 auto-route 时使用自定义路由而不是默认路由
|
||||
auto-redirect: false # 自动配置 iptables 以重定向 TCP 连接。仅支持 Linux。带有 auto-redirect 的 auto-route 现在可以在路由器上按预期工作,无需干预。
|
||||
# strict-route: true # 将所有连接路由到 tun 来防止泄漏,但你的设备将无法其他设备被访问
|
||||
route-address-set: # 将指定规则集中的目标 IP CIDR 规则添加到防火墙, 不匹配的流量将绕过路由, 仅支持 Linux,且需要 nftables,`auto-route` 和 `auto-redirect` 已启用。
|
||||
- ruleset-1
|
||||
- ruleset-2
|
||||
route-exclude-address-set: # 将指定规则集中的目标 IP CIDR 规则添加到防火墙, 匹配的流量将绕过路由, 仅支持 Linux,且需要 nftables,`auto-route` 和 `auto-redirect` 已启用。
|
||||
- ruleset-3
|
||||
- ruleset-4
|
||||
route-address: # 启用 auto-route 时使用自定义路由而不是默认路由
|
||||
- 0.0.0.0/1
|
||||
- 128.0.0.0/1
|
||||
inet6-route-address: # 启用 auto-route 时使用自定义路由而不是默认路由
|
||||
- "::/1"
|
||||
- "8000::/1"
|
||||
# inet4-route-address: # 启用 auto-route 时使用自定义路由而不是默认路由(旧写法)
|
||||
# - 0.0.0.0/1
|
||||
# - 128.0.0.0/1
|
||||
# inet6-route-address: # 启用 auto-route 时使用自定义路由而不是默认路由(旧写法)
|
||||
# - "::/1"
|
||||
# - "8000::/1"
|
||||
# endpoint-independent-nat: false # 启用独立于端点的 NAT
|
||||
# include-interface: # 限制被路由的接口。默认不限制, 与 `exclude-interface` 冲突
|
||||
# include-interface: # 限制被路由的接口。默认不限制,与 `exclude-interface` 冲突
|
||||
# - "lan0"
|
||||
# exclude-interface: # 排除路由的接口, 与 `include-interface` 冲突
|
||||
# exclude-interface: # 排除路由的接口,与 `include-interface` 冲突
|
||||
# - "lan1"
|
||||
# include-uid: # UID 规则仅在 Linux 下被支持,并且需要 auto-route
|
||||
# - 0
|
||||
@@ -143,7 +164,7 @@ tun:
|
||||
# exclude-package: # 排除被路由的 Android 应用包名
|
||||
# - com.android.captiveportallogin
|
||||
|
||||
#ebpf配置
|
||||
#ebpf 配置
|
||||
ebpf:
|
||||
auto-redir: # redirect 模式,仅支持 TCP
|
||||
- eth0
|
||||
@@ -200,15 +221,15 @@ tunnels: # one line config
|
||||
target: target.com
|
||||
proxy: proxy
|
||||
|
||||
# DNS配置
|
||||
# DNS 配置
|
||||
dns:
|
||||
cache-algorithm: arc
|
||||
enable: false # 关闭将使用系统 DNS
|
||||
prefer-h3: true # 开启 DoH 支持 HTTP/3,将并发尝试
|
||||
prefer-h3: false # 是否开启 DoH 支持 HTTP/3,将并发尝试
|
||||
listen: 0.0.0.0:53 # 开启 DNS 服务器监听
|
||||
# ipv6: false # false 将返回 AAAA 的空结果
|
||||
# ipv6-timeout: 300 # 单位:ms,内部双栈并发时,向上游查询 AAAA 时,等待 AAAA 的时间,默认 100ms
|
||||
# 用于解析 nameserver,fallback 以及其他DNS服务器配置的,DNS 服务域名
|
||||
# 用于解析 nameserver,fallback 以及其他 DNS 服务器配置的,DNS 服务域名
|
||||
# 只能使用纯 IP 地址,可使用加密 DNS
|
||||
default-nameserver:
|
||||
- 114.114.114.114
|
||||
@@ -222,12 +243,19 @@ dns:
|
||||
|
||||
# use-hosts: true # 查询 hosts
|
||||
|
||||
# 配置不使用fake-ip的域名
|
||||
# 配置后面的nameserver、fallback和nameserver-policy向dns服务器的连接过程是否遵守遵守rules规则
|
||||
# 如果为false(默认值)则这三部分的dns服务器在未特别指定的情况下会直连
|
||||
# 如果为true,将会按照rules的规则匹配链接方式(走代理或直连),如果有特别指定则任然以指定值为准
|
||||
# 仅当proxy-server-nameserver非空时可以开启此选项, 强烈不建议和prefer-h3一起使用
|
||||
# 此外,这三者配置中的dns服务器如果出现域名会采用default-nameserver配置项解析,也请确保正确配置default-nameserver
|
||||
respect-rules: false
|
||||
|
||||
# 配置不使用 fake-ip 的域名
|
||||
# fake-ip-filter:
|
||||
# - '*.lan'
|
||||
# - localhost.ptlogin2.qq.com
|
||||
|
||||
# DNS主要域名配置
|
||||
# DNS 主要域名配置
|
||||
# 支持 UDP,TCP,DoT,DoH,DoQ
|
||||
# 这部分为主要 DNS 配置,影响所有直连,确保使用对大陆解析精准的 DNS
|
||||
nameserver:
|
||||
@@ -239,7 +267,8 @@ dns:
|
||||
- https://mozilla.cloudflare-dns.com/dns-query#DNS&h3=true # 指定策略组和使用 HTTP/3
|
||||
- dhcp://en0 # dns from dhcp
|
||||
- quic://dns.adguard.com:784 # DNS over QUIC
|
||||
# - '8.8.8.8#en0' # 兼容指定DNS出口网卡
|
||||
# - '8.8.8.8#RULES' # 效果同respect-rules,但仅对该服务器生效
|
||||
# - '8.8.8.8#en0' # 兼容指定 DNS 出口网卡
|
||||
|
||||
# 当配置 fallback 时,会查询 nameserver 中返回的 IP 是否为 CN,非必要配置
|
||||
# 当不是 CN,则使用 fallback 中的 DNS 查询结果
|
||||
@@ -249,7 +278,6 @@ dns:
|
||||
# - 'tcp://1.1.1.1#ProxyGroupName' # 指定 DNS 过代理查询,ProxyGroupName 为策略组名或节点名,过代理配置优先于配置出口网卡,当找不到策略组或节点名则设置为出口网卡
|
||||
|
||||
# 专用于节点域名解析的 DNS 服务器,非必要配置项
|
||||
# 配置服务器若查询失败将使用 nameserver,非并发查询
|
||||
# proxy-server-nameserver:
|
||||
# - https://dns.google/dns-query
|
||||
# - tls://one.one.one.one
|
||||
@@ -338,7 +366,7 @@ proxies: # socks5
|
||||
# udp-over-tcp: false
|
||||
# ip-version: ipv4 # 设置节点使用 IP 版本,可选:dual,ipv4,ipv6,ipv4-prefer,ipv6-prefer。默认使用 dual
|
||||
# ipv4:仅使用 IPv4 ipv6:仅使用 IPv6
|
||||
# ipv4-prefer:优先使用 IPv4 对于 TCP 会进行双栈解析,并发链接但是优先使用 IPv4 链接,
|
||||
# ipv4-prefer:优先使用 IPv4 对于 TCP 会进行双栈解析,并发链接但是优先使用 IPv4 链接,
|
||||
# UDP 则为双栈解析,获取结果中的第一个 IPv4
|
||||
# ipv6-prefer 同 ipv4-prefer
|
||||
# 现有协议都支持此参数,TCP 效果仅在开启 tcp-concurrent 生效
|
||||
@@ -350,7 +378,7 @@ proxies: # socks5
|
||||
# max-streams: 0 # Maximum multiplexed streams in a connection before opening a new connection. Conflict with max-connections and min-streams.
|
||||
# padding: false # Enable padding. Requires sing-box server version 1.3-beta9 or later.
|
||||
# statistic: false # 控制是否将底层连接显示在面板中,方便打断底层连接
|
||||
# only-tcp: false # 如果设置为true, smux的设置将不会对udp生效,udp连接会直接走底层协议
|
||||
# only-tcp: false # 如果设置为 true, smux 的设置将不会对 udp 生效,udp 连接会直接走底层协议
|
||||
|
||||
- name: "ss2"
|
||||
type: ss
|
||||
@@ -383,6 +411,7 @@ proxies: # socks5
|
||||
# headers:
|
||||
# custom: value
|
||||
# v2ray-http-upgrade: false
|
||||
# v2ray-http-upgrade-fast-open: false
|
||||
|
||||
- name: "ss4-shadow-tls"
|
||||
type: ss
|
||||
@@ -405,18 +434,18 @@ proxies: # socks5
|
||||
password: [YOUR_SS_PASSWORD]
|
||||
client-fingerprint:
|
||||
chrome # One of: chrome, ios, firefox or safari
|
||||
# 可以是chrome, ios, firefox, safari中的一个
|
||||
# 可以是 chrome, ios, firefox, safari 中的一个
|
||||
plugin: restls
|
||||
plugin-opts:
|
||||
host:
|
||||
"www.microsoft.com" # Must be a TLS 1.3 server
|
||||
# 应当是一个TLS 1.3 服务器
|
||||
# 应当是一个 TLS 1.3 服务器
|
||||
password: [YOUR_RESTLS_PASSWORD]
|
||||
version-hint: "tls13"
|
||||
# Control your post-handshake traffic through restls-script
|
||||
# Hide proxy behaviors like "tls in tls".
|
||||
# see https://github.com/3andne/restls/blob/main/Restls-Script:%20Hide%20Your%20Proxy%20Traffic%20Behavior.md
|
||||
# 用restls剧本来控制握手后的行为,隐藏"tls in tls"等特征
|
||||
# 用 restls 剧本来控制握手后的行为,隐藏"tls in tls"等特征
|
||||
# 详情:https://github.com/3andne/restls/blob/main/Restls-Script:%20%E9%9A%90%E8%97%8F%E4%BD%A0%E7%9A%84%E4%BB%A3%E7%90%86%E8%A1%8C%E4%B8%BA.md
|
||||
restls-script: "300?100<1,400~100,350~100,600~100,300~200,300~100"
|
||||
|
||||
@@ -428,18 +457,18 @@ proxies: # socks5
|
||||
password: [YOUR_SS_PASSWORD]
|
||||
client-fingerprint:
|
||||
chrome # One of: chrome, ios, firefox or safari
|
||||
# 可以是chrome, ios, firefox, safari中的一个
|
||||
# 可以是 chrome, ios, firefox, safari 中的一个
|
||||
plugin: restls
|
||||
plugin-opts:
|
||||
host:
|
||||
"vscode.dev" # Must be a TLS 1.2 server
|
||||
# 应当是一个TLS 1.2 服务器
|
||||
# 应当是一个 TLS 1.2 服务器
|
||||
password: [YOUR_RESTLS_PASSWORD]
|
||||
version-hint: "tls12"
|
||||
restls-script: "1000?100<1,500~100,350~100,600~100,400~200"
|
||||
|
||||
# vmess
|
||||
# cipher支持 auto/aes-128-gcm/chacha20-poly1305/none
|
||||
# cipher 支持 auto/aes-128-gcm/chacha20-poly1305/none
|
||||
- name: "vmess"
|
||||
type: vmess
|
||||
server: server
|
||||
@@ -461,6 +490,7 @@ proxies: # socks5
|
||||
# max-early-data: 2048
|
||||
# early-data-header-name: Sec-WebSocket-Protocol
|
||||
# v2ray-http-upgrade: false
|
||||
# v2ray-http-upgrade-fast-open: false
|
||||
|
||||
- name: "vmess-h2"
|
||||
type: vmess
|
||||
@@ -589,6 +619,7 @@ proxies: # socks5
|
||||
headers:
|
||||
Host: example.com
|
||||
# v2ray-http-upgrade: false
|
||||
# v2ray-http-upgrade-fast-open: false
|
||||
|
||||
# Trojan
|
||||
- name: "trojan"
|
||||
@@ -604,6 +635,10 @@ proxies: # socks5
|
||||
# - h2
|
||||
# - http/1.1
|
||||
# skip-cert-verify: true
|
||||
# ss-opts: # like trojan-go's `shadowsocks` config
|
||||
# enabled: false
|
||||
# method: aes-128-gcm # aes-128-gcm/aes-256-gcm/chacha20-ietf-poly1305
|
||||
# password: "example"
|
||||
|
||||
- name: trojan-grpc
|
||||
server: server
|
||||
@@ -633,6 +668,7 @@ proxies: # socks5
|
||||
# headers:
|
||||
# Host: example.com
|
||||
# v2ray-http-upgrade: false
|
||||
# v2ray-http-upgrade-fast-open: false
|
||||
|
||||
- name: "trojan-xtls"
|
||||
type: trojan
|
||||
@@ -676,11 +712,11 @@ proxies: # socks5
|
||||
port: 443
|
||||
# ports: 1000,2000-3000,5000 # port 不可省略
|
||||
# hop-interval: 15
|
||||
# up和down均不写或为0则使用BBR流控
|
||||
# up 和 down 均不写或为 0 则使用 BBR 流控
|
||||
# up: "30 Mbps" # 若不写单位,默认为 Mbps
|
||||
# down: "200 Mbps" # 若不写单位,默认为 Mbps
|
||||
password: yourpassword
|
||||
# obfs: salamander # 默认为空,如果填写则开启obfs,目前仅支持salamander
|
||||
# obfs: salamander # 默认为空,如果填写则开启 obfs,目前仅支持 salamander
|
||||
# obfs-password: yourpassword
|
||||
# sni: server.com
|
||||
# skip-cert-verify: false
|
||||
@@ -706,9 +742,10 @@ proxies: # socks5
|
||||
# reserved: [209,98,59]
|
||||
# 一个出站代理的标识。当值不为空时,将使用指定的 proxy 发出连接
|
||||
# dialer-proxy: "ss1"
|
||||
# remote-dns-resolve: true # 强制dns远程解析,默认值为false
|
||||
# dns: [ 1.1.1.1, 8.8.8.8 ] # 仅在remote-dns-resolve为true时生效
|
||||
# 如果peers不为空,该段落中的allowed-ips不可为空;前面段落的server,port,public-key,pre-shared-key均会被忽略,但private-key会被保留且只能在顶层指定
|
||||
# remote-dns-resolve: true # 强制 dns 远程解析,默认值为 false
|
||||
# dns: [ 1.1.1.1, 8.8.8.8 ] # 仅在 remote-dns-resolve 为 true 时生效
|
||||
# refresh-server-ip-interval: 60 # 重新解析server ip的间隔,单位为秒,默认值为0即仅第一次链接时解析server域名,仅应在server域名对应的IP会发生变化时启用该选项(如家宽ddns)
|
||||
# 如果 peers 不为空,该段落中的 allowed-ips 不可为空;前面段落的 server,port,public-key,pre-shared-key 均会被忽略,但 private-key 会被保留且只能在顶层指定
|
||||
# peers:
|
||||
# - server: 162.159.192.1
|
||||
# port: 2480
|
||||
@@ -722,9 +759,9 @@ proxies: # socks5
|
||||
server: www.example.com
|
||||
port: 10443
|
||||
type: tuic
|
||||
# tuicV4必须填写token (不可同时填写uuid和password)
|
||||
# tuicV4 必须填写 token(不可同时填写 uuid 和 password)
|
||||
token: TOKEN
|
||||
# tuicV5必须填写uuid和password(不可同时填写token)
|
||||
# tuicV5 必须填写 uuid 和 password(不可同时填写 token)
|
||||
uuid: 00000000-0000-0000-0000-000000000001
|
||||
password: PASSWORD_1
|
||||
# ip: 127.0.0.1 # for overwriting the DNS lookup result of the server address set in option 'server'
|
||||
@@ -742,8 +779,8 @@ proxies: # socks5
|
||||
# max-open-streams: 20 # default 100, too many open streams may hurt performance
|
||||
# sni: example.com
|
||||
#
|
||||
# meta和sing-box私有扩展,将ss-uot用于udp中继,开启此选项后udp-relay-mode将失效
|
||||
# 警告,与原版tuic不兼容!!!
|
||||
# meta 和 sing-box 私有扩展,将 ss-uot 用于 udp 中继,开启此选项后 udp-relay-mode 将失效
|
||||
# 警告,与原版 tuic 不兼容!!!
|
||||
# udp-over-stream: false
|
||||
# udp-over-stream-version: 1
|
||||
|
||||
@@ -776,12 +813,12 @@ proxies: # socks5
|
||||
password: password
|
||||
privateKey: path
|
||||
|
||||
# dns出站会将请求劫持到内部dns模块,所有请求均在内部处理
|
||||
# dns 出站会将请求劫持到内部 dns 模块,所有请求均在内部处理
|
||||
- name: "dns-out"
|
||||
type: dns
|
||||
proxy-groups:
|
||||
# 代理链,目前relay可以支持udp的只有vmess/vless/trojan/ss/ssr/tuic
|
||||
# wireguard目前不支持在relay中使用,请使用proxy中的dialer-proxy配置项
|
||||
# 代理链,目前 relay 可以支持 udp 的只有 vmess/vless/trojan/ss/ssr/tuic
|
||||
# wireguard 目前不支持在 relay 中使用,请使用 proxy 中的 dialer-proxy 配置项
|
||||
# Traffic: mihomo <-> http <-> vmess <-> ss1 <-> ss2 <-> Internet
|
||||
- name: "relay"
|
||||
type: relay
|
||||
@@ -855,10 +892,19 @@ proxy-groups:
|
||||
# Mihomo 格式的节点或支持 *ray 的分享格式
|
||||
proxy-providers:
|
||||
provider1:
|
||||
type: http # http 的 path 可空置,默认储存路径为 homedir的proxies文件夹,文件名为url的md5
|
||||
type: http # http 的 path 可空置,默认储存路径为 homedir 的 proxies 文件夹,文件名为 url 的 md5
|
||||
url: "url"
|
||||
interval: 3600
|
||||
path: ./provider1.yaml # 默认只允许存储在 mihomo 的 Home Dir,如果想存储到任意位置,添加环境变量 SKIP_SAFE_PATH_CHECK=1
|
||||
proxy: DIRECT
|
||||
header:
|
||||
User-Agent:
|
||||
- "Clash/v1.18.0"
|
||||
- "mihomo/1.18.3"
|
||||
# Accept:
|
||||
# - 'application/vnd.github.v3.raw'
|
||||
# Authorization:
|
||||
# - 'token 1231231'
|
||||
health-check:
|
||||
enable: true
|
||||
interval: 600
|
||||
@@ -888,13 +934,32 @@ rule-providers:
|
||||
behavior: classical # domain ipcidr
|
||||
interval: 259200
|
||||
path: /path/to/save/file.yaml # 默认只允许存储在 mihomo 的 Home Dir,如果想存储到任意位置,添加环境变量 SKIP_SAFE_PATH_CHECK=1
|
||||
type: http # http 的 path 可空置,默认储存路径为 homedir的rules文件夹,文件名为url的md5
|
||||
type: http # http 的 path 可空置,默认储存路径为 homedir 的 rules 文件夹,文件名为 url 的 md5
|
||||
url: "url"
|
||||
proxy: DIRECT
|
||||
rule2:
|
||||
behavior: classical
|
||||
interval: 259200
|
||||
path: /path/to/save/file.yaml
|
||||
type: file
|
||||
rule3:
|
||||
# mrs类型ruleset,目前仅支持domain和ipcidr(即不支持classical),
|
||||
#
|
||||
# 对于behavior=domain:
|
||||
# - format=yaml 可以通过“mihomo convert-ruleset domain yaml XXX.yaml XXX.mrs”转换到mrs格式
|
||||
# - format=text 可以通过“mihomo convert-ruleset domain text XXX.text XXX.mrs”转换到mrs格式
|
||||
# - XXX.mrs 可以通过"mihomo convert-ruleset domain mrs XXX.mrs XXX.text"转换回text格式(暂不支持转换回ymal格式)
|
||||
#
|
||||
# 对于behavior=ipcidr:
|
||||
# - format=yaml 可以通过“mihomo convert-ruleset ipcidr yaml XXX.yaml XXX.mrs”转换到mrs格式
|
||||
# - format=text 可以通过“mihomo convert-ruleset ipcidr text XXX.text XXX.mrs”转换到mrs格式
|
||||
# - XXX.mrs 可以通过"mihomo convert-ruleset ipcidr mrs XXX.mrs XXX.text"转换回text格式(暂不支持转换回ymal格式)
|
||||
#
|
||||
type: http
|
||||
url: "url"
|
||||
format: mrs
|
||||
behavior: domain
|
||||
path: /path/to/save/file.mrs
|
||||
rules:
|
||||
- RULE-SET,rule1,REJECT
|
||||
- IP-ASN,1,PROXY
|
||||
@@ -938,7 +1003,7 @@ listeners:
|
||||
port: 10808
|
||||
#listen: 0.0.0.0 # 默认监听 0.0.0.0
|
||||
# rule: sub-rule-name1 # 默认使用 rules,如果未找到 sub-rule 则直接使用 rules
|
||||
# proxy: proxy # 如果不为空则直接将该入站流量交由指定proxy处理
|
||||
# proxy: proxy # 如果不为空则直接将该入站流量交由指定 proxy 处理
|
||||
# udp: false # 默认 true
|
||||
|
||||
- name: http-in-1
|
||||
@@ -946,14 +1011,14 @@ listeners:
|
||||
port: 10809
|
||||
listen: 0.0.0.0
|
||||
# rule: sub-rule-name1 # 默认使用 rules,如果未找到 sub-rule 则直接使用 rules
|
||||
# proxy: proxy # 如果不为空则直接将该入站流量交由指定proxy处理(当proxy不为空时,这里的proxy名称必须合法,否则会出错)
|
||||
# proxy: proxy # 如果不为空则直接将该入站流量交由指定 proxy 处理 (当 proxy 不为空时,这里的 proxy 名称必须合法,否则会出错)
|
||||
|
||||
- name: mixed-in-1
|
||||
type: mixed # HTTP(S) 和 SOCKS 代理混合
|
||||
port: 10810
|
||||
listen: 0.0.0.0
|
||||
# rule: sub-rule-name1 # 默认使用 rules,如果未找到 sub-rule 则直接使用 rules
|
||||
# proxy: proxy # 如果不为空则直接将该入站流量交由指定proxy处理(当proxy不为空时,这里的proxy名称必须合法,否则会出错)
|
||||
# proxy: proxy # 如果不为空则直接将该入站流量交由指定 proxy 处理 (当 proxy 不为空时,这里的 proxy 名称必须合法,否则会出错)
|
||||
# udp: false # 默认 true
|
||||
|
||||
- name: reidr-in-1
|
||||
@@ -961,14 +1026,14 @@ listeners:
|
||||
port: 10811
|
||||
listen: 0.0.0.0
|
||||
# rule: sub-rule-name1 # 默认使用 rules,如果未找到 sub-rule 则直接使用 rules
|
||||
# proxy: proxy # 如果不为空则直接将该入站流量交由指定proxy处理(当proxy不为空时,这里的proxy名称必须合法,否则会出错)
|
||||
# proxy: proxy # 如果不为空则直接将该入站流量交由指定 proxy 处理 (当 proxy 不为空时,这里的 proxy 名称必须合法,否则会出错)
|
||||
|
||||
- name: tproxy-in-1
|
||||
type: tproxy
|
||||
port: 10812
|
||||
listen: 0.0.0.0
|
||||
# rule: sub-rule-name1 # 默认使用 rules,如果未找到 sub-rule 则直接使用 rules
|
||||
# proxy: proxy # 如果不为空则直接将该入站流量交由指定proxy处理(当proxy不为空时,这里的proxy名称必须合法,否则会出错)
|
||||
# proxy: proxy # 如果不为空则直接将该入站流量交由指定 proxy 处理 (当 proxy 不为空时,这里的 proxy 名称必须合法,否则会出错)
|
||||
# udp: false # 默认 true
|
||||
|
||||
- name: shadowsocks-in-1
|
||||
@@ -976,7 +1041,7 @@ listeners:
|
||||
port: 10813
|
||||
listen: 0.0.0.0
|
||||
# rule: sub-rule-name1 # 默认使用 rules,如果未找到 sub-rule 则直接使用 rules
|
||||
# proxy: proxy # 如果不为空则直接将该入站流量交由指定proxy处理(当proxy不为空时,这里的proxy名称必须合法,否则会出错)
|
||||
# proxy: proxy # 如果不为空则直接将该入站流量交由指定 proxy 处理 (当 proxy 不为空时,这里的 proxy 名称必须合法,否则会出错)
|
||||
password: vlmpIPSyHH6f4S8WVPdRIHIlzmB+GIRfoH3aNJ/t9Gg=
|
||||
cipher: 2022-blake3-aes-256-gcm
|
||||
|
||||
@@ -985,13 +1050,13 @@ listeners:
|
||||
port: 10814
|
||||
listen: 0.0.0.0
|
||||
# rule: sub-rule-name1 # 默认使用 rules,如果未找到 sub-rule 则直接使用 rules
|
||||
# proxy: proxy # 如果不为空则直接将该入站流量交由指定proxy处理(当proxy不为空时,这里的proxy名称必须合法,否则会出错)
|
||||
# proxy: proxy # 如果不为空则直接将该入站流量交由指定 proxy 处理 (当 proxy 不为空时,这里的 proxy 名称必须合法,否则会出错)
|
||||
users:
|
||||
- username: 1
|
||||
uuid: 9d0cb9d0-964f-4ef6-897d-6c6b3ccf9e68
|
||||
alterId: 1
|
||||
# ws-path: "/" # 如果不为空则开启websocket传输层
|
||||
# 下面两项如果填写则开启tls(需要同时填写)
|
||||
# ws-path: "/" # 如果不为空则开启 websocket 传输层
|
||||
# 下面两项如果填写则开启 tls(需要同时填写)
|
||||
# certificate: ./server.crt
|
||||
# private-key: ./server.key
|
||||
|
||||
@@ -1000,10 +1065,10 @@ listeners:
|
||||
port: 10815
|
||||
listen: 0.0.0.0
|
||||
# rule: sub-rule-name1 # 默认使用 rules,如果未找到 sub-rule 则直接使用 rules
|
||||
# proxy: proxy # 如果不为空则直接将该入站流量交由指定proxy处理(当proxy不为空时,这里的proxy名称必须合法,否则会出错)
|
||||
# token: # tuicV4填写(可以同时填写users)
|
||||
# proxy: proxy # 如果不为空则直接将该入站流量交由指定 proxy 处理 (当 proxy 不为空时,这里的 proxy 名称必须合法,否则会出错)
|
||||
# token: # tuicV4 填写(可以同时填写 users)
|
||||
# - TOKEN
|
||||
# users: # tuicV5填写(可以同时填写token)
|
||||
# users: # tuicV5 填写(可以同时填写 token)
|
||||
# 00000000-0000-0000-0000-000000000000: PASSWORD_0
|
||||
# 00000000-0000-0000-0000-000000000001: PASSWORD_1
|
||||
# certificate: ./server.crt
|
||||
@@ -1020,25 +1085,25 @@ listeners:
|
||||
port: 10816
|
||||
listen: 0.0.0.0
|
||||
# rule: sub-rule-name1 # 默认使用 rules,如果未找到 sub-rule 则直接使用 rules
|
||||
# proxy: proxy # 如果不为空则直接将该入站流量交由指定proxy处理(当proxy不为空时,这里的proxy名称必须合法,否则会出错)
|
||||
# proxy: proxy # 如果不为空则直接将该入站流量交由指定 proxy 处理 (当 proxy 不为空时,这里的 proxy 名称必须合法,否则会出错)
|
||||
network: [tcp, udp]
|
||||
target: target.com
|
||||
|
||||
- name: tun-in-1
|
||||
type: tun
|
||||
# rule: sub-rule-name1 # 默认使用 rules,如果未找到 sub-rule 则直接使用 rules
|
||||
# proxy: proxy # 如果不为空则直接将该入站流量交由指定proxy处理(当proxy不为空时,这里的proxy名称必须合法,否则会出错)
|
||||
# proxy: proxy # 如果不为空则直接将该入站流量交由指定 proxy 处理 (当 proxy 不为空时,这里的 proxy 名称必须合法,否则会出错)
|
||||
stack: system # gvisor / mixed
|
||||
dns-hijack:
|
||||
- 0.0.0.0:53 # 需要劫持的 DNS
|
||||
# auto-detect-interface: false # 自动识别出口网卡
|
||||
# auto-route: false # 配置路由表
|
||||
# mtu: 9000 # 最大传输单元
|
||||
inet4-address: # 必须手动设置ipv4地址段
|
||||
inet4-address: # 必须手动设置 ipv4 地址段
|
||||
- 198.19.0.1/30
|
||||
inet6-address: # 必须手动设置ipv6地址段
|
||||
inet6-address: # 必须手动设置 ipv6 地址段
|
||||
- "fdfe:dcba:9877::1/126"
|
||||
# strict-route: true # 将所有连接路由到tun来防止泄漏,但你的设备将无法其他设备被访问
|
||||
# strict-route: true # 将所有连接路由到 tun 来防止泄漏,但你的设备将无法其他设备被访问
|
||||
# inet4-route-address: # 启用 auto-route 时使用自定义路由而不是默认路由
|
||||
# - 0.0.0.0/1
|
||||
# - 128.0.0.0/1
|
||||
@@ -1066,17 +1131,17 @@ listeners:
|
||||
# exclude-package: # 排除被路由的 Android 应用包名
|
||||
# - com.android.captiveportallogin
|
||||
# 入口配置与 Listener 等价,传入流量将和 socks,mixed 等入口一样按照 mode 所指定的方式进行匹配处理
|
||||
# shadowsocks,vmess 入口配置(传入流量将和socks,mixed等入口一样按照mode所指定的方式进行匹配处理)
|
||||
# shadowsocks,vmess 入口配置(传入流量将和 socks,mixed 等入口一样按照 mode 所指定的方式进行匹配处理)
|
||||
# ss-config: ss://2022-blake3-aes-256-gcm:vlmpIPSyHH6f4S8WVPdRIHIlzmB+GIRfoH3aNJ/t9Gg=@:23456
|
||||
# vmess-config: vmess://1:9d0cb9d0-964f-4ef6-897d-6c6b3ccf9e68@:12345
|
||||
|
||||
# tuic服务器入口(传入流量将和socks,mixed等入口一样按照mode所指定的方式进行匹配处理)
|
||||
# tuic 服务器入口(传入流量将和 socks,mixed 等入口一样按照 mode 所指定的方式进行匹配处理)
|
||||
# tuic-server:
|
||||
# enable: true
|
||||
# listen: 127.0.0.1:10443
|
||||
# token: # tuicV4填写(可以同时填写users)
|
||||
# token: # tuicV4 填写(可以同时填写 users)
|
||||
# - TOKEN
|
||||
# users: # tuicV5填写(可以同时填写token)
|
||||
# users: # tuicV5 填写(可以同时填写 token)
|
||||
# 00000000-0000-0000-0000-000000000000: PASSWORD_0
|
||||
# 00000000-0000-0000-0000-000000000001: PASSWORD_1
|
||||
# certificate: ./server.crt
|
||||
|
||||
71
go.mod
71
go.mod
@@ -4,57 +4,60 @@ go 1.20
|
||||
|
||||
require (
|
||||
github.com/3andne/restls-client-go v0.1.6
|
||||
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da
|
||||
github.com/bahlo/generic-list-go v0.2.0
|
||||
github.com/cilium/ebpf v0.12.3
|
||||
github.com/coreos/go-iptables v0.7.0
|
||||
github.com/dlclark/regexp2 v1.11.0
|
||||
github.com/go-chi/chi/v5 v5.0.12
|
||||
github.com/go-chi/chi/v5 v5.0.14
|
||||
github.com/go-chi/cors v1.2.1
|
||||
github.com/go-chi/render v1.0.3
|
||||
github.com/gobwas/ws v1.3.2
|
||||
github.com/gofrs/uuid/v5 v5.0.0
|
||||
github.com/insomniacslk/dhcp v0.0.0-20240227161007-c728f5dd21c8
|
||||
github.com/klauspost/cpuid/v2 v2.2.7
|
||||
github.com/gobwas/ws v1.4.0
|
||||
github.com/gofrs/uuid/v5 v5.2.0
|
||||
github.com/insomniacslk/dhcp v0.0.0-20240529192340-51bc6136a0a6
|
||||
github.com/klauspost/compress v1.17.9
|
||||
github.com/klauspost/cpuid/v2 v2.2.8
|
||||
github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40
|
||||
github.com/mdlayher/netlink v1.7.2
|
||||
github.com/metacubex/chacha v0.1.0
|
||||
github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759
|
||||
github.com/metacubex/quic-go v0.42.1-0.20240319071510-a251e5c66a5c
|
||||
github.com/metacubex/sing-quic v0.0.0-20240310154810-47bca850fc01
|
||||
github.com/metacubex/sing-shadowsocks v0.2.6
|
||||
github.com/metacubex/sing-shadowsocks2 v0.2.0
|
||||
github.com/metacubex/sing-tun v0.2.1-0.20240320004934-5d2b35447bfd
|
||||
github.com/metacubex/sing-vmess v0.1.9-0.20231207122118-72303677451f
|
||||
github.com/metacubex/sing-wireguard v0.0.0-20240321042214-224f96122a63
|
||||
github.com/metacubex/quic-go v0.45.1-0.20240610004319-163fee60637e
|
||||
github.com/metacubex/randv2 v0.2.0
|
||||
github.com/metacubex/sing-quic v0.0.0-20240518034124-7696d3f7da72
|
||||
github.com/metacubex/sing-shadowsocks v0.2.7
|
||||
github.com/metacubex/sing-shadowsocks2 v0.2.1
|
||||
github.com/metacubex/sing-tun v0.2.7-0.20240719141246-19c49ac9589d
|
||||
github.com/metacubex/sing-vmess v0.1.9-0.20240719134745-1df6fb20bbf9
|
||||
github.com/metacubex/sing-wireguard v0.0.0-20240618022557-a6efaa37127a
|
||||
github.com/metacubex/tfo-go v0.0.0-20240228025757-be1269474a66
|
||||
github.com/miekg/dns v1.1.58
|
||||
github.com/metacubex/utls v1.6.6
|
||||
github.com/miekg/dns v1.1.61
|
||||
github.com/mroth/weightedrand/v2 v2.1.0
|
||||
github.com/openacid/low v0.1.21
|
||||
github.com/oschwald/maxminddb-golang v1.12.0
|
||||
github.com/puzpuzpuz/xsync/v3 v3.1.0
|
||||
github.com/puzpuzpuz/xsync/v3 v3.2.0
|
||||
github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a
|
||||
github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97
|
||||
github.com/sagernet/sing v0.3.6
|
||||
github.com/sagernet/fswatch v0.1.1
|
||||
github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a
|
||||
github.com/sagernet/sing v0.5.0-alpha.13
|
||||
github.com/sagernet/sing-mux v0.2.1-0.20240124034317-9bfb33698bb6
|
||||
github.com/sagernet/sing-shadowtls v0.1.4
|
||||
github.com/sagernet/utls v1.5.4
|
||||
github.com/sagernet/wireguard-go v0.0.0-20231209092712-9a439356a62e
|
||||
github.com/samber/lo v1.39.0
|
||||
github.com/shirou/gopsutil/v3 v3.24.2
|
||||
github.com/shirou/gopsutil/v3 v3.24.5
|
||||
github.com/sirupsen/logrus v1.9.3
|
||||
github.com/stretchr/testify v1.9.0
|
||||
github.com/wk8/go-ordered-map/v2 v2.1.8
|
||||
github.com/zhangyunhao116/fastrand v0.3.0
|
||||
gitlab.com/go-extension/aes-ccm v0.0.0-20230221065045-e58665ef23c7
|
||||
go.uber.org/automaxprocs v1.5.3
|
||||
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba
|
||||
golang.org/x/crypto v0.21.0
|
||||
golang.org/x/exp v0.0.0-20240222234643-814bf88cf225
|
||||
golang.org/x/net v0.22.0
|
||||
golang.org/x/sync v0.6.0
|
||||
golang.org/x/sys v0.18.0
|
||||
google.golang.org/protobuf v1.33.0
|
||||
golang.org/x/crypto v0.24.0
|
||||
golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8
|
||||
golang.org/x/net v0.26.0
|
||||
golang.org/x/sync v0.7.0
|
||||
golang.org/x/sys v0.22.0
|
||||
google.golang.org/protobuf v1.34.2
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
lukechampine.com/blake3 v1.2.1
|
||||
lukechampine.com/blake3 v1.3.0
|
||||
)
|
||||
|
||||
require (
|
||||
@@ -63,7 +66,7 @@ require (
|
||||
github.com/ajg/form v1.5.1 // indirect
|
||||
github.com/andybalholm/brotli v1.0.6 // indirect
|
||||
github.com/buger/jsonparser v1.1.1 // indirect
|
||||
github.com/cloudflare/circl v1.3.6 // indirect
|
||||
github.com/cloudflare/circl v1.3.7 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/ericlagergren/aegis v0.0.0-20230312195928-b4ce538b56f9 // indirect
|
||||
github.com/ericlagergren/polyval v0.0.0-20220411101811-e25bc10ba391 // indirect
|
||||
@@ -80,7 +83,6 @@ require (
|
||||
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 // indirect
|
||||
github.com/hashicorp/yamux v0.1.1 // indirect
|
||||
github.com/josharian/native v1.1.0 // indirect
|
||||
github.com/klauspost/compress v1.17.4 // indirect
|
||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
|
||||
github.com/mailru/easyjson v0.7.7 // indirect
|
||||
github.com/mdlayher/socket v0.4.1 // indirect
|
||||
@@ -92,6 +94,7 @@ require (
|
||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
|
||||
github.com/quic-go/qpack v0.4.0 // indirect
|
||||
github.com/quic-go/qtls-go1-20 v0.4.1 // indirect
|
||||
github.com/sagernet/nftables v0.3.0-beta.4 // indirect
|
||||
github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 // indirect
|
||||
github.com/shoenig/go-m1cpu v0.1.6 // indirect
|
||||
github.com/sina-ghaderi/poly1305 v0.0.0-20220724002748-c5926b03988b // indirect
|
||||
@@ -100,14 +103,14 @@ require (
|
||||
github.com/tklauser/go-sysconf v0.3.12 // indirect
|
||||
github.com/tklauser/numcpus v0.6.1 // indirect
|
||||
github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923 // indirect
|
||||
github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 // indirect
|
||||
github.com/vishvananda/netns v0.0.4 // indirect
|
||||
github.com/yusufpapurcu/wmi v1.2.4 // indirect
|
||||
gitlab.com/yawning/bsaes.git v0.0.0-20190805113838-0a714cd429ec // indirect
|
||||
go.uber.org/mock v0.4.0 // indirect
|
||||
golang.org/x/mod v0.15.0 // indirect
|
||||
golang.org/x/text v0.14.0 // indirect
|
||||
golang.org/x/mod v0.18.0 // indirect
|
||||
golang.org/x/text v0.16.0 // indirect
|
||||
golang.org/x/time v0.5.0 // indirect
|
||||
golang.org/x/tools v0.18.0 // indirect
|
||||
golang.org/x/tools v0.22.0 // indirect
|
||||
)
|
||||
|
||||
replace github.com/sagernet/sing => github.com/metacubex/sing v0.0.0-20240313064558-c197257f6542
|
||||
replace github.com/sagernet/sing => github.com/metacubex/sing v0.0.0-20240724044459-6f3cf5896297
|
||||
|
||||
144
go.sum
144
go.sum
@@ -5,8 +5,6 @@ github.com/RyuaNerin/go-krypto v1.2.4 h1:mXuNdK6M317aPV0llW6Xpjbo4moOlPF7Yxz4tb4
|
||||
github.com/RyuaNerin/go-krypto v1.2.4/go.mod h1:QqCYkoutU3yInyD9INt2PGolVRsc3W4oraQadVGXJ/8=
|
||||
github.com/Yawning/aez v0.0.0-20211027044916-e49e68abd344 h1:cDVUiFo+npB0ZASqnw4q90ylaVAbnYyx0JYqK4YcGok=
|
||||
github.com/Yawning/aez v0.0.0-20211027044916-e49e68abd344/go.mod h1:9pIqrY6SXNL8vjRQE5Hd/OL5GyK/9MrGUWs87z/eFfk=
|
||||
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da h1:KjTM2ks9d14ZYCvmHS9iAKVt9AyzRSqNU1qabPih5BY=
|
||||
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da/go.mod h1:eHEWzANqSiWQsof+nXEI9bUVUyV6F53Fp89EuCh2EAA=
|
||||
github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU=
|
||||
github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY=
|
||||
github.com/andybalholm/brotli v1.0.6 h1:Yf9fFpf49Zrxb9NlQaluyE92/+X7UVHlhMNJN2sxfOI=
|
||||
@@ -21,8 +19,8 @@ github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5P
|
||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||
github.com/cilium/ebpf v0.12.3 h1:8ht6F9MquybnY97at+VDZb3eQQr8ev79RueWeVaEcG4=
|
||||
github.com/cilium/ebpf v0.12.3/go.mod h1:TctK1ivibvI3znr66ljgi4hqOT8EYQjz1KWBfb1UVgM=
|
||||
github.com/cloudflare/circl v1.3.6 h1:/xbKIqSHbZXHwkhbrhrt2YOHIwYJlXH94E3tI/gDlUg=
|
||||
github.com/cloudflare/circl v1.3.6/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA=
|
||||
github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU=
|
||||
github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA=
|
||||
github.com/coreos/go-iptables v0.7.0 h1:XWM3V+MPRr5/q51NuWSgU0fqMad64Zyxs8ZUoMsamr8=
|
||||
github.com/coreos/go-iptables v0.7.0/go.mod h1:Qe8Bv2Xik5FyTXwgIbLAnv2sWSBmvWdFETJConOQ//Q=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
@@ -44,8 +42,8 @@ github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nos
|
||||
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
|
||||
github.com/gaukas/godicttls v0.0.4 h1:NlRaXb3J6hAnTmWdsEKb9bcSBD6BvcIjdGdeb0zfXbk=
|
||||
github.com/gaukas/godicttls v0.0.4/go.mod h1:l6EenT4TLWgTdwslVb4sEMOCf7Bv0JAK67deKr9/NCI=
|
||||
github.com/go-chi/chi/v5 v5.0.12 h1:9euLV5sTrTNTRUU9POmDUvfxyj6LAABLUcEWO+JJb4s=
|
||||
github.com/go-chi/chi/v5 v5.0.12/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
|
||||
github.com/go-chi/chi/v5 v5.0.14 h1:PyEwo2Vudraa0x/Wl6eDRRW2NXBvekgfxyydcM0WGE0=
|
||||
github.com/go-chi/chi/v5 v5.0.14/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
|
||||
github.com/go-chi/cors v1.2.1 h1:xEC8UT3Rlp2QuWNEr4Fs/c2EAGVKBwy/1vHx3bppil4=
|
||||
github.com/go-chi/cors v1.2.1/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58=
|
||||
github.com/go-chi/render v1.0.3 h1:AsXqd2a1/INaIfUSKq3G5uA8weYx20FOsM7uSoCyyt4=
|
||||
@@ -60,16 +58,15 @@ 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/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og=
|
||||
github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
|
||||
github.com/gobwas/ws v1.3.2 h1:zlnbNHxumkRvfPWgfXu8RBwyNR1x8wh9cf5PTOCqs9Q=
|
||||
github.com/gobwas/ws v1.3.2/go.mod h1:hRKAFb8wOxFROYNsT1bqfWnhX+b5MFeJM9r2ZSwg/KY=
|
||||
github.com/gofrs/uuid/v5 v5.0.0 h1:p544++a97kEL+svbcFbCQVM9KFu0Yo25UoISXGNNH9M=
|
||||
github.com/gofrs/uuid/v5 v5.0.0/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8=
|
||||
github.com/gobwas/ws v1.4.0 h1:CTaoG1tojrh4ucGPcoJFiAQUAsEWekEWvLy7GsVNqGs=
|
||||
github.com/gobwas/ws v1.4.0/go.mod h1:G3gNqMNtPppf5XUz7O4shetPpcZ1VJ7zt18dlUeakrc=
|
||||
github.com/gofrs/uuid/v5 v5.2.0 h1:qw1GMx6/y8vhVsx626ImfKMuS5CvJmhIKKtuyvfajMM=
|
||||
github.com/gofrs/uuid/v5 v5.2.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.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
|
||||
github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU=
|
||||
github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
|
||||
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
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/pprof v0.0.0-20210407192527-94a9f03dee38 h1:yAJXTCF9TqKcTiHJAE8dj7HMvPfh66eeA2JYW7eFpSE=
|
||||
@@ -78,16 +75,16 @@ github.com/google/tink/go v1.6.1 h1:t7JHqO8Ath2w2ig5vjwQYJzhGEZymedQc90lQXUBa4I=
|
||||
github.com/hashicorp/yamux v0.1.1 h1:yrQxtgseBDrq9Y652vSRDvsKCJKOUD+GzTS4Y0Y8pvE=
|
||||
github.com/hashicorp/yamux v0.1.1/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
github.com/insomniacslk/dhcp v0.0.0-20240227161007-c728f5dd21c8 h1:V3plQrMHRWOB5zMm3yNqvBxDQVW1+/wHBSok5uPdmVs=
|
||||
github.com/insomniacslk/dhcp v0.0.0-20240227161007-c728f5dd21c8/go.mod h1:izxuNQZeFrbx2nK2fAyN5iNUB34Fe9j0nK4PwLzAkKw=
|
||||
github.com/insomniacslk/dhcp v0.0.0-20240529192340-51bc6136a0a6 h1:dh8D8FksyMhD64mRMbUhZHWYJfNoNMCxfVq6eexleMw=
|
||||
github.com/insomniacslk/dhcp v0.0.0-20240529192340-51bc6136a0a6/go.mod h1:KclMyHxX06VrVr0DJmeFSUb1ankt7xTfoOA35pCkoic=
|
||||
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
||||
github.com/josharian/native v1.0.1-0.20221213033349-c1e37c09b531/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
|
||||
github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtLA=
|
||||
github.com/josharian/native v1.1.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
|
||||
github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4=
|
||||
github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM=
|
||||
github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM=
|
||||
github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
||||
github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA=
|
||||
github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
|
||||
github.com/klauspost/cpuid/v2 v2.2.8 h1:+StwCXwm9PdpiEkPyzBXIy+M9KUb4ODm0Zarf1kS5BM=
|
||||
github.com/klauspost/cpuid/v2 v2.2.8/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4=
|
||||
@@ -100,30 +97,36 @@ github.com/mdlayher/netlink v1.7.2 h1:/UtM3ofJap7Vl4QWCPDGXY8d3GIY2UGSDbK+QWmY8/
|
||||
github.com/mdlayher/netlink v1.7.2/go.mod h1:xraEF7uJbxLhc5fpHL4cPe221LI2bdttWlU+ZGLfQSw=
|
||||
github.com/mdlayher/socket v0.4.1 h1:eM9y2/jlbs1M615oshPQOHZzj6R6wMT7bX5NPiQvn2U=
|
||||
github.com/mdlayher/socket v0.4.1/go.mod h1:cAqeGjoufqdxWkD7DkpyS+wcefOtmu5OQ8KuoJGIReA=
|
||||
github.com/metacubex/chacha v0.1.0 h1:tg9RSJ18NvL38cCWNyYH1eiG6qDCyyXIaTLQthon0sc=
|
||||
github.com/metacubex/chacha v0.1.0/go.mod h1:Djn9bPZxLTXbJFSeyo0/qzEzQI+gUSSzttuzZM75GH8=
|
||||
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/gvisor v0.0.0-20240320004321-933faba989ec h1:HxreOiFTUrJXJautEo8rnE1uKTVGY8wtZepY1Tii/Nc=
|
||||
github.com/metacubex/gvisor v0.0.0-20240320004321-933faba989ec/go.mod h1:8BVmQ+3cxjqzWElafm24rb2Ae4jRI6vAXNXWqWjfrXw=
|
||||
github.com/metacubex/quic-go v0.42.1-0.20240319071510-a251e5c66a5c h1:AhaPKvVqF3N/jXFmlW51Cf1+KddslKAsZqcdgGhZjr0=
|
||||
github.com/metacubex/quic-go v0.42.1-0.20240319071510-a251e5c66a5c/go.mod h1:iGx3Y1zynls/FjFgykLSqDcM81U0IKePRTXEz5g3iiQ=
|
||||
github.com/metacubex/sing v0.0.0-20240313064558-c197257f6542 h1:e9nBnrJBv3HzZVeSzJN0G2SADjebd2ZLF1F5dmsjUTc=
|
||||
github.com/metacubex/sing v0.0.0-20240313064558-c197257f6542/go.mod h1:+60H3Cm91RnL9dpVGWDPHt0zTQImO9Vfqt9a4rSambI=
|
||||
github.com/metacubex/sing-quic v0.0.0-20240310154810-47bca850fc01 h1:5INHs85Gp1JZsdF7fQp1pXUjfJOX2dhwZjuUQWJVSt8=
|
||||
github.com/metacubex/sing-quic v0.0.0-20240310154810-47bca850fc01/go.mod h1:WyY0zYxv+o+18R/Ece+QFontlgXoobKbNqbtYn2zjz8=
|
||||
github.com/metacubex/sing-shadowsocks v0.2.6 h1:6oEB3QcsFYnNiFeoevcXrCwJ3sAablwVSgtE9R3QeFQ=
|
||||
github.com/metacubex/sing-shadowsocks v0.2.6/go.mod h1:zIkMeSnb8Mbf4hdqhw0pjzkn1d99YJ3JQm/VBg5WMTg=
|
||||
github.com/metacubex/sing-shadowsocks2 v0.2.0 h1:hqwT/AfI5d5UdPefIzR6onGHJfDXs5zgOM5QSgaM/9A=
|
||||
github.com/metacubex/sing-shadowsocks2 v0.2.0/go.mod h1:LCKF6j1P94zN8ZS+LXRK1gmYTVGB3squivBSXAFnOg8=
|
||||
github.com/metacubex/sing-tun v0.2.1-0.20240320004934-5d2b35447bfd h1:NgLb6Lvr8ZxX0inWswVYjal2SUzsJJ54dFQNOluUJuE=
|
||||
github.com/metacubex/sing-tun v0.2.1-0.20240320004934-5d2b35447bfd/go.mod h1:GfLZG/QgGpW9+BPjltzONrL5vVms86TWqmZ23J68ISc=
|
||||
github.com/metacubex/sing-vmess v0.1.9-0.20231207122118-72303677451f h1:QjXrHKbTMBip/C+R79bvbfr42xH1gZl3uFb0RELdZiQ=
|
||||
github.com/metacubex/sing-vmess v0.1.9-0.20231207122118-72303677451f/go.mod h1:olVkD4FChQ5gKMHG4ZzuD7+fMkJY1G8vwOKpRehjrmY=
|
||||
github.com/metacubex/sing-wireguard v0.0.0-20240321042214-224f96122a63 h1:AGyIB55UfQm/0ZH0HtQO9u3l//yjtHUpjeRjjPGfGRI=
|
||||
github.com/metacubex/sing-wireguard v0.0.0-20240321042214-224f96122a63/go.mod h1:uY+BYb0UEknLrqvbGcwi9i++KgrKxsurysgI6G1Pveo=
|
||||
github.com/metacubex/quic-go v0.45.1-0.20240610004319-163fee60637e h1:bLYn3GuRvWDcBDAkIv5kUYIhzHwafDVq635BuybnKqI=
|
||||
github.com/metacubex/quic-go v0.45.1-0.20240610004319-163fee60637e/go.mod h1:Yza2H7Ax1rxWPUcJx0vW+oAt9EsPuSiyQFhFabUPzwU=
|
||||
github.com/metacubex/randv2 v0.2.0 h1:uP38uBvV2SxYfLj53kuvAjbND4RUDfFJjwr4UigMiLs=
|
||||
github.com/metacubex/randv2 v0.2.0/go.mod h1:kFi2SzrQ5WuneuoLLCMkABtiBu6VRrMrWFqSPyj2cxY=
|
||||
github.com/metacubex/sing v0.0.0-20240724044459-6f3cf5896297 h1:YG/JkwGPbca5rUtEMHIu8ZuqzR7BSVm1iqY8hNoMeMA=
|
||||
github.com/metacubex/sing v0.0.0-20240724044459-6f3cf5896297/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
|
||||
github.com/metacubex/sing-quic v0.0.0-20240518034124-7696d3f7da72 h1:Wr4g1HCb5Z/QIFwFiVNjO2qL+dRu25+Mdn9xtAZZ+ew=
|
||||
github.com/metacubex/sing-quic v0.0.0-20240518034124-7696d3f7da72/go.mod h1:g7Mxj7b7zm7YVqD975mk/hSmrb0A0G4bVvIMr2MMzn8=
|
||||
github.com/metacubex/sing-shadowsocks v0.2.7 h1:9f3Dt2+71TNp0e202llA2ug5h/rkWs2EZxQ5IMpf+9g=
|
||||
github.com/metacubex/sing-shadowsocks v0.2.7/go.mod h1:X3x88XtJpBxG0W0/ECOJL6Ib0SJ3xdniAkU/6/RMWU0=
|
||||
github.com/metacubex/sing-shadowsocks2 v0.2.1 h1:XIZBXlazp8EEoPp1S0DViAhLkJakjQ2f+AOwwdKKNYg=
|
||||
github.com/metacubex/sing-shadowsocks2 v0.2.1/go.mod h1:BhOug03a/RbI7y6hp6q+6ITM1dXjnLTmeWBHSTwvv2Q=
|
||||
github.com/metacubex/sing-tun v0.2.7-0.20240719141246-19c49ac9589d h1:iYlepjRCYlPXtELupDL+pQjGqkCnQz4KQOfKImP9sog=
|
||||
github.com/metacubex/sing-tun v0.2.7-0.20240719141246-19c49ac9589d/go.mod h1:olbEx9yVcaw5tHTNlRamRoxmMKcvDvcVS1YLnQGzvWE=
|
||||
github.com/metacubex/sing-vmess v0.1.9-0.20240719134745-1df6fb20bbf9 h1:OAXiCosqY8xKDp3pqTW3qbrCprZ1l6WkrXSFSCwyY4I=
|
||||
github.com/metacubex/sing-vmess v0.1.9-0.20240719134745-1df6fb20bbf9/go.mod h1:olVkD4FChQ5gKMHG4ZzuD7+fMkJY1G8vwOKpRehjrmY=
|
||||
github.com/metacubex/sing-wireguard v0.0.0-20240618022557-a6efaa37127a h1:NpSGclHJUYndUwBmyIpFBSoBVg8PoVX7QQKhYg0DjM0=
|
||||
github.com/metacubex/sing-wireguard v0.0.0-20240618022557-a6efaa37127a/go.mod h1:uY+BYb0UEknLrqvbGcwi9i++KgrKxsurysgI6G1Pveo=
|
||||
github.com/metacubex/tfo-go v0.0.0-20240228025757-be1269474a66 h1:as/aO/fM8nv4W4pOr9EETP6kV/Oaujk3fUNyQSJK61c=
|
||||
github.com/metacubex/tfo-go v0.0.0-20240228025757-be1269474a66/go.mod h1:c7bVFM9f5+VzeZ/6Kg77T/jrg1Xp8QpqlSHvG/aXVts=
|
||||
github.com/miekg/dns v1.1.58 h1:ca2Hdkz+cDg/7eNF6V56jjzuZ4aCAE+DbVkILdQWG/4=
|
||||
github.com/miekg/dns v1.1.58/go.mod h1:Ypv+3b/KadlvW9vJfXOTf300O4UqaHFzFCuHz+rPkBY=
|
||||
github.com/metacubex/utls v1.6.6 h1:3D12YKHTf2Z41UPhQU2dWerNWJ5TVQD9gKoQ+H+iLC8=
|
||||
github.com/metacubex/utls v1.6.6/go.mod h1:+WLFUnXjcpdxXCnyX25nggw8C6YonZ8zOK2Zm/oRvdo=
|
||||
github.com/miekg/dns v1.1.61 h1:nLxbwF3XxhwVSm8g9Dghm9MHPaUZuqhPiGL+675ZmEs=
|
||||
github.com/miekg/dns v1.1.61/go.mod h1:mnAarhS3nWaW+NVP2wTkYVIZyHNJ098SJZUki3eykwQ=
|
||||
github.com/mroth/weightedrand/v2 v2.1.0 h1:o1ascnB1CIVzsqlfArQQjeMy1U0NcIbBO5rfd5E/OeU=
|
||||
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=
|
||||
@@ -146,8 +149,8 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN
|
||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw=
|
||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
|
||||
github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g=
|
||||
github.com/puzpuzpuz/xsync/v3 v3.1.0 h1:EewKT7/LNac5SLiEblJeUu8z5eERHrmRLnMQL2d7qX4=
|
||||
github.com/puzpuzpuz/xsync/v3 v3.1.0/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPKP9jpmh0nnA=
|
||||
github.com/puzpuzpuz/xsync/v3 v3.2.0 h1:9AzuUeF88YC5bK8u2vEG1Fpvu4wgpM1wfPIExfaaDxQ=
|
||||
github.com/puzpuzpuz/xsync/v3 v3.2.0/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPKP9jpmh0nnA=
|
||||
github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo=
|
||||
github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A=
|
||||
github.com/quic-go/qtls-go1-20 v0.4.1 h1:D33340mCNDAIKBqXuAvexTNMUByrYmFYVfKfDN5nfFs=
|
||||
@@ -155,26 +158,27 @@ github.com/quic-go/qtls-go1-20 v0.4.1/go.mod h1:X9Nh97ZL80Z+bX/gUXMbipO6OxdiDi58
|
||||
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
||||
github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a h1:+NkI2670SQpQWvkkD2QgdTuzQG263YZ+2emfpeyGqW0=
|
||||
github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a/go.mod h1:63s7jpZqcDAIpj8oI/1v4Izok+npJOHACFCU6+huCkM=
|
||||
github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97 h1:iL5gZI3uFp0X6EslacyapiRz7LLSJyr4RajF/BhMVyE=
|
||||
github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM=
|
||||
github.com/sagernet/fswatch v0.1.1 h1:YqID+93B7VRfqIH3PArW/XpJv5H4OLEVWDfProGoRQs=
|
||||
github.com/sagernet/fswatch v0.1.1/go.mod h1:nz85laH0mkQqJfaOrqPpkwtU1znMFNVTpT/5oRsVz/o=
|
||||
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/nftables v0.3.0-beta.4 h1:kbULlAwAC3jvdGAC1P5Fa3GSxVwQJibNenDW2zaXr8I=
|
||||
github.com/sagernet/nftables v0.3.0-beta.4/go.mod h1:OQXAjvjNGGFxaTgVCSTRIhYB5/llyVDeapVoENYBDS8=
|
||||
github.com/sagernet/sing-mux v0.2.1-0.20240124034317-9bfb33698bb6 h1:5bCAkvDDzSMITiHFjolBwpdqYsvycdTu71FsMEFXQ14=
|
||||
github.com/sagernet/sing-mux v0.2.1-0.20240124034317-9bfb33698bb6/go.mod h1:khzr9AOPocLa+g53dBplwNDz4gdsyx/YM3swtAhlkHQ=
|
||||
github.com/sagernet/sing-shadowtls v0.1.4 h1:aTgBSJEgnumzFenPvc+kbD9/W0PywzWevnVpEx6Tw3k=
|
||||
github.com/sagernet/sing-shadowtls v0.1.4/go.mod h1:F8NBgsY5YN2beQavdgdm1DPlhaKQlaL6lpDdcBglGK4=
|
||||
github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 h1:DImB4lELfQhplLTxeq2z31Fpv8CQqqrUwTbrIRumZqQ=
|
||||
github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7/go.mod h1:FP9X2xjT/Az1EsG/orYYoC+5MojWnuI7hrffz8fGwwo=
|
||||
github.com/sagernet/utls v1.5.4 h1:KmsEGbB2dKUtCNC+44NwAdNAqnqQ6GA4pTO0Yik56co=
|
||||
github.com/sagernet/utls v1.5.4/go.mod h1:CTGxPWExIloRipK3XFpYv0OVyhO8kk3XCGW/ieyTh1s=
|
||||
github.com/sagernet/wireguard-go v0.0.0-20231209092712-9a439356a62e h1:iGH0RMv2FzELOFNFQtvsxH7NPmlo7X5JizEK51UCojo=
|
||||
github.com/sagernet/wireguard-go v0.0.0-20231209092712-9a439356a62e/go.mod h1:YbL4TKHRR6APYQv3U2RGfwLDpPYSyWz6oUlpISBEzBE=
|
||||
github.com/samber/lo v1.39.0 h1:4gTz1wUhNYLhFSKl6O+8peW0v2F4BCY034GRpU9WnuA=
|
||||
github.com/samber/lo v1.39.0/go.mod h1:+m/ZKRl6ClXCE2Lgf3MsQlWfh4bn1bz6CXEOxnEXnEA=
|
||||
github.com/shirou/gopsutil/v3 v3.24.2 h1:kcR0erMbLg5/3LcInpw0X/rrPSqq4CDPyI6A6ZRC18Y=
|
||||
github.com/shirou/gopsutil/v3 v3.24.2/go.mod h1:tSg/594BcA+8UdQU2XcW803GWYgdtauFFPgJCJKZlVk=
|
||||
github.com/shirou/gopsutil/v3 v3.24.5 h1:i0t8kL+kQTvpAYToeuiVk3TgDeKOFioZO3Ztz/iZ9pI=
|
||||
github.com/shirou/gopsutil/v3 v3.24.5/go.mod h1:bsoOS1aStSs9ErQ1WWfxllSeS1K5D+U30r2NfcubMVk=
|
||||
github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM=
|
||||
github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ=
|
||||
github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU=
|
||||
github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k=
|
||||
github.com/sina-ghaderi/poly1305 v0.0.0-20220724002748-c5926b03988b h1:rXHg9GrUEtWZhEkrykicdND3VPjlVbYiLdX9J7gimS8=
|
||||
github.com/sina-ghaderi/poly1305 v0.0.0-20220724002748-c5926b03988b/go.mod h1:X7qrxNQViEaAN9LNZOPl9PfvQtp3V3c7LTo0dvGi0fM=
|
||||
github.com/sina-ghaderi/rabaead v0.0.0-20220730151906-ab6e06b96e8c h1:DjKMC30y6yjG3IxDaeAj3PCoRr+IsO+bzyT+Se2m2Hk=
|
||||
@@ -204,14 +208,14 @@ github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923/go.mod h1:eLL9Nub3yfAho
|
||||
github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE=
|
||||
github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU=
|
||||
github.com/vishvananda/netns v0.0.0-20210104183010-2eb08e3e575f/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0=
|
||||
github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 h1:gga7acRE695APm9hlsSMoOoE65U4/TcqNj90mc69Rlg=
|
||||
github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0=
|
||||
github.com/vishvananda/netns v0.0.4 h1:Oeaw1EM2JMxD51g9uhtC0D7erkIjgmj8+JZc26m1YX8=
|
||||
github.com/vishvananda/netns v0.0.4/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=
|
||||
github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/fJgbpc=
|
||||
github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw=
|
||||
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
|
||||
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
||||
github.com/zhangyunhao116/fastrand v0.3.0 h1:7bwe124xcckPulX6fxtr2lFdO2KQqaefdtbk+mqO/Ig=
|
||||
github.com/zhangyunhao116/fastrand v0.3.0/go.mod h1:0v5KgHho0VE6HU192HnY15de/oDS8UrbBChIFjIhBtc=
|
||||
gitlab.com/go-extension/aes-ccm v0.0.0-20230221065045-e58665ef23c7 h1:UNrDfkQqiEYzdMlNsVvBYOAJWZjdktqFE9tQh5BT2+4=
|
||||
gitlab.com/go-extension/aes-ccm v0.0.0-20230221065045-e58665ef23c7/go.mod h1:E+rxHvJG9H6PUdzq9NRG6csuLN3XUx98BfGOVWNYnXs=
|
||||
gitlab.com/yawning/bsaes.git v0.0.0-20190805113838-0a714cd429ec h1:FpfFs4EhNehiVfzQttTuxanPIT43FtkkCFypIod8LHo=
|
||||
gitlab.com/yawning/bsaes.git v0.0.0-20190805113838-0a714cd429ec/go.mod h1:BZ1RAoRPbCxum9Grlv5aeksu2H8BiKehBYooU2LFiOQ=
|
||||
go.uber.org/automaxprocs v1.5.3 h1:kWazyxZUrS3Gs4qUpbwo5kEIMGe/DAvi5Z4tl2NW4j8=
|
||||
@@ -222,21 +226,21 @@ go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBs
|
||||
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA=
|
||||
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
|
||||
golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 h1:LfspQV/FYTatPTr/3HzIcmiUFH7PGP+OQ6mgDYo3yuQ=
|
||||
golang.org/x/exp v0.0.0-20240222234643-814bf88cf225/go.mod h1:CxmFvTBINI24O/j8iY7H1xHzx2i4OsyguNBmN/uPtqc=
|
||||
golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI=
|
||||
golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM=
|
||||
golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 h1:yixxcjnhBmY0nkL253HFVIm0JsFHwrHdT3Yh6szTnfY=
|
||||
golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8/go.mod h1:jj3sYF3dwk5D+ghuXyeI3r5MFf+NT2An6/9dOA95KSI=
|
||||
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/mod v0.15.0 h1:SernR4v+D55NyBH2QiEQrlBAnj1ECL6AGrA5+dPaMY8=
|
||||
golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0=
|
||||
golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc=
|
||||
golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
|
||||
golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ=
|
||||
golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
|
||||
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
|
||||
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
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=
|
||||
@@ -252,26 +256,26 @@ golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
|
||||
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8=
|
||||
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
|
||||
golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.21.0 h1:WVXCp+/EBEHOj53Rvu+7KiT/iElMrO8ACK16SMZ3jaA=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
|
||||
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
|
||||
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
|
||||
golang.org/x/time v0.5.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.18.0 h1:k8NLag8AGHnn+PHbl7g43CtqZAwG60vZkLqgyZgIHgQ=
|
||||
golang.org/x/tools v0.18.0/go.mod h1:GL7B4CwcLLeo59yx/9UWWuNOW1n3VZ4f5axWfML7Lcg=
|
||||
golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA=
|
||||
golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
|
||||
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
|
||||
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
lukechampine.com/blake3 v1.2.1 h1:YuqqRuaqsGV71BV/nm9xlI0MKUv4QC54jQnBChWbGnI=
|
||||
lukechampine.com/blake3 v1.2.1/go.mod h1:0OFRp7fBtAylGVCO40o87sbupkyIGgbpv1+M1k1LM6k=
|
||||
lukechampine.com/blake3 v1.3.0 h1:sJ3XhFINmHSrYCgl958hscfIa3bw8x4DqMP3u1YvoYE=
|
||||
lukechampine.com/blake3 v1.3.0/go.mod h1:0OFRp7fBtAylGVCO40o87sbupkyIGgbpv1+M1k1LM6k=
|
||||
|
||||
@@ -97,7 +97,7 @@ func ApplyConfig(cfg *config.Config, force bool) {
|
||||
updateHosts(cfg.Hosts)
|
||||
updateGeneral(cfg.General)
|
||||
updateNTP(cfg.NTP)
|
||||
updateDNS(cfg.DNS, cfg.RuleProviders, cfg.General.IPv6)
|
||||
updateDNS(cfg.DNS, cfg.General.IPv6)
|
||||
updateListeners(cfg.General, cfg.Listeners, force)
|
||||
updateIPTables(cfg)
|
||||
updateTun(cfg.General)
|
||||
@@ -211,7 +211,7 @@ func updateNTP(c *config.NTP) {
|
||||
}
|
||||
}
|
||||
|
||||
func updateDNS(c *config.DNS, ruleProvider map[string]provider.RuleProvider, generalIPv6 bool) {
|
||||
func updateDNS(c *config.DNS, generalIPv6 bool) {
|
||||
if !c.Enable {
|
||||
resolver.DefaultResolver = nil
|
||||
resolver.DefaultHostMapper = nil
|
||||
@@ -237,7 +237,7 @@ func updateDNS(c *config.DNS, ruleProvider map[string]provider.RuleProvider, gen
|
||||
Default: c.DefaultNameserver,
|
||||
Policy: c.NameServerPolicy,
|
||||
ProxyServer: c.ProxyServerNameserver,
|
||||
RuleProviders: ruleProvider,
|
||||
Tunnel: tunnel.Tunnel,
|
||||
CacheAlgorithm: c.CacheAlgorithm,
|
||||
}
|
||||
|
||||
@@ -253,6 +253,7 @@ func updateDNS(c *config.DNS, ruleProvider map[string]provider.RuleProvider, gen
|
||||
resolver.DefaultResolver = r
|
||||
resolver.DefaultHostMapper = m
|
||||
resolver.DefaultLocalServer = dns.NewLocalServer(r, m)
|
||||
resolver.UseSystemHosts = c.UseSystemHosts
|
||||
|
||||
if pr.Invalid() {
|
||||
resolver.ProxyServerHostResolver = pr
|
||||
@@ -354,7 +355,7 @@ func updateTun(general *config.General) {
|
||||
return
|
||||
}
|
||||
listener.ReCreateTun(general.Tun, tunnel.Tunnel)
|
||||
listener.ReCreateRedirToTun(general.Tun.RedirectToTun)
|
||||
listener.ReCreateRedirToTun(general.EBpf.RedirectToTun)
|
||||
}
|
||||
|
||||
func updateSniffer(sniffer *config.Sniffer) {
|
||||
@@ -506,9 +507,7 @@ func updateIPTables(cfg *config.Config) {
|
||||
inboundInterface = iptables.InboundInterface
|
||||
}
|
||||
|
||||
if dialer.DefaultRoutingMark.Load() == 0 {
|
||||
dialer.DefaultRoutingMark.Store(2158)
|
||||
}
|
||||
dialer.DefaultRoutingMark.CompareAndSwap(0, 2158)
|
||||
|
||||
err = tproxy.SetTProxyIPTables(inboundInterface, bypass, uint16(tProxyPort), DnsRedirect, dnsPort.Port())
|
||||
if err != nil {
|
||||
|
||||
13
hub/hub.go
13
hub/hub.go
@@ -21,6 +21,12 @@ func WithExternalController(externalController string) Option {
|
||||
}
|
||||
}
|
||||
|
||||
func WithExternalControllerUnix(externalControllerUnix string) Option {
|
||||
return func(cfg *config.Config) {
|
||||
cfg.General.ExternalControllerUnix = externalControllerUnix
|
||||
}
|
||||
}
|
||||
|
||||
func WithSecret(secret string) Option {
|
||||
return func(cfg *config.Config) {
|
||||
cfg.General.Secret = secret
|
||||
@@ -44,7 +50,12 @@ func Parse(options ...Option) error {
|
||||
|
||||
if cfg.General.ExternalController != "" {
|
||||
go route.Start(cfg.General.ExternalController, cfg.General.ExternalControllerTLS,
|
||||
cfg.General.Secret, cfg.TLS.Certificate, cfg.TLS.PrivateKey, cfg.General.LogLevel == log.DEBUG)
|
||||
cfg.General.Secret, cfg.TLS.Certificate, cfg.TLS.PrivateKey, cfg.General.ExternalDohServer,
|
||||
cfg.General.LogLevel == log.DEBUG)
|
||||
}
|
||||
|
||||
if cfg.General.ExternalControllerUnix != "" {
|
||||
go route.StartUnix(cfg.General.ExternalControllerUnix, cfg.General.ExternalDohServer, cfg.General.LogLevel == log.DEBUG)
|
||||
}
|
||||
|
||||
executor.ApplyConfig(cfg, true)
|
||||
|
||||
@@ -4,11 +4,11 @@ import (
|
||||
"net/http"
|
||||
"net/netip"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
|
||||
"github.com/metacubex/mihomo/adapter/inbound"
|
||||
"github.com/metacubex/mihomo/component/dialer"
|
||||
"github.com/metacubex/mihomo/component/resolver"
|
||||
"github.com/metacubex/mihomo/component/updater"
|
||||
"github.com/metacubex/mihomo/config"
|
||||
C "github.com/metacubex/mihomo/constant"
|
||||
"github.com/metacubex/mihomo/hub/executor"
|
||||
@@ -21,11 +21,6 @@ import (
|
||||
"github.com/go-chi/render"
|
||||
)
|
||||
|
||||
var (
|
||||
updateGeoMux sync.Mutex
|
||||
updatingGeo = false
|
||||
)
|
||||
|
||||
func configRouter() http.Handler {
|
||||
r := chi.NewRouter()
|
||||
r.Get("/", getConfigs)
|
||||
@@ -73,25 +68,34 @@ type tunSchema struct {
|
||||
GSO *bool `yaml:"gso" json:"gso,omitempty"`
|
||||
GSOMaxSize *uint32 `yaml:"gso-max-size" json:"gso-max-size,omitempty"`
|
||||
//Inet4Address *[]netip.Prefix `yaml:"inet4-address" json:"inet4-address,omitempty"`
|
||||
Inet6Address *[]netip.Prefix `yaml:"inet6-address" json:"inet6-address,omitempty"`
|
||||
StrictRoute *bool `yaml:"strict-route" json:"strict-route,omitempty"`
|
||||
Inet6Address *[]netip.Prefix `yaml:"inet6-address" json:"inet6-address,omitempty"`
|
||||
IPRoute2TableIndex *int `yaml:"iproute2-table-index" json:"iproute2_table_index,omitempty"`
|
||||
IPRoute2RuleIndex *int `yaml:"iproute2-rule-index" json:"iproute2_rule_index,omitempty"`
|
||||
AutoRedirect *bool `yaml:"auto-redirect" json:"auto_redirect,omitempty"`
|
||||
AutoRedirectInputMark *uint32 `yaml:"auto-redirect-input-mark" json:"auto_redirect_input_mark,omitempty"`
|
||||
AutoRedirectOutputMark *uint32 `yaml:"auto-redirect-output-mark" json:"auto_redirect_output_mark,omitempty"`
|
||||
StrictRoute *bool `yaml:"strict-route" json:"strict-route,omitempty"`
|
||||
RouteAddress *[]netip.Prefix `yaml:"route-address" json:"route_address,omitempty"`
|
||||
RouteAddressSet *[]string `yaml:"route-address-set" json:"route_address_set,omitempty"`
|
||||
RouteExcludeAddress *[]netip.Prefix `yaml:"route-exclude-address" json:"route_exclude_address,omitempty"`
|
||||
RouteExcludeAddressSet *[]string `yaml:"route-exclude-address-set" json:"route_exclude_address_set,omitempty"`
|
||||
IncludeInterface *[]string `yaml:"include-interface" json:"include-interface,omitempty"`
|
||||
ExcludeInterface *[]string `yaml:"exclude-interface" json:"exclude-interface,omitempty"`
|
||||
IncludeUID *[]uint32 `yaml:"include-uid" json:"include-uid,omitempty"`
|
||||
IncludeUIDRange *[]string `yaml:"include-uid-range" json:"include-uid-range,omitempty"`
|
||||
ExcludeUID *[]uint32 `yaml:"exclude-uid" json:"exclude-uid,omitempty"`
|
||||
ExcludeUIDRange *[]string `yaml:"exclude-uid-range" json:"exclude-uid-range,omitempty"`
|
||||
IncludeAndroidUser *[]int `yaml:"include-android-user" json:"include-android-user,omitempty"`
|
||||
IncludePackage *[]string `yaml:"include-package" json:"include-package,omitempty"`
|
||||
ExcludePackage *[]string `yaml:"exclude-package" json:"exclude-package,omitempty"`
|
||||
EndpointIndependentNat *bool `yaml:"endpoint-independent-nat" json:"endpoint-independent-nat,omitempty"`
|
||||
UDPTimeout *int64 `yaml:"udp-timeout" json:"udp-timeout,omitempty"`
|
||||
FileDescriptor *int `yaml:"file-descriptor" json:"file-descriptor"`
|
||||
|
||||
Inet4RouteAddress *[]netip.Prefix `yaml:"inet4-route-address" json:"inet4-route-address,omitempty"`
|
||||
Inet6RouteAddress *[]netip.Prefix `yaml:"inet6-route-address" json:"inet6-route-address,omitempty"`
|
||||
Inet4RouteExcludeAddress *[]netip.Prefix `yaml:"inet4-route-exclude-address" json:"inet4-route-exclude-address,omitempty"`
|
||||
Inet6RouteExcludeAddress *[]netip.Prefix `yaml:"inet6-route-exclude-address" json:"inet6-route-exclude-address,omitempty"`
|
||||
IncludeInterface *[]string `yaml:"include-interface" json:"include-interface,omitempty"`
|
||||
ExcludeInterface *[]string `yaml:"exclude-interface" json:"exclude-interface,omitempty"`
|
||||
IncludeUID *[]uint32 `yaml:"include-uid" json:"include-uid,omitempty"`
|
||||
IncludeUIDRange *[]string `yaml:"include-uid-range" json:"include-uid-range,omitempty"`
|
||||
ExcludeUID *[]uint32 `yaml:"exclude-uid" json:"exclude-uid,omitempty"`
|
||||
ExcludeUIDRange *[]string `yaml:"exclude-uid-range" json:"exclude-uid-range,omitempty"`
|
||||
IncludeAndroidUser *[]int `yaml:"include-android-user" json:"include-android-user,omitempty"`
|
||||
IncludePackage *[]string `yaml:"include-package" json:"include-package,omitempty"`
|
||||
ExcludePackage *[]string `yaml:"exclude-package" json:"exclude-package,omitempty"`
|
||||
EndpointIndependentNat *bool `yaml:"endpoint-independent-nat" json:"endpoint-independent-nat,omitempty"`
|
||||
UDPTimeout *int64 `yaml:"udp-timeout" json:"udp-timeout,omitempty"`
|
||||
FileDescriptor *int `yaml:"file-descriptor" json:"file-descriptor"`
|
||||
TableIndex *int `yaml:"table-index" json:"table-index"`
|
||||
}
|
||||
|
||||
type tuicServerSchema struct {
|
||||
@@ -162,6 +166,36 @@ func pointerOrDefaultTun(p *tunSchema, def LC.Tun) LC.Tun {
|
||||
if p.Inet6Address != nil {
|
||||
def.Inet6Address = *p.Inet6Address
|
||||
}
|
||||
if p.IPRoute2TableIndex != nil {
|
||||
def.IPRoute2TableIndex = *p.IPRoute2TableIndex
|
||||
}
|
||||
if p.IPRoute2RuleIndex != nil {
|
||||
def.IPRoute2RuleIndex = *p.IPRoute2RuleIndex
|
||||
}
|
||||
if p.AutoRedirect != nil {
|
||||
def.AutoRedirect = *p.AutoRedirect
|
||||
}
|
||||
if p.AutoRedirectInputMark != nil {
|
||||
def.AutoRedirectInputMark = *p.AutoRedirectInputMark
|
||||
}
|
||||
if p.AutoRedirectOutputMark != nil {
|
||||
def.AutoRedirectOutputMark = *p.AutoRedirectOutputMark
|
||||
}
|
||||
if p.StrictRoute != nil {
|
||||
def.StrictRoute = *p.StrictRoute
|
||||
}
|
||||
if p.RouteAddress != nil {
|
||||
def.RouteAddress = *p.RouteAddress
|
||||
}
|
||||
if p.RouteAddressSet != nil {
|
||||
def.RouteAddressSet = *p.RouteAddressSet
|
||||
}
|
||||
if p.RouteExcludeAddress != nil {
|
||||
def.RouteExcludeAddress = *p.RouteExcludeAddress
|
||||
}
|
||||
if p.RouteExcludeAddressSet != nil {
|
||||
def.RouteExcludeAddressSet = *p.RouteExcludeAddressSet
|
||||
}
|
||||
if p.Inet4RouteAddress != nil {
|
||||
def.Inet4RouteAddress = *p.Inet4RouteAddress
|
||||
}
|
||||
@@ -210,9 +244,6 @@ func pointerOrDefaultTun(p *tunSchema, def LC.Tun) LC.Tun {
|
||||
if p.FileDescriptor != nil {
|
||||
def.FileDescriptor = *p.FileDescriptor
|
||||
}
|
||||
if p.TableIndex != nil {
|
||||
def.TableIndex = *p.TableIndex
|
||||
}
|
||||
}
|
||||
return def
|
||||
}
|
||||
@@ -369,40 +400,25 @@ func updateConfigs(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
func updateGeoDatabases(w http.ResponseWriter, r *http.Request) {
|
||||
updateGeoMux.Lock()
|
||||
|
||||
if updatingGeo {
|
||||
updateGeoMux.Unlock()
|
||||
render.Status(r, http.StatusBadRequest)
|
||||
render.JSON(w, r, newError("updating..."))
|
||||
err := updater.UpdateGeoDatabases()
|
||||
if err != nil {
|
||||
log.Errorln("[REST-API] update GEO databases failed: %v", err)
|
||||
render.Status(r, http.StatusInternalServerError)
|
||||
render.JSON(w, r, newError(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
updatingGeo = true
|
||||
updateGeoMux.Unlock()
|
||||
cfg, err := executor.ParseWithPath(C.Path.Config())
|
||||
if err != nil {
|
||||
log.Errorln("[REST-API] update GEO databases failed: %v", err)
|
||||
render.Status(r, http.StatusInternalServerError)
|
||||
render.JSON(w, r, newError("Error parsing configuration"))
|
||||
return
|
||||
}
|
||||
|
||||
go func() {
|
||||
defer func() {
|
||||
updatingGeo = false
|
||||
}()
|
||||
log.Warnln("[GEO] update GEO databases success, applying config")
|
||||
|
||||
log.Warnln("[REST-API] updating GEO databases...")
|
||||
|
||||
if err := config.UpdateGeoDatabases(); err != nil {
|
||||
log.Errorln("[REST-API] update GEO databases failed: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
cfg, err := executor.ParseWithPath(C.Path.Config())
|
||||
if err != nil {
|
||||
log.Errorln("[REST-API] update GEO databases failed: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
log.Warnln("[REST-API] update GEO databases successful, apply config...")
|
||||
|
||||
executor.ApplyConfig(cfg, false)
|
||||
}()
|
||||
executor.ApplyConfig(cfg, false)
|
||||
|
||||
render.NoContent(w, r)
|
||||
}
|
||||
|
||||
63
hub/route/doh.go
Normal file
63
hub/route/doh.go
Normal file
@@ -0,0 +1,63 @@
|
||||
package route
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
"github.com/metacubex/mihomo/component/resolver"
|
||||
|
||||
"github.com/go-chi/render"
|
||||
)
|
||||
|
||||
func dohRouter() http.Handler {
|
||||
return http.HandlerFunc(dohHandler)
|
||||
}
|
||||
|
||||
func dohHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if resolver.DefaultResolver == nil {
|
||||
render.Status(r, http.StatusInternalServerError)
|
||||
render.PlainText(w, r, "DNS section is disabled")
|
||||
return
|
||||
}
|
||||
|
||||
var dnsData []byte
|
||||
var err error
|
||||
switch r.Method {
|
||||
case "GET":
|
||||
dnsData, err = base64.RawURLEncoding.DecodeString(r.URL.Query().Get("dns"))
|
||||
case "POST":
|
||||
if r.Header.Get("Content-Type") != "application/dns-message" {
|
||||
render.Status(r, http.StatusInternalServerError)
|
||||
render.PlainText(w, r, "invalid content-type")
|
||||
return
|
||||
}
|
||||
reader := io.LimitReader(r.Body, 65535) // according to rfc8484, the maximum size of the DNS message is 65535 bytes
|
||||
dnsData, err = io.ReadAll(reader)
|
||||
_ = r.Body.Close()
|
||||
default:
|
||||
render.Status(r, http.StatusMethodNotAllowed)
|
||||
render.PlainText(w, r, "method not allowed")
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
render.Status(r, http.StatusInternalServerError)
|
||||
render.PlainText(w, r, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), resolver.DefaultDNSTimeout)
|
||||
defer cancel()
|
||||
|
||||
dnsData, err = resolver.RelayDnsPacket(ctx, dnsData, dnsData)
|
||||
if err != nil {
|
||||
render.Status(r, http.StatusInternalServerError)
|
||||
render.PlainText(w, r, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/dns-message")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
_, _ = w.Write(dnsData)
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user