Compare commits

..

92 Commits

Author SHA1 Message Date
github-actions[bot]
0128a0bb1f Merge branch 'Alpha' into Meta 2024-07-28 05:50:11 +00:00
wwqgtxx
c830b8aaf7 feat: support convert mrs format back to text format 2024-07-28 11:00:27 +08:00
wwqgtxx
1db3e4583b chore: better converter 2024-07-27 23:54:28 +08:00
wwqgtxx
4f8a5a5f54 feat: add mrs format ipcidr ruleset 2024-07-27 10:36:11 +08:00
wwqgtxx
303f6e4567 feat: add mrs format domain ruleset 2024-07-26 22:30:42 +08:00
wwqgtxx
0d90a93645 chore: sort proxies and providers by name before include all 2024-07-26 10:59:39 +08:00
wwqgtxx
40c9829328 fix: auth with CFMA compile issue 2024-07-26 10:55:03 +08:00
wwqgtxx
4051ea522a chore: improve authentication parsing logic in http listener (#1336) 2024-07-25 19:49:56 +08:00
wwqgtxx
cc7823dad8 fix: remove unneeded http proxy compression 2024-07-24 14:56:46 +08:00
wwqgtxx
d6a1af23a7 feat: local file type provider will auto update after modify 2024-07-24 14:37:10 +08:00
wwqgtxx
313493cc94 chore: add fswatch 2024-07-24 14:37:10 +08:00
wwqgtxx
4b9fdacbad feat: doh client support plain http and skip-cert-verify 2024-07-23 10:48:54 +08:00
wwqgtxx
13b7ab8da3 fix: better doh server compatibility 2024-07-23 08:46:27 +08:00
wwqgtxx
de61e81ff7 feat: support external-doh-server 2024-07-23 00:01:41 +08:00
wwqgtxx
4eb13a73bf fix: wrong usage of RLock 2024-07-22 09:57:57 +08:00
wwqgtxx
fd5b537ab1 fix: doh concurrent race issue 2024-07-21 23:03:04 +08:00
ruokeqx
28794c62c4 chore: reduce func findProcessName mem allocs and copy (#1393) 2024-07-19 22:24:27 +08:00
wwqgtxx
e263518f01 fix: some auto-redirect issue 2024-07-19 22:20:07 +08:00
wwqgtxx
345061a7cc chore: support some chacha8 method 2024-07-19 22:08:05 +08:00
wwqgtxx
a05016a5da chore: better dns logging 2024-07-19 19:27:29 +08:00
xishang0128
9e3589d638 chore: include-all-providers logic correction 2024-07-15 13:12:40 +08:00
xishang0128
fc03bd2f0d chore: Modify the default value to avoid outputting Deprecated 2024-07-12 02:59:14 +08:00
github-actions[bot]
4277dc6eab Merge branch 'Alpha' into Meta 2024-07-01 15:05:18 +00:00
xishang0128
0e228765fc fix: Make the ruleset take effect in a single line 2024-06-28 14:14:36 +08:00
wwqgtxx
f45ccc0761 chore: update dependencies 2024-06-27 09:52:47 +08:00
xishang0128
9f4cd646c2 fix: dhcp:// with special notation cannot be parsed 2024-06-23 15:33:38 +08:00
wwqgtxx
a9ecc627e6 fix: subrule can't recursion correctly (#1339) 2024-06-22 13:18:23 +08:00
wwqgtxx
50286678bf fix: auto-redirect rule error 2024-06-22 13:08:15 +08:00
wwqgtxx
917c5fdd80 fix: auto-redirect android rules 2024-06-19 12:11:00 +08:00
wwqgtxx
1457f83530 fix: dns server using direct outbound lookback resolve problem 2024-06-18 13:15:20 +08:00
wwqgtxx
5ab8154e7e fix: wireguard ip update 2024-06-18 10:30:43 +08:00
wwqgtxx
09be5cbc99 feat: tun support auto-redirect, route-address-set and route-exclude-address-set 2024-06-17 22:04:51 +08:00
xishang0128
0738e18100 chore: add override fields 2024-06-16 18:19:04 +08:00
wwqgtxx
40f40f6d24 fix: dns dial to wrong target 2024-06-15 13:32:57 +08:00
wwqgtxx
ad5bc51c77 chore: deprecated the relay group type, please using dialer-proxy instead 2024-06-15 13:32:57 +08:00
wwqgtxx
75c16f9b87 feat: add refresh-server-ip-interval for wireguard outbound 2024-06-14 14:01:52 +08:00
wwqgtxx
d96d7651ca chore: add inner dns proxied connection log 2024-06-13 09:07:05 +08:00
wwqgtxx
a5f25a2246 chore: code split 2024-06-12 20:54:43 +08:00
wwqgtxx
2b4741fbc7 chore: add inner dns proxied connection statistic to restful api 2024-06-12 17:10:47 +08:00
wwqgtxx
f317baa8de feat: add respect-rules for dns 2024-06-12 15:25:34 +08:00
wwqgtxx
5678131591 fix: wireguard server resolve when only a server in peers 2024-06-12 11:37:23 +08:00
xishang0128
10f8ba4434 chore: Disable the loop back detector for CMFA 2024-06-12 04:46:13 +08:00
wwqgtxx
cacfefad4b fix: quic-go cached dial error 2024-06-10 08:48:23 +08:00
wwqgtxx
0d4e57cb21 chore: update quic-go to 0.45.0 2024-06-07 21:41:49 +08:00
wwqgtxx
063836fe5d chore: sync hysteria2 bbr changes
e0e75c4630
2024-06-05 11:56:27 +08:00
xishang0128
7b3c9e94e6 chore: Better package name handling on Android 2024-06-02 02:36:15 +08:00
wwqgtxx
be3d121ec6 fix: darwin calculate correct tunIndex 2024-06-01 13:36:57 +08:00
xishang0128
59fd3cffe3 ci: fix arm package create 2024-05-31 17:02:31 +08:00
wwqgtxx
39eda257a7 chore: replace zhangyunhao116/fastrand to our metacubex/randv2 2024-05-31 13:07:48 +08:00
wwqgtxx
d3fea909e9 chore: remove tfo windows support
Golang officially decided not to open `internal/poll.execIO` to third-party libraries after 1.23 was released, so we can only choose to remove tfo support on the Windows platform.
2024-05-30 10:39:17 +08:00
wwqgtxx
7eb70aeb4d fix: windows build number 2024-05-29 00:08:32 +08:00
xishang0128
846bdfa812 chore: Allow customization of GLOBAL 2024-05-25 08:09:59 +08:00
wwqgtxx
0b6ae6ffb8 feat: add ss-opts for trojan outbound like trojan-go's shadowsocks config
https://github.com/MetaCubeX/mihomo/issues/1269
2024-05-22 09:00:59 +08:00
wwqgtxx
71922dd0b1 fix: bad usage for exec in sing-tun
https://github.com/MetaCubeX/mihomo/issues/1234
2024-05-21 23:53:13 +08:00
moexiami
5eb8958ff2 fix: correct type for vmess.ws-opts.path in ConvertsV2Ray (#1145)
It should be a string for the following reasons:
1. During conversion, it is conditionally assigned with
   `wsOpts["path"] = path.(string)`
2. After conversion, it is decoded into `WSOptions.Path` in
   `adapter/outbound/vmess.go` which requires a string.
2024-05-21 23:31:28 +08:00
5aaee9
ac2506154f fix: possibly using released buffer in tproxy (#1286) 2024-05-21 23:25:28 +08:00
wwqgtxx
43bdc76f87 fix: darwin calculate correct tunIndex
https://github.com/MetaCubeX/mihomo/pull/1285
2024-05-21 19:13:44 +08:00
wwqgtxx
3195c678c7 chore: update quic-go to 0.44.0 2024-05-21 08:45:36 +08:00
Larvan2
bd43eca09d ci:docker tags 2024-05-20 19:47:25 +08:00
xishang0128
c504985b99 chore: Adjust sniff logs 2024-05-19 19:35:12 +08:00
Larvan2
4243a74284 chore: auto update geo 2024-05-19 18:51:06 +08:00
github-actions[bot]
48e481d0a2 Merge branch 'Alpha' into Meta 2024-05-19 08:50:40 +00:00
Larvan2
e749c7e492 ci: docker 2024-05-19 16:41:05 +08:00
Larvan2
c3ee921d30 chore: apply config when geo update 2024-05-19 15:46:23 +08:00
wwqgtxx
df69a31e62 chore: stop using go:linkname for crypto/tls.aesgcmPreferred and update utls to 1.6.6 2024-05-19 11:32:22 +08:00
Larvan2
b7c02a5923 ci: fix docker 2024-05-18 23:09:21 +08:00
wwqgtxx
bfb6caeeaf chore: stop using go:linkname for x/sys/windows 2024-05-18 20:54:28 +08:00
wwqgtxx
30a913aad6 chore: stop using go:linkname for net.lookupStaticHost 2024-05-18 20:45:15 +08:00
hunshcn
00e361c5ac chore: stop using go:linkname for http.registerOnHitEOF, http.requestBodyRemains (#1275)
relate to https://github.com/MetaCubeX/mihomo/pull/952#issuecomment-2118639385
2024-05-18 20:16:53 +08:00
Larvan2
56edd8f671 ci: better release 2024-05-18 18:36:00 +08:00
wwqgtxx
2b52809d2c chore: update quic-go to 0.43.1 2024-05-18 11:47:45 +08:00
Larvan2
5c3a9b1dfc fix: geo auto update #1261 2024-05-17 11:51:32 +08:00
xishang0128
fe88f0e437 chore: Ensure that some expressions take effect 2024-05-15 15:38:55 +08:00
wwqgtxx
87877d1b80 fix: don't ignore http.NewRequest's error 2024-05-15 13:53:18 +08:00
wwqgtxx
1bc3c16b59 feat: add PROCESS-NAME-REGEX and PROCESS-PATH-REGEX 2024-05-15 10:44:56 +08:00
wwqgtxx
ed1e7e32c7 action: revert more golang1.22 commit for win7 2024-05-15 09:14:34 +08:00
xishang0128
5da9ccaa98 action: Upgrade loongarch golang version 2024-05-15 08:32:57 +08:00
xishang0128
fd7ecc004f chore: Add filter for include-all-proxies 2024-05-13 20:30:31 +08:00
wwqgtxx
3ae4014b39 chore: disable tfo when lower than Windows 10.0.14393 2024-05-12 20:44:12 +08:00
wwqgtxx
a50339bd5f chore: swtich RtlGetNtVersionNumbers to RtlGetVersion
https://go-review.googlesource.com/c/go/+/571015
2024-05-12 20:23:13 +08:00
wwqgtxx
7df1c26942 fix: fingerprint passing 2024-05-12 19:34:25 +08:00
wwqgtxx
fc82a32a48 fix: system tun stack not working in win7 2024-05-12 15:52:10 +08:00
wwqgtxx
adf0ff588f action: let golang1.22's build can work on windows7/8 2024-05-12 13:32:07 +08:00
wwqgtxx
b840eae4c6 fix: x509 error in windows7/8 2024-05-12 12:36:48 +08:00
wwqgtxx
619f34119e action: add golang1.21 with special revert commit to work on Windows7 2024-05-12 00:10:51 +08:00
wwqgtxx
6d1c62bbf0 fix: shadowsocks uot not work with dialer-proxy 2024-05-08 09:27:31 +08:00
xishang0128
5dd883e790 chore: Add use-system-hosts option 2024-05-06 14:03:29 +08:00
Larvan2
a2b43faa0b Update README.md 2024-05-04 18:41:08 +08:00
wwqgtxx
8861eaf903 chore: hysteria2 will only change remote port in hopLoop 2024-05-01 09:41:22 +08:00
Pylogmon
107e3e7630 feat: Allow upgrade to latest release (#1235) 2024-04-30 17:01:46 +08:00
wwqgtxx
314c0bb34b fix: hy2 udp incompatible with quic-go 0.43.0 2024-04-29 12:14:11 +08:00
wwqgtxx
89a097faa8 chore: update quic-go to 0.43.0 2024-04-28 13:24:33 +08:00
153 changed files with 3899 additions and 1349 deletions

View 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

View 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

View 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

View File

@@ -1,6 +1,10 @@
name: Build name: Build
on: on:
workflow_dispatch: workflow_dispatch:
inputs:
version:
description: "Tag version to release"
required: true
push: push:
paths-ignore: paths-ignore:
- "docs/**" - "docs/**"
@@ -13,9 +17,8 @@ on:
pull_request_target: pull_request_target:
branches: branches:
- Alpha - Alpha
concurrency: concurrency:
group: ${{ github.workflow }}-${{ github.ref }} group: "${{ github.workflow }}-${{ github.ref }}"
cancel-in-progress: true cancel-in-progress: true
env: env:
@@ -64,6 +67,13 @@ jobs:
- { goos: android, goarch: arm, ndk: armv7a-linux-androideabi34, output: armv7 } - { goos: android, goarch: arm, ndk: armv7a-linux-androideabi34, output: armv7 }
- { goos: android, goarch: arm64, ndk: aarch64-linux-android34, output: arm64-v8 } - { 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. # 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: '386', output: '386-go120', goversion: '1.20' }
- { goos: windows, goarch: amd64, goamd64: v1, output: amd64-compatible-go120, goversion: '1.20' } - { goos: windows, goarch: amd64, goamd64: v1, output: amd64-compatible-go120, goversion: '1.20' }
@@ -94,28 +104,50 @@ jobs:
with: with:
go-version: ${{ matrix.jobs.goversion }} 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' }} if: ${{ matrix.jobs.goarch == 'loong64' && matrix.jobs.abi == '1' }}
run: | run: |
wget -q https://github.com/xishang0128/loongarch64-golang/releases/download/1.21.5/go1.21.5.linux-amd64-abi1.tar.gz 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.21.5.linux-amd64-abi1.tar.gz -C /usr/local sudo tar zxf go1.22.0.linux-amd64-abi1.tar.gz -C /usr/local
echo "/usr/local/go/bin" >> $GITHUB_PATH 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' }} if: ${{ matrix.jobs.goarch == 'loong64' && matrix.jobs.abi == '2' }}
run: | run: |
wget -q https://github.com/xishang0128/loongarch64-golang/releases/download/1.21.5/go1.21.5.linux-amd64-abi2.tar.gz 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.21.5.linux-amd64-abi2.tar.gz -C /usr/local sudo tar zxf go1.22.0.linux-amd64-abi2.tar.gz -C /usr/local
echo "/usr/local/go/bin" >> $GITHUB_PATH 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 - name: Set variables
if: ${{github.ref_name=='Alpha'}} if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.version != '' }}
run: echo "VERSION=alpha-$(git rev-parse --short HEAD)" >> $GITHUB_ENV run: echo "VERSION=${{ github.event.inputs.version }}" >> $GITHUB_ENV
shell: bash shell: bash
- name: Set variables - name: Set variables
if: ${{github.ref_name=='' || github.ref_type=='tag'}} if: ${{ github.event_name != 'workflow_dispatch' && github.ref_name == 'Alpha' }}
run: echo "VERSION=$(git describe --tags)" >> $GITHUB_ENV run: echo "VERSION=alpha-$(git rev-parse --short HEAD)" >> $GITHUB_ENV
shell: bash shell: bash
- name: Set Time Variable - name: Set Time Variable
@@ -174,6 +206,10 @@ jobs:
sudo apt-get install dpkg sudo apt-get install dpkg
if [ "${{matrix.jobs.abi}}" = "1" ]; then if [ "${{matrix.jobs.abi}}" = "1" ]; then
ARCH=loongarch64 ARCH=loongarch64
elif [ "${{matrix.jobs.goarm}}" = "7" ]; then
ARCH=armhf
elif [ "${{matrix.jobs.goarch}}" = "arm" ]; then
ARCH=armel
else else
ARCH=${{matrix.jobs.goarch}} ARCH=${{matrix.jobs.goarch}}
fi fi
@@ -238,7 +274,7 @@ jobs:
- name: Archive production artifacts - name: Archive production artifacts
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
with: with:
name: ${{ matrix.jobs.goos }}-${{ matrix.jobs.output }} name: "${{ matrix.jobs.goos }}-${{ matrix.jobs.output }}"
path: | path: |
mihomo*.gz mihomo*.gz
mihomo*.deb mihomo*.deb
@@ -248,7 +284,7 @@ jobs:
Upload-Prerelease: Upload-Prerelease:
permissions: write-all 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] needs: [build]
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
@@ -299,44 +335,62 @@ jobs:
Upload-Release: Upload-Release:
permissions: write-all permissions: write-all
if: ${{ github.ref_type=='tag' }} if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.version != '' }}
needs: [build] needs: [build]
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v4
with: with:
fetch-depth: 0 ref: Meta
fetch-depth: '0'
fetch-tags: 'true'
- name: Get tags - name: Get tags
run: | run: |
echo "CURRENTVERSION=${GITHUB_REF#refs/tags/}" >> $GITHUB_ENV echo "CURRENTVERSION=${{ github.event.inputs.version }}" >> $GITHUB_ENV
git fetch --tags git fetch --tags
echo "PREVERSION=$(git describe --tags --abbrev=0 HEAD^)" >> $GITHUB_ENV echo "PREVERSION=$(git describe --tags --abbrev=0 HEAD)" >> $GITHUB_ENV
- name: Generate release notes - name: Merge Alpha branch into Meta
run: | run: |
cp ./.github/genReleaseNote.sh ./ git config --global user.email "github-actions[bot]@users.noreply.github.com"
bash ./genReleaseNote.sh -v ${PREVERSION}...${CURRENTVERSION} git config --global user.name "github-actions[bot]"
rm ./genReleaseNote.sh git fetch origin Alpha:Alpha
git merge Alpha
git push origin Meta
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- uses: actions/download-artifact@v4 - name: Tag the commit
with: run: |
path: bin/ git tag ${{ github.event.inputs.version }}
merge-multiple: true git push origin ${{ github.event.inputs.version }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Display structure of downloaded files - name: Generate release notes
run: ls -R run: |
working-directory: bin cp ./.github/genReleaseNote.sh ./
bash ./genReleaseNote.sh -v ${PREVERSION}...${CURRENTVERSION}
- name: Upload Release rm ./genReleaseNote.sh
uses: softprops/action-gh-release@v1
if: ${{ success() }} - uses: actions/download-artifact@v4
with: with:
tag_name: ${{ github.ref_name }} path: bin/
files: bin/* merge-multiple: true
generate_release_notes: true
body_path: release.md - 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: Docker:
if: ${{ !startsWith(github.event_name, 'pull_request') }} if: ${{ !startsWith(github.event_name, 'pull_request') }}
@@ -365,20 +419,35 @@ jobs:
uses: docker/setup-buildx-action@v3 uses: docker/setup-buildx-action@v3
with: with:
version: latest version: latest
# Extract metadata (tags, labels) for Docker # Extract metadata (tags, labels) for Docker
# https://github.com/docker/metadata-action # https://github.com/docker/metadata-action
- name: Extract Docker metadata - name: Extract Docker metadata
id: meta if: ${{ github.event_name != 'workflow_dispatch' }}
id: meta_alpha
uses: docker/metadata-action@v5 uses: docker/metadata-action@v5
with: 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 - name: Show files
run: | run: |
ls . ls .
ls bin/ ls bin/
- name: login to docker REGISTRY - name: login to docker REGISTRY
uses: docker/login-action@v3 uses: docker/login-action@v3
with: with:
@@ -389,7 +458,7 @@ jobs:
# Build and push Docker image with Buildx (don't push on PR) # Build and push Docker image with Buildx (don't push on PR)
# https://github.com/docker/build-push-action # https://github.com/docker/build-push-action
- name: Build and push Docker image - name: Build and push Docker image
id: build-and-push if: ${{ github.event_name != 'workflow_dispatch' }}
uses: docker/build-push-action@v5 uses: docker/build-push-action@v5
with: with:
context: . context: .
@@ -400,5 +469,20 @@ jobs:
linux/amd64 linux/amd64
linux/arm64 linux/arm64
linux/arm/v7 linux/arm/v7
tags: ${{ steps.meta.outputs.tags }} tags: ${{ steps.meta_alpha.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }} 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 }}

View File

@@ -98,4 +98,3 @@ API.
This software is released under the GPL-3.0 license. This software is released under the GPL-3.0 license.
[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2FMetaCubeX%2Fmihomo.svg?type=large)](https://app.fossa.io/projects/git%2Bgithub.com%2FMetaCubeX%2Fmihomo?ref=badge_large)

View File

@@ -2,6 +2,7 @@ package adapter
import ( import (
"context" "context"
"crypto/tls"
"encoding/json" "encoding/json"
"fmt" "fmt"
"net" "net"
@@ -14,6 +15,7 @@ import (
"github.com/metacubex/mihomo/common/atomic" "github.com/metacubex/mihomo/common/atomic"
"github.com/metacubex/mihomo/common/queue" "github.com/metacubex/mihomo/common/queue"
"github.com/metacubex/mihomo/common/utils" "github.com/metacubex/mihomo/common/utils"
"github.com/metacubex/mihomo/component/ca"
"github.com/metacubex/mihomo/component/dialer" "github.com/metacubex/mihomo/component/dialer"
C "github.com/metacubex/mihomo/constant" C "github.com/metacubex/mihomo/constant"
"github.com/puzpuzpuz/xsync/v3" "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, IdleConnTimeout: 90 * time.Second,
TLSHandshakeTimeout: 10 * time.Second, TLSHandshakeTimeout: 10 * time.Second,
ExpectContinueTimeout: 1 * time.Second, ExpectContinueTimeout: 1 * time.Second,
TLSClientConfig: ca.GetGlobalTLSConfig(&tls.Config{}),
} }
client := http.Client{ client := http.Client{

View File

@@ -69,3 +69,5 @@ func WithDSCP(dscp uint8) Addition {
metadata.DSCP = dscp metadata.DSCP = dscp
} }
} }
func Placeholder(metadata *C.Metadata) {}

View File

@@ -3,22 +3,10 @@ package inbound
import ( import (
"context" "context"
"net" "net"
"github.com/metacubex/tfo-go"
) )
var (
lc = tfo.ListenConfig{
DisableTFO: true,
}
)
func SetTfo(open bool) {
lc.DisableTFO = !open
}
func SetMPTCP(open bool) { func SetMPTCP(open bool) {
setMultiPathTCP(&lc.ListenConfig, open) setMultiPathTCP(getListenConfig(), open)
} }
func ListenContext(ctx context.Context, network, address string) (net.Listener, error) { func ListenContext(ctx context.Context, network, address string) (net.Listener, error) {

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

View File

@@ -0,0 +1,15 @@
package inbound
import (
"net"
)
var (
lc = net.ListenConfig{}
)
func SetTfo(open bool) {}
func getListenConfig() *net.ListenConfig {
return &lc
}

View File

@@ -3,15 +3,19 @@ package outbound
import ( import (
"context" "context"
"errors" "errors"
"net/netip" "os"
"strconv"
N "github.com/metacubex/mihomo/common/net" N "github.com/metacubex/mihomo/common/net"
"github.com/metacubex/mihomo/component/dialer" "github.com/metacubex/mihomo/component/dialer"
"github.com/metacubex/mihomo/component/loopback" "github.com/metacubex/mihomo/component/loopback"
"github.com/metacubex/mihomo/component/resolver" "github.com/metacubex/mihomo/component/resolver"
C "github.com/metacubex/mihomo/constant" C "github.com/metacubex/mihomo/constant"
"github.com/metacubex/mihomo/constant/features"
) )
var DisableLoopBackDetector, _ = strconv.ParseBool(os.Getenv("DISABLE_LOOPBACK_DETECTOR"))
type Direct struct { type Direct struct {
*Base *Base
loopBack *loopback.Detector loopBack *loopback.Detector
@@ -24,8 +28,10 @@ type DirectOption struct {
// DialContext implements C.ProxyAdapter // DialContext implements C.ProxyAdapter
func (d *Direct) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.Conn, error) { func (d *Direct) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.Conn, error) {
if err := d.loopBack.CheckConn(metadata); err != nil { if !features.CMFA && !DisableLoopBackDetector {
return nil, err if err := d.loopBack.CheckConn(metadata); err != nil {
return nil, err
}
} }
opts = append(opts, dialer.WithResolver(resolver.DefaultResolver)) opts = append(opts, dialer.WithResolver(resolver.DefaultResolver))
c, err := dialer.DialContext(ctx, "tcp", metadata.RemoteAddress(), d.Base.DialOptions(opts...)...) 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 // ListenPacketContext implements C.ProxyAdapter
func (d *Direct) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) { func (d *Direct) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) {
if err := d.loopBack.CheckPacketConn(metadata); err != nil { if !features.CMFA && !DisableLoopBackDetector {
return nil, err 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 // net.UDPConn.WriteTo only working with *net.UDPAddr, so we need a net.UDPAddr
if !metadata.Resolved() { if !metadata.Resolved() {
@@ -49,13 +57,17 @@ func (d *Direct) ListenPacketContext(ctx context.Context, metadata *C.Metadata,
} }
metadata.DstIP = ip 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 { if err != nil {
return nil, err return nil, err
} }
return d.loopBack.NewPacketConn(newPacketConn(pc, d)), nil 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 { func NewDirectWithOption(option DirectOption) *Direct {
return &Direct{ return &Direct{
Base: &Base{ Base: &Base{

View File

@@ -21,8 +21,8 @@ import (
"github.com/metacubex/sing-quic/hysteria2" "github.com/metacubex/sing-quic/hysteria2"
"github.com/metacubex/randv2"
M "github.com/sagernet/sing/common/metadata" M "github.com/sagernet/sing/common/metadata"
"github.com/zhangyunhao116/fastrand"
) )
func init() { func init() {
@@ -165,7 +165,7 @@ func NewHysteria2(option Hysteria2Option) (*Hysteria2, error) {
}) })
if len(serverAddress) > 0 { if len(serverAddress) > 0 {
clientOptions.ServerAddress = func(ctx context.Context) (*net.UDPAddr, error) { 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 { if option.HopInterval == 0 {

View File

@@ -166,12 +166,6 @@ func (ss *ShadowSocks) ListenPacketContext(ctx context.Context, metadata *C.Meta
// ListenPacketWithDialer implements C.ProxyAdapter // ListenPacketWithDialer implements C.ProxyAdapter
func (ss *ShadowSocks) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, metadata *C.Metadata) (_ C.PacketConn, err error) { 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 { if ss.option.UDPOverTCP {
tcpConn, err := ss.DialContextWithDialer(ctx, dialer, metadata) tcpConn, err := ss.DialContextWithDialer(ctx, dialer, metadata)
if err != nil { if err != nil {
@@ -179,6 +173,12 @@ func (ss *ShadowSocks) ListenPacketWithDialer(ctx context.Context, dialer C.Dial
} }
return ss.ListenPacketOnStreamConn(ctx, tcpConn, metadata) 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) addr, err := resolveUDPAddrWithPrefer(ctx, "udp", ss.addr, ss.prefer)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -273,6 +273,7 @@ func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) {
if opts.TLS { if opts.TLS {
v2rayOption.TLS = true v2rayOption.TLS = true
v2rayOption.SkipCertVerify = opts.SkipCertVerify v2rayOption.SkipCertVerify = opts.SkipCertVerify
v2rayOption.Fingerprint = opts.Fingerprint
} }
} else if option.Plugin == shadowtls.Mode { } else if option.Plugin == shadowtls.Mode {
obfsMode = shadowtls.Mode obfsMode = shadowtls.Mode

View File

@@ -17,7 +17,7 @@ import (
"github.com/metacubex/mihomo/component/proxydialer" "github.com/metacubex/mihomo/component/proxydialer"
C "github.com/metacubex/mihomo/constant" C "github.com/metacubex/mihomo/constant"
"github.com/zhangyunhao116/fastrand" "github.com/metacubex/randv2"
"golang.org/x/crypto/ssh" "golang.org/x/crypto/ssh"
) )
@@ -180,10 +180,10 @@ func NewSsh(option SshOption) (*Ssh, error) {
} }
version := "SSH-2.0-OpenSSH_" version := "SSH-2.0-OpenSSH_"
if fastrand.Intn(2) == 0 { if randv2.IntN(2) == 0 {
version += "7." + strconv.Itoa(fastrand.Intn(10)) version += "7." + strconv.Itoa(randv2.IntN(10))
} else { } else {
version += "8." + strconv.Itoa(fastrand.Intn(9)) version += "8." + strconv.Itoa(randv2.IntN(9))
} }
config.ClientVersion = version config.ClientVersion = version

View File

@@ -3,6 +3,7 @@ package outbound
import ( import (
"context" "context"
"crypto/tls" "crypto/tls"
"errors"
"fmt" "fmt"
"net" "net"
"net/http" "net/http"
@@ -15,6 +16,7 @@ import (
tlsC "github.com/metacubex/mihomo/component/tls" tlsC "github.com/metacubex/mihomo/component/tls"
C "github.com/metacubex/mihomo/constant" C "github.com/metacubex/mihomo/constant"
"github.com/metacubex/mihomo/transport/gun" "github.com/metacubex/mihomo/transport/gun"
"github.com/metacubex/mihomo/transport/shadowsocks/core"
"github.com/metacubex/mihomo/transport/trojan" "github.com/metacubex/mihomo/transport/trojan"
) )
@@ -29,6 +31,8 @@ type Trojan struct {
transport *gun.TransportWrap transport *gun.TransportWrap
realityConfig *tlsC.RealityConfig realityConfig *tlsC.RealityConfig
ssCipher core.Cipher
} }
type TrojanOption struct { type TrojanOption struct {
@@ -46,9 +50,17 @@ type TrojanOption struct {
RealityOpts RealityOptions `proxy:"reality-opts,omitempty"` RealityOpts RealityOptions `proxy:"reality-opts,omitempty"`
GrpcOpts GrpcOptions `proxy:"grpc-opts,omitempty"` GrpcOpts GrpcOptions `proxy:"grpc-opts,omitempty"`
WSOpts WSOptions `proxy:"ws-opts,omitempty"` WSOpts WSOptions `proxy:"ws-opts,omitempty"`
SSOpts TrojanSSOption `proxy:"ss-opts,omitempty"`
ClientFingerprint string `proxy:"client-fingerprint,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) { func (t *Trojan) plainStream(ctx context.Context, c net.Conn) (net.Conn, error) {
if t.option.Network == "ws" { if t.option.Network == "ws" {
host, port, _ := net.SplitHostPort(t.addr) 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) 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 { if metadata.NetWork == C.UDP {
err = t.instance.WriteHeader(c, trojan.CommandUDP, serializesSocksAddr(metadata)) err = t.instance.WriteHeader(c, trojan.CommandUDP, serializesSocksAddr(metadata))
return c, err return c, err
@@ -112,6 +128,10 @@ func (t *Trojan) DialContext(ctx context.Context, metadata *C.Metadata, opts ...
return nil, err return nil, err
} }
if t.ssCipher != nil {
c = t.ssCipher.StreamConn(c)
}
if err = t.instance.WriteHeader(c, trojan.CommandTCP, serializesSocksAddr(metadata)); err != nil { if err = t.instance.WriteHeader(c, trojan.CommandTCP, serializesSocksAddr(metadata)); err != nil {
c.Close() c.Close()
return nil, err return nil, err
@@ -161,6 +181,11 @@ func (t *Trojan) ListenPacketContext(ctx context.Context, metadata *C.Metadata,
defer func(c net.Conn) { defer func(c net.Conn) {
safeConnClose(c, err) safeConnClose(c, err)
}(c) }(c)
if t.ssCipher != nil {
c = t.ssCipher.StreamConn(c)
}
err = t.instance.WriteHeader(c, trojan.CommandUDP, serializesSocksAddr(metadata)) err = t.instance.WriteHeader(c, trojan.CommandUDP, serializesSocksAddr(metadata))
if err != nil { if err != nil {
return nil, err 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) 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)) err = t.instance.WriteHeader(c, trojan.CommandUDP, serializesSocksAddr(metadata))
if err != nil { if err != nil {
return nil, err return nil, err
@@ -257,6 +286,20 @@ func NewTrojan(option TrojanOption) (*Trojan, error) {
} }
tOption.Reality = t.realityConfig 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" { if option.Network == "grpc" {
dialFn := func(network, addr string) (net.Conn, error) { dialFn := func(network, addr string) (net.Conn, error) {
var err error var err error

View File

@@ -179,6 +179,7 @@ func (v *Vmess) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.M
tlsOpts := mihomoVMess.TLSConfig{ tlsOpts := mihomoVMess.TLSConfig{
Host: host, Host: host,
SkipCertVerify: v.option.SkipCertVerify, SkipCertVerify: v.option.SkipCertVerify,
FingerPrint: v.option.Fingerprint,
NextProtos: []string{"h2"}, NextProtos: []string{"h2"},
ClientFingerprint: v.option.ClientFingerprint, ClientFingerprint: v.option.ClientFingerprint,
Reality: v.realityConfig, Reality: v.realityConfig,
@@ -208,6 +209,7 @@ func (v *Vmess) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.M
tlsOpts := &mihomoVMess.TLSConfig{ tlsOpts := &mihomoVMess.TLSConfig{
Host: host, Host: host,
SkipCertVerify: v.option.SkipCertVerify, SkipCertVerify: v.option.SkipCertVerify,
FingerPrint: v.option.Fingerprint,
ClientFingerprint: v.option.ClientFingerprint, ClientFingerprint: v.option.ClientFingerprint,
Reality: v.realityConfig, Reality: v.realityConfig,
NextProtos: v.option.ALPN, NextProtos: v.option.ALPN,

View File

@@ -12,6 +12,7 @@ import (
"strconv" "strconv"
"strings" "strings"
"sync" "sync"
"time"
"github.com/metacubex/mihomo/common/atomic" "github.com/metacubex/mihomo/common/atomic"
CN "github.com/metacubex/mihomo/common/net" CN "github.com/metacubex/mihomo/common/net"
@@ -48,6 +49,10 @@ type WireGuard struct {
connectAddr M.Socksaddr connectAddr M.Socksaddr
localPrefixes []netip.Prefix localPrefixes []netip.Prefix
serverAddrMap map[M.Socksaddr]netip.AddrPort
serverAddrTime atomic.TypedValue[time.Time]
serverAddrMutex sync.Mutex
closeCh chan struct{} // for test closeCh chan struct{} // for test
} }
@@ -67,6 +72,8 @@ type WireGuardOption struct {
RemoteDnsResolve bool `proxy:"remote-dns-resolve,omitempty"` RemoteDnsResolve bool `proxy:"remote-dns-resolve,omitempty"`
Dns []string `proxy:"dns,omitempty"` Dns []string `proxy:"dns,omitempty"`
RefreshServerIPInterval int `proxy:"refresh-server-ip-interval,omitempty"`
} }
type WireGuardPeerOption struct { type WireGuardPeerOption struct {
@@ -287,6 +294,15 @@ func (w *WireGuard) resolve(ctx context.Context, address M.Socksaddr) (netip.Add
} }
func (w *WireGuard) init(ctx context.Context) error { 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() { if w.initOk.Load() {
return nil return nil
} }
@@ -301,41 +317,118 @@ func (w *WireGuard) init(ctx context.Context) error {
} }
w.bind.ResetReservedForEndpoint() w.bind.ResetReservedForEndpoint()
ipcConf := "private_key=" + w.option.PrivateKey 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 { if len(w.option.Peers) > 0 {
for i, peer := range w.option.Peers { for i, peer := range w.option.Peers {
destination, err := w.resolve(ctx, peer.Addr()) peerAddr := peer.Addr()
destination, err := w.resolve(ctx, peerAddr)
if err != nil { if err != nil {
// !!! do not set initErr here !!! return "", E.Cause(err, "resolve endpoint domain for peer ", i)
// let us can retry domain resolve in next time
return E.Cause(err, "resolve endpoint domain for peer ", i)
} }
ipcConf += "\npublic_key=" + peer.PublicKey if w.serverAddrMap[peerAddr] != destination {
ipcConf += "\nendpoint=" + destination.String() w.serverAddrMap[peerAddr] = destination
if peer.PreSharedKey != "" { } else if updateOnly {
ipcConf += "\npreshared_key=" + peer.PreSharedKey continue
} }
for _, allowedIP := range peer.AllowedIPs {
ipcConf += "\nallowed_ip=" + allowedIP 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 { if len(peer.Reserved) > 0 {
var reserved [3]uint8 var reserved [3]uint8
copy(reserved[:], w.option.Reserved) copy(reserved[:], w.option.Reserved)
w.bind.SetReservedForEndpoint(destination, 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 { } else {
ipcConf += "\npublic_key=" + w.option.PublicKey
destination, err := w.resolve(ctx, w.connectAddr) destination, err := w.resolve(ctx, w.connectAddr)
if err != nil { if err != nil {
// !!! do not set initErr here !!! return "", E.Cause(err, "resolve endpoint domain")
// let us can retry domain resolve in next time }
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
} }
w.bind.SetConnectAddr(destination)
ipcConf += "\nendpoint=" + destination.String()
if w.option.PreSharedKey != "" { if w.option.PreSharedKey != "" {
ipcConf += "\npreshared_key=" + w.option.PreSharedKey ipcConf += "preshared_key=" + w.option.PreSharedKey + "\n"
} }
var has4, has6 bool var has4, has6 bool
for _, address := range w.localPrefixes { for _, address := range w.localPrefixes {
@@ -346,34 +439,17 @@ func (w *WireGuard) init(ctx context.Context) error {
} }
} }
if has4 { if has4 {
ipcConf += "\nallowed_ip=0.0.0.0/0" ipcConf += "allowed_ip=0.0.0.0/0\n"
} }
if has6 { if has6 {
ipcConf += "\nallowed_ip=::/0" ipcConf += "allowed_ip=::/0\n"
}
if w.option.PersistentKeepalive != 0 {
ipcConf += fmt.Sprintf("persistent_keepalive_interval=%d\n", w.option.PersistentKeepalive)
} }
} }
return ipcConf, nil
if w.option.PersistentKeepalive != 0 {
ipcConf += fmt.Sprintf("\npersistent_keepalive_interval=%d", w.option.PersistentKeepalive)
}
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
}
err = w.tunDevice.Start()
if err != nil {
w.initErr = err
return w.initErr
}
w.initOk.Store(true)
return nil
} }
func closeWireGuard(w *WireGuard) { func closeWireGuard(w *WireGuard) {

View File

@@ -48,7 +48,7 @@ type GroupBaseOption struct {
func NewGroupBase(opt GroupBaseOption) *GroupBase { func NewGroupBase(opt GroupBaseOption) *GroupBase {
var excludeFilterReg *regexp2.Regexp var excludeFilterReg *regexp2.Regexp
if opt.excludeFilter != "" { if opt.excludeFilter != "" {
excludeFilterReg = regexp2.MustCompile(opt.excludeFilter, 0) excludeFilterReg = regexp2.MustCompile(opt.excludeFilter, regexp2.None)
} }
var excludeTypeArray []string var excludeTypeArray []string
if opt.excludeType != "" { if opt.excludeType != "" {
@@ -58,7 +58,7 @@ func NewGroupBase(opt GroupBaseOption) *GroupBase {
var filterRegs []*regexp2.Regexp var filterRegs []*regexp2.Regexp
if opt.filter != "" { if opt.filter != "" {
for _, filter := range strings.Split(opt.filter, "`") { for _, filter := range strings.Split(opt.filter, "`") {
filterReg := regexp2.MustCompile(filter, 0) filterReg := regexp2.MustCompile(filter, regexp2.None)
filterRegs = append(filterRegs, filterReg) filterRegs = append(filterRegs, filterReg)
} }
} }
@@ -126,7 +126,7 @@ func (gb *GroupBase) GetProxies(touch bool) []C.Proxy {
for _, filterReg := range gb.filterRegs { for _, filterReg := range gb.filterRegs {
for _, p := range proxies { for _, p := range proxies {
name := p.Name() name := p.Name()
if mat, _ := filterReg.FindStringMatch(name); mat != nil { if mat, _ := filterReg.MatchString(name); mat {
if _, ok := proxiesSet[name]; !ok { if _, ok := proxiesSet[name]; !ok {
proxiesSet[name] = struct{}{} proxiesSet[name] = struct{}{}
newProxies = append(newProxies, p) newProxies = append(newProxies, p)
@@ -150,7 +150,7 @@ func (gb *GroupBase) GetProxies(touch bool) []C.Proxy {
for _, filterReg := range gb.filterRegs { for _, filterReg := range gb.filterRegs {
for _, p := range proxies { for _, p := range proxies {
name := p.Name() name := p.Name()
if mat, _ := filterReg.FindStringMatch(name); mat != nil { if mat, _ := filterReg.MatchString(name); mat {
if _, ok := proxiesSet[name]; !ok { if _, ok := proxiesSet[name]; !ok {
proxiesSet[name] = struct{}{} proxiesSet[name] = struct{}{}
newProxies = append(newProxies, p) newProxies = append(newProxies, p)
@@ -191,7 +191,7 @@ func (gb *GroupBase) GetProxies(touch bool) []C.Proxy {
var newProxies []C.Proxy var newProxies []C.Proxy
for _, p := range proxies { for _, p := range proxies {
name := p.Name() name := p.Name()
if mat, _ := gb.excludeFilterReg.FindStringMatch(name); mat != nil { if mat, _ := gb.excludeFilterReg.MatchString(name); mat {
continue continue
} }
newProxies = append(newProxies, p) newProxies = append(newProxies, p)

View File

@@ -205,7 +205,6 @@ func strategyStickySessions(url string) strategyFn {
proxy := proxies[nowIdx] proxy := proxies[nowIdx]
if proxy.AliveForTestUrl(url) { if proxy.AliveForTestUrl(url) {
if nowIdx != idx { if nowIdx != idx {
lruCache.Delete(key)
lruCache.Set(key, nowIdx) lruCache.Set(key, nowIdx)
} }
@@ -215,7 +214,6 @@ func strategyStickySessions(url string) strategyFn {
} }
} }
lruCache.Delete(key)
lruCache.Set(key, 0) lruCache.Set(key, 0)
return proxies[0] return proxies[0]
} }

View File

@@ -5,6 +5,8 @@ import (
"fmt" "fmt"
"strings" "strings"
"github.com/dlclark/regexp2"
"github.com/metacubex/mihomo/adapter/outbound" "github.com/metacubex/mihomo/adapter/outbound"
"github.com/metacubex/mihomo/adapter/provider" "github.com/metacubex/mihomo/adapter/provider"
"github.com/metacubex/mihomo/common/structure" "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 { if groupOption.IncludeAllProviders {
groupOption.Use = append(groupOption.Use, AllProviders...) groupOption.Use = AllProviders
} }
if groupOption.IncludeAllProxies { 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 { if len(groupOption.Proxies) == 0 && len(groupOption.Use) == 0 {

View File

@@ -9,6 +9,7 @@ import (
"github.com/metacubex/mihomo/component/proxydialer" "github.com/metacubex/mihomo/component/proxydialer"
C "github.com/metacubex/mihomo/constant" C "github.com/metacubex/mihomo/constant"
"github.com/metacubex/mihomo/constant/provider" "github.com/metacubex/mihomo/constant/provider"
"github.com/metacubex/mihomo/log"
) )
type Relay struct { type Relay struct {
@@ -149,6 +150,7 @@ func (r *Relay) Addr() string {
} }
func NewRelay(option *GroupCommonOption, providers []provider.ProxyProvider) *Relay { 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{ return &Relay{
GroupBase: NewGroupBase(GroupBaseOption{ GroupBase: NewGroupBase(GroupBaseOption{
outbound.BaseOption{ outbound.BaseOption{

View File

@@ -181,14 +181,14 @@ func (hc *HealthCheck) execute(b *batch.Batch[bool], url, uid string, option *ex
filters = append(filters, filter) filters = append(filters, filter)
} }
filterReg = regexp2.MustCompile(strings.Join(filters, "|"), 0) filterReg = regexp2.MustCompile(strings.Join(filters, "|"), regexp2.None)
} }
} }
for _, proxy := range hc.proxies { for _, proxy := range hc.proxies {
// skip proxies that do not require health check // skip proxies that do not require health check
if filterReg != nil { if filterReg != nil {
if match, _ := filterReg.FindStringMatch(proxy.Name()); match == nil { if match, _ := filterReg.MatchString(proxy.Name()); !match {
continue continue
} }
} }

View File

@@ -28,7 +28,10 @@ type healthCheckSchema struct {
} }
type OverrideSchema struct { type OverrideSchema struct {
TFO *bool `provider:"tfo,omitempty"`
MPTcp *bool `provider:"mptcp,omitempty"`
UDP *bool `provider:"udp,omitempty"` UDP *bool `provider:"udp,omitempty"`
UDPOverTCP *bool `provider:"udp-over-tcp,omitempty"`
Up *string `provider:"up,omitempty"` Up *string `provider:"up,omitempty"`
Down *string `provider:"down,omitempty"` Down *string `provider:"down,omitempty"`
DialerProxy *string `provider:"dialer-proxy,omitempty"` DialerProxy *string `provider:"dialer-proxy,omitempty"`

View File

@@ -6,6 +6,7 @@ import (
"errors" "errors"
"fmt" "fmt"
"net/http" "net/http"
"reflect"
"runtime" "runtime"
"strings" "strings"
"time" "time"
@@ -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) { 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 { if err != nil {
return nil, fmt.Errorf("invalid excludeFilter regex: %w", err) 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 var filterRegs []*regexp2.Regexp
for _, filter := range strings.Split(filter, "`") { for _, filter := range strings.Split(filter, "`") {
filterReg, err := regexp2.Compile(filter, 0) filterReg, err := regexp2.Compile(filter, regexp2.None)
if err != nil { if err != nil {
return nil, fmt.Errorf("invalid filter regex: %w", err) return nil, fmt.Errorf("invalid filter regex: %w", err)
} }
@@ -356,12 +357,12 @@ func proxiesParseAndFilter(filter string, excludeFilter string, excludeTypeArray
continue continue
} }
if len(excludeFilter) > 0 { if len(excludeFilter) > 0 {
if mat, _ := excludeFilterReg.FindStringMatch(name); mat != nil { if mat, _ := excludeFilterReg.MatchString(name); mat {
continue continue
} }
} }
if len(filter) > 0 { if len(filter) > 0 {
if mat, _ := filterReg.FindStringMatch(name); mat == nil { if mat, _ := filterReg.MatchString(name); !mat {
continue continue
} }
} }
@@ -373,37 +374,23 @@ func proxiesParseAndFilter(filter string, excludeFilter string, excludeTypeArray
mapping["dialer-proxy"] = dialerProxy mapping["dialer-proxy"] = dialerProxy
} }
if override.UDP != nil { val := reflect.ValueOf(override)
mapping["udp"] = *override.UDP for i := 0; i < val.NumField(); i++ {
} field := val.Field(i)
if override.Up != nil { if field.IsNil() {
mapping["up"] = *override.Up continue
} }
if override.Down != nil { fieldName := strings.Split(val.Type().Field(i).Tag.Get("provider"), ",")[0]
mapping["down"] = *override.Down switch fieldName {
} case "additional-prefix":
if override.DialerProxy != nil { name := mapping["name"].(string)
mapping["dialer-proxy"] = *override.DialerProxy mapping["name"] = *field.Interface().(*string) + name
} case "additional-suffix":
if override.SkipCertVerify != nil { name := mapping["name"].(string)
mapping["skip-cert-verify"] = *override.SkipCertVerify mapping["name"] = name + *field.Interface().(*string)
} default:
if override.Interface != nil { mapping[fieldName] = field.Elem().Interface()
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
} }
proxy, err := adapter.ParseProxy(mapping) proxy, err := adapter.ParseProxy(mapping)

View File

@@ -333,7 +333,7 @@ func ConvertsV2Ray(buf []byte) ([]map[string]any, error) {
case "ws", "httpupgrade": case "ws", "httpupgrade":
headers := make(map[string]any) headers := make(map[string]any)
wsOpts := make(map[string]any) wsOpts := make(map[string]any)
wsOpts["path"] = []string{"/"} wsOpts["path"] = "/"
if host, ok := values["host"]; ok && host != "" { if host, ok := values["host"]; ok && host != "" {
headers["Host"] = host.(string) headers["Host"] = host.(string)
} }

View File

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

View File

@@ -223,6 +223,10 @@ func (c *LruCache[K, V]) Delete(key K) {
c.mu.Lock() c.mu.Lock()
defer c.mu.Unlock() defer c.mu.Unlock()
c.delete(key)
}
func (c *LruCache[K, V]) delete(key K) {
if le, ok := c.cache[key]; ok { if le, ok := c.cache[key]; ok {
c.deleteElement(le) c.deleteElement(le)
} }
@@ -255,6 +259,34 @@ func (c *LruCache[K, V]) Clear() error {
return nil 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 { type entry[K comparable, V any] struct {
key K key K
value V value V

View File

@@ -3,8 +3,8 @@ package pool
import ( import (
"testing" "testing"
"github.com/metacubex/randv2"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/zhangyunhao116/fastrand"
) )
func TestAllocGet(t *testing.T) { func TestAllocGet(t *testing.T) {
@@ -43,6 +43,6 @@ func TestAllocPutThenGet(t *testing.T) {
func BenchmarkMSB(b *testing.B) { func BenchmarkMSB(b *testing.B) {
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
msb(fastrand.Int()) msb(randv2.Int())
} }
} }

View File

@@ -59,8 +59,8 @@ func (q *Queue[T]) Copy() []T {
// Len returns the number of items in this queue. // Len returns the number of items in this queue.
func (q *Queue[T]) Len() int64 { func (q *Queue[T]) Len() int64 {
q.lock.Lock() q.lock.RLock()
defer q.lock.Unlock() defer q.lock.RUnlock()
return int64(len(q.items)) return int64(len(q.items))
} }

50
common/utils/callback.go Normal file
View 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
}

View File

@@ -2,19 +2,39 @@ package utils
import ( import (
"github.com/gofrs/uuid/v5" "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) { func (r unsafeRandReader) Read(p []byte) (n int, err error) {
return fastrand.Read(p) // 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 { 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 return u
} }
@@ -23,7 +43,7 @@ func NewUUIDV3(ns uuid.UUID, name string) uuid.UUID {
} }
func NewUUIDV4() 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 return u
} }
@@ -32,12 +52,12 @@ func NewUUIDV5(ns uuid.UUID, name string) uuid.UUID {
} }
func NewUUIDV6() 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 return u
} }
func NewUUIDV7() uuid.UUID { 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 return u
} }

View File

@@ -67,9 +67,6 @@ func ResetCertificate() {
} }
func getCertPool() *x509.CertPool { func getCertPool() *x509.CertPool {
if len(trustCerts) == 0 {
return nil
}
if globalCertPool == nil { if globalCertPool == nil {
mutex.Lock() mutex.Lock()
defer mutex.Unlock() defer mutex.Unlock()

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

View File

@@ -43,12 +43,12 @@ func (set *IpCidrSet) IsContainForString(ipString string) bool {
} }
func (set *IpCidrSet) IsContain(ip netip.Addr) 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 { func (set *IpCidrSet) Merge() error {
var b netipx.IPSetBuilder var b netipx.IPSetBuilder
b.AddSet(set.toIPSet()) b.AddSet(set.ToIPSet())
i, err := b.IPSet() i, err := b.IPSet()
if err != nil { if err != nil {
return err return err
@@ -57,7 +57,19 @@ func (set *IpCidrSet) Merge() error {
return nil 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)) return (*netipx.IPSet)(unsafe.Pointer(set))
} }

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

View File

@@ -164,7 +164,7 @@ func dialContext(ctx context.Context, network string, destination netip.Addr, po
if opt.mpTcp { if opt.mpTcp {
setMultiPathTCP(dialer) setMultiPathTCP(dialer)
} }
if opt.tfo { if opt.tfo && !DisableTFO {
return dialTFO(ctx, *dialer, network, address) return dialTFO(ctx, *dialer, network, address)
} }
return dialer.DialContext(ctx, network, address) return dialer.DialContext(ctx, network, address)
@@ -378,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) { 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() { if rAddrPort.Addr().Unmap().IsLoopback() {
// avoid "The requested address is not valid in its context." // avoid "The requested address is not valid in its context."
opt = WithInterface("") WithInterface("")(&opt)
} }
return ListenPacket(ctx, ParseNetwork(network, rAddrPort.Addr()), address, rAddrPort, opt) return ListenPacket(ctx, ParseNetwork(network, rAddrPort.Addr()), address, rAddrPort, WithOption(opt))
} }
func NewDialer(options ...Option) Dialer { func NewDialer(options ...Option) Dialer {

View File

@@ -5,8 +5,6 @@ import (
"io" "io"
"net" "net"
"time" "time"
"github.com/metacubex/tfo-go"
) )
type tfoConn struct { type tfoConn struct {
@@ -122,16 +120,3 @@ func (c *tfoConn) ReaderReplaceable() bool {
func (c *tfoConn) WriterReplaceable() bool { func (c *tfoConn) WriterReplaceable() bool {
return c.Conn != nil 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
}

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

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

View File

@@ -11,8 +11,9 @@ import (
type Interface struct { type Interface struct {
Index int Index int
MTU int
Name string Name string
Addrs []netip.Prefix Addresses []netip.Prefix
HardwareAddr net.HardwareAddr HardwareAddr net.HardwareAddr
} }
@@ -61,8 +62,9 @@ func Interfaces() (map[string]*Interface, error) {
r[iface.Name] = &Interface{ r[iface.Name] = &Interface{
Index: iface.Index, Index: iface.Index,
MTU: iface.MTU,
Name: iface.Name, Name: iface.Name,
Addrs: ipNets, Addresses: ipNets,
HardwareAddr: iface.HardwareAddr, HardwareAddr: iface.HardwareAddr,
} }
} }
@@ -92,7 +94,7 @@ func IsLocalIp(ip netip.Addr) (bool, error) {
return false, err return false, err
} }
for _, iface := range ifaces { for _, iface := range ifaces {
for _, addr := range iface.Addrs { for _, addr := range iface.Addresses {
if addr.Contains(ip) { if addr.Contains(ip) {
return true, nil return true, nil
} }
@@ -120,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) { func (iface *Interface) pickIPAddr(destination netip.Addr, accept func(addr netip.Prefix) bool) (netip.Prefix, error) {
var fallback netip.Prefix var fallback netip.Prefix
for _, addr := range iface.Addrs { for _, addr := range iface.Addresses {
if !accept(addr) { if !accept(addr) {
continue continue
} }

View File

@@ -3,6 +3,8 @@ package process
import ( import (
"errors" "errors"
"net/netip" "net/netip"
C "github.com/metacubex/mihomo/constant"
) )
var ( var (
@@ -19,3 +21,18 @@ const (
func FindProcessName(network string, srcIP netip.Addr, srcPort int) (uint32, string, error) { func FindProcessName(network string, srcIP netip.Addr, srcPort int) (uint32, string, error) {
return findProcessName(network, srcIP, srcPort) 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
}

View File

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

View File

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

View File

@@ -46,12 +46,12 @@ func findProcessName(network string, ip netip.Addr, port int) (uint32, string, e
isIPv4 := ip.Is4() isIPv4 := ip.Is4()
value, err := syscall.Sysctl(spath) value, err := unix.SysctlRaw(spath)
if err != nil { if err != nil {
return 0, "", err return 0, "", err
} }
buf := []byte(value) buf := value
itemSize := structSize itemSize := structSize
if network == TCP { if network == TCP {
// rup8(sizeof(xtcpcb_n)) // rup8(sizeof(xtcpcb_n))

View File

@@ -64,7 +64,6 @@ func findProcessName(network string, ip netip.Addr, srcPort int) (uint32, string
if err != nil { if err != nil {
return 0, "", err return 0, "", err
} }
pp, err := resolveProcessNameByProcSearch(inode, uid) pp, err := resolveProcessNameByProcSearch(inode, uid)
return uid, pp, err return uid, pp, err
} }
@@ -160,6 +159,7 @@ func resolveProcessNameByProcSearch(inode, uid uint32) (string, error) {
if err != nil { if err != nil {
continue continue
} }
if runtime.GOOS == "android" { if runtime.GOOS == "android" {
if bytes.Equal(buffer[:n], socket) { if bytes.Equal(buffer[:n], socket) {
cmdline, err := os.ReadFile(path.Join(processPath, "cmdline")) 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")) return os.Readlink(filepath.Join(processPath, "exe"))
} }
} }
} }
} }
@@ -185,7 +184,7 @@ func splitCmdline(cmdline []byte) string {
cmdline = bytes.Trim(cmdline, " ") cmdline = bytes.Trim(cmdline, " ")
idx := bytes.IndexFunc(cmdline, func(r rune) bool { 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 { if idx == -1 {

View File

@@ -9,11 +9,15 @@ import (
_ "unsafe" _ "unsafe"
"github.com/metacubex/mihomo/common/utils" "github.com/metacubex/mihomo/common/utils"
"github.com/metacubex/mihomo/component/resolver/hosts"
"github.com/metacubex/mihomo/component/trie" "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 { type Hosts struct {
*trie.DomainTrie[HostValue] *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` // Return the search result and whether to match the parameter `isDomain`
func (h *Hosts) Search(domain string, isDomain bool) (*HostValue, bool) { func (h *Hosts) Search(domain string, isDomain bool) (*HostValue, bool) {
if value := h.DomainTrie.Search(domain); value != nil { 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 return &hostValue, false
} }
if !isDomain && !DisableSystemHosts {
addr, _ := lookupStaticHost(domain) if !isDomain && !DisableSystemHosts && UseSystemHosts {
addr, _ := hosts.LookupStaticHost(domain)
if hostValue, err := NewHostValue(addr); err == nil { if hostValue, err := NewHostValue(addr); err == nil {
return &hostValue, true return &hostValue, true
} }
@@ -125,5 +125,5 @@ func (hv HostValue) RandIP() (netip.Addr, error) {
if hv.IsDomain { if hv.IsDomain {
return netip.Addr{}, errors.New("value type is error") 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
} }

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

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

View File

@@ -12,8 +12,8 @@ import (
"github.com/metacubex/mihomo/common/utils" "github.com/metacubex/mihomo/common/utils"
"github.com/metacubex/mihomo/component/trie" "github.com/metacubex/mihomo/component/trie"
"github.com/metacubex/randv2"
"github.com/miekg/dns" "github.com/miekg/dns"
"github.com/zhangyunhao116/fastrand"
) )
var ( var (
@@ -93,7 +93,7 @@ func ResolveIPv4WithResolver(ctx context.Context, host string, r Resolver) (neti
} else if len(ips) == 0 { } else if len(ips) == 0 {
return netip.Addr{}, fmt.Errorf("%w: %s", ErrIPNotFound, host) 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 // 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 { } else if len(ips) == 0 {
return netip.Addr{}, fmt.Errorf("%w: %s", ErrIPNotFound, host) 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) { 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) ipv4s, ipv6s := SortationAddr(ips)
if len(ipv4s) > 0 { 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 // ResolveIP with a host, return ip and priority return TypeA

View File

@@ -10,6 +10,7 @@ import (
types "github.com/metacubex/mihomo/constant/provider" types "github.com/metacubex/mihomo/constant/provider"
"github.com/metacubex/mihomo/log" "github.com/metacubex/mihomo/log"
"github.com/sagernet/fswatch"
"github.com/samber/lo" "github.com/samber/lo"
) )
@@ -30,6 +31,7 @@ type Fetcher[V any] struct {
parser Parser[V] parser Parser[V]
interval time.Duration interval time.Duration
OnUpdate func(V) OnUpdate func(V)
watcher *fswatch.Watcher
} }
func (f *Fetcher[V]) Name() string { func (f *Fetcher[V]) Name() string {
@@ -113,7 +115,20 @@ func (f *Fetcher[V]) Initial() (V, error) {
f.hash = md5.Sum(buf) f.hash = md5.Sum(buf)
// pull contents automatically // 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() go f.pullLoop()
} }
@@ -155,6 +170,9 @@ func (f *Fetcher[V]) Destroy() error {
if f.interval > 0 { if f.interval > 0 {
f.done <- struct{}{} f.done <- struct{}{}
} }
if f.watcher != nil {
_ = f.watcher.Close()
}
return nil return nil
} }
@@ -170,27 +188,31 @@ func (f *Fetcher[V]) pullLoop() {
select { select {
case <-timer.C: case <-timer.C:
timer.Reset(f.interval) timer.Reset(f.interval)
elm, same, err := f.Update() f.update(f.vehicle.Path())
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)
}
case <-f.done: case <-f.done:
return 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 { func safeWrite(path string, buf []byte) error {
dir := filepath.Dir(path) dir := filepath.Dir(path)

View File

@@ -5,7 +5,6 @@ import (
"fmt" "fmt"
"net" "net"
"net/netip" "net/netip"
"sync"
"time" "time"
"github.com/metacubex/mihomo/common/lru" "github.com/metacubex/mihomo/common/lru"
@@ -30,7 +29,6 @@ type SnifferDispatcher struct {
forceDomain *trie.DomainSet forceDomain *trie.DomainSet
skipSNI *trie.DomainSet skipSNI *trie.DomainSet
skipList *lru.LruCache[string, uint8] skipList *lru.LruCache[string, uint8]
rwMux sync.RWMutex
forceDnsMapping bool forceDnsMapping bool
parsePureIp bool parsePureIp bool
} }
@@ -85,14 +83,11 @@ func (sd *SnifferDispatcher) TCPSniff(conn *N.BufferedConn, metadata *C.Metadata
return false return false
} }
sd.rwMux.RLock()
dst := fmt.Sprintf("%s:%d", metadata.DstIP, metadata.DstPort) dst := fmt.Sprintf("%s:%d", metadata.DstIP, metadata.DstPort)
if count, ok := sd.skipList.Get(dst); ok && count > 5 { if count, ok := sd.skipList.Get(dst); ok && count > 5 {
log.Debugln("[Sniffer] Skip sniffing[%s] due to multiple failures", dst) log.Debugln("[Sniffer] Skip sniffing[%s] due to multiple failures", dst)
defer sd.rwMux.RUnlock()
return false return false
} }
sd.rwMux.RUnlock()
if host, err := sd.sniffDomain(conn, metadata); err != nil { if host, err := sd.sniffDomain(conn, metadata); err != nil {
sd.cacheSniffFailed(metadata) sd.cacheSniffFailed(metadata)
@@ -104,9 +99,7 @@ func (sd *SnifferDispatcher) TCPSniff(conn *N.BufferedConn, metadata *C.Metadata
return false return false
} }
sd.rwMux.RLock()
sd.skipList.Delete(dst) sd.skipList.Delete(dst)
sd.rwMux.RUnlock()
sd.replaceDomain(metadata, host, overrideDest) sd.replaceDomain(metadata, host, overrideDest)
return true 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) { 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 metadata.SniffHost = host
if overrideDest { 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.Host = host
} }
metadata.DNSMode = C.DNSNormal 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) { func (sd *SnifferDispatcher) cacheSniffFailed(metadata *C.Metadata) {
sd.rwMux.Lock()
dst := fmt.Sprintf("%s:%d", metadata.DstIP, metadata.DstPort) dst := fmt.Sprintf("%s:%d", metadata.DstIP, metadata.DstPort)
count, _ := sd.skipList.Get(dst) sd.skipList.Compute(dst, func(oldValue uint8, loaded bool) (newValue uint8, delete bool) {
if count <= 5 { if oldValue <= 5 {
count++ oldValue++
} }
sd.skipList.Set(dst, count) return oldValue, false
sd.rwMux.Unlock() })
} }
func NewCloseSnifferDispatcher() (*SnifferDispatcher, error) { func NewCloseSnifferDispatcher() (*SnifferDispatcher, error) {

View File

@@ -16,17 +16,14 @@ import (
"errors" "errors"
"net" "net"
"net/http" "net/http"
"reflect"
"strings" "strings"
"time" "time"
"unsafe"
"github.com/metacubex/mihomo/common/utils"
"github.com/metacubex/mihomo/log" "github.com/metacubex/mihomo/log"
"github.com/metacubex/mihomo/ntp" "github.com/metacubex/mihomo/ntp"
utls "github.com/sagernet/utls" "github.com/metacubex/randv2"
"github.com/zhangyunhao116/fastrand" utls "github.com/metacubex/utls"
"golang.org/x/crypto/chacha20poly1305" "golang.org/x/crypto/chacha20poly1305"
"golang.org/x/crypto/hkdf" "golang.org/x/crypto/hkdf"
"golang.org/x/net/http2" "golang.org/x/net/http2"
@@ -39,9 +36,6 @@ type RealityConfig struct {
ShortID [RealityMaxShortIDLen]byte 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) { func GetRealityConn(ctx context.Context, conn net.Conn, ClientFingerprint string, tlsConfig *tls.Config, realityConfig *RealityConfig) (net.Conn, error) {
retry := 0 retry := 0
for fingerprint, exists := GetFingerprint(ClientFingerprint); exists; retry++ { for fingerprint, exists := GetFingerprint(ClientFingerprint); exists; retry++ {
@@ -102,7 +96,7 @@ func GetRealityConn(ctx context.Context, conn net.Conn, ClientFingerprint string
return nil, err return nil, err
} }
var aeadCipher cipher.AEAD var aeadCipher cipher.AEAD
if aesgcmPreferred(hello.CipherSuites) { if utls.AesgcmPreferred(hello.CipherSuites) {
aesBlock, _ := aes.NewCipher(authKey) aesBlock, _ := aes.NewCipher(authKey)
aeadCipher, _ = cipher.NewGCM(aesBlock) aeadCipher, _ = cipher.NewGCM(aesBlock)
} else { } 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.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) response, err := client.Do(request)
if err != nil { if err != nil {
return return
} }
//_, _ = io.Copy(io.Discard, response.Body) //_, _ = 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() response.Body.Close()
client.CloseIdleConnections() client.CloseIdleConnections()
} }
@@ -159,11 +156,12 @@ type realityVerifier struct {
verified bool 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 { func (c *realityVerifier) VerifyPeerCertificate(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {
//p, _ := reflect.TypeOf(c.Conn).Elem().FieldByName("peerCertificates") //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 { if pub, ok := certs[0].PublicKey.(ed25519.PublicKey); ok {
h := hmac.New(sha512.New, c.authKey) h := hmac.New(sha512.New, c.authKey)
h.Write(pub) h.Write(pub)

View File

@@ -6,8 +6,8 @@ import (
"github.com/metacubex/mihomo/log" "github.com/metacubex/mihomo/log"
utls "github.com/metacubex/utls"
"github.com/mroth/weightedrand/v2" "github.com/mroth/weightedrand/v2"
utls "github.com/sagernet/utls"
) )
type UConn struct { type UConn struct {

View File

@@ -123,16 +123,18 @@ func (t *DomainTrie[T]) Optimize() {
t.root.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() { for key, data := range t.root.getChildren() {
recursion([]string{key}, data, print) recursion([]string{key}, data, fn)
if data != nil && data.inited { 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() { for key, data := range node.getChildren() {
newItems := append([]string{key}, items...) newItems := append([]string{key}, items...)
if data != nil && data.inited { 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 { if domain[0] == domainStepByte {
domain = complexWildcard + domain 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 { func joinDomain(items []string) string {

View File

@@ -28,8 +28,9 @@ type qElt struct{ s, e, col int }
// NewDomainSet creates a new *DomainSet struct, from a DomainTrie. // NewDomainSet creates a new *DomainSet struct, from a DomainTrie.
func (t *DomainTrie[T]) NewDomainSet() *DomainSet { func (t *DomainTrie[T]) NewDomainSet() *DomainSet {
reserveDomains := make([]string, 0) 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)) reserveDomains = append(reserveDomains, utils.Reverse(domain))
return true
}) })
// ensure that the same prefix is continuous // ensure that the same prefix is continuous
// and according to the ascending sequence of length // 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) { func setBit(bm *[]uint64, i int, v int) {
for i>>6 >= len(*bm) { for i>>6 >= len(*bm) {
*bm = append(*bm, 0) *bm = append(*bm, 0)

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

View File

@@ -1,12 +1,29 @@
package trie_test package trie_test
import ( import (
"golang.org/x/exp/slices"
"testing" "testing"
"github.com/metacubex/mihomo/component/trie" "github.com/metacubex/mihomo/component/trie"
"github.com/stretchr/testify/assert" "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) { func TestDomainSet(t *testing.T) {
tree := trie.New[struct{}]() tree := trie.New[struct{}]()
domainSet := []string{ domainSet := []string{
@@ -33,6 +50,7 @@ func TestDomainSet(t *testing.T) {
assert.True(t, set.Has("google.com")) assert.True(t, set.Has("google.com"))
assert.False(t, set.Has("qq.com")) assert.False(t, set.Has("qq.com"))
assert.False(t, set.Has("www.baidu.com")) assert.False(t, set.Has("www.baidu.com"))
testDump(t, tree, set)
} }
func TestDomainSetComplexWildcard(t *testing.T) { func TestDomainSetComplexWildcard(t *testing.T) {
@@ -55,6 +73,7 @@ func TestDomainSetComplexWildcard(t *testing.T) {
assert.False(t, set.Has("google.com")) assert.False(t, set.Has("google.com"))
assert.True(t, set.Has("www.baidu.com")) assert.True(t, set.Has("www.baidu.com"))
assert.True(t, set.Has("test.test.baidu.com")) assert.True(t, set.Has("test.test.baidu.com"))
testDump(t, tree, set)
} }
func TestDomainSetWildcard(t *testing.T) { 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("a.www.google.com"))
assert.False(t, set.Has("test.qq.com")) assert.False(t, set.Has("test.qq.com"))
assert.False(t, set.Has("test.test.test.qq.com")) assert.False(t, set.Has("test.test.test.qq.com"))
testDump(t, tree, set)
} }

View File

@@ -121,8 +121,9 @@ func TestTrie_Foreach(t *testing.T) {
assert.NoError(t, tree.Insert(domain, localIP)) assert.NoError(t, tree.Insert(domain, localIP))
} }
count := 0 count := 0
tree.Foreach(func(domain string, data netip.Addr) { tree.Foreach(func(domain string, data netip.Addr) bool {
count++ count++
return true
}) })
assert.Equal(t, 7, count) assert.Equal(t, 7, count)
} }

View File

@@ -16,7 +16,6 @@ import (
"time" "time"
mihomoHttp "github.com/metacubex/mihomo/component/http" mihomoHttp "github.com/metacubex/mihomo/component/http"
"github.com/metacubex/mihomo/constant"
C "github.com/metacubex/mihomo/constant" C "github.com/metacubex/mihomo/constant"
"github.com/metacubex/mihomo/log" "github.com/metacubex/mihomo/log"
@@ -52,6 +51,10 @@ func init() {
if runtime.GOARCH == "amd64" && cpuid.CPU.X64Level() < 3 { if runtime.GOARCH == "amd64" && cpuid.CPU.X64Level() < 3 {
amd64Compatible = "-compatible" 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 { 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. // 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. // 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() mu.Lock()
defer mu.Unlock() defer mu.Unlock()
@@ -73,9 +76,9 @@ func Update(execPath string) (err error) {
return err 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"} err := &updateError{Message: "already using latest version"}
return err return err
} }

View File

@@ -1,18 +1,27 @@
package config package updater
import ( import (
"errors"
"fmt" "fmt"
"os"
"runtime" "runtime"
"time"
"github.com/metacubex/mihomo/common/atomic"
"github.com/metacubex/mihomo/component/geodata" "github.com/metacubex/mihomo/component/geodata"
_ "github.com/metacubex/mihomo/component/geodata/standard" _ "github.com/metacubex/mihomo/component/geodata/standard"
"github.com/metacubex/mihomo/component/mmdb" "github.com/metacubex/mihomo/component/mmdb"
C "github.com/metacubex/mihomo/constant" C "github.com/metacubex/mihomo/constant"
"github.com/metacubex/mihomo/log"
"github.com/oschwald/maxminddb-golang" "github.com/oschwald/maxminddb-golang"
) )
func UpdateGeoDatabases() error { var (
UpdatingGeo atomic.Bool
)
func updateGeoDatabases() error {
defer runtime.GC() defer runtime.GC()
geoLoader, err := geodata.GetGeoDataLoader("standard") geoLoader, err := geodata.GetGeoDataLoader("standard")
if err != nil { if err != nil {
@@ -88,3 +97,80 @@ func UpdateGeoDatabases() error {
return nil 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()
}
}
}()
}

View File

@@ -1,4 +1,4 @@
package config package updater
import ( import (
"archive/zip" "archive/zip"
@@ -29,7 +29,7 @@ func UpdateUI() error {
xdMutex.Lock() xdMutex.Lock()
defer xdMutex.Unlock() defer xdMutex.Unlock()
err := prepare() err := prepare_ui()
if err != nil { if err != nil {
return err return err
} }
@@ -64,7 +64,7 @@ func UpdateUI() error {
return nil return nil
} }
func prepare() error { func prepare_ui() error {
if ExternalUIPath == "" || ExternalUIURL == "" { if ExternalUIPath == "" || ExternalUIURL == "" {
return ErrIncompleteConf return ErrIncompleteConf
} }

View File

@@ -1,12 +1,35 @@
package updater package updater
import ( import (
"context"
"fmt" "fmt"
"io" "io"
"net/http"
"os"
"time"
mihomoHttp "github.com/metacubex/mihomo/component/http"
C "github.com/metacubex/mihomo/constant"
"golang.org/x/exp/constraints" "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. // LimitReachedError records the limit and the operation that caused it.
type LimitReachedError struct { type LimitReachedError struct {
Limit int64 Limit int64

View File

@@ -28,6 +28,7 @@ import (
SNIFF "github.com/metacubex/mihomo/component/sniffer" SNIFF "github.com/metacubex/mihomo/component/sniffer"
tlsC "github.com/metacubex/mihomo/component/tls" tlsC "github.com/metacubex/mihomo/component/tls"
"github.com/metacubex/mihomo/component/trie" "github.com/metacubex/mihomo/component/trie"
"github.com/metacubex/mihomo/component/updater"
C "github.com/metacubex/mihomo/constant" C "github.com/metacubex/mihomo/constant"
"github.com/metacubex/mihomo/constant/features" "github.com/metacubex/mihomo/constant/features"
providerTypes "github.com/metacubex/mihomo/constant/provider" providerTypes "github.com/metacubex/mihomo/constant/provider"
@@ -41,6 +42,7 @@ import (
T "github.com/metacubex/mihomo/tunnel" T "github.com/metacubex/mihomo/tunnel"
orderedmap "github.com/wk8/go-ordered-map/v2" orderedmap "github.com/wk8/go-ordered-map/v2"
"golang.org/x/exp/slices"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
) )
@@ -95,6 +97,7 @@ type Controller struct {
ExternalControllerTLS string `json:"-"` ExternalControllerTLS string `json:"-"`
ExternalControllerUnix string `json:"-"` ExternalControllerUnix string `json:"-"`
ExternalUI string `json:"-"` ExternalUI string `json:"-"`
ExternalDohServer string `json:"-"`
Secret string `json:"-"` Secret string `json:"-"`
} }
@@ -114,6 +117,7 @@ type DNS struct {
PreferH3 bool `yaml:"prefer-h3"` PreferH3 bool `yaml:"prefer-h3"`
IPv6 bool `yaml:"ipv6"` IPv6 bool `yaml:"ipv6"`
IPv6Timeout uint `yaml:"ipv6-timeout"` IPv6Timeout uint `yaml:"ipv6-timeout"`
UseSystemHosts bool `yaml:"use-system-hosts"`
NameServer []dns.NameServer `yaml:"nameserver"` NameServer []dns.NameServer `yaml:"nameserver"`
Fallback []dns.NameServer `yaml:"fallback"` Fallback []dns.NameServer `yaml:"fallback"`
FallbackFilter FallbackFilter `yaml:"fallback-filter"` FallbackFilter FallbackFilter `yaml:"fallback-filter"`
@@ -209,6 +213,8 @@ type RawDNS struct {
IPv6 bool `yaml:"ipv6" json:"ipv6"` IPv6 bool `yaml:"ipv6" json:"ipv6"`
IPv6Timeout uint `yaml:"ipv6-timeout" json:"ipv6-timeout"` IPv6Timeout uint `yaml:"ipv6-timeout" json:"ipv6-timeout"`
UseHosts bool `yaml:"use-hosts" json:"use-hosts"` 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"` NameServer []string `yaml:"nameserver" json:"nameserver"`
Fallback []string `yaml:"fallback" json:"fallback"` Fallback []string `yaml:"fallback" json:"fallback"`
FallbackFilter RawFallbackFilter `yaml:"fallback-filter" json:"fallback-filter"` FallbackFilter RawFallbackFilter `yaml:"fallback-filter" json:"fallback-filter"`
@@ -242,31 +248,39 @@ type RawTun struct {
DNSHijack []string `yaml:"dns-hijack" json:"dns-hijack"` DNSHijack []string `yaml:"dns-hijack" json:"dns-hijack"`
AutoRoute bool `yaml:"auto-route" json:"auto-route"` AutoRoute bool `yaml:"auto-route" json:"auto-route"`
AutoDetectInterface bool `yaml:"auto-detect-interface"` AutoDetectInterface bool `yaml:"auto-detect-interface"`
RedirectToTun []string `yaml:"-" json:"-"`
MTU uint32 `yaml:"mtu" json:"mtu,omitempty"` MTU uint32 `yaml:"mtu" json:"mtu,omitempty"`
GSO bool `yaml:"gso" json:"gso,omitempty"` GSO bool `yaml:"gso" json:"gso,omitempty"`
GSOMaxSize uint32 `yaml:"gso-max-size" json:"gso-max-size,omitempty"` GSOMaxSize uint32 `yaml:"gso-max-size" json:"gso-max-size,omitempty"`
//Inet4Address []netip.Prefix `yaml:"inet4-address" json:"inet4_address,omitempty"` //Inet4Address []netip.Prefix `yaml:"inet4-address" json:"inet4_address,omitempty"`
Inet6Address []netip.Prefix `yaml:"inet6-address" json:"inet6_address,omitempty"` Inet6Address []netip.Prefix `yaml:"inet6-address" json:"inet6_address,omitempty"`
StrictRoute bool `yaml:"strict-route" json:"strict_route,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"` Inet4RouteAddress []netip.Prefix `yaml:"inet4-route-address" json:"inet4_route_address,omitempty"`
Inet6RouteAddress []netip.Prefix `yaml:"inet6-route-address" json:"inet6_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"` 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"` 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 { type RawTuicServer struct {
@@ -310,6 +324,7 @@ type RawConfig struct {
ExternalUI string `yaml:"external-ui"` ExternalUI string `yaml:"external-ui"`
ExternalUIURL string `yaml:"external-ui-url" json:"external-ui-url"` ExternalUIURL string `yaml:"external-ui-url" json:"external-ui-url"`
ExternalUIName string `yaml:"external-ui-name" json:"external-ui-name"` ExternalUIName string `yaml:"external-ui-name" json:"external-ui-name"`
ExternalDohServer string `yaml:"external-doh-server"`
Secret string `yaml:"secret"` Secret string `yaml:"secret"`
Interface string `yaml:"interface-name"` Interface string `yaml:"interface-name"`
RoutingMark int `yaml:"routing-mark"` RoutingMark int `yaml:"routing-mark"`
@@ -456,12 +471,13 @@ func UnmarshalRawConfig(buf []byte) (*RawConfig, error) {
Interval: 30, Interval: 30,
}, },
DNS: RawDNS{ DNS: RawDNS{
Enable: false, Enable: false,
IPv6: false, IPv6: false,
UseHosts: true, UseHosts: true,
IPv6Timeout: 100, UseSystemHosts: true,
EnhancedMode: C.DNSMapping, IPv6Timeout: 100,
FakeIPRange: "198.18.0.1/16", EnhancedMode: C.DNSMapping,
FakeIPRange: "198.18.0.1/16",
FallbackFilter: RawFallbackFilter{ FallbackFilter: RawFallbackFilter{
GeoIP: true, GeoIP: true,
GeoIPCode: "CN", GeoIPCode: "CN",
@@ -491,7 +507,7 @@ func UnmarshalRawConfig(buf []byte) (*RawConfig, error) {
}, },
Sniffer: RawSniffer{ Sniffer: RawSniffer{
Enable: false, Enable: false,
Sniffing: []string{}, Sniff: map[string]RawSniffingConfig{},
ForceDomain: []string{}, ForceDomain: []string{},
SkipDomain: []string{}, SkipDomain: []string{},
Ports: []string{}, Ports: []string{},
@@ -559,13 +575,13 @@ func ParseRawConfig(rawCfg *RawConfig) (*Config, error) {
} }
config.RuleProviders = ruleProviders config.RuleProviders = ruleProviders
subRules, err := parseSubRules(rawCfg, proxies) subRules, err := parseSubRules(rawCfg, proxies, ruleProviders)
if err != nil { if err != nil {
return nil, err return nil, err
} }
config.SubRules = subRules config.SubRules = subRules
rules, err := parseRules(rawCfg.Rule, proxies, subRules, "rules") rules, err := parseRules(rawCfg.Rule, proxies, ruleProviders, subRules, "rules")
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -637,31 +653,30 @@ func parseGeneral(cfg *RawConfig) (*General, error) {
N.KeepAliveInterval = time.Duration(cfg.KeepAliveInterval) * time.Second N.KeepAliveInterval = time.Duration(cfg.KeepAliveInterval) * time.Second
} }
ExternalUIPath = cfg.ExternalUI updater.ExternalUIPath = cfg.ExternalUI
// checkout externalUI exist // checkout externalUI exist
if ExternalUIPath != "" { if updater.ExternalUIPath != "" {
ExternalUIPath = C.Path.Resolve(ExternalUIPath) updater.ExternalUIPath = C.Path.Resolve(updater.ExternalUIPath)
if _, err := os.Stat(ExternalUIPath); os.IsNotExist(err) { if _, err := os.Stat(updater.ExternalUIPath); os.IsNotExist(err) {
defaultUIpath := path.Join(C.Path.HomeDir(), "ui") 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 { if err := os.MkdirAll(defaultUIpath, os.ModePerm); err != nil {
return nil, err return nil, err
} }
ExternalUIPath = defaultUIpath updater.ExternalUIPath = defaultUIpath
cfg.ExternalUI = defaultUIpath cfg.ExternalUI = defaultUIpath
} }
} }
// checkout UIpath/name exist // checkout UIpath/name exist
if cfg.ExternalUIName != "" { if cfg.ExternalUIName != "" {
ExternalUIName = cfg.ExternalUIName updater.ExternalUIName = cfg.ExternalUIName
} else { } else {
ExternalUIFolder = ExternalUIPath updater.ExternalUIFolder = updater.ExternalUIPath
} }
if cfg.ExternalUIURL != "" { if cfg.ExternalUIURL != "" {
ExternalUIURL = cfg.ExternalUIURL updater.ExternalUIURL = cfg.ExternalUIURL
} }
cfg.Tun.RedirectToTun = cfg.EBpf.RedirectToTun
return &General{ return &General{
Inbound: Inbound{ Inbound: Inbound{
Port: cfg.Port, Port: cfg.Port,
@@ -685,6 +700,7 @@ func parseGeneral(cfg *RawConfig) (*General, error) {
Secret: cfg.Secret, Secret: cfg.Secret,
ExternalControllerUnix: cfg.ExternalControllerUnix, ExternalControllerUnix: cfg.ExternalControllerUnix,
ExternalControllerTLS: cfg.ExternalControllerTLS, ExternalControllerTLS: cfg.ExternalControllerTLS,
ExternalDohServer: cfg.ExternalDohServer,
}, },
UnifiedDelay: cfg.UnifiedDelay, UnifiedDelay: cfg.UnifiedDelay,
Mode: cfg.Mode, Mode: cfg.Mode,
@@ -712,8 +728,11 @@ func parseProxies(cfg *RawConfig) (proxies map[string]C.Proxy, providersMap map[
groupsConfig := cfg.ProxyGroup groupsConfig := cfg.ProxyGroup
providersConfig := cfg.ProxyProvider providersConfig := cfg.ProxyProvider
var proxyList []string var (
var AllProxies []string proxyList []string
AllProxies []string
hasGlobal bool
)
proxiesList := list.New() proxiesList := list.New()
groupsList := list.New() groupsList := list.New()
@@ -746,6 +765,9 @@ func parseProxies(cfg *RawConfig) (proxies map[string]C.Proxy, providersMap map[
if !existName { if !existName {
return nil, nil, fmt.Errorf("proxy group %d: missing name", idx) return nil, nil, fmt.Errorf("proxy group %d: missing name", idx)
} }
if groupName == "GLOBAL" {
hasGlobal = true
}
proxyList = append(proxyList, groupName) proxyList = append(proxyList, groupName)
groupsList.PushBack(mapping) groupsList.PushBack(mapping)
} }
@@ -771,6 +793,9 @@ func parseProxies(cfg *RawConfig) (proxies map[string]C.Proxy, providersMap map[
AllProviders = append(AllProviders, name) AllProviders = append(AllProviders, name)
} }
slices.Sort(AllProxies)
slices.Sort(AllProviders)
// parse proxy group // parse proxy group
for idx, mapping := range groupsConfig { for idx, mapping := range groupsConfig {
group, err := outboundgroup.ParseProxyGroup(mapping, proxies, providersMap, AllProxies, AllProviders) group, err := outboundgroup.ParseProxyGroup(mapping, proxies, providersMap, AllProxies, AllProviders)
@@ -797,13 +822,15 @@ func parseProxies(cfg *RawConfig) (proxies map[string]C.Proxy, providersMap map[
pd, _ := provider.NewCompatibleProvider(provider.ReservedName, ps, hc) pd, _ := provider.NewCompatibleProvider(provider.ReservedName, ps, hc)
providersMap[provider.ReservedName] = pd providersMap[provider.ReservedName] = pd
global := outboundgroup.NewSelector( if !hasGlobal {
&outboundgroup.GroupCommonOption{ global := outboundgroup.NewSelector(
Name: "GLOBAL", &outboundgroup.GroupCommonOption{
}, Name: "GLOBAL",
[]providerTypes.ProxyProvider{pd}, },
) []providerTypes.ProxyProvider{pd},
proxies["GLOBAL"] = adapter.NewProxy(global) )
proxies["GLOBAL"] = adapter.NewProxy(global)
}
ProxiesList = proxiesList ProxiesList = proxiesList
GroupsList = groupsList GroupsList = groupsList
if ParsingProxiesCallback != nil { if ParsingProxiesCallback != nil {
@@ -832,6 +859,7 @@ func parseListeners(cfg *RawConfig) (listeners map[string]C.InboundListener, err
} }
func parseRuleProviders(cfg *RawConfig) (ruleProviders map[string]providerTypes.RuleProvider, err error) { func parseRuleProviders(cfg *RawConfig) (ruleProviders map[string]providerTypes.RuleProvider, err error) {
RP.SetTunnel(T.Tunnel)
ruleProviders = map[string]providerTypes.RuleProvider{} ruleProviders = map[string]providerTypes.RuleProvider{}
// parse rule provider // parse rule provider
for name, mapping := range cfg.RuleProvider { for name, mapping := range cfg.RuleProvider {
@@ -841,12 +869,11 @@ func parseRuleProviders(cfg *RawConfig) (ruleProviders map[string]providerTypes.
} }
ruleProviders[name] = rp ruleProviders[name] = rp
RP.SetRuleProvider(rp)
} }
return 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{} subRules = map[string][]C.Rule{}
for name := range cfg.SubRules { for name := range cfg.SubRules {
subRules[name] = make([]C.Rule, 0) subRules[name] = make([]C.Rule, 0)
@@ -856,7 +883,7 @@ func parseSubRules(cfg *RawConfig, proxies map[string]C.Proxy) (subRules map[str
return nil, fmt.Errorf("sub-rule name is empty") return nil, fmt.Errorf("sub-rule name is empty")
} }
var rules []C.Rule 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 { if err != nil {
return nil, err return nil, err
} }
@@ -909,7 +936,7 @@ func verifySubRuleCircularReferences(n string, subRules map[string][]C.Rule, arr
return nil 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 var rules []C.Rule
// parse rules // parse rules
@@ -924,7 +951,7 @@ func parseRules(rulesConfig []string, proxies map[string]C.Proxy, subRules map[s
l := len(rule) 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] target = rule[l-1]
payload = strings.Join(rule[1:l-1], ",") payload = strings.Join(rule[1:l-1], ",")
} else { } else {
@@ -958,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()) 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) rules = append(rules, parsed)
} }
@@ -1027,10 +1060,20 @@ func hostWithDefaultPort(host string, defPort string) (string, error) {
return net.JoinHostPort(hostname, port), nil 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 var nameservers []dns.NameServer
for idx, server := range servers { 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) server = parsePureDNSServer(server)
u, err := url.Parse(server) u, err := url.Parse(server)
if err != nil { if err != nil {
@@ -1051,13 +1094,16 @@ func parseNameServer(servers []string, preferH3 bool) ([]dns.NameServer, error)
case "tls": case "tls":
addr, err = hostWithDefaultPort(u.Host, "853") addr, err = hostWithDefaultPort(u.Host, "853")
dnsNetType = "tcp-tls" // DNS over TLS dnsNetType = "tcp-tls" // DNS over TLS
case "https": case "http", "https":
addr, err = hostWithDefaultPort(u.Host, "443") addr, err = hostWithDefaultPort(u.Host, "443")
dnsNetType = "https" // DNS over HTTPS
if u.Scheme == "http" {
addr, err = hostWithDefaultPort(u.Host, "80")
}
if err == nil { if err == nil {
proxyName = "" 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() addr = clearURL.String()
dnsNetType = "https" // DNS over HTTPS
if len(u.Fragment) != 0 { if len(u.Fragment) != 0 {
for _, s := range strings.Split(u.Fragment, "&") { for _, s := range strings.Split(u.Fragment, "&") {
arr := strings.Split(s, "=") arr := strings.Split(s, "=")
@@ -1073,9 +1119,6 @@ func parseNameServer(servers []string, preferH3 bool) ([]dns.NameServer, error)
} }
} }
} }
case "dhcp":
addr = u.Host
dnsNetType = "dhcp" // UDP from DHCP
case "quic": case "quic":
addr, err = hostWithDefaultPort(u.Host, "853") addr, err = hostWithDefaultPort(u.Host, "853")
dnsNetType = "quic" // DNS over QUIC dnsNetType = "quic" // DNS over QUIC
@@ -1102,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()) return nil, fmt.Errorf("DNS NameServer[%d] format error: %s", idx, err.Error())
} }
if respectRules && len(proxyName) == 0 {
proxyName = dns.RespectRules
}
nameservers = append( nameservers = append(
nameservers, nameservers,
dns.NameServer{ dns.NameServer{
@@ -1118,7 +1165,7 @@ func parseNameServer(servers []string, preferH3 bool) ([]dns.NameServer, error)
func init() { func init() {
dns.ParseNameServer = func(servers []string) ([]dns.NameServer, error) { // using by wireguard dns.ParseNameServer = func(servers []string) ([]dns.NameServer, error) { // using by wireguard
return parseNameServer(servers, false) return parseNameServer(servers, false, false)
} }
} }
@@ -1144,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]() policy := orderedmap.New[string, []dns.NameServer]()
updatedPolicy := orderedmap.New[string, any]() updatedPolicy := orderedmap.New[string, any]()
re := regexp.MustCompile(`[a-zA-Z0-9\-]+\.[a-zA-Z]{2,}(\.[a-zA-Z]{2,})?`) re := regexp.MustCompile(`[a-zA-Z0-9\-]+\.[a-zA-Z]{2,}(\.[a-zA-Z]{2,})?`)
@@ -1190,7 +1238,7 @@ func parseNameServerPolicy(nsPolicy *orderedmap.OrderedMap[string, any], rulePro
if err != nil { if err != nil {
return nil, err return nil, err
} }
nameservers, err := parseNameServer(servers, preferH3) nameservers, err := parseNameServer(servers, respectRules, preferH3)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -1284,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") 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{ dnsCfg := &DNS{
Enable: cfg.Enable, Enable: cfg.Enable,
Listen: cfg.Listen, Listen: cfg.Listen,
PreferH3: cfg.PreferH3, PreferH3: cfg.PreferH3,
IPv6Timeout: cfg.IPv6Timeout, IPv6Timeout: cfg.IPv6Timeout,
IPv6: cfg.IPv6, IPv6: cfg.IPv6,
EnhancedMode: cfg.EnhancedMode, UseSystemHosts: cfg.UseSystemHosts,
EnhancedMode: cfg.EnhancedMode,
FallbackFilter: FallbackFilter{ FallbackFilter: FallbackFilter{
IPCIDR: []netip.Prefix{}, IPCIDR: []netip.Prefix{},
GeoSite: []router.DomainMatcher{}, GeoSite: []router.DomainMatcher{},
}, },
} }
var err error 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 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 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 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 return nil, err
} }
if len(cfg.DefaultNameserver) == 0 { if len(cfg.DefaultNameserver) == 0 {
return nil, errors.New("default nameserver should have at least one nameserver") 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 return nil, err
} }
// check default nameserver is pure ip addr // check default nameserver is pure ip addr
@@ -1433,31 +1486,39 @@ func parseTun(rawTun RawTun, general *General) error {
DNSHijack: rawTun.DNSHijack, DNSHijack: rawTun.DNSHijack,
AutoRoute: rawTun.AutoRoute, AutoRoute: rawTun.AutoRoute,
AutoDetectInterface: rawTun.AutoDetectInterface, AutoDetectInterface: rawTun.AutoDetectInterface,
RedirectToTun: rawTun.RedirectToTun,
MTU: rawTun.MTU, MTU: rawTun.MTU,
GSO: rawTun.GSO, GSO: rawTun.GSO,
GSOMaxSize: rawTun.GSOMaxSize, GSOMaxSize: rawTun.GSOMaxSize,
Inet4Address: []netip.Prefix{tunAddressPrefix}, Inet4Address: []netip.Prefix{tunAddressPrefix},
Inet6Address: rawTun.Inet6Address, Inet6Address: rawTun.Inet6Address,
StrictRoute: rawTun.StrictRoute, 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, Inet4RouteAddress: rawTun.Inet4RouteAddress,
Inet6RouteAddress: rawTun.Inet6RouteAddress, Inet6RouteAddress: rawTun.Inet6RouteAddress,
Inet4RouteExcludeAddress: rawTun.Inet4RouteExcludeAddress, Inet4RouteExcludeAddress: rawTun.Inet4RouteExcludeAddress,
Inet6RouteExcludeAddress: rawTun.Inet6RouteExcludeAddress, 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 return nil
@@ -1515,7 +1576,7 @@ func parseSniffer(snifferRaw RawSniffer) (*Sniffer, error) {
} }
} }
} else { } else {
if sniffer.Enable { if sniffer.Enable && len(snifferRaw.Sniffing) != 0 {
// Deprecated: Use Sniff instead // Deprecated: Use Sniff instead
log.Warnln("Deprecated: Use Sniff instead") log.Warnln("Deprecated: Use Sniff instead")
} }

View File

@@ -1,38 +1,15 @@
package config package config
import ( import (
"context"
"fmt" "fmt"
"io"
"net" "net"
"net/http"
"net/netip" "net/netip"
"os"
"strings" "strings"
"time"
"github.com/metacubex/mihomo/adapter/outboundgroup" "github.com/metacubex/mihomo/adapter/outboundgroup"
"github.com/metacubex/mihomo/common/structure" "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) { func trimArr(arr []string) (r []string) {
for _, e := range arr { for _, e := range arr {
r = append(r, strings.Trim(e, " ")) r = append(r, strings.Trim(e, " "))

View File

@@ -0,0 +1,5 @@
package features
var WindowsMajorVersion uint32
var WindowsMinorVersion uint32
var WindowsBuildNumber uint32

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

View File

@@ -1,6 +1,8 @@
package provider package provider
import ( import (
"fmt"
"github.com/metacubex/mihomo/common/utils" "github.com/metacubex/mihomo/common/utils"
"github.com/metacubex/mihomo/constant" "github.com/metacubex/mihomo/constant"
) )
@@ -84,7 +86,7 @@ type RuleProvider interface {
Match(*constant.Metadata) bool Match(*constant.Metadata) bool
ShouldResolveIP() bool ShouldResolveIP() bool
ShouldFindProcess() bool ShouldFindProcess() bool
AsRule(adaptor string) constant.Rule Strategy() any
} }
// Rule Behavior // Rule Behavior
@@ -110,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 ( const (
YamlRule RuleFormat = iota YamlRule RuleFormat = iota
TextRule TextRule
MrsRule
) )
type RuleFormat int type RuleFormat int
@@ -123,7 +153,29 @@ func (rf RuleFormat) String() string {
return "YamlRule" return "YamlRule"
case TextRule: case TextRule:
return "TextRule" return "TextRule"
case MrsRule:
return "MrsRule"
default: default:
return "Unknown" 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]
}

View File

@@ -22,8 +22,10 @@ const (
InUser InUser
InName InName
InType InType
Process ProcessName
ProcessPath ProcessPath
ProcessNameRegex
ProcessPathRegex
RuleSet RuleSet
Network Network
Uid Uid
@@ -76,10 +78,14 @@ func (rt RuleType) String() string {
return "InName" return "InName"
case InType: case InType:
return "InType" return "InType"
case Process: case ProcessName:
return "Process" return "ProcessName"
case ProcessPath: case ProcessPath:
return "ProcessPath" return "ProcessPath"
case ProcessNameRegex:
return "ProcessNameRegex"
case ProcessPathRegex:
return "ProcessPathRegex"
case MATCH: case MATCH:
return "Match" return "Match"
case RuleSet: case RuleSet:
@@ -110,4 +116,5 @@ type Rule interface {
Payload() string Payload() string
ShouldResolveIP() bool ShouldResolveIP() bool
ShouldFindProcess() bool ShouldFindProcess() bool
ProviderNames() []string
} }

View File

@@ -5,28 +5,20 @@ import (
"crypto/tls" "crypto/tls"
"fmt" "fmt"
"net" "net"
"net/netip"
"strings" "strings"
"github.com/metacubex/mihomo/component/ca" "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" "github.com/metacubex/mihomo/log"
D "github.com/miekg/dns" D "github.com/miekg/dns"
"github.com/zhangyunhao116/fastrand"
) )
type client struct { type client struct {
*D.Client *D.Client
r *Resolver port string
port string host string
host string dialer *dnsDialer
iface string addr string
proxyAdapter C.ProxyAdapter
proxyName string
addr string
} }
var _ dnsClient = (*client)(nil) 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) { 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" network := "udp"
if strings.HasPrefix(c.Client.Net, "tcp") { if strings.HasPrefix(c.Client.Net, "tcp") {
network = "tcp" network = "tcp"
} }
var options []dialer.Option addr := net.JoinHostPort(c.host, c.port)
if c.iface != "" { conn, err := c.dialer.DialContext(ctx, network, addr)
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)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -115,7 +82,7 @@ func (c *client) ExchangeContext(ctx context.Context, m *D.Msg) (*D.Msg, error)
tcpClient.Net = "tcp" tcpClient.Net = "tcp"
network = "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()) 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 { if err != nil {
ch <- result{msg, err} ch <- result{msg, err}
return return

11
dns/dialer.go Normal file
View 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

View File

@@ -61,12 +61,12 @@ type dnsOverHTTPS struct {
// for this upstream. // for this upstream.
quicConfig *quic.Config quicConfig *quic.Config
quicConfigGuard sync.Mutex quicConfigGuard sync.Mutex
url *url.URL
r *Resolver url *url.URL
httpVersions []C.HTTPVersion httpVersions []C.HTTPVersion
proxyAdapter C.ProxyAdapter dialer *dnsDialer
proxyName string addr string
addr string skipCertVerify bool
} }
// type check // type check
@@ -85,11 +85,9 @@ func newDoHClient(urlString string, r *Resolver, preferH3 bool, params map[strin
} }
doh := &dnsOverHTTPS{ doh := &dnsOverHTTPS{
url: u, url: u,
addr: u.String(), addr: u.String(),
r: r, dialer: newDNSDialer(r, proxyAdapter, proxyName),
proxyAdapter: proxyAdapter,
proxyName: proxyName,
quicConfig: &quic.Config{ quicConfig: &quic.Config{
KeepAlivePeriod: QUICKeepAlivePeriod, KeepAlivePeriod: QUICKeepAlivePeriod,
TokenStore: newQUICTokenStore(), TokenStore: newQUICTokenStore(),
@@ -97,6 +95,10 @@ func newDoHClient(urlString string, r *Resolver, preferH3 bool, params map[strin
httpVersions: httpVersions, httpVersions: httpVersions,
} }
if params["skip-cert-verify"] == "true" {
doh.skipCertVerify = true
}
runtime.SetFinalizer(doh, (*dnsOverHTTPS).Close) runtime.SetFinalizer(doh, (*dnsOverHTTPS).Close)
return doh return doh
@@ -106,6 +108,7 @@ func newDoHClient(urlString string, r *Resolver, preferH3 bool, params map[strin
func (doh *dnsOverHTTPS) Address() string { func (doh *dnsOverHTTPS) Address() string {
return doh.addr return doh.addr
} }
func (doh *dnsOverHTTPS) ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg, err error) { 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: // Quote from https://www.rfc-editor.org/rfc/rfc8484.html:
// In order to maximize HTTP cache friendliness, DoH clients using media // 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 return nil
} }
// exchangeHTTPS logs the request and its result and calls exchangeHTTPSClient. // exchangeHTTPS sends the DNS query to a DoH resolver using the specified
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
// http.Client instance. // http.Client instance.
func (doh *dnsOverHTTPS) exchangeHTTPSClient( func (doh *dnsOverHTTPS) exchangeHTTPS(ctx context.Context, client *http.Client, req *D.Msg) (resp *D.Msg, err error) {
ctx context.Context,
client *http.Client,
req *D.Msg,
) (resp *D.Msg, err error) {
buf, err := req.Pack() buf, err := req.Pack()
if err != nil { if err != nil {
return nil, fmt.Errorf("packing message: %w", err) return nil, fmt.Errorf("packing message: %w", err)
@@ -208,24 +201,24 @@ func (doh *dnsOverHTTPS) exchangeHTTPSClient(
method = http3.MethodGet0RTT method = http3.MethodGet0RTT
} }
url := doh.url requestUrl := *doh.url // don't modify origin url
url.RawQuery = fmt.Sprintf("dns=%s", base64.RawURLEncoding.EncodeToString(buf)) requestUrl.RawQuery = fmt.Sprintf("dns=%s", base64.RawURLEncoding.EncodeToString(buf))
httpReq, err := http.NewRequestWithContext(ctx, method, url.String(), nil) httpReq, err := http.NewRequestWithContext(ctx, method, requestUrl.String(), nil)
if err != 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("Accept", "application/dns-message")
httpReq.Header.Set("User-Agent", "") httpReq.Header.Set("User-Agent", "")
httpResp, err := client.Do(httpReq) httpResp, err := client.Do(httpReq)
if err != nil { 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() defer httpResp.Body.Close()
body, err := io.ReadAll(httpResp.Body) body, err := io.ReadAll(httpResp.Body)
if err != nil { 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 { if httpResp.StatusCode != http.StatusOK {
@@ -234,7 +227,7 @@ func (doh *dnsOverHTTPS) exchangeHTTPSClient(
"expected status %d, got %d from %s", "expected status %d, got %d from %s",
http.StatusOK, http.StatusOK,
httpResp.StatusCode, httpResp.StatusCode,
url, doh.url,
) )
} }
@@ -243,7 +236,7 @@ func (doh *dnsOverHTTPS) exchangeHTTPSClient(
if err != nil { if err != nil {
return nil, fmt.Errorf( return nil, fmt.Errorf(
"unpacking response from %s: body is %s: %w", "unpacking response from %s: body is %s: %w",
url, doh.url,
body, body,
err, 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, // HTTP3 is enabled in the upstream options). If this attempt is successful,
// it returns an HTTP3 transport, otherwise it returns the H1/H2 transport. // it returns an HTTP3 transport, otherwise it returns the H1/H2 transport.
func (doh *dnsOverHTTPS) createTransport(ctx context.Context) (t http.RoundTripper, err error) { 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( tlsConfig := ca.GetGlobalTLSConfig(
&tls.Config{ &tls.Config{
InsecureSkipVerify: false, InsecureSkipVerify: doh.skipCertVerify,
MinVersion: tls.VersionTLS12, MinVersion: tls.VersionTLS12,
SessionTicketsDisabled: false, SessionTicketsDisabled: false,
}) })
@@ -388,13 +393,13 @@ func (doh *dnsOverHTTPS) createTransport(ctx context.Context) (t http.RoundTripp
nextProtos = append(nextProtos, string(v)) nextProtos = append(nextProtos, string(v))
} }
tlsConfig.NextProtos = nextProtos tlsConfig.NextProtos = nextProtos
dialContext := getDialHandler(doh.r, doh.proxyAdapter, doh.proxyName) transport.TLSClientConfig = tlsConfig
if slices.Contains(doh.httpVersions, C.HTTPVersion3) { if slices.Contains(doh.httpVersions, C.HTTPVersion3) {
// First, we attempt to create an HTTP3 transport. If the probe QUIC // First, we attempt to create an HTTP3 transport. If the probe QUIC
// connection is established successfully, we'll be using HTTP3 for this // connection is established successfully, we'll be using HTTP3 for this
// upstream. // upstream.
transportH3, err := doh.createTransportH3(ctx, tlsConfig, dialContext) transportH3, err := doh.createTransportH3(ctx, tlsConfig)
if err == nil { if err == nil {
log.Debugln("[%s] using HTTP/3 for this upstream: QUIC was faster", doh.url.String()) log.Debugln("[%s] using HTTP/3 for this upstream: QUIC was faster", doh.url.String())
return transportH3, nil 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") return nil, errors.New("HTTP1/1 and HTTP2 are not supported by this upstream")
} }
transport := &http.Transport{ // Since we have a custom DialContext, we need to use this field to
TLSClientConfig: tlsConfig, // make golang http.Client attempt to use HTTP/2. Otherwise, it would
DisableCompression: true, // only be used when negotiated on the TLS level.
DialContext: dialContext, transport.ForceAttemptHTTP2 = true
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,
}
// Explicitly configure transport to use HTTP/2. // Explicitly configure transport to use HTTP/2.
// //
@@ -490,13 +487,12 @@ func (h *http3Transport) Close() (err error) {
func (doh *dnsOverHTTPS) createTransportH3( func (doh *dnsOverHTTPS) createTransportH3(
ctx context.Context, ctx context.Context,
tlsConfig *tls.Config, tlsConfig *tls.Config,
dialContext dialHandler,
) (roundTripper http.RoundTripper, err error) { ) (roundTripper http.RoundTripper, err error) {
if !doh.supportsH3() { if !doh.supportsH3() {
return nil, errors.New("HTTP3 support is not enabled") 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 { if err != nil {
return nil, err return nil, err
} }
@@ -515,7 +511,7 @@ func (doh *dnsOverHTTPS) createTransportH3(
}, },
DisableCompression: true, DisableCompression: true,
TLSClientConfig: tlsConfig, TLSClientConfig: tlsConfig,
QuicConfig: doh.getQUICConfig(), QUICConfig: doh.getQUICConfig(),
} }
return &http3Transport{baseTransport: rt}, nil return &http3Transport{baseTransport: rt}, nil
@@ -534,7 +530,7 @@ func (doh *dnsOverHTTPS) dialQuic(ctx context.Context, addr string, tlsCfg *tls.
IP: net.ParseIP(ip), IP: net.ParseIP(ip),
Port: portInt, 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 { if err != nil {
return nil, err return nil, err
} }
@@ -557,12 +553,11 @@ func (doh *dnsOverHTTPS) dialQuic(ctx context.Context, addr string, tlsCfg *tls.
func (doh *dnsOverHTTPS) probeH3( func (doh *dnsOverHTTPS) probeH3(
ctx context.Context, ctx context.Context,
tlsConfig *tls.Config, tlsConfig *tls.Config,
dialContext dialHandler,
) (addr string, err error) { ) (addr string, err error) {
// We're using bootstrapped address instead of what's passed to the function // 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 // it does not create an actual connection, but it helps us determine
// what IP is actually reachable (when there are v4/v6 addresses). // 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 { if err != nil {
return "", fmt.Errorf("failed to dial: %w", err) return "", fmt.Errorf("failed to dial: %w", err)
} }
@@ -592,7 +587,7 @@ func (doh *dnsOverHTTPS) probeH3(
chQuic := make(chan error, 1) chQuic := make(chan error, 1)
chTLS := make(chan error, 1) chTLS := make(chan error, 1)
go doh.probeQUIC(ctx, addr, probeTLSCfg, chQuic) go doh.probeQUIC(ctx, addr, probeTLSCfg, chQuic)
go doh.probeTLS(ctx, dialContext, probeTLSCfg, chTLS) go doh.probeTLS(ctx, probeTLSCfg, chTLS)
select { select {
case quicErr := <-chQuic: 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 // probeTLS attempts to establish a TLS connection to the specified address. We
// run probeQUIC and probeTLS in parallel and see which one is faster. // 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() startTime := time.Now()
conn, err := doh.tlsDial(ctx, dialContext, "tcp", tlsConfig) conn, err := doh.tlsDial(ctx, "tcp", tlsConfig)
if err != nil { if err != nil {
ch <- fmt.Errorf("opening TLS connection: %w", err) ch <- fmt.Errorf("opening TLS connection: %w", err)
return 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 // tlsDial is basically the same as tls.DialWithDialer, but we will call our own
// dialContext function to get connection. // 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 // We're using bootstrapped address instead of what's passed
// to the function. // to the function.
rawConn, err := dialContext(ctx, network, doh.url.Host) rawConn, err := doh.dialer.DialContext(ctx, network, doh.url.Host)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@@ -60,10 +60,8 @@ type dnsOverQUIC struct {
bytesPool *sync.Pool bytesPool *sync.Pool
bytesPoolGuard sync.Mutex bytesPoolGuard sync.Mutex
addr string addr string
proxyAdapter C.ProxyAdapter dialer *dnsDialer
proxyName string
r *Resolver
} }
// type check // type check
@@ -72,10 +70,8 @@ var _ dnsClient = (*dnsOverQUIC)(nil)
// newDoQ returns the DNS-over-QUIC Upstream. // newDoQ returns the DNS-over-QUIC Upstream.
func newDoQ(resolver *Resolver, addr string, proxyAdapter C.ProxyAdapter, proxyName string) (dnsClient, error) { func newDoQ(resolver *Resolver, addr string, proxyAdapter C.ProxyAdapter, proxyName string) (dnsClient, error) {
doq := &dnsOverQUIC{ doq := &dnsOverQUIC{
addr: addr, addr: addr,
proxyAdapter: proxyAdapter, dialer: newDNSDialer(resolver, proxyAdapter, proxyName),
proxyName: proxyName,
r: resolver,
quicConfig: &quic.Config{ quicConfig: &quic.Config{
KeepAlivePeriod: QUICKeepAlivePeriod, KeepAlivePeriod: QUICKeepAlivePeriod,
TokenStore: newQUICTokenStore(), 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 // 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 // it does not create an actual connection, but it helps us determine
// what IP is actually reachable (when there're v4/v6 addresses). // 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 { if err != nil {
return nil, fmt.Errorf("failed to open a QUIC connection: %w", err) 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) p, err := strconv.Atoi(port)
udpAddr := net.UDPAddr{IP: net.ParseIP(ip), Port: p} 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 { if err != nil {
return nil, err return nil, err
} }

View File

@@ -37,14 +37,17 @@ func (p geositePolicy) Match(domain string) []dnsClient {
} }
type domainSetPolicy struct { type domainSetPolicy struct {
domainSetProvider provider.RuleProvider tunnel provider.Tunnel
dnsClients []dnsClient name string
dnsClients []dnsClient
} }
func (p domainSetPolicy) Match(domain string) []dnsClient { func (p domainSetPolicy) Match(domain string) []dnsClient {
metadata := &C.Metadata{Host: domain} if ruleProvider, ok := p.tunnel.RuleProviders()[p.name]; ok {
if ok := p.domainSetProvider.Match(metadata); ok { metadata := &C.Metadata{Host: domain}
return p.dnsClients if ok := ruleProvider.Match(metadata); ok {
return p.dnsClients
}
} }
return nil return nil
} }

View File

@@ -146,9 +146,12 @@ func (r *Resolver) ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg, e
}() }()
q := m.Question[0] q := m.Question[0]
domain := msgToDomain(m)
_, qTypeStr := msgToQtype(m)
cacheM, expireTime, hit := r.cache.GetWithExpire(q.String()) cacheM, expireTime, hit := r.cache.GetWithExpire(q.String())
if hit { 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() now := time.Now()
msg = cacheM.Copy() msg = cacheM.Copy()
if expireTime.Before(now) { if expireTime.Before(now) {
@@ -414,7 +417,7 @@ type Config struct {
Pool *fakeip.Pool Pool *fakeip.Pool
Hosts *trie.DomainTrie[resolver.HostValue] Hosts *trie.DomainTrie[resolver.HostValue]
Policy *orderedmap.OrderedMap[string, []NameServer] Policy *orderedmap.OrderedMap[string, []NameServer]
RuleProviders map[string]provider.RuleProvider Tunnel provider.Tunnel
CacheAlgorithm string CacheAlgorithm string
} }
@@ -502,11 +505,12 @@ func NewResolver(config Config) *Resolver {
key := temp[1] key := temp[1]
switch prefix { switch prefix {
case "rule-set": 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) log.Debugln("Adding rule-set policy: %s ", key)
insertPolicy(domainSetPolicy{ insertPolicy(domainSetPolicy{
domainSetProvider: p, tunnel: config.Tunnel,
dnsClients: cacheTransform(nameserver), name: key,
dnsClients: cacheTransform(nameserver),
}) })
continue continue
} else { } else {

View File

@@ -7,18 +7,14 @@ import (
"fmt" "fmt"
"net" "net"
"net/netip" "net/netip"
"strconv"
"strings" "strings"
"time" "time"
N "github.com/metacubex/mihomo/common/net"
"github.com/metacubex/mihomo/common/nnip" "github.com/metacubex/mihomo/common/nnip"
"github.com/metacubex/mihomo/common/picker" "github.com/metacubex/mihomo/common/picker"
"github.com/metacubex/mihomo/component/dialer" "github.com/metacubex/mihomo/component/dialer"
"github.com/metacubex/mihomo/component/resolver" "github.com/metacubex/mihomo/component/resolver"
C "github.com/metacubex/mihomo/constant"
"github.com/metacubex/mihomo/log" "github.com/metacubex/mihomo/log"
"github.com/metacubex/mihomo/tunnel"
D "github.com/miekg/dns" D "github.com/miekg/dns"
"github.com/samber/lo" "github.com/samber/lo"
@@ -120,6 +116,11 @@ func transform(servers []NameServer, resolver *Resolver) []dnsClient {
continue continue
} }
var options []dialer.Option
if s.Interface != "" {
options = append(options, dialer.WithInterface(s.Interface))
}
host, port, _ := net.SplitHostPort(s.Addr) host, port, _ := net.SplitHostPort(s.Addr)
ret = append(ret, &client{ ret = append(ret, &client{
Client: &D.Client{ Client: &D.Client{
@@ -130,12 +131,9 @@ func transform(servers []NameServer, resolver *Resolver) []dnsClient {
UDPSize: 4096, UDPSize: 4096,
Timeout: 5 * time.Second, Timeout: 5 * time.Second,
}, },
port: port, port: port,
host: host, host: host,
iface: s.Interface, dialer: newDNSDialer(resolver, s.ProxyAdapter, s.ProxyName, options...),
r: resolver,
proxyAdapter: s.ProxyAdapter,
proxyName: s.ProxyName,
}) })
} }
return ret return ret
@@ -175,118 +173,12 @@ func msgToDomain(msg *D.Msg) string {
return "" return ""
} }
type dialHandler func(ctx context.Context, network, addr string) (net.Conn, error) func msgToQtype(msg *D.Msg) (uint16, string) {
if len(msg.Question) > 0 {
func getDialHandler(r *Resolver, proxyAdapter C.ProxyAdapter, proxyName string, opts ...dialer.Option) dialHandler { qType := msg.Question[0].Qtype
return func(ctx context.Context, network, addr string) (net.Conn, error) { return qType, D.Type(qType).String()
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
}
}
} }
} return 0, ""
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...)
} }
func batchExchange(ctx context.Context, clients []dnsClient, m *D.Msg) (msg *D.Msg, cache bool, err error) { func batchExchange(ctx context.Context, clients []dnsClient, m *D.Msg) (msg *D.Msg, cache bool, err error) {
@@ -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) fast, ctx := picker.WithTimeout[*D.Msg](ctx, resolver.DefaultDNSTimeout)
defer fast.Close() defer fast.Close()
domain := msgToDomain(m) domain := msgToDomain(m)
qType, qTypeStr := msgToQtype(m)
var noIpMsg *D.Msg var noIpMsg *D.Msg
for _, client := range clients { for _, client := range clients {
if _, isRCodeClient := client.(rcodeClient); isRCodeClient { 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 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) { 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) m, err := client.ExchangeContext(ctx, m)
if err != nil { if err != nil {
return nil, err 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. // so we would ignore RCode errors from RCode clients.
return nil, errors.New("server failure: " + D.RcodeToString[m.Rcode]) return nil, errors.New("server failure: " + D.RcodeToString[m.Rcode])
} }
if ips := msgToIP(m); len(m.Question) > 0 { ips := msgToIP(m)
qType := m.Question[0].Qtype log.Debugln("[DNS] %s --> %s %s from %s", domain, ips, qTypeStr, client.Address())
log.Debugln("[DNS] %s --> %s %s from %s", domain, ips, D.Type(qType), client.Address()) switch qType {
switch qType { case D.TypeAAAA:
case D.TypeAAAA: if len(ips) == 0 {
if len(ips) == 0 { noIpMsg = m
noIpMsg = m return nil, resolver.ErrIPNotFound
return nil, resolver.ErrIPNotFound }
} case D.TypeA:
case D.TypeA: if len(ips) == 0 {
if len(ips) == 0 { noIpMsg = m
noIpMsg = m return nil, resolver.ErrIPNotFound
return nil, resolver.ErrIPNotFound
}
} }
} }
return m, nil return m, nil

View File

@@ -70,6 +70,10 @@ external-ui: /path/to/ui/folder/
external-ui-name: xd external-ui-name: xd
external-ui-url: "https://github.com/MetaCubeX/metacubexd/archive/refs/heads/gh-pages.zip" 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 # 设置出口网卡 # interface-name: en0 # 设置出口网卡
# 全局 TLS 指纹,优先低于 proxy 内的 client-fingerprint # 全局 TLS 指纹,优先低于 proxy 内的 client-fingerprint
@@ -116,13 +120,25 @@ tun:
# mtu: 9000 # 最大传输单元 # mtu: 9000 # 最大传输单元
# gso: false # 启用通用分段卸载,仅支持 Linux # gso: false # 启用通用分段卸载,仅支持 Linux
# gso-max-size: 65536 # 通用分段卸载包的最大大小 # gso-max-size: 65536 # 通用分段卸载包的最大大小
auto-redirect: false # 自动配置 iptables 以重定向 TCP 连接。仅支持 Linux。带有 auto-redirect 的 auto-route 现在可以在路由器上按预期工作,无需干预。
# strict-route: true # 将所有连接路由到 tun 来防止泄漏,但你的设备将无法其他设备被访问 # strict-route: true # 将所有连接路由到 tun 来防止泄漏,但你的设备将无法其他设备被访问
inet4-route-address: # 启用 auto-route 时使用自定义路由而不是默认路由 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 - 0.0.0.0/1
- 128.0.0.0/1 - 128.0.0.0/1
inet6-route-address: # 启用 auto-route 时使用自定义路由而不是默认路由
- "::/1" - "::/1"
- "8000::/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 # endpoint-independent-nat: false # 启用独立于端点的 NAT
# include-interface: # 限制被路由的接口。默认不限制,与 `exclude-interface` 冲突 # include-interface: # 限制被路由的接口。默认不限制,与 `exclude-interface` 冲突
# - "lan0" # - "lan0"
@@ -209,7 +225,7 @@ tunnels: # one line config
dns: dns:
cache-algorithm: arc cache-algorithm: arc
enable: false # 关闭将使用系统 DNS enable: false # 关闭将使用系统 DNS
prefer-h3: true # 开启 DoH 支持 HTTP/3将并发尝试 prefer-h3: false # 是否开启 DoH 支持 HTTP/3将并发尝试
listen: 0.0.0.0:53 # 开启 DNS 服务器监听 listen: 0.0.0.0:53 # 开启 DNS 服务器监听
# ipv6: false # false 将返回 AAAA 的空结果 # ipv6: false # false 将返回 AAAA 的空结果
# ipv6-timeout: 300 # 单位ms内部双栈并发时向上游查询 AAAA 时,等待 AAAA 的时间,默认 100ms # ipv6-timeout: 300 # 单位ms内部双栈并发时向上游查询 AAAA 时,等待 AAAA 的时间,默认 100ms
@@ -227,6 +243,13 @@ dns:
# use-hosts: true # 查询 hosts # use-hosts: true # 查询 hosts
# 配置后面的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 的域名
# fake-ip-filter: # fake-ip-filter:
# - '*.lan' # - '*.lan'
@@ -244,6 +267,7 @@ dns:
- https://mozilla.cloudflare-dns.com/dns-query#DNS&h3=true # 指定策略组和使用 HTTP/3 - https://mozilla.cloudflare-dns.com/dns-query#DNS&h3=true # 指定策略组和使用 HTTP/3
- dhcp://en0 # dns from dhcp - dhcp://en0 # dns from dhcp
- quic://dns.adguard.com:784 # DNS over QUIC - quic://dns.adguard.com:784 # DNS over QUIC
# - '8.8.8.8#RULES' # 效果同respect-rules但仅对该服务器生效
# - '8.8.8.8#en0' # 兼容指定 DNS 出口网卡 # - '8.8.8.8#en0' # 兼容指定 DNS 出口网卡
# 当配置 fallback 时,会查询 nameserver 中返回的 IP 是否为 CN非必要配置 # 当配置 fallback 时,会查询 nameserver 中返回的 IP 是否为 CN非必要配置
@@ -611,6 +635,10 @@ proxies: # socks5
# - h2 # - h2
# - http/1.1 # - http/1.1
# skip-cert-verify: true # 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 - name: trojan-grpc
server: server server: server
@@ -716,6 +744,7 @@ proxies: # socks5
# dialer-proxy: "ss1" # dialer-proxy: "ss1"
# remote-dns-resolve: true # 强制 dns 远程解析,默认值为 false # remote-dns-resolve: true # 强制 dns 远程解析,默认值为 false
# dns: [ 1.1.1.1, 8.8.8.8 ] # 仅在 remote-dns-resolve 为 true 时生效 # 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 不为空,该段落中的 allowed-ips 不可为空;前面段落的 server,port,public-key,pre-shared-key 均会被忽略,但 private-key 会被保留且只能在顶层指定
# peers: # peers:
# - server: 162.159.192.1 # - server: 162.159.192.1
@@ -913,6 +942,24 @@ rule-providers:
interval: 259200 interval: 259200
path: /path/to/save/file.yaml path: /path/to/save/file.yaml
type: file 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: rules:
- RULE-SET,rule1,REJECT - RULE-SET,rule1,REJECT
- IP-ASN,1,PROXY - IP-ASN,1,PROXY

69
go.mod
View File

@@ -4,57 +4,60 @@ go 1.20
require ( require (
github.com/3andne/restls-client-go v0.1.6 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/bahlo/generic-list-go v0.2.0
github.com/cilium/ebpf v0.12.3 github.com/cilium/ebpf v0.12.3
github.com/coreos/go-iptables v0.7.0 github.com/coreos/go-iptables v0.7.0
github.com/dlclark/regexp2 v1.11.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/cors v1.2.1
github.com/go-chi/render v1.0.3 github.com/go-chi/render v1.0.3
github.com/gobwas/ws v1.3.2 github.com/gobwas/ws v1.4.0
github.com/gofrs/uuid/v5 v5.1.0 github.com/gofrs/uuid/v5 v5.2.0
github.com/insomniacslk/dhcp v0.0.0-20240419123447-f1cffa2c0c49 github.com/insomniacslk/dhcp v0.0.0-20240529192340-51bc6136a0a6
github.com/klauspost/cpuid/v2 v2.2.7 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/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40
github.com/mdlayher/netlink v1.7.2 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/gopacket v1.1.20-0.20230608035415-7e2f98a3e759
github.com/metacubex/quic-go v0.42.1-0.20240418003344-f006b5735d98 github.com/metacubex/quic-go v0.45.1-0.20240610004319-163fee60637e
github.com/metacubex/sing-quic v0.0.0-20240418004036-814c531c378d github.com/metacubex/randv2 v0.2.0
github.com/metacubex/sing-shadowsocks v0.2.6 github.com/metacubex/sing-quic v0.0.0-20240518034124-7696d3f7da72
github.com/metacubex/sing-shadowsocks2 v0.2.0 github.com/metacubex/sing-shadowsocks v0.2.7
github.com/metacubex/sing-tun v0.2.6 github.com/metacubex/sing-shadowsocks2 v0.2.1
github.com/metacubex/sing-vmess v0.1.9-0.20231207122118-72303677451f github.com/metacubex/sing-tun v0.2.7-0.20240719141246-19c49ac9589d
github.com/metacubex/sing-wireguard v0.0.0-20240321042214-224f96122a63 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/metacubex/tfo-go v0.0.0-20240228025757-be1269474a66
github.com/miekg/dns v1.1.59 github.com/metacubex/utls v1.6.6
github.com/miekg/dns v1.1.61
github.com/mroth/weightedrand/v2 v2.1.0 github.com/mroth/weightedrand/v2 v2.1.0
github.com/openacid/low v0.1.21 github.com/openacid/low v0.1.21
github.com/oschwald/maxminddb-golang v1.12.0 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/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a
github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97 github.com/sagernet/fswatch v0.1.1
github.com/sagernet/sing v0.3.8 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-mux v0.2.1-0.20240124034317-9bfb33698bb6
github.com/sagernet/sing-shadowtls v0.1.4 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/sagernet/wireguard-go v0.0.0-20231209092712-9a439356a62e
github.com/samber/lo v1.39.0 github.com/samber/lo v1.39.0
github.com/shirou/gopsutil/v3 v3.24.3 github.com/shirou/gopsutil/v3 v3.24.5
github.com/sirupsen/logrus v1.9.3 github.com/sirupsen/logrus v1.9.3
github.com/stretchr/testify v1.9.0 github.com/stretchr/testify v1.9.0
github.com/wk8/go-ordered-map/v2 v2.1.8 github.com/wk8/go-ordered-map/v2 v2.1.8
github.com/zhangyunhao116/fastrand v0.4.0 gitlab.com/go-extension/aes-ccm v0.0.0-20230221065045-e58665ef23c7
go.uber.org/automaxprocs v1.5.3 go.uber.org/automaxprocs v1.5.3
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba go4.org/netipx v0.0.0-20231129151722-fdeea329fbba
golang.org/x/crypto v0.22.0 golang.org/x/crypto v0.24.0
golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8
golang.org/x/net v0.24.0 golang.org/x/net v0.26.0
golang.org/x/sync v0.7.0 golang.org/x/sync v0.7.0
golang.org/x/sys v0.19.0 golang.org/x/sys v0.22.0
google.golang.org/protobuf v1.33.0 google.golang.org/protobuf v1.34.2
gopkg.in/yaml.v3 v3.0.1 gopkg.in/yaml.v3 v3.0.1
lukechampine.com/blake3 v1.2.2 lukechampine.com/blake3 v1.3.0
) )
require ( require (
@@ -63,7 +66,7 @@ require (
github.com/ajg/form v1.5.1 // indirect github.com/ajg/form v1.5.1 // indirect
github.com/andybalholm/brotli v1.0.6 // indirect github.com/andybalholm/brotli v1.0.6 // indirect
github.com/buger/jsonparser v1.1.1 // 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/davecgh/go-spew v1.1.1 // indirect
github.com/ericlagergren/aegis v0.0.0-20230312195928-b4ce538b56f9 // indirect github.com/ericlagergren/aegis v0.0.0-20230312195928-b4ce538b56f9 // indirect
github.com/ericlagergren/polyval v0.0.0-20220411101811-e25bc10ba391 // 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/google/pprof v0.0.0-20210407192527-94a9f03dee38 // indirect
github.com/hashicorp/yamux v0.1.1 // indirect github.com/hashicorp/yamux v0.1.1 // indirect
github.com/josharian/native v1.1.0 // 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/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
github.com/mailru/easyjson v0.7.7 // indirect github.com/mailru/easyjson v0.7.7 // indirect
github.com/mdlayher/socket v0.4.1 // indirect github.com/mdlayher/socket v0.4.1 // indirect
@@ -92,6 +94,7 @@ require (
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
github.com/quic-go/qpack v0.4.0 // indirect github.com/quic-go/qpack v0.4.0 // indirect
github.com/quic-go/qtls-go1-20 v0.4.1 // 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/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 // indirect
github.com/shoenig/go-m1cpu v0.1.6 // indirect github.com/shoenig/go-m1cpu v0.1.6 // indirect
github.com/sina-ghaderi/poly1305 v0.0.0-20220724002748-c5926b03988b // 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/go-sysconf v0.3.12 // indirect
github.com/tklauser/numcpus v0.6.1 // indirect github.com/tklauser/numcpus v0.6.1 // indirect
github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923 // 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 github.com/yusufpapurcu/wmi v1.2.4 // indirect
gitlab.com/yawning/bsaes.git v0.0.0-20190805113838-0a714cd429ec // indirect gitlab.com/yawning/bsaes.git v0.0.0-20190805113838-0a714cd429ec // indirect
go.uber.org/mock v0.4.0 // indirect go.uber.org/mock v0.4.0 // indirect
golang.org/x/mod v0.17.0 // indirect golang.org/x/mod v0.18.0 // indirect
golang.org/x/text v0.14.0 // indirect golang.org/x/text v0.16.0 // indirect
golang.org/x/time v0.5.0 // indirect golang.org/x/time v0.5.0 // indirect
golang.org/x/tools v0.20.0 // indirect golang.org/x/tools v0.22.0 // indirect
) )
replace github.com/sagernet/sing => github.com/metacubex/sing v0.0.0-20240408015159-aa61c96df764 replace github.com/sagernet/sing => github.com/metacubex/sing v0.0.0-20240724044459-6f3cf5896297

142
go.sum
View File

@@ -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/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 h1:cDVUiFo+npB0ZASqnw4q90ylaVAbnYyx0JYqK4YcGok=
github.com/Yawning/aez v0.0.0-20211027044916-e49e68abd344/go.mod h1:9pIqrY6SXNL8vjRQE5Hd/OL5GyK/9MrGUWs87z/eFfk= 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 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU=
github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY= github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY=
github.com/andybalholm/brotli v1.0.6 h1:Yf9fFpf49Zrxb9NlQaluyE92/+X7UVHlhMNJN2sxfOI= 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/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 h1:8ht6F9MquybnY97at+VDZb3eQQr8ev79RueWeVaEcG4=
github.com/cilium/ebpf v0.12.3/go.mod h1:TctK1ivibvI3znr66ljgi4hqOT8EYQjz1KWBfb1UVgM= 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.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU=
github.com/cloudflare/circl v1.3.6/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA= 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 h1:XWM3V+MPRr5/q51NuWSgU0fqMad64Zyxs8ZUoMsamr8=
github.com/coreos/go-iptables v0.7.0/go.mod h1:Qe8Bv2Xik5FyTXwgIbLAnv2sWSBmvWdFETJConOQ//Q= 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= 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/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 h1:NlRaXb3J6hAnTmWdsEKb9bcSBD6BvcIjdGdeb0zfXbk=
github.com/gaukas/godicttls v0.0.4/go.mod h1:l6EenT4TLWgTdwslVb4sEMOCf7Bv0JAK67deKr9/NCI= github.com/gaukas/godicttls v0.0.4/go.mod h1:l6EenT4TLWgTdwslVb4sEMOCf7Bv0JAK67deKr9/NCI=
github.com/go-chi/chi/v5 v5.0.12 h1:9euLV5sTrTNTRUU9POmDUvfxyj6LAABLUcEWO+JJb4s= github.com/go-chi/chi/v5 v5.0.14 h1:PyEwo2Vudraa0x/Wl6eDRRW2NXBvekgfxyydcM0WGE0=
github.com/go-chi/chi/v5 v5.0.12/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= 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 h1:xEC8UT3Rlp2QuWNEr4Fs/c2EAGVKBwy/1vHx3bppil4=
github.com/go-chi/cors v1.2.1/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58= github.com/go-chi/cors v1.2.1/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58=
github.com/go-chi/render v1.0.3 h1:AsXqd2a1/INaIfUSKq3G5uA8weYx20FOsM7uSoCyyt4= 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/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM=
github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og= github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og=
github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= 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.4.0 h1:CTaoG1tojrh4ucGPcoJFiAQUAsEWekEWvLy7GsVNqGs=
github.com/gobwas/ws v1.3.2/go.mod h1:hRKAFb8wOxFROYNsT1bqfWnhX+b5MFeJM9r2ZSwg/KY= github.com/gobwas/ws v1.4.0/go.mod h1:G3gNqMNtPppf5XUz7O4shetPpcZ1VJ7zt18dlUeakrc=
github.com/gofrs/uuid/v5 v5.1.0 h1:S5rqVKIigghZTCBKPCw0Y+bXkn26K3TB5mvQq2Ix8dk= github.com/gofrs/uuid/v5 v5.2.0 h1:qw1GMx6/y8vhVsx626ImfKMuS5CvJmhIKKtuyvfajMM=
github.com/gofrs/uuid/v5 v5.1.0/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8= 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.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= 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 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU=
github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= 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.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 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 h1:yAJXTCF9TqKcTiHJAE8dj7HMvPfh66eeA2JYW7eFpSE= github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 h1:yAJXTCF9TqKcTiHJAE8dj7HMvPfh66eeA2JYW7eFpSE=
@@ -78,18 +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 h1:yrQxtgseBDrq9Y652vSRDvsKCJKOUD+GzTS4Y0Y8pvE=
github.com/hashicorp/yamux v0.1.1/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ= 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/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-20240529192340-51bc6136a0a6 h1:dh8D8FksyMhD64mRMbUhZHWYJfNoNMCxfVq6eexleMw=
github.com/insomniacslk/dhcp v0.0.0-20240227161007-c728f5dd21c8/go.mod h1:izxuNQZeFrbx2nK2fAyN5iNUB34Fe9j0nK4PwLzAkKw= github.com/insomniacslk/dhcp v0.0.0-20240529192340-51bc6136a0a6/go.mod h1:KclMyHxX06VrVr0DJmeFSUb1ankt7xTfoOA35pCkoic=
github.com/insomniacslk/dhcp v0.0.0-20240419123447-f1cffa2c0c49 h1:/OuvSMGT9+xnyZ+7MZQ1zdngaCCAdPoSw8B/uurZ7pg=
github.com/insomniacslk/dhcp v0.0.0-20240419123447-f1cffa2c0c49/go.mod h1:KclMyHxX06VrVr0DJmeFSUb1ankt7xTfoOA35pCkoic=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/josharian/native v1.0.1-0.20221213033349-c1e37c09b531/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w= 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 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtLA=
github.com/josharian/native v1.1.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w= 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.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA=
github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM= github.com/klauspost/cpuid/v2 v2.2.8 h1:+StwCXwm9PdpiEkPyzBXIy+M9KUb4ODm0Zarf1kS5BM=
github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= 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/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4= github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4=
@@ -102,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/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 h1:eM9y2/jlbs1M615oshPQOHZzj6R6wMT7bX5NPiQvn2U=
github.com/mdlayher/socket v0.4.1/go.mod h1:cAqeGjoufqdxWkD7DkpyS+wcefOtmu5OQ8KuoJGIReA= 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 h1:cjd4biTvOzK9ubNCCkQ+ldc4YSH/rILn53l/xGBFHHI=
github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759/go.mod h1:UHOv2xu+RIgLwpXca7TLrXleEd4oR3sPatW6IF8wU88= github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759/go.mod h1:UHOv2xu+RIgLwpXca7TLrXleEd4oR3sPatW6IF8wU88=
github.com/metacubex/gvisor v0.0.0-20240320004321-933faba989ec h1:HxreOiFTUrJXJautEo8rnE1uKTVGY8wtZepY1Tii/Nc= 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/gvisor v0.0.0-20240320004321-933faba989ec/go.mod h1:8BVmQ+3cxjqzWElafm24rb2Ae4jRI6vAXNXWqWjfrXw=
github.com/metacubex/quic-go v0.42.1-0.20240418003344-f006b5735d98 h1:oMLlJV4a9AylNo8ZLBNUiqZ02Vme6GLLHjuWJz8amSk= github.com/metacubex/quic-go v0.45.1-0.20240610004319-163fee60637e h1:bLYn3GuRvWDcBDAkIv5kUYIhzHwafDVq635BuybnKqI=
github.com/metacubex/quic-go v0.42.1-0.20240418003344-f006b5735d98/go.mod h1:iGx3Y1zynls/FjFgykLSqDcM81U0IKePRTXEz5g3iiQ= github.com/metacubex/quic-go v0.45.1-0.20240610004319-163fee60637e/go.mod h1:Yza2H7Ax1rxWPUcJx0vW+oAt9EsPuSiyQFhFabUPzwU=
github.com/metacubex/sing v0.0.0-20240408015159-aa61c96df764 h1:+czGKoynxYA90YaL3NlCAIJHnlqwoUlLWgmOhdm5ZU8= github.com/metacubex/randv2 v0.2.0 h1:uP38uBvV2SxYfLj53kuvAjbND4RUDfFJjwr4UigMiLs=
github.com/metacubex/sing v0.0.0-20240408015159-aa61c96df764/go.mod h1:+60H3Cm91RnL9dpVGWDPHt0zTQImO9Vfqt9a4rSambI= github.com/metacubex/randv2 v0.2.0/go.mod h1:kFi2SzrQ5WuneuoLLCMkABtiBu6VRrMrWFqSPyj2cxY=
github.com/metacubex/sing-quic v0.0.0-20240418004036-814c531c378d h1:RAe0ND8J5SOPGI623oEXfaHKaaUrrzHx+U1DN9Awcco= github.com/metacubex/sing v0.0.0-20240724044459-6f3cf5896297 h1:YG/JkwGPbca5rUtEMHIu8ZuqzR7BSVm1iqY8hNoMeMA=
github.com/metacubex/sing-quic v0.0.0-20240418004036-814c531c378d/go.mod h1:WyY0zYxv+o+18R/Ece+QFontlgXoobKbNqbtYn2zjz8= github.com/metacubex/sing v0.0.0-20240724044459-6f3cf5896297/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
github.com/metacubex/sing-shadowsocks v0.2.6 h1:6oEB3QcsFYnNiFeoevcXrCwJ3sAablwVSgtE9R3QeFQ= github.com/metacubex/sing-quic v0.0.0-20240518034124-7696d3f7da72 h1:Wr4g1HCb5Z/QIFwFiVNjO2qL+dRu25+Mdn9xtAZZ+ew=
github.com/metacubex/sing-shadowsocks v0.2.6/go.mod h1:zIkMeSnb8Mbf4hdqhw0pjzkn1d99YJ3JQm/VBg5WMTg= github.com/metacubex/sing-quic v0.0.0-20240518034124-7696d3f7da72/go.mod h1:g7Mxj7b7zm7YVqD975mk/hSmrb0A0G4bVvIMr2MMzn8=
github.com/metacubex/sing-shadowsocks2 v0.2.0 h1:hqwT/AfI5d5UdPefIzR6onGHJfDXs5zgOM5QSgaM/9A= github.com/metacubex/sing-shadowsocks v0.2.7 h1:9f3Dt2+71TNp0e202llA2ug5h/rkWs2EZxQ5IMpf+9g=
github.com/metacubex/sing-shadowsocks2 v0.2.0/go.mod h1:LCKF6j1P94zN8ZS+LXRK1gmYTVGB3squivBSXAFnOg8= github.com/metacubex/sing-shadowsocks v0.2.7/go.mod h1:X3x88XtJpBxG0W0/ECOJL6Ib0SJ3xdniAkU/6/RMWU0=
github.com/metacubex/sing-tun v0.2.6 h1:frc58BqnIClqcC9KcYBfVAn5bgO6WW1ANKvZW3/HYAQ= github.com/metacubex/sing-shadowsocks2 v0.2.1 h1:XIZBXlazp8EEoPp1S0DViAhLkJakjQ2f+AOwwdKKNYg=
github.com/metacubex/sing-tun v0.2.6/go.mod h1:4VsMwZH1IlgPGFK1ZbBomZ/B2MYkTgs2+gnBAr5GOIo= github.com/metacubex/sing-shadowsocks2 v0.2.1/go.mod h1:BhOug03a/RbI7y6hp6q+6ITM1dXjnLTmeWBHSTwvv2Q=
github.com/metacubex/sing-vmess v0.1.9-0.20231207122118-72303677451f h1:QjXrHKbTMBip/C+R79bvbfr42xH1gZl3uFb0RELdZiQ= github.com/metacubex/sing-tun v0.2.7-0.20240719141246-19c49ac9589d h1:iYlepjRCYlPXtELupDL+pQjGqkCnQz4KQOfKImP9sog=
github.com/metacubex/sing-vmess v0.1.9-0.20231207122118-72303677451f/go.mod h1:olVkD4FChQ5gKMHG4ZzuD7+fMkJY1G8vwOKpRehjrmY= github.com/metacubex/sing-tun v0.2.7-0.20240719141246-19c49ac9589d/go.mod h1:olbEx9yVcaw5tHTNlRamRoxmMKcvDvcVS1YLnQGzvWE=
github.com/metacubex/sing-wireguard v0.0.0-20240321042214-224f96122a63 h1:AGyIB55UfQm/0ZH0HtQO9u3l//yjtHUpjeRjjPGfGRI= github.com/metacubex/sing-vmess v0.1.9-0.20240719134745-1df6fb20bbf9 h1:OAXiCosqY8xKDp3pqTW3qbrCprZ1l6WkrXSFSCwyY4I=
github.com/metacubex/sing-wireguard v0.0.0-20240321042214-224f96122a63/go.mod h1:uY+BYb0UEknLrqvbGcwi9i++KgrKxsurysgI6G1Pveo= 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 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/metacubex/tfo-go v0.0.0-20240228025757-be1269474a66/go.mod h1:c7bVFM9f5+VzeZ/6Kg77T/jrg1Xp8QpqlSHvG/aXVts=
github.com/miekg/dns v1.1.59 h1:C9EXc/UToRwKLhK5wKU/I4QVsBUc8kE6MkHBkeypWZs= github.com/metacubex/utls v1.6.6 h1:3D12YKHTf2Z41UPhQU2dWerNWJ5TVQD9gKoQ+H+iLC8=
github.com/miekg/dns v1.1.59/go.mod h1:nZpewl5p6IvctfgrckopVx2OlSEHPRO/U4SYkRklrEk= 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 h1:o1ascnB1CIVzsqlfArQQjeMy1U0NcIbBO5rfd5E/OeU=
github.com/mroth/weightedrand/v2 v2.1.0/go.mod h1:f2faGsfOGOwc1p94wzHKKZyTpcJUW7OJ/9U4yfiNAOU= github.com/mroth/weightedrand/v2 v2.1.0/go.mod h1:f2faGsfOGOwc1p94wzHKKZyTpcJUW7OJ/9U4yfiNAOU=
github.com/oasisprotocol/deoxysii v0.0.0-20220228165953-2091330c22b7 h1:1102pQc2SEPp5+xrS26wEaeb26sZy6k9/ZXlZN+eXE4= github.com/oasisprotocol/deoxysii v0.0.0-20220228165953-2091330c22b7 h1:1102pQc2SEPp5+xrS26wEaeb26sZy6k9/ZXlZN+eXE4=
@@ -148,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 h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw=
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= 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/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g=
github.com/puzpuzpuz/xsync/v3 v3.1.0 h1:EewKT7/LNac5SLiEblJeUu8z5eERHrmRLnMQL2d7qX4= github.com/puzpuzpuz/xsync/v3 v3.2.0 h1:9AzuUeF88YC5bK8u2vEG1Fpvu4wgpM1wfPIExfaaDxQ=
github.com/puzpuzpuz/xsync/v3 v3.1.0/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPKP9jpmh0nnA= 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 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo=
github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A= 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= github.com/quic-go/qtls-go1-20 v0.4.1 h1:D33340mCNDAIKBqXuAvexTNMUByrYmFYVfKfDN5nfFs=
@@ -157,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/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 h1:+NkI2670SQpQWvkkD2QgdTuzQG263YZ+2emfpeyGqW0=
github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a/go.mod h1:63s7jpZqcDAIpj8oI/1v4Izok+npJOHACFCU6+huCkM= 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/fswatch v0.1.1 h1:YqID+93B7VRfqIH3PArW/XpJv5H4OLEVWDfProGoRQs=
github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM= 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 h1:5bCAkvDDzSMITiHFjolBwpdqYsvycdTu71FsMEFXQ14=
github.com/sagernet/sing-mux v0.2.1-0.20240124034317-9bfb33698bb6/go.mod h1:khzr9AOPocLa+g53dBplwNDz4gdsyx/YM3swtAhlkHQ= 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 h1:aTgBSJEgnumzFenPvc+kbD9/W0PywzWevnVpEx6Tw3k=
github.com/sagernet/sing-shadowtls v0.1.4/go.mod h1:F8NBgsY5YN2beQavdgdm1DPlhaKQlaL6lpDdcBglGK4= 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 h1:DImB4lELfQhplLTxeq2z31Fpv8CQqqrUwTbrIRumZqQ=
github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7/go.mod h1:FP9X2xjT/Az1EsG/orYYoC+5MojWnuI7hrffz8fGwwo= 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 h1:iGH0RMv2FzELOFNFQtvsxH7NPmlo7X5JizEK51UCojo=
github.com/sagernet/wireguard-go v0.0.0-20231209092712-9a439356a62e/go.mod h1:YbL4TKHRR6APYQv3U2RGfwLDpPYSyWz6oUlpISBEzBE= 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 h1:4gTz1wUhNYLhFSKl6O+8peW0v2F4BCY034GRpU9WnuA=
github.com/samber/lo v1.39.0/go.mod h1:+m/ZKRl6ClXCE2Lgf3MsQlWfh4bn1bz6CXEOxnEXnEA= github.com/samber/lo v1.39.0/go.mod h1:+m/ZKRl6ClXCE2Lgf3MsQlWfh4bn1bz6CXEOxnEXnEA=
github.com/shirou/gopsutil/v3 v3.24.3 h1:eoUGJSmdfLzJ3mxIhmOAhgKEKgQkeOwKpz1NbhVnuPE= github.com/shirou/gopsutil/v3 v3.24.5 h1:i0t8kL+kQTvpAYToeuiVk3TgDeKOFioZO3Ztz/iZ9pI=
github.com/shirou/gopsutil/v3 v3.24.3/go.mod h1:JpND7O217xa72ewWz9zN2eIIkPWsDN/3pl0H8Qt0uwg= 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 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM=
github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ= 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 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 h1:rXHg9GrUEtWZhEkrykicdND3VPjlVbYiLdX9J7gimS8=
github.com/sina-ghaderi/poly1305 v0.0.0-20220724002748-c5926b03988b/go.mod h1:X7qrxNQViEaAN9LNZOPl9PfvQtp3V3c7LTo0dvGi0fM= 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= github.com/sina-ghaderi/rabaead v0.0.0-20220730151906-ab6e06b96e8c h1:DjKMC30y6yjG3IxDaeAj3PCoRr+IsO+bzyT+Se2m2Hk=
@@ -206,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/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-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-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.4 h1:Oeaw1EM2JMxD51g9uhtC0D7erkIjgmj8+JZc26m1YX8=
github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0= 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 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/fJgbpc=
github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw= 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 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
github.com/zhangyunhao116/fastrand v0.4.0 h1:86QB6Y+GGgLZRFRDCjMmAS28QULwspK9sgL5d1Bx3H4= gitlab.com/go-extension/aes-ccm v0.0.0-20230221065045-e58665ef23c7 h1:UNrDfkQqiEYzdMlNsVvBYOAJWZjdktqFE9tQh5BT2+4=
github.com/zhangyunhao116/fastrand v0.4.0/go.mod h1:vIyo6EyBhjGKpZv6qVlkPl4JVAklpMM4DSKzbAkMguA= 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 h1:FpfFs4EhNehiVfzQttTuxanPIT43FtkkCFypIod8LHo=
gitlab.com/yawning/bsaes.git v0.0.0-20190805113838-0a714cd429ec/go.mod h1:BZ1RAoRPbCxum9Grlv5aeksu2H8BiKehBYooU2LFiOQ= gitlab.com/yawning/bsaes.git v0.0.0-20190805113838-0a714cd429ec/go.mod h1:BZ1RAoRPbCxum9Grlv5aeksu2H8BiKehBYooU2LFiOQ=
go.uber.org/automaxprocs v1.5.3 h1:kWazyxZUrS3Gs4qUpbwo5kEIMGe/DAvi5Z4tl2NW4j8= go.uber.org/automaxprocs v1.5.3 h1:kWazyxZUrS3Gs4qUpbwo5kEIMGe/DAvi5Z4tl2NW4j8=
@@ -224,18 +226,18 @@ 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= 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-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.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30= golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI=
golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM=
golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f h1:99ci1mjWVBWwJiEKYY6jWa4d2nTQVIEhZIptnrVb1XY= golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 h1:yixxcjnhBmY0nkL253HFVIm0JsFHwrHdT3Yh6szTnfY=
golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f/go.mod h1:/lliqkxwWAhPjf5oSOIJup2XcqJaw8RGS6k3TGEc7GI= 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/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.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0=
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= 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-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.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w= golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ=
golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8= 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.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
@@ -254,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.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.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.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.19.0 h1:+ThwsDv+tYfnJFhF4L8jITxu1tdTWRTZpdsWgEgjL6Q= 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.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.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= 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 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= 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.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.20.0 h1:hz/CVckiOxybQvFw6h7b/q80NTr9IUQb4s1IIzW7KNY= golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA=
golang.org/x/tools v0.20.0/go.mod h1:WvitBU7JJf6A4jOdg4S1tviW9bhUxkgeCui/0JHctQg= 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-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/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.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= 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 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/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.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 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
lukechampine.com/blake3 v1.2.2 h1:wEAbSg0IVU4ih44CVlpMqMZMpzr5hf/6aqodLlevd/w= lukechampine.com/blake3 v1.3.0 h1:sJ3XhFINmHSrYCgl958hscfIa3bw8x4DqMP3u1YvoYE=
lukechampine.com/blake3 v1.2.2/go.mod h1:0OFRp7fBtAylGVCO40o87sbupkyIGgbpv1+M1k1LM6k= lukechampine.com/blake3 v1.3.0/go.mod h1:0OFRp7fBtAylGVCO40o87sbupkyIGgbpv1+M1k1LM6k=

View File

@@ -97,7 +97,7 @@ func ApplyConfig(cfg *config.Config, force bool) {
updateHosts(cfg.Hosts) updateHosts(cfg.Hosts)
updateGeneral(cfg.General) updateGeneral(cfg.General)
updateNTP(cfg.NTP) updateNTP(cfg.NTP)
updateDNS(cfg.DNS, cfg.RuleProviders, cfg.General.IPv6) updateDNS(cfg.DNS, cfg.General.IPv6)
updateListeners(cfg.General, cfg.Listeners, force) updateListeners(cfg.General, cfg.Listeners, force)
updateIPTables(cfg) updateIPTables(cfg)
updateTun(cfg.General) 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 { if !c.Enable {
resolver.DefaultResolver = nil resolver.DefaultResolver = nil
resolver.DefaultHostMapper = nil resolver.DefaultHostMapper = nil
@@ -237,7 +237,7 @@ func updateDNS(c *config.DNS, ruleProvider map[string]provider.RuleProvider, gen
Default: c.DefaultNameserver, Default: c.DefaultNameserver,
Policy: c.NameServerPolicy, Policy: c.NameServerPolicy,
ProxyServer: c.ProxyServerNameserver, ProxyServer: c.ProxyServerNameserver,
RuleProviders: ruleProvider, Tunnel: tunnel.Tunnel,
CacheAlgorithm: c.CacheAlgorithm, CacheAlgorithm: c.CacheAlgorithm,
} }
@@ -253,6 +253,7 @@ func updateDNS(c *config.DNS, ruleProvider map[string]provider.RuleProvider, gen
resolver.DefaultResolver = r resolver.DefaultResolver = r
resolver.DefaultHostMapper = m resolver.DefaultHostMapper = m
resolver.DefaultLocalServer = dns.NewLocalServer(r, m) resolver.DefaultLocalServer = dns.NewLocalServer(r, m)
resolver.UseSystemHosts = c.UseSystemHosts
if pr.Invalid() { if pr.Invalid() {
resolver.ProxyServerHostResolver = pr resolver.ProxyServerHostResolver = pr
@@ -354,7 +355,7 @@ func updateTun(general *config.General) {
return return
} }
listener.ReCreateTun(general.Tun, tunnel.Tunnel) listener.ReCreateTun(general.Tun, tunnel.Tunnel)
listener.ReCreateRedirToTun(general.Tun.RedirectToTun) listener.ReCreateRedirToTun(general.EBpf.RedirectToTun)
} }
func updateSniffer(sniffer *config.Sniffer) { func updateSniffer(sniffer *config.Sniffer) {
@@ -506,9 +507,7 @@ func updateIPTables(cfg *config.Config) {
inboundInterface = iptables.InboundInterface inboundInterface = iptables.InboundInterface
} }
if dialer.DefaultRoutingMark.Load() == 0 { dialer.DefaultRoutingMark.CompareAndSwap(0, 2158)
dialer.DefaultRoutingMark.Store(2158)
}
err = tproxy.SetTProxyIPTables(inboundInterface, bypass, uint16(tProxyPort), DnsRedirect, dnsPort.Port()) err = tproxy.SetTProxyIPTables(inboundInterface, bypass, uint16(tProxyPort), DnsRedirect, dnsPort.Port())
if err != nil { if err != nil {

View File

@@ -50,11 +50,12 @@ func Parse(options ...Option) error {
if cfg.General.ExternalController != "" { if cfg.General.ExternalController != "" {
go route.Start(cfg.General.ExternalController, cfg.General.ExternalControllerTLS, 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 != "" { if cfg.General.ExternalControllerUnix != "" {
go route.StartUnix(cfg.General.ExternalControllerUnix, cfg.General.LogLevel == log.DEBUG) go route.StartUnix(cfg.General.ExternalControllerUnix, cfg.General.ExternalDohServer, cfg.General.LogLevel == log.DEBUG)
} }
executor.ApplyConfig(cfg, true) executor.ApplyConfig(cfg, true)

View File

@@ -4,11 +4,11 @@ import (
"net/http" "net/http"
"net/netip" "net/netip"
"path/filepath" "path/filepath"
"sync"
"github.com/metacubex/mihomo/adapter/inbound" "github.com/metacubex/mihomo/adapter/inbound"
"github.com/metacubex/mihomo/component/dialer" "github.com/metacubex/mihomo/component/dialer"
"github.com/metacubex/mihomo/component/resolver" "github.com/metacubex/mihomo/component/resolver"
"github.com/metacubex/mihomo/component/updater"
"github.com/metacubex/mihomo/config" "github.com/metacubex/mihomo/config"
C "github.com/metacubex/mihomo/constant" C "github.com/metacubex/mihomo/constant"
"github.com/metacubex/mihomo/hub/executor" "github.com/metacubex/mihomo/hub/executor"
@@ -21,11 +21,6 @@ import (
"github.com/go-chi/render" "github.com/go-chi/render"
) )
var (
updateGeoMux sync.Mutex
updatingGeo = false
)
func configRouter() http.Handler { func configRouter() http.Handler {
r := chi.NewRouter() r := chi.NewRouter()
r.Get("/", getConfigs) r.Get("/", getConfigs)
@@ -73,25 +68,34 @@ type tunSchema struct {
GSO *bool `yaml:"gso" json:"gso,omitempty"` GSO *bool `yaml:"gso" json:"gso,omitempty"`
GSOMaxSize *uint32 `yaml:"gso-max-size" json:"gso-max-size,omitempty"` GSOMaxSize *uint32 `yaml:"gso-max-size" json:"gso-max-size,omitempty"`
//Inet4Address *[]netip.Prefix `yaml:"inet4-address" json:"inet4-address,omitempty"` //Inet4Address *[]netip.Prefix `yaml:"inet4-address" json:"inet4-address,omitempty"`
Inet6Address *[]netip.Prefix `yaml:"inet6-address" json:"inet6-address,omitempty"` Inet6Address *[]netip.Prefix `yaml:"inet6-address" json:"inet6-address,omitempty"`
StrictRoute *bool `yaml:"strict-route" json:"strict-route,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"` Inet4RouteAddress *[]netip.Prefix `yaml:"inet4-route-address" json:"inet4-route-address,omitempty"`
Inet6RouteAddress *[]netip.Prefix `yaml:"inet6-route-address" json:"inet6-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"` 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"` 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 { type tuicServerSchema struct {
@@ -162,6 +166,36 @@ func pointerOrDefaultTun(p *tunSchema, def LC.Tun) LC.Tun {
if p.Inet6Address != nil { if p.Inet6Address != nil {
def.Inet6Address = *p.Inet6Address 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 { if p.Inet4RouteAddress != nil {
def.Inet4RouteAddress = *p.Inet4RouteAddress def.Inet4RouteAddress = *p.Inet4RouteAddress
} }
@@ -210,9 +244,6 @@ func pointerOrDefaultTun(p *tunSchema, def LC.Tun) LC.Tun {
if p.FileDescriptor != nil { if p.FileDescriptor != nil {
def.FileDescriptor = *p.FileDescriptor def.FileDescriptor = *p.FileDescriptor
} }
if p.TableIndex != nil {
def.TableIndex = *p.TableIndex
}
} }
return def return def
} }
@@ -369,40 +400,25 @@ func updateConfigs(w http.ResponseWriter, r *http.Request) {
} }
func updateGeoDatabases(w http.ResponseWriter, r *http.Request) { func updateGeoDatabases(w http.ResponseWriter, r *http.Request) {
updateGeoMux.Lock() err := updater.UpdateGeoDatabases()
if err != nil {
if updatingGeo { log.Errorln("[REST-API] update GEO databases failed: %v", err)
updateGeoMux.Unlock() render.Status(r, http.StatusInternalServerError)
render.Status(r, http.StatusBadRequest) render.JSON(w, r, newError(err.Error()))
render.JSON(w, r, newError("updating..."))
return return
} }
updatingGeo = true cfg, err := executor.ParseWithPath(C.Path.Config())
updateGeoMux.Unlock() 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() { log.Warnln("[GEO] update GEO databases success, applying config")
defer func() {
updatingGeo = false
}()
log.Warnln("[REST-API] updating GEO databases...") executor.ApplyConfig(cfg, false)
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)
}()
render.NoContent(w, r) render.NoContent(w, r)
} }

63
hub/route/doh.go Normal file
View 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)
}

View File

@@ -50,7 +50,7 @@ func SetUIPath(path string) {
uiPath = C.Path.Resolve(path) uiPath = C.Path.Resolve(path)
} }
func router(isDebug bool, withAuth bool) *chi.Mux { func router(isDebug bool, withAuth bool, dohServer string) *chi.Mux {
r := chi.NewRouter() r := chi.NewRouter()
corsM := cors.New(cors.Options{ corsM := cors.New(cors.Options{
AllowedOrigins: []string{"*"}, AllowedOrigins: []string{"*"},
@@ -104,11 +104,15 @@ func router(isDebug bool, withAuth bool) *chi.Mux {
}) })
}) })
} }
if len(dohServer) > 0 && dohServer[0] == '/' {
r.Mount(dohServer, dohRouter())
}
return r return r
} }
func Start(addr string, tlsAddr string, secret string, func Start(addr string, tlsAddr string, secret string,
certificate, privateKey string, isDebug bool) { certificate, privateKey string, dohServer string, isDebug bool) {
if serverAddr != "" { if serverAddr != "" {
return return
} }
@@ -133,7 +137,7 @@ func Start(addr string, tlsAddr string, secret string,
serverAddr = l.Addr().String() serverAddr = l.Addr().String()
log.Infoln("RESTful API tls listening at: %s", serverAddr) log.Infoln("RESTful API tls listening at: %s", serverAddr)
tlsServe := &http.Server{ tlsServe := &http.Server{
Handler: router(isDebug, true), Handler: router(isDebug, true, dohServer),
TLSConfig: &tls.Config{ TLSConfig: &tls.Config{
Certificates: []tls.Certificate{c}, Certificates: []tls.Certificate{c},
}, },
@@ -152,13 +156,13 @@ func Start(addr string, tlsAddr string, secret string,
serverAddr = l.Addr().String() serverAddr = l.Addr().String()
log.Infoln("RESTful API listening at: %s", serverAddr) log.Infoln("RESTful API listening at: %s", serverAddr)
if err = http.Serve(l, router(isDebug, true)); err != nil { if err = http.Serve(l, router(isDebug, true, dohServer)); err != nil {
log.Errorln("External controller serve error: %s", err) log.Errorln("External controller serve error: %s", err)
} }
} }
func StartUnix(addr string, isDebug bool) { func StartUnix(addr string, dohServer string, isDebug bool) {
addr = C.Path.Resolve(addr) addr = C.Path.Resolve(addr)
dir := filepath.Dir(addr) dir := filepath.Dir(addr)
@@ -186,7 +190,7 @@ func StartUnix(addr string, isDebug bool) {
serverAddr = l.Addr().String() serverAddr = l.Addr().String()
log.Infoln("RESTful API unix listening at: %s", serverAddr) log.Infoln("RESTful API unix listening at: %s", serverAddr)
if err = http.Serve(l, router(isDebug, false)); err != nil { if err = http.Serve(l, router(isDebug, false, dohServer)); err != nil {
log.Errorln("External controller unix serve error: %s", err) log.Errorln("External controller unix serve error: %s", err)
} }
} }

View File

@@ -6,8 +6,7 @@ import (
"net/http" "net/http"
"os" "os"
"github.com/metacubex/mihomo/config" "github.com/metacubex/mihomo/component/updater"
"github.com/metacubex/mihomo/hub/updater"
"github.com/metacubex/mihomo/log" "github.com/metacubex/mihomo/log"
"github.com/go-chi/chi/v5" "github.com/go-chi/chi/v5"
@@ -18,6 +17,7 @@ func upgradeRouter() http.Handler {
r := chi.NewRouter() r := chi.NewRouter()
r.Post("/", upgradeCore) r.Post("/", upgradeCore)
r.Post("/ui", updateUI) r.Post("/ui", updateUI)
r.Post("/geo", updateGeoDatabases)
return r return r
} }
@@ -31,7 +31,7 @@ func upgradeCore(w http.ResponseWriter, r *http.Request) {
return return
} }
err = updater.Update(execPath) err = updater.UpdateCore(execPath)
if err != nil { if err != nil {
log.Warnln("%s", err) log.Warnln("%s", err)
render.Status(r, http.StatusInternalServerError) render.Status(r, http.StatusInternalServerError)
@@ -48,9 +48,9 @@ func upgradeCore(w http.ResponseWriter, r *http.Request) {
} }
func updateUI(w http.ResponseWriter, r *http.Request) { func updateUI(w http.ResponseWriter, r *http.Request) {
err := config.UpdateUI() err := updater.UpdateUI()
if err != nil { if err != nil {
if errors.Is(err, config.ErrIncompleteConf) { if errors.Is(err, updater.ErrIncompleteConf) {
log.Warnln("%s", err) log.Warnln("%s", err)
render.Status(r, http.StatusNotImplemented) render.Status(r, http.StatusNotImplemented)
render.JSON(w, r, newError(fmt.Sprintf("%s", err))) render.JSON(w, r, newError(fmt.Sprintf("%s", err)))

View File

@@ -27,27 +27,36 @@ type Tun struct {
AutoDetectInterface bool `yaml:"auto-detect-interface" json:"auto-detect-interface"` AutoDetectInterface bool `yaml:"auto-detect-interface" json:"auto-detect-interface"`
RedirectToTun []string `yaml:"-" json:"-"` RedirectToTun []string `yaml:"-" json:"-"`
MTU uint32 `yaml:"mtu" json:"mtu,omitempty"` MTU uint32 `yaml:"mtu" json:"mtu,omitempty"`
GSO bool `yaml:"gso" json:"gso,omitempty"` GSO bool `yaml:"gso" json:"gso,omitempty"`
GSOMaxSize uint32 `yaml:"gso-max-size" json:"gso-max-size,omitempty"` GSOMaxSize uint32 `yaml:"gso-max-size" json:"gso-max-size,omitempty"`
Inet4Address []netip.Prefix `yaml:"inet4-address" json:"inet4-address,omitempty"` Inet4Address []netip.Prefix `yaml:"inet4-address" json:"inet4-address,omitempty"`
Inet6Address []netip.Prefix `yaml:"inet6-address" json:"inet6-address,omitempty"` Inet6Address []netip.Prefix `yaml:"inet6-address" json:"inet6-address,omitempty"`
StrictRoute bool `yaml:"strict-route" json:"strict-route,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"` Inet4RouteAddress []netip.Prefix `yaml:"inet4-route-address" json:"inet4-route-address,omitempty"`
Inet6RouteAddress []netip.Prefix `yaml:"inet6-route-address" json:"inet6-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"` 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"` 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"`
} }

View File

@@ -13,7 +13,7 @@ import (
"github.com/metacubex/mihomo/transport/socks5" "github.com/metacubex/mihomo/transport/socks5"
) )
func newClient(srcConn net.Conn, tunnel C.Tunnel, additions ...inbound.Addition) *http.Client { func newClient(srcConn net.Conn, tunnel C.Tunnel, additions []inbound.Addition) *http.Client { // additions using slice let caller can change its value (without size) after newClient return
return &http.Client{ return &http.Client{
Transport: &http.Transport{ Transport: &http.Transport{
// from http.DefaultTransport // from http.DefaultTransport
@@ -21,6 +21,7 @@ func newClient(srcConn net.Conn, tunnel C.Tunnel, additions ...inbound.Addition)
IdleConnTimeout: 90 * time.Second, IdleConnTimeout: 90 * time.Second,
TLSHandshakeTimeout: 10 * time.Second, TLSHandshakeTimeout: 10 * time.Second,
ExpectContinueTimeout: 1 * time.Second, ExpectContinueTimeout: 1 * time.Second,
DisableCompression: true, // prevents the Transport add "Accept-Encoding: gzip"
DialContext: func(context context.Context, network, address string) (net.Conn, error) { DialContext: func(context context.Context, network, address string) (net.Conn, error) {
if network != "tcp" && network != "tcp4" && network != "tcp6" { if network != "tcp" && network != "tcp4" && network != "tcp6" {
return nil, errors.New("unsupported network " + network) return nil, errors.New("unsupported network " + network)

View File

@@ -8,24 +8,32 @@ import (
"net/http" "net/http"
"strings" "strings"
"sync" "sync"
_ "unsafe"
"github.com/metacubex/mihomo/adapter/inbound" "github.com/metacubex/mihomo/adapter/inbound"
"github.com/metacubex/mihomo/common/lru"
N "github.com/metacubex/mihomo/common/net" N "github.com/metacubex/mihomo/common/net"
"github.com/metacubex/mihomo/component/auth"
C "github.com/metacubex/mihomo/constant" C "github.com/metacubex/mihomo/constant"
authStore "github.com/metacubex/mihomo/listener/auth"
"github.com/metacubex/mihomo/log" "github.com/metacubex/mihomo/log"
) )
//go:linkname registerOnHitEOF net/http.registerOnHitEOF type bodyWrapper struct {
func registerOnHitEOF(rc io.ReadCloser, fn func()) io.ReadCloser
once sync.Once
onHitEOF func()
}
//go:linkname requestBodyRemains net/http.requestBodyRemains func (b *bodyWrapper) Read(p []byte) (n int, err error) {
func requestBodyRemains(rc io.ReadCloser) bool n, err = b.ReadCloser.Read(p)
if err == io.EOF && b.onHitEOF != nil {
b.once.Do(b.onHitEOF)
}
return n, err
}
func HandleConn(c net.Conn, tunnel C.Tunnel, cache *lru.LruCache[string, bool], additions ...inbound.Addition) { func HandleConn(c net.Conn, tunnel C.Tunnel, authenticator auth.Authenticator, additions ...inbound.Addition) {
client := newClient(c, tunnel, additions...) additions = append(additions, inbound.Placeholder) // Add a placeholder for InUser
inUserIdx := len(additions) - 1
client := newClient(c, tunnel, additions)
defer client.CloseIdleConnections() defer client.CloseIdleConnections()
ctx, cancel := context.WithCancel(context.Background()) ctx, cancel := context.WithCancel(context.Background())
defer cancel() defer cancel()
@@ -34,7 +42,8 @@ func HandleConn(c net.Conn, tunnel C.Tunnel, cache *lru.LruCache[string, bool],
conn := N.NewBufferedConn(c) conn := N.NewBufferedConn(c)
keepAlive := true keepAlive := true
trusted := cache == nil // disable authenticate if lru is nil trusted := authenticator == nil // disable authenticate if lru is nil
lastUser := ""
for keepAlive { for keepAlive {
peekMutex.Lock() peekMutex.Lock()
@@ -50,12 +59,10 @@ func HandleConn(c net.Conn, tunnel C.Tunnel, cache *lru.LruCache[string, bool],
var resp *http.Response var resp *http.Response
if !trusted { var user string
var user string resp, user = authenticate(request, authenticator) // always call authenticate function to get user
resp, user = authenticate(request, cache) trusted = trusted || resp == nil
additions = append(additions, inbound.WithInUser(user)) additions[inUserIdx] = inbound.WithInUser(user)
trusted = resp == nil
}
if trusted { if trusted {
if request.Method == http.MethodConnect { if request.Method == http.MethodConnect {
@@ -82,6 +89,13 @@ func HandleConn(c net.Conn, tunnel C.Tunnel, cache *lru.LruCache[string, bool],
return // hijack connection return // hijack connection
} }
// ensure there is a client with correct additions
// when the authenticated user changed, outbound client should close idle connections
if user != lastUser {
client.CloseIdleConnections()
lastUser = user
}
removeHopByHopHeaders(request.Header) removeHopByHopHeaders(request.Header)
removeExtraHTTPHostPort(request) removeExtraHTTPHostPort(request)
@@ -100,10 +114,10 @@ func HandleConn(c net.Conn, tunnel C.Tunnel, cache *lru.LruCache[string, bool],
} }
}() }()
} }
if requestBodyRemains(request.Body) { if request.Body == nil || request.Body == http.NoBody {
registerOnHitEOF(request.Body, startBackgroundRead)
} else {
startBackgroundRead() startBackgroundRead()
} else {
request.Body = &bodyWrapper{ReadCloser: request.Body, onHitEOF: startBackgroundRead}
} }
resp, err = client.Do(request) resp, err = client.Do(request)
if err != nil { if err != nil {
@@ -131,34 +145,24 @@ func HandleConn(c net.Conn, tunnel C.Tunnel, cache *lru.LruCache[string, bool],
_ = conn.Close() _ = conn.Close()
} }
func authenticate(request *http.Request, cache *lru.LruCache[string, bool]) (resp *http.Response, u string) { func authenticate(request *http.Request, authenticator auth.Authenticator) (resp *http.Response, user string) {
authenticator := authStore.Authenticator()
if inbound.SkipAuthRemoteAddress(request.RemoteAddr) { if inbound.SkipAuthRemoteAddress(request.RemoteAddr) {
authenticator = nil authenticator = nil
} }
if authenticator != nil { credential := parseBasicProxyAuthorization(request)
credential := parseBasicProxyAuthorization(request) if credential == "" && authenticator != nil {
if credential == "" { resp = responseWith(request, http.StatusProxyAuthRequired)
resp := responseWith(request, http.StatusProxyAuthRequired) resp.Header.Set("Proxy-Authenticate", "Basic")
resp.Header.Set("Proxy-Authenticate", "Basic") return
return resp, ""
}
authed, exist := cache.Get(credential)
if !exist {
user, pass, err := decodeBasicProxyAuthorization(credential)
authed = err == nil && authenticator.Verify(user, pass)
u = user
cache.Set(credential, authed)
}
if !authed {
log.Infoln("Auth failed from %s", request.RemoteAddr)
return responseWith(request, http.StatusForbidden), u
}
} }
user, pass, err := decodeBasicProxyAuthorization(credential)
return nil, u authed := authenticator == nil || (err == nil && authenticator.Verify(user, pass))
if !authed {
log.Infoln("Auth failed from %s", request.RemoteAddr)
return responseWith(request, http.StatusForbidden), user
}
log.Debugln("Auth success from %s -> %s", request.RemoteAddr, user)
return
} }
func responseWith(request *http.Request, statusCode int) *http.Response { func responseWith(request *http.Request, statusCode int) *http.Response {

View File

@@ -4,9 +4,10 @@ import (
"net" "net"
"github.com/metacubex/mihomo/adapter/inbound" "github.com/metacubex/mihomo/adapter/inbound"
"github.com/metacubex/mihomo/common/lru" "github.com/metacubex/mihomo/component/auth"
C "github.com/metacubex/mihomo/constant" C "github.com/metacubex/mihomo/constant"
"github.com/metacubex/mihomo/constant/features" "github.com/metacubex/mihomo/constant/features"
authStore "github.com/metacubex/mihomo/listener/auth"
) )
type Listener struct { type Listener struct {
@@ -32,10 +33,20 @@ func (l *Listener) Close() error {
} }
func New(addr string, tunnel C.Tunnel, additions ...inbound.Addition) (*Listener, error) { func New(addr string, tunnel C.Tunnel, additions ...inbound.Addition) (*Listener, error) {
return NewWithAuthenticate(addr, tunnel, true, additions...) return NewWithAuthenticator(addr, tunnel, authStore.Authenticator(), additions...)
} }
// NewWithAuthenticate
// never change type traits because it's used in CFMA
func NewWithAuthenticate(addr string, tunnel C.Tunnel, authenticate bool, additions ...inbound.Addition) (*Listener, error) { func NewWithAuthenticate(addr string, tunnel C.Tunnel, authenticate bool, additions ...inbound.Addition) (*Listener, error) {
authenticator := authStore.Authenticator()
if !authenticate {
authenticator = nil
}
return NewWithAuthenticator(addr, tunnel, authenticator, additions...)
}
func NewWithAuthenticator(addr string, tunnel C.Tunnel, authenticator auth.Authenticator, additions ...inbound.Addition) (*Listener, error) {
isDefault := false isDefault := false
if len(additions) == 0 { if len(additions) == 0 {
isDefault = true isDefault = true
@@ -50,11 +61,6 @@ func NewWithAuthenticate(addr string, tunnel C.Tunnel, authenticate bool, additi
return nil, err return nil, err
} }
var c *lru.LruCache[string, bool]
if authenticate {
c = lru.New[string, bool](lru.WithAge[string, bool](30))
}
hl := &Listener{ hl := &Listener{
listener: l, listener: l,
addr: addr, addr: addr,
@@ -79,7 +85,7 @@ func NewWithAuthenticate(addr string, tunnel C.Tunnel, authenticate bool, additi
continue continue
} }
} }
go HandleConn(conn, tunnel, c, additions...) go HandleConn(conn, tunnel, authenticator, additions...)
} }
}() }()

View File

@@ -18,29 +18,38 @@ type TunOption struct {
AutoRoute bool `inbound:"auto-route,omitempty"` AutoRoute bool `inbound:"auto-route,omitempty"`
AutoDetectInterface bool `inbound:"auto-detect-interface,omitempty"` AutoDetectInterface bool `inbound:"auto-detect-interface,omitempty"`
MTU uint32 `inbound:"mtu,omitempty"` MTU uint32 `inbound:"mtu,omitempty"`
GSO bool `inbound:"gso,omitempty"` GSO bool `inbound:"gso,omitempty"`
GSOMaxSize uint32 `inbound:"gso-max-size,omitempty"` GSOMaxSize uint32 `inbound:"gso-max-size,omitempty"`
Inet4Address []string `inbound:"inet4_address,omitempty"` Inet4Address []string `inbound:"inet4_address,omitempty"`
Inet6Address []string `inbound:"inet6_address,omitempty"` Inet6Address []string `inbound:"inet6_address,omitempty"`
StrictRoute bool `inbound:"strict_route,omitempty"` IPRoute2TableIndex int `inbound:"iproute2-table-index"`
IPRoute2RuleIndex int `inbound:"iproute2-rule-index"`
AutoRedirect bool `inbound:"auto-redirect"`
AutoRedirectInputMark uint32 `inbound:"auto-redirect-input-mark"`
AutoRedirectOutputMark uint32 `inbound:"auto-redirect-output-mark"`
StrictRoute bool `inbound:"strict_route,omitempty"`
RouteAddress []string `inbound:"route-address"`
RouteAddressSet []string `inbound:"route-address-set"`
RouteExcludeAddress []string `inbound:"route-exclude-address"`
RouteExcludeAddressSet []string `inbound:"route-exclude-address-set"`
IncludeInterface []string `inbound:"include-interface,omitempty"`
ExcludeInterface []string `inbound:"exclude-interface"`
IncludeUID []uint32 `inbound:"include_uid,omitempty"`
IncludeUIDRange []string `inbound:"include_uid_range,omitempty"`
ExcludeUID []uint32 `inbound:"exclude_uid,omitempty"`
ExcludeUIDRange []string `inbound:"exclude_uid_range,omitempty"`
IncludeAndroidUser []int `inbound:"include_android_user,omitempty"`
IncludePackage []string `inbound:"include_package,omitempty"`
ExcludePackage []string `inbound:"exclude_package,omitempty"`
EndpointIndependentNat bool `inbound:"endpoint_independent_nat,omitempty"`
UDPTimeout int64 `inbound:"udp_timeout,omitempty"`
FileDescriptor int `inbound:"file-descriptor,omitempty"`
Inet4RouteAddress []string `inbound:"inet4_route_address,omitempty"` Inet4RouteAddress []string `inbound:"inet4_route_address,omitempty"`
Inet6RouteAddress []string `inbound:"inet6_route_address,omitempty"` Inet6RouteAddress []string `inbound:"inet6_route_address,omitempty"`
Inet4RouteExcludeAddress []string `inbound:"inet4_route_exclude_address,omitempty"` Inet4RouteExcludeAddress []string `inbound:"inet4_route_exclude_address,omitempty"`
Inet6RouteExcludeAddress []string `inbound:"inet6_route_exclude_address,omitempty"` Inet6RouteExcludeAddress []string `inbound:"inet6_route_exclude_address,omitempty"`
IncludeInterface []string `inbound:"include-interface,omitempty"`
ExcludeInterface []string `inbound:"exclude-interface" json:"exclude-interface,omitempty"`
IncludeUID []uint32 `inbound:"include_uid,omitempty"`
IncludeUIDRange []string `inbound:"include_uid_range,omitempty"`
ExcludeUID []uint32 `inbound:"exclude_uid,omitempty"`
ExcludeUIDRange []string `inbound:"exclude_uid_range,omitempty"`
IncludeAndroidUser []int `inbound:"include_android_user,omitempty"`
IncludePackage []string `inbound:"include_package,omitempty"`
ExcludePackage []string `inbound:"exclude_package,omitempty"`
EndpointIndependentNat bool `inbound:"endpoint_independent_nat,omitempty"`
UDPTimeout int64 `inbound:"udp_timeout,omitempty"`
FileDescriptor int `inbound:"file-descriptor,omitempty"`
TableIndex int `inbound:"table-index,omitempty"`
} }
func (o TunOption) Equal(config C.InboundConfig) bool { func (o TunOption) Equal(config C.InboundConfig) bool {
@@ -63,6 +72,16 @@ func NewTun(options *TunOption) (*Tun, error) {
if !exist { if !exist {
return nil, errors.New("invalid tun stack") return nil, errors.New("invalid tun stack")
} }
routeAddress, err := LC.StringSliceToNetipPrefixSlice(options.RouteAddress)
if err != nil {
return nil, err
}
routeExcludeAddress, err := LC.StringSliceToNetipPrefixSlice(options.RouteExcludeAddress)
if err != nil {
return nil, err
}
inet4Address, err := LC.StringSliceToNetipPrefixSlice(options.Inet4Address) inet4Address, err := LC.StringSliceToNetipPrefixSlice(options.Inet4Address)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -91,35 +110,44 @@ func NewTun(options *TunOption) (*Tun, error) {
Base: base, Base: base,
config: options, config: options,
tun: LC.Tun{ tun: LC.Tun{
Enable: true, Enable: true,
Device: options.Device, Device: options.Device,
Stack: stack, Stack: stack,
DNSHijack: options.DNSHijack, DNSHijack: options.DNSHijack,
AutoRoute: options.AutoRoute, AutoRoute: options.AutoRoute,
AutoDetectInterface: options.AutoDetectInterface, AutoDetectInterface: options.AutoDetectInterface,
MTU: options.MTU, MTU: options.MTU,
GSO: options.GSO, GSO: options.GSO,
GSOMaxSize: options.GSOMaxSize, GSOMaxSize: options.GSOMaxSize,
Inet4Address: inet4Address, Inet4Address: inet4Address,
Inet6Address: inet6Address, Inet6Address: inet6Address,
StrictRoute: options.StrictRoute, IPRoute2TableIndex: options.IPRoute2TableIndex,
IPRoute2RuleIndex: options.IPRoute2RuleIndex,
AutoRedirect: options.AutoRedirect,
AutoRedirectInputMark: options.AutoRedirectInputMark,
AutoRedirectOutputMark: options.AutoRedirectOutputMark,
StrictRoute: options.StrictRoute,
RouteAddress: routeAddress,
RouteAddressSet: options.RouteAddressSet,
RouteExcludeAddress: routeExcludeAddress,
RouteExcludeAddressSet: options.RouteExcludeAddressSet,
IncludeInterface: options.IncludeInterface,
ExcludeInterface: options.ExcludeInterface,
IncludeUID: options.IncludeUID,
IncludeUIDRange: options.IncludeUIDRange,
ExcludeUID: options.ExcludeUID,
ExcludeUIDRange: options.ExcludeUIDRange,
IncludeAndroidUser: options.IncludeAndroidUser,
IncludePackage: options.IncludePackage,
ExcludePackage: options.ExcludePackage,
EndpointIndependentNat: options.EndpointIndependentNat,
UDPTimeout: options.UDPTimeout,
FileDescriptor: options.FileDescriptor,
Inet4RouteAddress: inet4RouteAddress, Inet4RouteAddress: inet4RouteAddress,
Inet6RouteAddress: inet6RouteAddress, Inet6RouteAddress: inet6RouteAddress,
Inet4RouteExcludeAddress: inet4RouteExcludeAddress, Inet4RouteExcludeAddress: inet4RouteExcludeAddress,
Inet6RouteExcludeAddress: inet6RouteExcludeAddress, Inet6RouteExcludeAddress: inet6RouteExcludeAddress,
IncludeInterface: options.IncludeInterface,
ExcludeInterface: options.ExcludeInterface,
IncludeUID: options.IncludeUID,
IncludeUIDRange: options.IncludeUIDRange,
ExcludeUID: options.ExcludeUID,
ExcludeUIDRange: options.ExcludeUIDRange,
IncludeAndroidUser: options.IncludeAndroidUser,
IncludePackage: options.IncludePackage,
ExcludePackage: options.ExcludePackage,
EndpointIndependentNat: options.EndpointIndependentNat,
UDPTimeout: options.UDPTimeout,
FileDescriptor: options.FileDescriptor,
TableIndex: options.TableIndex,
}, },
}, nil }, nil
} }

View File

@@ -820,11 +820,15 @@ func hasTunConfigChange(tunConf *LC.Tun) bool {
LastTunConf.MTU != tunConf.MTU || LastTunConf.MTU != tunConf.MTU ||
LastTunConf.GSO != tunConf.GSO || LastTunConf.GSO != tunConf.GSO ||
LastTunConf.GSOMaxSize != tunConf.GSOMaxSize || LastTunConf.GSOMaxSize != tunConf.GSOMaxSize ||
LastTunConf.IPRoute2TableIndex != tunConf.IPRoute2TableIndex ||
LastTunConf.IPRoute2RuleIndex != tunConf.IPRoute2RuleIndex ||
LastTunConf.AutoRedirect != tunConf.AutoRedirect ||
LastTunConf.AutoRedirectInputMark != tunConf.AutoRedirectInputMark ||
LastTunConf.AutoRedirectOutputMark != tunConf.AutoRedirectOutputMark ||
LastTunConf.StrictRoute != tunConf.StrictRoute || LastTunConf.StrictRoute != tunConf.StrictRoute ||
LastTunConf.EndpointIndependentNat != tunConf.EndpointIndependentNat || LastTunConf.EndpointIndependentNat != tunConf.EndpointIndependentNat ||
LastTunConf.UDPTimeout != tunConf.UDPTimeout || LastTunConf.UDPTimeout != tunConf.UDPTimeout ||
LastTunConf.FileDescriptor != tunConf.FileDescriptor || LastTunConf.FileDescriptor != tunConf.FileDescriptor {
LastTunConf.TableIndex != tunConf.TableIndex {
return true return true
} }
@@ -836,6 +840,22 @@ func hasTunConfigChange(tunConf *LC.Tun) bool {
return tunConf.DNSHijack[i] < tunConf.DNSHijack[j] return tunConf.DNSHijack[i] < tunConf.DNSHijack[j]
}) })
sort.Slice(tunConf.RouteAddress, func(i, j int) bool {
return tunConf.RouteAddress[i].String() < tunConf.RouteAddress[j].String()
})
sort.Slice(tunConf.RouteAddressSet, func(i, j int) bool {
return tunConf.RouteAddressSet[i] < tunConf.RouteAddressSet[j]
})
sort.Slice(tunConf.RouteExcludeAddress, func(i, j int) bool {
return tunConf.RouteExcludeAddress[i].String() < tunConf.RouteExcludeAddress[j].String()
})
sort.Slice(tunConf.RouteExcludeAddressSet, func(i, j int) bool {
return tunConf.RouteExcludeAddressSet[i] < tunConf.RouteExcludeAddressSet[j]
})
sort.Slice(tunConf.Inet4Address, func(i, j int) bool { sort.Slice(tunConf.Inet4Address, func(i, j int) bool {
return tunConf.Inet4Address[i].String() < tunConf.Inet4Address[j].String() return tunConf.Inet4Address[i].String() < tunConf.Inet4Address[j].String()
}) })
@@ -897,6 +917,10 @@ func hasTunConfigChange(tunConf *LC.Tun) bool {
}) })
if !slices.Equal(tunConf.DNSHijack, LastTunConf.DNSHijack) || if !slices.Equal(tunConf.DNSHijack, LastTunConf.DNSHijack) ||
!slices.Equal(tunConf.RouteAddress, LastTunConf.RouteAddress) ||
!slices.Equal(tunConf.RouteAddressSet, LastTunConf.RouteAddressSet) ||
!slices.Equal(tunConf.RouteExcludeAddress, LastTunConf.RouteExcludeAddress) ||
!slices.Equal(tunConf.RouteExcludeAddressSet, LastTunConf.RouteExcludeAddressSet) ||
!slices.Equal(tunConf.Inet4Address, LastTunConf.Inet4Address) || !slices.Equal(tunConf.Inet4Address, LastTunConf.Inet4Address) ||
!slices.Equal(tunConf.Inet6Address, LastTunConf.Inet6Address) || !slices.Equal(tunConf.Inet6Address, LastTunConf.Inet6Address) ||
!slices.Equal(tunConf.Inet4RouteAddress, LastTunConf.Inet4RouteAddress) || !slices.Equal(tunConf.Inet4RouteAddress, LastTunConf.Inet4RouteAddress) ||

View File

@@ -4,9 +4,9 @@ import (
"net" "net"
"github.com/metacubex/mihomo/adapter/inbound" "github.com/metacubex/mihomo/adapter/inbound"
"github.com/metacubex/mihomo/common/lru"
N "github.com/metacubex/mihomo/common/net" N "github.com/metacubex/mihomo/common/net"
C "github.com/metacubex/mihomo/constant" C "github.com/metacubex/mihomo/constant"
authStore "github.com/metacubex/mihomo/listener/auth"
"github.com/metacubex/mihomo/listener/http" "github.com/metacubex/mihomo/listener/http"
"github.com/metacubex/mihomo/listener/socks" "github.com/metacubex/mihomo/listener/socks"
"github.com/metacubex/mihomo/transport/socks4" "github.com/metacubex/mihomo/transport/socks4"
@@ -16,7 +16,6 @@ import (
type Listener struct { type Listener struct {
listener net.Listener listener net.Listener
addr string addr string
cache *lru.LruCache[string, bool]
closed bool closed bool
} }
@@ -53,7 +52,6 @@ func New(addr string, tunnel C.Tunnel, additions ...inbound.Addition) (*Listener
ml := &Listener{ ml := &Listener{
listener: l, listener: l,
addr: addr, addr: addr,
cache: lru.New[string, bool](lru.WithAge[string, bool](30)),
} }
go func() { go func() {
for { for {
@@ -70,14 +68,14 @@ func New(addr string, tunnel C.Tunnel, additions ...inbound.Addition) (*Listener
continue continue
} }
} }
go handleConn(c, tunnel, ml.cache, additions...) go handleConn(c, tunnel, additions...)
} }
}() }()
return ml, nil return ml, nil
} }
func handleConn(conn net.Conn, tunnel C.Tunnel, cache *lru.LruCache[string, bool], additions ...inbound.Addition) { func handleConn(conn net.Conn, tunnel C.Tunnel, additions ...inbound.Addition) {
N.TCPKeepAlive(conn) N.TCPKeepAlive(conn)
bufConn := N.NewBufferedConn(conn) bufConn := N.NewBufferedConn(conn)
@@ -92,6 +90,6 @@ func handleConn(conn net.Conn, tunnel C.Tunnel, cache *lru.LruCache[string, bool
case socks5.Version: case socks5.Version:
socks.HandleSocks5(bufConn, tunnel, additions...) socks.HandleSocks5(bufConn, tunnel, additions...)
default: default:
http.HandleConn(bufConn, tunnel, cache, additions...) http.HandleConn(bufConn, tunnel, authStore.Authenticator(), additions...)
} }
} }

View File

@@ -198,6 +198,12 @@ func (h *ListenerHandler) NewError(ctx context.Context, err error) {
log.Warnln("%s listener get error: %+v", h.Type.String(), err) log.Warnln("%s listener get error: %+v", h.Type.String(), err)
} }
func (h *ListenerHandler) TypeMutation(typ C.Type) *ListenerHandler {
handler := *h
handler.Type = typ
return &handler
}
func ShouldIgnorePacketError(err error) bool { func ShouldIgnorePacketError(err error) bool {
// ignore simple error // ignore simple error
if E.IsTimeout(err) || E.IsClosed(err) || E.IsCanceled(err) { if E.IsTimeout(err) || E.IsClosed(err) || E.IsCanceled(err) {

View File

@@ -8,6 +8,7 @@ import (
"time" "time"
"github.com/metacubex/mihomo/component/resolver" "github.com/metacubex/mihomo/component/resolver"
C "github.com/metacubex/mihomo/constant"
"github.com/metacubex/mihomo/listener/sing" "github.com/metacubex/mihomo/listener/sing"
"github.com/metacubex/mihomo/log" "github.com/metacubex/mihomo/log"
@@ -124,3 +125,9 @@ func (h *ListenerHandler) NewPacketConnection(ctx context.Context, conn network.
} }
return h.ListenerHandler.NewPacketConnection(ctx, conn, metadata) return h.ListenerHandler.NewPacketConnection(ctx, conn, metadata)
} }
func (h *ListenerHandler) TypeMutation(typ C.Type) *ListenerHandler {
handle := *h
handle.ListenerHandler = h.ListenerHandler.TypeMutation(typ)
return &handle
}

View File

@@ -0,0 +1,70 @@
package sing_tun
import (
"errors"
"net/netip"
"github.com/metacubex/mihomo/component/iface"
"github.com/sagernet/sing/common/control"
)
type defaultInterfaceFinder struct{}
var DefaultInterfaceFinder control.InterfaceFinder = (*defaultInterfaceFinder)(nil)
func (f *defaultInterfaceFinder) Interfaces() []control.Interface {
ifaces, err := iface.Interfaces()
if err != nil {
return nil
}
interfaces := make([]control.Interface, 0, len(ifaces))
for _, _interface := range ifaces {
interfaces = append(interfaces, control.Interface(*_interface))
}
return interfaces
}
var errNoSuchInterface = errors.New("no such network interface")
func (f *defaultInterfaceFinder) InterfaceIndexByName(name string) (int, error) {
ifaces, err := iface.Interfaces()
if err != nil {
return 0, err
}
for _, netInterface := range ifaces {
if netInterface.Name == name {
return netInterface.Index, nil
}
}
return 0, errNoSuchInterface
}
func (f *defaultInterfaceFinder) InterfaceNameByIndex(index int) (string, error) {
ifaces, err := iface.Interfaces()
if err != nil {
return "", err
}
for _, netInterface := range ifaces {
if netInterface.Index == index {
return netInterface.Name, nil
}
}
return "", errNoSuchInterface
}
func (f *defaultInterfaceFinder) InterfaceByAddr(addr netip.Addr) (*control.Interface, error) {
ifaces, err := iface.Interfaces()
if err != nil {
return nil, err
}
for _, netInterface := range ifaces {
for _, prefix := range netInterface.Addresses {
if prefix.Contains(addr) {
return (*control.Interface)(netInterface), nil
}
}
}
return nil, errNoSuchInterface
}

View File

@@ -0,0 +1,3 @@
package sing_tun
const supportRedirect = true

View File

@@ -0,0 +1,5 @@
//go:build !linux
package sing_tun
const supportRedirect = false

View File

@@ -3,17 +3,21 @@ package sing_tun
import ( import (
"context" "context"
"fmt" "fmt"
"io"
"net" "net"
"net/netip" "net/netip"
"os"
"runtime" "runtime"
"strconv" "strconv"
"strings" "strings"
"sync"
"github.com/metacubex/mihomo/adapter/inbound" "github.com/metacubex/mihomo/adapter/inbound"
"github.com/metacubex/mihomo/component/dialer" "github.com/metacubex/mihomo/component/dialer"
"github.com/metacubex/mihomo/component/iface" "github.com/metacubex/mihomo/component/iface"
"github.com/metacubex/mihomo/component/resolver" "github.com/metacubex/mihomo/component/resolver"
C "github.com/metacubex/mihomo/constant" C "github.com/metacubex/mihomo/constant"
"github.com/metacubex/mihomo/constant/provider"
LC "github.com/metacubex/mihomo/listener/config" LC "github.com/metacubex/mihomo/listener/config"
"github.com/metacubex/mihomo/listener/sing" "github.com/metacubex/mihomo/listener/sing"
"github.com/metacubex/mihomo/log" "github.com/metacubex/mihomo/log"
@@ -23,9 +27,14 @@ import (
E "github.com/sagernet/sing/common/exceptions" E "github.com/sagernet/sing/common/exceptions"
F "github.com/sagernet/sing/common/format" F "github.com/sagernet/sing/common/format"
"github.com/sagernet/sing/common/ranges" "github.com/sagernet/sing/common/ranges"
"go4.org/netipx"
"golang.org/x/exp/maps"
"golang.org/x/exp/slices"
) )
var InterfaceName = "Meta" var InterfaceName = "Meta"
var EnforceBindInterface = false
type Listener struct { type Listener struct {
closed bool closed bool
@@ -40,10 +49,21 @@ type Listener struct {
networkUpdateMonitor tun.NetworkUpdateMonitor networkUpdateMonitor tun.NetworkUpdateMonitor
defaultInterfaceMonitor tun.DefaultInterfaceMonitor defaultInterfaceMonitor tun.DefaultInterfaceMonitor
packageManager tun.PackageManager packageManager tun.PackageManager
autoRedirect tun.AutoRedirect
autoRedirectOutputMark int32
ruleUpdateCallbackCloser io.Closer
ruleUpdateMutex sync.Mutex
routeAddressMap map[string]*netipx.IPSet
routeExcludeAddressMap map[string]*netipx.IPSet
routeAddressSet []*netipx.IPSet
routeExcludeAddressSet []*netipx.IPSet
dnsServerIp []string dnsServerIp []string
} }
var emptyAddressSet = []*netipx.IPSet{{}}
func CalculateInterfaceName(name string) (tunName string) { func CalculateInterfaceName(name string) (tunName string) {
if runtime.GOOS == "darwin" { if runtime.GOOS == "darwin" {
tunName = "utun" tunName = "utun"
@@ -57,15 +77,25 @@ func CalculateInterfaceName(name string) (tunName string) {
if err != nil { if err != nil {
return return
} }
var tunIndex int tunIndex := 0
indexArr := make([]int, 0, len(interfaces))
for _, netInterface := range interfaces { for _, netInterface := range interfaces {
if strings.HasPrefix(netInterface.Name, tunName) { if strings.HasPrefix(netInterface.Name, tunName) {
index, parseErr := strconv.ParseInt(netInterface.Name[len(tunName):], 10, 16) index, parseErr := strconv.ParseInt(netInterface.Name[len(tunName):], 10, 16)
if parseErr == nil { if parseErr == nil {
tunIndex = int(index) + 1 indexArr = append(indexArr, int(index))
} }
} }
} }
slices.Sort(indexArr)
indexArr = slices.Compact(indexArr)
for _, index := range indexArr {
if index == tunIndex {
tunIndex += 1
} else { // indexArr already sorted and distinct, so this tunIndex nobody used
break
}
}
tunName = F.ToString(tunName, tunIndex) tunName = F.ToString(tunName, tunIndex)
return return
} }
@@ -97,14 +127,45 @@ func New(options LC.Tun, tunnel C.Tunnel, additions ...inbound.Addition) (l *Lis
inbound.WithSpecialRules(""), inbound.WithSpecialRules(""),
} }
} }
ctx := context.TODO()
rpTunnel := tunnel.(provider.Tunnel)
if options.GSOMaxSize == 0 { if options.GSOMaxSize == 0 {
options.GSOMaxSize = 65536 options.GSOMaxSize = 65536
} }
if !supportRedirect {
options.AutoRedirect = false
}
tunName := options.Device tunName := options.Device
if tunName == "" || !checkTunName(tunName) { if tunName == "" || !checkTunName(tunName) {
tunName = CalculateInterfaceName(InterfaceName) tunName = CalculateInterfaceName(InterfaceName)
options.Device = tunName options.Device = tunName
} }
routeAddress := options.RouteAddress
if len(options.Inet4RouteAddress) > 0 {
routeAddress = append(routeAddress, options.Inet4RouteAddress...)
}
if len(options.Inet6RouteAddress) > 0 {
routeAddress = append(routeAddress, options.Inet6RouteAddress...)
}
inet4RouteAddress := common.Filter(routeAddress, func(it netip.Prefix) bool {
return it.Addr().Is4()
})
inet6RouteAddress := common.Filter(routeAddress, func(it netip.Prefix) bool {
return it.Addr().Is6()
})
routeExcludeAddress := options.RouteExcludeAddress
if len(options.Inet4RouteExcludeAddress) > 0 {
routeExcludeAddress = append(routeExcludeAddress, options.Inet4RouteExcludeAddress...)
}
if len(options.Inet6RouteExcludeAddress) > 0 {
routeExcludeAddress = append(routeExcludeAddress, options.Inet6RouteExcludeAddress...)
}
inet4RouteExcludeAddress := common.Filter(routeExcludeAddress, func(it netip.Prefix) bool {
return it.Addr().Is4()
})
inet6RouteExcludeAddress := common.Filter(routeExcludeAddress, func(it netip.Prefix) bool {
return it.Addr().Is6()
})
tunMTU := options.MTU tunMTU := options.MTU
if tunMTU == 0 { if tunMTU == 0 {
tunMTU = 9000 tunMTU = 9000
@@ -115,9 +176,21 @@ func New(options LC.Tun, tunnel C.Tunnel, additions ...inbound.Addition) (l *Lis
} else { } else {
udpTimeout = int64(sing.UDPTimeout.Seconds()) udpTimeout = int64(sing.UDPTimeout.Seconds())
} }
tableIndex := options.TableIndex tableIndex := options.IPRoute2TableIndex
if tableIndex == 0 { if tableIndex == 0 {
tableIndex = 2022 tableIndex = tun.DefaultIPRoute2TableIndex
}
ruleIndex := options.IPRoute2RuleIndex
if ruleIndex == 0 {
ruleIndex = tun.DefaultIPRoute2RuleIndex
}
inputMark := options.AutoRedirectInputMark
if inputMark == 0 {
inputMark = tun.DefaultAutoRedirectInputMark
}
outputMark := options.AutoRedirectOutputMark
if outputMark == 0 {
outputMark = tun.DefaultAutoRedirectOutputMark
} }
includeUID := uidToRange(options.IncludeUID) includeUID := uidToRange(options.IncludeUID)
if len(options.IncludeUIDRange) > 0 { if len(options.IncludeUIDRange) > 0 {
@@ -189,6 +262,8 @@ func New(options LC.Tun, tunnel C.Tunnel, additions ...inbound.Addition) (l *Lis
} }
}() }()
interfaceFinder := DefaultInterfaceFinder
networkUpdateMonitor, err := tun.NewNetworkUpdateMonitor(log.SingLogger) networkUpdateMonitor, err := tun.NewNetworkUpdateMonitor(log.SingLogger)
if err != nil { if err != nil {
err = E.Cause(err, "create NetworkUpdateMonitor") err = E.Cause(err, "create NetworkUpdateMonitor")
@@ -223,11 +298,15 @@ func New(options LC.Tun, tunnel C.Tunnel, additions ...inbound.Addition) (l *Lis
Inet4Address: options.Inet4Address, Inet4Address: options.Inet4Address,
Inet6Address: options.Inet6Address, Inet6Address: options.Inet6Address,
AutoRoute: options.AutoRoute, AutoRoute: options.AutoRoute,
IPRoute2TableIndex: tableIndex,
IPRoute2RuleIndex: ruleIndex,
AutoRedirectInputMark: inputMark,
AutoRedirectOutputMark: outputMark,
StrictRoute: options.StrictRoute, StrictRoute: options.StrictRoute,
Inet4RouteAddress: options.Inet4RouteAddress, Inet4RouteAddress: inet4RouteAddress,
Inet6RouteAddress: options.Inet6RouteAddress, Inet6RouteAddress: inet6RouteAddress,
Inet4RouteExcludeAddress: options.Inet4RouteExcludeAddress, Inet4RouteExcludeAddress: inet4RouteExcludeAddress,
Inet6RouteExcludeAddress: options.Inet6RouteExcludeAddress, Inet6RouteExcludeAddress: inet6RouteExcludeAddress,
IncludeInterface: options.IncludeInterface, IncludeInterface: options.IncludeInterface,
ExcludeInterface: options.ExcludeInterface, ExcludeInterface: options.ExcludeInterface,
IncludeUID: includeUID, IncludeUID: includeUID,
@@ -237,7 +316,56 @@ func New(options LC.Tun, tunnel C.Tunnel, additions ...inbound.Addition) (l *Lis
ExcludePackage: options.ExcludePackage, ExcludePackage: options.ExcludePackage,
FileDescriptor: options.FileDescriptor, FileDescriptor: options.FileDescriptor,
InterfaceMonitor: defaultInterfaceMonitor, InterfaceMonitor: defaultInterfaceMonitor,
TableIndex: tableIndex, }
if options.AutoRedirect {
l.routeAddressMap = make(map[string]*netipx.IPSet)
l.routeExcludeAddressMap = make(map[string]*netipx.IPSet)
if !options.AutoRoute {
return nil, E.New("`auto-route` is required by `auto-redirect`")
}
disableNFTables, dErr := strconv.ParseBool(os.Getenv("DISABLE_NFTABLES"))
l.autoRedirect, err = tun.NewAutoRedirect(tun.AutoRedirectOptions{
TunOptions: &tunOptions,
Context: ctx,
Handler: handler.TypeMutation(C.REDIR),
Logger: log.SingLogger,
NetworkMonitor: networkUpdateMonitor,
InterfaceFinder: interfaceFinder,
TableName: "mihomo",
DisableNFTables: dErr == nil && disableNFTables,
RouteAddressSet: &l.routeAddressSet,
RouteExcludeAddressSet: &l.routeExcludeAddressSet,
})
if err != nil {
err = E.Cause(err, "initialize auto redirect")
return
}
var markMode bool
for _, routeAddressSet := range options.RouteAddressSet {
rp, loaded := rpTunnel.RuleProviders()[routeAddressSet]
if !loaded {
err = E.New("parse route-address-set: rule-set not found: ", routeAddressSet)
return
}
l.updateRule(rp, false, false)
markMode = true
}
for _, routeExcludeAddressSet := range options.RouteExcludeAddressSet {
rp, loaded := rpTunnel.RuleProviders()[routeExcludeAddressSet]
if !loaded {
err = E.New("parse route-exclude_address-set: rule-set not found: ", routeExcludeAddressSet)
return
}
l.updateRule(rp, true, false)
markMode = true
}
if markMode {
tunOptions.AutoRedirectMarkMode = true
}
} }
err = l.buildAndroidRules(&tunOptions) err = l.buildAndroidRules(&tunOptions)
@@ -256,13 +384,15 @@ func New(options LC.Tun, tunnel C.Tunnel, additions ...inbound.Addition) (l *Lis
resolver.AddSystemDnsBlacklist(dnsServerIp...) resolver.AddSystemDnsBlacklist(dnsServerIp...)
stackOptions := tun.StackOptions{ stackOptions := tun.StackOptions{
Context: context.TODO(), Context: ctx,
Tun: tunIf, Tun: tunIf,
TunOptions: tunOptions, TunOptions: tunOptions,
EndpointIndependentNat: options.EndpointIndependentNat, EndpointIndependentNat: options.EndpointIndependentNat,
UDPTimeout: udpTimeout, UDPTimeout: udpTimeout,
Handler: handler, Handler: handler,
Logger: log.SingLogger, Logger: log.SingLogger,
InterfaceFinder: interfaceFinder,
EnforceBindInterface: EnforceBindInterface,
} }
if options.FileDescriptor > 0 { if options.FileDescriptor > 0 {
@@ -284,13 +414,80 @@ func New(options LC.Tun, tunnel C.Tunnel, additions ...inbound.Addition) (l *Lis
} }
l.tunStack = tunStack l.tunStack = tunStack
if l.autoRedirect != nil {
if len(l.options.RouteAddressSet) > 0 && len(l.routeAddressSet) == 0 {
l.routeAddressSet = emptyAddressSet // without this we can't call UpdateRouteAddressSet after Start
}
if len(l.options.RouteExcludeAddressSet) > 0 && len(l.routeExcludeAddressSet) == 0 {
l.routeExcludeAddressSet = emptyAddressSet // without this we can't call UpdateRouteAddressSet after Start
}
err = l.autoRedirect.Start()
if err != nil {
err = E.Cause(err, "auto redirect")
return
}
if tunOptions.AutoRedirectMarkMode {
l.autoRedirectOutputMark = int32(outputMark)
dialer.DefaultRoutingMark.Store(l.autoRedirectOutputMark)
l.autoRedirect.UpdateRouteAddressSet()
l.ruleUpdateCallbackCloser = rpTunnel.RuleUpdateCallback().Register(l.ruleUpdateCallback)
}
}
//l.openAndroidHotspot(tunOptions) //l.openAndroidHotspot(tunOptions)
l.addrStr = fmt.Sprintf("%s(%s,%s), mtu: %d, auto route: %v, ip stack: %s", l.addrStr = fmt.Sprintf("%s(%s,%s), mtu: %d, auto route: %v, auto redir: %v, ip stack: %s",
tunName, tunOptions.Inet4Address, tunOptions.Inet6Address, tunMTU, options.AutoRoute, options.Stack) tunName, tunOptions.Inet4Address, tunOptions.Inet6Address, tunMTU, options.AutoRoute, options.AutoRedirect, options.Stack)
return return
} }
func (l *Listener) ruleUpdateCallback(ruleProvider provider.RuleProvider) {
name := ruleProvider.Name()
if slices.Contains(l.options.RouteAddressSet, name) {
l.updateRule(ruleProvider, false, true)
return
}
if slices.Contains(l.options.RouteExcludeAddressSet, name) {
l.updateRule(ruleProvider, true, true)
return
}
}
type toIpCidr interface {
ToIpCidr() *netipx.IPSet
}
func (l *Listener) updateRule(ruleProvider provider.RuleProvider, exclude bool, update bool) {
l.ruleUpdateMutex.Lock()
defer l.ruleUpdateMutex.Unlock()
name := ruleProvider.Name()
switch rp := ruleProvider.Strategy().(type) {
case toIpCidr:
if !exclude {
ipCidr := rp.ToIpCidr()
if ipCidr != nil {
l.routeAddressMap[name] = ipCidr
} else {
delete(l.routeAddressMap, name)
}
l.routeAddressSet = maps.Values(l.routeAddressMap)
} else {
ipCidr := rp.ToIpCidr()
if ipCidr != nil {
l.routeExcludeAddressMap[name] = ipCidr
} else {
delete(l.routeExcludeAddressMap, name)
}
l.routeExcludeAddressSet = maps.Values(l.routeExcludeAddressMap)
}
default:
return
}
if update && l.autoRedirect != nil {
l.autoRedirect.UpdateRouteAddressSet()
}
}
func (l *Listener) FlushDefaultInterface() { func (l *Listener) FlushDefaultInterface() {
if l.options.AutoDetectInterface { if l.options.AutoDetectInterface {
for _, destination := range []netip.Addr{netip.IPv4Unspecified(), netip.IPv6Unspecified(), netip.MustParseAddr("1.1.1.1")} { for _, destination := range []netip.Addr{netip.IPv4Unspecified(), netip.IPv6Unspecified(), netip.MustParseAddr("1.1.1.1")} {
@@ -332,11 +529,11 @@ func parseRange(uidRanges []ranges.Range[uint32], rangeList []string) ([]ranges.
} }
var start, end uint64 var start, end uint64
var err error var err error
start, err = strconv.ParseUint(uidRange[:subIndex], 10, 32) start, err = strconv.ParseUint(uidRange[:subIndex], 0, 32)
if err != nil { if err != nil {
return nil, E.Cause(err, "parse range start") return nil, E.Cause(err, "parse range start")
} }
end, err = strconv.ParseUint(uidRange[subIndex+1:], 10, 32) end, err = strconv.ParseUint(uidRange[subIndex+1:], 0, 32)
if err != nil { if err != nil {
return nil, E.Cause(err, "parse range end") return nil, E.Cause(err, "parse range end")
} }
@@ -348,9 +545,14 @@ func parseRange(uidRanges []ranges.Range[uint32], rangeList []string) ([]ranges.
func (l *Listener) Close() error { func (l *Listener) Close() error {
l.closed = true l.closed = true
resolver.RemoveSystemDnsBlacklist(l.dnsServerIp...) resolver.RemoveSystemDnsBlacklist(l.dnsServerIp...)
if l.autoRedirectOutputMark != 0 {
dialer.DefaultRoutingMark.CompareAndSwap(l.autoRedirectOutputMark, 0)
}
return common.Close( return common.Close(
l.ruleUpdateCallbackCloser,
l.tunStack, l.tunStack,
l.tunIf, l.tunIf,
l.autoRedirect,
l.defaultInterfaceMonitor, l.defaultInterfaceMonitor,
l.networkUpdateMonitor, l.networkUpdateMonitor,
l.packageManager, l.packageManager,

View File

@@ -1,29 +1,80 @@
package sing_tun package sing_tun
import ( import (
"errors"
"runtime"
"sync"
"github.com/metacubex/mihomo/component/process"
"github.com/metacubex/mihomo/constant"
"github.com/metacubex/mihomo/constant/features"
"github.com/metacubex/mihomo/log" "github.com/metacubex/mihomo/log"
tun "github.com/metacubex/sing-tun"
"github.com/metacubex/sing-tun"
"github.com/sagernet/netlink" "github.com/sagernet/netlink"
"golang.org/x/sys/unix" "golang.org/x/sys/unix"
"runtime"
) )
func (l *Listener) buildAndroidRules(tunOptions *tun.Options) error { type packageManagerCallback struct{}
packageManager, err := tun.NewPackageManager(l.handler)
func (cb *packageManagerCallback) OnPackagesUpdated(packageCount int, sharedCount int) {}
func newPackageManager() (tun.PackageManager, error) {
packageManager, err := tun.NewPackageManager(tun.PackageManagerOptions{
Callback: &packageManagerCallback{},
Logger: log.SingLogger,
})
if err != nil { if err != nil {
return err return nil, err
} }
err = packageManager.Start() err = packageManager.Start()
if err != nil {
return nil, err
}
return packageManager, nil
}
var (
globalPM tun.PackageManager
pmOnce sync.Once
pmErr error
)
func getPackageManager() (tun.PackageManager, error) {
pmOnce.Do(func() {
globalPM, pmErr = newPackageManager()
})
return globalPM, pmErr
}
func (l *Listener) buildAndroidRules(tunOptions *tun.Options) error {
packageManager, err := getPackageManager()
if err != nil { if err != nil {
return err return err
} }
l.packageManager = packageManager
tunOptions.BuildAndroidRules(packageManager, l.handler) tunOptions.BuildAndroidRules(packageManager, l.handler)
return nil return nil
} }
func (h *ListenerHandler) OnPackagesUpdated(packages int, sharedUsers int) { func findPackageName(metadata *constant.Metadata) (string, error) {
return packageManager, err := getPackageManager()
if err != nil {
return "", err
}
uid := metadata.Uid
if sharedPackage, loaded := packageManager.SharedPackageByID(uid % 100000); loaded {
return sharedPackage, nil
}
if packageName, loaded := packageManager.PackageByID(uid % 100000); loaded {
return packageName, nil
}
return "", errors.New("package not found")
}
func init() {
if !features.CMFA {
process.DefaultPackageNameResolver = findPackageName
}
} }
func (l *Listener) openAndroidHotspot(tunOptions tun.Options) { func (l *Listener) openAndroidHotspot(tunOptions tun.Options) {

View File

@@ -3,6 +3,7 @@ package sing_tun
import ( import (
"time" "time"
"github.com/metacubex/mihomo/constant/features"
"github.com/metacubex/mihomo/log" "github.com/metacubex/mihomo/log"
tun "github.com/metacubex/sing-tun" tun "github.com/metacubex/sing-tun"
@@ -27,4 +28,9 @@ func tunNew(options tun.Options) (tunIf tun.Tun, err error) {
func init() { func init() {
tun.TunnelType = InterfaceName tun.TunnelType = InterfaceName
if features.WindowsMajorVersion < 10 {
// to resolve "bind: The requested address is not valid in its context"
EnforceBindInterface = true
}
} }

View File

@@ -105,9 +105,9 @@ func listenLocalConn(rAddr, lAddr net.Addr, tunnel C.Tunnel) (*net.UDPConn, erro
buf := pool.Get(pool.UDPBufferSize) buf := pool.Get(pool.UDPBufferSize)
br, err := lc.Read(buf) br, err := lc.Read(buf)
if err != nil { if err != nil {
pool.Put(buf)
if errors.Is(err, net.ErrClosed) { if errors.Is(err, net.ErrClosed) {
log.Debugln("TProxy local conn listener exit.. rAddr=%s lAddr=%s", rAddr.String(), lAddr.String()) log.Debugln("TProxy local conn listener exit.. rAddr=%s lAddr=%s", rAddr.String(), lAddr.String())
pool.Put(buf)
return return
} }
} }

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