mirror of
https://gitclone.com/github.com/MetaCubeX/Clash.Meta
synced 2024-11-14 05:11:17 +08:00
Compare commits
14 Commits
951cae2156
...
0793998de8
Author | SHA1 | Date | |
---|---|---|---|
|
0793998de8 | ||
|
6bf419c5fe | ||
|
4fedfc47b0 | ||
|
92ec5f2236 | ||
|
4c10d42fbf | ||
|
7fd0467aef | ||
|
696b75ee37 | ||
|
f20f371a61 | ||
|
24c6e7d819 | ||
|
acaacd8ab1 | ||
|
12c5cf361d | ||
|
50d0cd363c | ||
|
5bf22422d9 | ||
|
c17d7c0281 |
@ -1,54 +0,0 @@
|
||||
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
|
@ -1,158 +0,0 @@
|
||||
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
|
@ -1,162 +0,0 @@
|
||||
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
|
56
.github/workflows/build.yml
vendored
56
.github/workflows/build.yml
vendored
@ -67,6 +67,12 @@ jobs:
|
||||
- { goos: android, goarch: arm, ndk: armv7a-linux-androideabi34, output: armv7 }
|
||||
- { goos: android, goarch: arm64, ndk: aarch64-linux-android34, output: arm64-v8 }
|
||||
|
||||
# Go 1.22 with special patch can work on Windows 7
|
||||
# https://github.com/MetaCubeX/go/commits/release-branch.go1.22/
|
||||
- { goos: windows, goarch: '386', output: '386-go122', goversion: '1.22' }
|
||||
- { goos: windows, goarch: amd64, goamd64: v1, output: amd64-compatible-go122, goversion: '1.22' }
|
||||
- { goos: windows, goarch: amd64, goamd64: v3, output: amd64-go122, goversion: '1.22' }
|
||||
|
||||
# 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)
|
||||
@ -79,6 +85,11 @@ jobs:
|
||||
- { goos: windows, goarch: amd64, goamd64: v1, output: amd64-compatible-go120, goversion: '1.20' }
|
||||
- { goos: windows, goarch: amd64, goamd64: v3, output: amd64-go120, goversion: '1.20' }
|
||||
|
||||
# Go 1.22 is the last release that will run on macOS 10.15 Catalina. Go 1.23 will require macOS 11 Big Sur or later.
|
||||
- { goos: darwin, goarch: arm64, output: arm64-go122, goversion: '1.22' }
|
||||
- { goos: darwin, goarch: amd64, goamd64: v1, output: amd64-compatible-go122, goversion: '1.22' }
|
||||
- { goos: darwin, goarch: amd64, goamd64: v3, output: amd64-go122, goversion: '1.22' }
|
||||
|
||||
# Go 1.20 is the last release that will run on macOS 10.13 High Sierra or 10.14 Mojave. Go 1.21 will require macOS 10.15 Catalina or later.
|
||||
- { goos: darwin, goarch: arm64, output: arm64-go120, goversion: '1.20' }
|
||||
- { goos: darwin, goarch: amd64, goamd64: v1, output: amd64-compatible-go120, goversion: '1.20' }
|
||||
@ -96,7 +107,7 @@ jobs:
|
||||
if: ${{ matrix.jobs.goversion == '' && matrix.jobs.goarch != 'loong64' }}
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: '1.22'
|
||||
go-version: '1.23'
|
||||
|
||||
- name: Set up Go
|
||||
if: ${{ matrix.jobs.goversion != '' && matrix.jobs.goarch != 'loong64' }}
|
||||
@ -107,31 +118,52 @@ jobs:
|
||||
- name: Set up Go1.22 loongarch abi1
|
||||
if: ${{ matrix.jobs.goarch == 'loong64' && matrix.jobs.abi == '1' }}
|
||||
run: |
|
||||
wget -q https://github.com/xishang0128/loongarch64-golang/releases/download/1.22.0/go1.22.0.linux-amd64-abi1.tar.gz
|
||||
sudo tar zxf go1.22.0.linux-amd64-abi1.tar.gz -C /usr/local
|
||||
wget -q https://github.com/MetaCubeX/loongarch64-golang/releases/download/1.22.4/go1.22.4.linux-amd64-abi1.tar.gz
|
||||
sudo tar zxf go1.22.4.linux-amd64-abi1.tar.gz -C /usr/local
|
||||
echo "/usr/local/go/bin" >> $GITHUB_PATH
|
||||
|
||||
- name: Set up Go1.22 loongarch abi2
|
||||
if: ${{ matrix.jobs.goarch == 'loong64' && matrix.jobs.abi == '2' }}
|
||||
run: |
|
||||
wget -q https://github.com/xishang0128/loongarch64-golang/releases/download/1.22.0/go1.22.0.linux-amd64-abi2.tar.gz
|
||||
sudo tar zxf go1.22.0.linux-amd64-abi2.tar.gz -C /usr/local
|
||||
wget -q https://github.com/MetaCubeX/loongarch64-golang/releases/download/1.22.4/go1.22.4.linux-amd64-abi2.tar.gz
|
||||
sudo tar zxf go1.22.4.linux-amd64-abi2.tar.gz -C /usr/local
|
||||
echo "/usr/local/go/bin" >> $GITHUB_PATH
|
||||
|
||||
# modify from https://github.com/restic/restic/issues/4636#issuecomment-1896455557
|
||||
# this patch file only works on golang1.22.x
|
||||
# that means after golang1.23 release it must be changed
|
||||
# this patch file only works on golang1.23.x
|
||||
# that means after golang1.24 release it must be changed
|
||||
# see: https://github.com/MetaCubeX/go/commits/release-branch.go1.23/
|
||||
# revert:
|
||||
# 693def151adff1af707d82d28f55dba81ceb08e1: "crypto/rand,runtime: switch RtlGenRandom for ProcessPrng"
|
||||
# 7c1157f9544922e96945196b47b95664b1e39108: "net: remove sysSocket fallback for Windows 7"
|
||||
# 48042aa09c2f878c4faa576948b07fe625c4707a: "syscall: remove Windows 7 console handle workaround"
|
||||
- name: Revert Golang1.22 commit for Windows7/8
|
||||
# a17d959debdb04cd550016a3501dd09d50cd62e7: "runtime: always use LoadLibraryEx to load system libraries"
|
||||
- name: Revert Golang1.23 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
|
||||
curl https://github.com/MetaCubeX/go/commit/9ac42137ef6730e8b7daca016ece831297a1d75b.diff | patch --verbose -p 1
|
||||
curl https://github.com/MetaCubeX/go/commit/21290de8a4c91408de7c2b5b68757b1e90af49dd.diff | patch --verbose -p 1
|
||||
curl https://github.com/MetaCubeX/go/commit/6a31d3fa8e47ddabc10bd97bff10d9a85f4cfb76.diff | patch --verbose -p 1
|
||||
curl https://github.com/MetaCubeX/go/commit/69e2eed6dd0f6d815ebf15797761c13f31213dd6.diff | patch --verbose -p 1
|
||||
|
||||
# modify from https://github.com/restic/restic/issues/4636#issuecomment-1896455557
|
||||
# this patch file only works on golang1.22.x
|
||||
# that means after golang1.23 release it must be changed
|
||||
# see: https://github.com/MetaCubeX/go/commits/release-branch.go1.22/
|
||||
# revert:
|
||||
# 693def151adff1af707d82d28f55dba81ceb08e1: "crypto/rand,runtime: switch RtlGenRandom for ProcessPrng"
|
||||
# 7c1157f9544922e96945196b47b95664b1e39108: "net: remove sysSocket fallback for Windows 7"
|
||||
# 48042aa09c2f878c4faa576948b07fe625c4707a: "syscall: remove Windows 7 console handle workaround"
|
||||
# a17d959debdb04cd550016a3501dd09d50cd62e7: "runtime: always use LoadLibraryEx to load system libraries"
|
||||
- name: Revert Golang1.22 commit for Windows7/8
|
||||
if: ${{ matrix.jobs.goos == 'windows' && matrix.jobs.goversion == '1.22' }}
|
||||
run: |
|
||||
cd $(go env GOROOT)
|
||||
curl https://github.com/MetaCubeX/go/commit/9779155f18b6556a034f7bb79fb7fb2aad1e26a9.diff | patch --verbose -p 1
|
||||
curl https://github.com/MetaCubeX/go/commit/ef0606261340e608017860b423ffae5c1ce78239.diff | patch --verbose -p 1
|
||||
curl https://github.com/MetaCubeX/go/commit/7f83badcb925a7e743188041cb6e561fc9b5b642.diff | patch --verbose -p 1
|
||||
curl https://github.com/MetaCubeX/go/commit/83ff9782e024cb328b690cbf0da4e7848a327f4f.diff | patch --verbose -p 1
|
||||
|
||||
# modify from https://github.com/restic/restic/issues/4636#issuecomment-1896455557
|
||||
- name: Revert Golang1.21 commit for Windows7/8
|
||||
@ -162,7 +194,7 @@ jobs:
|
||||
uses: nttld/setup-ndk@v1
|
||||
id: setup-ndk
|
||||
with:
|
||||
ndk-version: r26c
|
||||
ndk-version: r27
|
||||
|
||||
- name: Set NDK path
|
||||
if: ${{ matrix.jobs.goos == 'android' }}
|
||||
|
4
Makefile
4
Makefile
@ -163,7 +163,3 @@ clean:
|
||||
CLANG ?= clang-14
|
||||
CFLAGS := -O2 -g -Wall -Werror $(CFLAGS)
|
||||
|
||||
ebpf: export BPF_CLANG := $(CLANG)
|
||||
ebpf: export BPF_CFLAGS := $(CFLAGS)
|
||||
ebpf:
|
||||
cd component/ebpf/ && go generate ./...
|
||||
|
23
common/net/tcp_keepalive.go
Normal file
23
common/net/tcp_keepalive.go
Normal file
@ -0,0 +1,23 @@
|
||||
package net
|
||||
|
||||
import (
|
||||
"net"
|
||||
"runtime"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
KeepAliveIdle = 0 * time.Second
|
||||
KeepAliveInterval = 0 * time.Second
|
||||
DisableKeepAlive = false
|
||||
)
|
||||
|
||||
func TCPKeepAlive(c net.Conn) {
|
||||
if tcp, ok := c.(*net.TCPConn); ok {
|
||||
if runtime.GOOS == "android" || DisableKeepAlive {
|
||||
_ = tcp.SetKeepAlive(false)
|
||||
} else {
|
||||
tcpKeepAlive(tcp)
|
||||
}
|
||||
}
|
||||
}
|
10
common/net/tcp_keepalive_go122.go
Normal file
10
common/net/tcp_keepalive_go122.go
Normal file
@ -0,0 +1,10 @@
|
||||
//go:build !go1.23
|
||||
|
||||
package net
|
||||
|
||||
import "net"
|
||||
|
||||
func tcpKeepAlive(tcp *net.TCPConn) {
|
||||
_ = tcp.SetKeepAlive(true)
|
||||
_ = tcp.SetKeepAlivePeriod(KeepAliveInterval)
|
||||
}
|
19
common/net/tcp_keepalive_go123.go
Normal file
19
common/net/tcp_keepalive_go123.go
Normal file
@ -0,0 +1,19 @@
|
||||
//go:build go1.23
|
||||
|
||||
package net
|
||||
|
||||
import "net"
|
||||
|
||||
func tcpKeepAlive(tcp *net.TCPConn) {
|
||||
config := net.KeepAliveConfig{
|
||||
Enable: true,
|
||||
Idle: KeepAliveIdle,
|
||||
Interval: KeepAliveInterval,
|
||||
}
|
||||
if !SupportTCPKeepAliveCount() {
|
||||
// it's recommended to set both Idle and Interval to non-negative values in conjunction with a -1
|
||||
// for Count on those old Windows if you intend to customize the TCP keep-alive settings.
|
||||
config.Count = -1
|
||||
}
|
||||
_ = tcp.SetKeepAliveConfig(config)
|
||||
}
|
15
common/net/tcp_keepalive_go123_unix.go
Normal file
15
common/net/tcp_keepalive_go123_unix.go
Normal file
@ -0,0 +1,15 @@
|
||||
//go:build go1.23 && unix
|
||||
|
||||
package net
|
||||
|
||||
func SupportTCPKeepAliveIdle() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func SupportTCPKeepAliveInterval() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func SupportTCPKeepAliveCount() bool {
|
||||
return true
|
||||
}
|
63
common/net/tcp_keepalive_go123_windows.go
Normal file
63
common/net/tcp_keepalive_go123_windows.go
Normal file
@ -0,0 +1,63 @@
|
||||
//go:build go1.23 && windows
|
||||
|
||||
// copy and modify from golang1.23's internal/syscall/windows/version_windows.go
|
||||
|
||||
package net
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"sync"
|
||||
"syscall"
|
||||
|
||||
"github.com/metacubex/mihomo/constant/features"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
var (
|
||||
supportTCPKeepAliveIdle bool
|
||||
supportTCPKeepAliveInterval bool
|
||||
supportTCPKeepAliveCount bool
|
||||
)
|
||||
|
||||
var initTCPKeepAlive = sync.OnceFunc(func() {
|
||||
s, err := windows.WSASocket(syscall.AF_INET, syscall.SOCK_STREAM, syscall.IPPROTO_TCP, nil, 0, windows.WSA_FLAG_NO_HANDLE_INHERIT)
|
||||
if err != nil {
|
||||
// Fallback to checking the Windows version.
|
||||
major, build := features.WindowsMajorVersion, features.WindowsBuildNumber
|
||||
supportTCPKeepAliveIdle = major >= 10 && build >= 16299
|
||||
supportTCPKeepAliveInterval = major >= 10 && build >= 16299
|
||||
supportTCPKeepAliveCount = major >= 10 && build >= 15063
|
||||
return
|
||||
}
|
||||
defer windows.Closesocket(s)
|
||||
var optSupported = func(opt int) bool {
|
||||
err := windows.SetsockoptInt(s, syscall.IPPROTO_TCP, opt, 1)
|
||||
return !errors.Is(err, syscall.WSAENOPROTOOPT)
|
||||
}
|
||||
supportTCPKeepAliveIdle = optSupported(windows.TCP_KEEPIDLE)
|
||||
supportTCPKeepAliveInterval = optSupported(windows.TCP_KEEPINTVL)
|
||||
supportTCPKeepAliveCount = optSupported(windows.TCP_KEEPCNT)
|
||||
})
|
||||
|
||||
// SupportTCPKeepAliveIdle indicates whether TCP_KEEPIDLE is supported.
|
||||
// The minimal requirement is Windows 10.0.16299.
|
||||
func SupportTCPKeepAliveIdle() bool {
|
||||
initTCPKeepAlive()
|
||||
return supportTCPKeepAliveIdle
|
||||
}
|
||||
|
||||
// SupportTCPKeepAliveInterval indicates whether TCP_KEEPINTVL is supported.
|
||||
// The minimal requirement is Windows 10.0.16299.
|
||||
func SupportTCPKeepAliveInterval() bool {
|
||||
initTCPKeepAlive()
|
||||
return supportTCPKeepAliveInterval
|
||||
}
|
||||
|
||||
// SupportTCPKeepAliveCount indicates whether TCP_KEEPCNT is supported.
|
||||
// supports TCP_KEEPCNT.
|
||||
// The minimal requirement is Windows 10.0.15063.
|
||||
func SupportTCPKeepAliveCount() bool {
|
||||
initTCPKeepAlive()
|
||||
return supportTCPKeepAliveCount
|
||||
}
|
@ -4,11 +4,8 @@ import (
|
||||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
var KeepAliveInterval = 15 * time.Second
|
||||
|
||||
func SplitNetworkType(s string) (string, string, error) {
|
||||
var (
|
||||
shecme string
|
||||
@ -47,10 +44,3 @@ func SplitHostPort(s string) (host, port string, hasPort bool, err error) {
|
||||
host, port, err = net.SplitHostPort(temp)
|
||||
return
|
||||
}
|
||||
|
||||
func TCPKeepAlive(c net.Conn) {
|
||||
if tcp, ok := c.(*net.TCPConn); ok {
|
||||
_ = tcp.SetKeepAlive(true)
|
||||
_ = tcp.SetKeepAlivePeriod(KeepAliveInterval)
|
||||
}
|
||||
}
|
||||
|
224
common/singleflight/singleflight.go
Normal file
224
common/singleflight/singleflight.go
Normal file
@ -0,0 +1,224 @@
|
||||
// copy and modify from "golang.org/x/sync/singleflight"
|
||||
|
||||
// Copyright 2013 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 singleflight provides a duplicate function call suppression
|
||||
// mechanism.
|
||||
package singleflight
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"runtime"
|
||||
"runtime/debug"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// errGoexit indicates the runtime.Goexit was called in
|
||||
// the user given function.
|
||||
var errGoexit = errors.New("runtime.Goexit was called")
|
||||
|
||||
// A panicError is an arbitrary value recovered from a panic
|
||||
// with the stack trace during the execution of given function.
|
||||
type panicError struct {
|
||||
value interface{}
|
||||
stack []byte
|
||||
}
|
||||
|
||||
// Error implements error interface.
|
||||
func (p *panicError) Error() string {
|
||||
return fmt.Sprintf("%v\n\n%s", p.value, p.stack)
|
||||
}
|
||||
|
||||
func (p *panicError) Unwrap() error {
|
||||
err, ok := p.value.(error)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func newPanicError(v interface{}) error {
|
||||
stack := debug.Stack()
|
||||
|
||||
// The first line of the stack trace is of the form "goroutine N [status]:"
|
||||
// but by the time the panic reaches Do the goroutine may no longer exist
|
||||
// and its status will have changed. Trim out the misleading line.
|
||||
if line := bytes.IndexByte(stack[:], '\n'); line >= 0 {
|
||||
stack = stack[line+1:]
|
||||
}
|
||||
return &panicError{value: v, stack: stack}
|
||||
}
|
||||
|
||||
// call is an in-flight or completed singleflight.Do call
|
||||
type call[T any] struct {
|
||||
wg sync.WaitGroup
|
||||
|
||||
// These fields are written once before the WaitGroup is done
|
||||
// and are only read after the WaitGroup is done.
|
||||
val T
|
||||
err error
|
||||
|
||||
// These fields are read and written with the singleflight
|
||||
// mutex held before the WaitGroup is done, and are read but
|
||||
// not written after the WaitGroup is done.
|
||||
dups int
|
||||
chans []chan<- Result[T]
|
||||
}
|
||||
|
||||
// Group represents a class of work and forms a namespace in
|
||||
// which units of work can be executed with duplicate suppression.
|
||||
type Group[T any] struct {
|
||||
mu sync.Mutex // protects m
|
||||
m map[string]*call[T] // lazily initialized
|
||||
|
||||
StoreResult bool
|
||||
}
|
||||
|
||||
// Result holds the results of Do, so they can be passed
|
||||
// on a channel.
|
||||
type Result[T any] struct {
|
||||
Val T
|
||||
Err error
|
||||
Shared bool
|
||||
}
|
||||
|
||||
// Do executes and returns the results of the given function, making
|
||||
// sure that only one execution is in-flight for a given key at a
|
||||
// time. If a duplicate comes in, the duplicate caller waits for the
|
||||
// original to complete and receives the same results.
|
||||
// The return value shared indicates whether v was given to multiple callers.
|
||||
func (g *Group[T]) Do(key string, fn func() (T, error)) (v T, err error, shared bool) {
|
||||
g.mu.Lock()
|
||||
if g.m == nil {
|
||||
g.m = make(map[string]*call[T])
|
||||
}
|
||||
if c, ok := g.m[key]; ok {
|
||||
c.dups++
|
||||
g.mu.Unlock()
|
||||
c.wg.Wait()
|
||||
|
||||
if e, ok := c.err.(*panicError); ok {
|
||||
panic(e)
|
||||
} else if c.err == errGoexit {
|
||||
runtime.Goexit()
|
||||
}
|
||||
return c.val, c.err, true
|
||||
}
|
||||
c := new(call[T])
|
||||
c.wg.Add(1)
|
||||
g.m[key] = c
|
||||
g.mu.Unlock()
|
||||
|
||||
g.doCall(c, key, fn)
|
||||
return c.val, c.err, c.dups > 0
|
||||
}
|
||||
|
||||
// DoChan is like Do but returns a channel that will receive the
|
||||
// results when they are ready.
|
||||
//
|
||||
// The returned channel will not be closed.
|
||||
func (g *Group[T]) DoChan(key string, fn func() (T, error)) <-chan Result[T] {
|
||||
ch := make(chan Result[T], 1)
|
||||
g.mu.Lock()
|
||||
if g.m == nil {
|
||||
g.m = make(map[string]*call[T])
|
||||
}
|
||||
if c, ok := g.m[key]; ok {
|
||||
c.dups++
|
||||
c.chans = append(c.chans, ch)
|
||||
g.mu.Unlock()
|
||||
return ch
|
||||
}
|
||||
c := &call[T]{chans: []chan<- Result[T]{ch}}
|
||||
c.wg.Add(1)
|
||||
g.m[key] = c
|
||||
g.mu.Unlock()
|
||||
|
||||
go g.doCall(c, key, fn)
|
||||
|
||||
return ch
|
||||
}
|
||||
|
||||
// doCall handles the single call for a key.
|
||||
func (g *Group[T]) doCall(c *call[T], key string, fn func() (T, error)) {
|
||||
normalReturn := false
|
||||
recovered := false
|
||||
|
||||
// use double-defer to distinguish panic from runtime.Goexit,
|
||||
// more details see https://golang.org/cl/134395
|
||||
defer func() {
|
||||
// the given function invoked runtime.Goexit
|
||||
if !normalReturn && !recovered {
|
||||
c.err = errGoexit
|
||||
}
|
||||
|
||||
g.mu.Lock()
|
||||
defer g.mu.Unlock()
|
||||
c.wg.Done()
|
||||
if g.m[key] == c && !g.StoreResult {
|
||||
delete(g.m, key)
|
||||
}
|
||||
|
||||
if e, ok := c.err.(*panicError); ok {
|
||||
// In order to prevent the waiting channels from being blocked forever,
|
||||
// needs to ensure that this panic cannot be recovered.
|
||||
if len(c.chans) > 0 {
|
||||
go panic(e)
|
||||
select {} // Keep this goroutine around so that it will appear in the crash dump.
|
||||
} else {
|
||||
panic(e)
|
||||
}
|
||||
} else if c.err == errGoexit {
|
||||
// Already in the process of goexit, no need to call again
|
||||
} else {
|
||||
// Normal return
|
||||
for _, ch := range c.chans {
|
||||
ch <- Result[T]{c.val, c.err, c.dups > 0}
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
func() {
|
||||
defer func() {
|
||||
if !normalReturn {
|
||||
// Ideally, we would wait to take a stack trace until we've determined
|
||||
// whether this is a panic or a runtime.Goexit.
|
||||
//
|
||||
// Unfortunately, the only way we can distinguish the two is to see
|
||||
// whether the recover stopped the goroutine from terminating, and by
|
||||
// the time we know that, the part of the stack trace relevant to the
|
||||
// panic has been discarded.
|
||||
if r := recover(); r != nil {
|
||||
c.err = newPanicError(r)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
c.val, c.err = fn()
|
||||
normalReturn = true
|
||||
}()
|
||||
|
||||
if !normalReturn {
|
||||
recovered = true
|
||||
}
|
||||
}
|
||||
|
||||
// Forget tells the singleflight to forget about a key. Future calls
|
||||
// to Do for this key will call the function rather than waiting for
|
||||
// an earlier call to complete.
|
||||
func (g *Group[T]) Forget(key string) {
|
||||
g.mu.Lock()
|
||||
delete(g.m, key)
|
||||
g.mu.Unlock()
|
||||
}
|
||||
|
||||
func (g *Group[T]) Reset() {
|
||||
g.mu.Lock()
|
||||
g.m = nil
|
||||
g.mu.Unlock()
|
||||
}
|
@ -13,7 +13,6 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/metacubex/mihomo/component/resolver"
|
||||
"github.com/metacubex/mihomo/constant/features"
|
||||
"github.com/metacubex/mihomo/log"
|
||||
)
|
||||
|
||||
@ -79,29 +78,29 @@ func DialContext(ctx context.Context, network, address string, options ...Option
|
||||
}
|
||||
|
||||
func ListenPacket(ctx context.Context, network, address string, rAddrPort netip.AddrPort, options ...Option) (net.PacketConn, error) {
|
||||
if features.CMFA && DefaultSocketHook != nil {
|
||||
return listenPacketHooked(ctx, network, address)
|
||||
}
|
||||
|
||||
cfg := applyOptions(options...)
|
||||
|
||||
lc := &net.ListenConfig{}
|
||||
if cfg.interfaceName != "" {
|
||||
bind := bindIfaceToListenConfig
|
||||
if cfg.fallbackBind {
|
||||
bind = fallbackBindIfaceToListenConfig
|
||||
}
|
||||
addr, err := bind(cfg.interfaceName, lc, network, address, rAddrPort)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
address = addr
|
||||
}
|
||||
if cfg.addrReuse {
|
||||
addrReuseToListenConfig(lc)
|
||||
}
|
||||
if cfg.routingMark != 0 {
|
||||
bindMarkToListenConfig(cfg.routingMark, lc, network, address)
|
||||
if DefaultSocketHook != nil { // ignore interfaceName, routingMark when DefaultSocketHook not null (in CFMA)
|
||||
socketHookToListenConfig(lc)
|
||||
} else {
|
||||
if cfg.interfaceName != "" {
|
||||
bind := bindIfaceToListenConfig
|
||||
if cfg.fallbackBind {
|
||||
bind = fallbackBindIfaceToListenConfig
|
||||
}
|
||||
addr, err := bind(cfg.interfaceName, lc, network, address, rAddrPort)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
address = addr
|
||||
}
|
||||
if cfg.routingMark != 0 {
|
||||
bindMarkToListenConfig(cfg.routingMark, lc, network, address)
|
||||
}
|
||||
}
|
||||
|
||||
return lc.ListenPacket(ctx, network, address)
|
||||
@ -127,10 +126,6 @@ func GetTcpConcurrent() bool {
|
||||
}
|
||||
|
||||
func dialContext(ctx context.Context, network string, destination netip.Addr, port string, opt *option) (net.Conn, error) {
|
||||
if features.CMFA && DefaultSocketHook != nil {
|
||||
return dialContextHooked(ctx, network, destination, port)
|
||||
}
|
||||
|
||||
var address string
|
||||
if IP4PEnable {
|
||||
destination, port = lookupIP4P(destination, port)
|
||||
@ -149,24 +144,30 @@ func dialContext(ctx context.Context, network string, destination netip.Addr, po
|
||||
}
|
||||
|
||||
dialer := netDialer.(*net.Dialer)
|
||||
if opt.interfaceName != "" {
|
||||
bind := bindIfaceToDialer
|
||||
if opt.fallbackBind {
|
||||
bind = fallbackBindIfaceToDialer
|
||||
}
|
||||
if err := bind(opt.interfaceName, dialer, network, destination); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if opt.routingMark != 0 {
|
||||
bindMarkToDialer(opt.routingMark, dialer, network, destination)
|
||||
}
|
||||
if opt.mpTcp {
|
||||
setMultiPathTCP(dialer)
|
||||
}
|
||||
if opt.tfo && !DisableTFO {
|
||||
return dialTFO(ctx, *dialer, network, address)
|
||||
|
||||
if DefaultSocketHook != nil { // ignore interfaceName, routingMark and tfo when DefaultSocketHook not null (in CFMA)
|
||||
socketHookToToDialer(dialer)
|
||||
} else {
|
||||
if opt.interfaceName != "" {
|
||||
bind := bindIfaceToDialer
|
||||
if opt.fallbackBind {
|
||||
bind = fallbackBindIfaceToDialer
|
||||
}
|
||||
if err := bind(opt.interfaceName, dialer, network, destination); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if opt.routingMark != 0 {
|
||||
bindMarkToDialer(opt.routingMark, dialer, network, destination)
|
||||
}
|
||||
if opt.tfo && !DisableTFO {
|
||||
return dialTFO(ctx, *dialer, network, address)
|
||||
}
|
||||
}
|
||||
|
||||
return dialer.DialContext(ctx, network, address)
|
||||
}
|
||||
|
||||
|
@ -1,39 +0,0 @@
|
||||
//go:build android && cmfa
|
||||
|
||||
package dialer
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"net/netip"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
type SocketControl func(network, address string, conn syscall.RawConn) error
|
||||
|
||||
var DefaultSocketHook SocketControl
|
||||
|
||||
func dialContextHooked(ctx context.Context, network string, destination netip.Addr, port string) (net.Conn, error) {
|
||||
dialer := &net.Dialer{
|
||||
Control: DefaultSocketHook,
|
||||
}
|
||||
|
||||
conn, err := dialer.DialContext(ctx, network, net.JoinHostPort(destination.String(), port))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if t, ok := conn.(*net.TCPConn); ok {
|
||||
t.SetKeepAlive(false)
|
||||
}
|
||||
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
func listenPacketHooked(ctx context.Context, network, address string) (net.PacketConn, error) {
|
||||
lc := &net.ListenConfig{
|
||||
Control: DefaultSocketHook,
|
||||
}
|
||||
|
||||
return lc.ListenPacket(ctx, network, address)
|
||||
}
|
@ -1,22 +0,0 @@
|
||||
//go:build !(android && cmfa)
|
||||
|
||||
package dialer
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"net/netip"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
type SocketControl func(network, address string, conn syscall.RawConn) error
|
||||
|
||||
var DefaultSocketHook SocketControl
|
||||
|
||||
func dialContextHooked(ctx context.Context, network string, destination netip.Addr, port string) (net.Conn, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func listenPacketHooked(ctx context.Context, network, address string) (net.PacketConn, error) {
|
||||
return nil, nil
|
||||
}
|
27
component/dialer/socket_hook.go
Normal file
27
component/dialer/socket_hook.go
Normal file
@ -0,0 +1,27 @@
|
||||
package dialer
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
// SocketControl
|
||||
// never change type traits because it's used in CFMA
|
||||
type SocketControl func(network, address string, conn syscall.RawConn) error
|
||||
|
||||
// DefaultSocketHook
|
||||
// never change type traits because it's used in CFMA
|
||||
var DefaultSocketHook SocketControl
|
||||
|
||||
func socketHookToToDialer(dialer *net.Dialer) {
|
||||
addControlToDialer(dialer, func(ctx context.Context, network, address string, c syscall.RawConn) error {
|
||||
return DefaultSocketHook(network, address, c)
|
||||
})
|
||||
}
|
||||
|
||||
func socketHookToListenConfig(lc *net.ListenConfig) {
|
||||
addControlToListenConfig(lc, func(ctx context.Context, network, address string, c syscall.RawConn) error {
|
||||
return DefaultSocketHook(network, address, c)
|
||||
})
|
||||
}
|
@ -1,99 +0,0 @@
|
||||
/* SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) */
|
||||
#ifndef __BPF_ENDIAN__
|
||||
#define __BPF_ENDIAN__
|
||||
|
||||
/*
|
||||
* Isolate byte #n and put it into byte #m, for __u##b type.
|
||||
* E.g., moving byte #6 (nnnnnnnn) into byte #1 (mmmmmmmm) for __u64:
|
||||
* 1) xxxxxxxx nnnnnnnn xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx mmmmmmmm xxxxxxxx
|
||||
* 2) nnnnnnnn xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx mmmmmmmm xxxxxxxx 00000000
|
||||
* 3) 00000000 00000000 00000000 00000000 00000000 00000000 00000000 nnnnnnnn
|
||||
* 4) 00000000 00000000 00000000 00000000 00000000 00000000 nnnnnnnn 00000000
|
||||
*/
|
||||
#define ___bpf_mvb(x, b, n, m) ((__u##b)(x) << (b-(n+1)*8) >> (b-8) << (m*8))
|
||||
|
||||
#define ___bpf_swab16(x) ((__u16)( \
|
||||
___bpf_mvb(x, 16, 0, 1) | \
|
||||
___bpf_mvb(x, 16, 1, 0)))
|
||||
|
||||
#define ___bpf_swab32(x) ((__u32)( \
|
||||
___bpf_mvb(x, 32, 0, 3) | \
|
||||
___bpf_mvb(x, 32, 1, 2) | \
|
||||
___bpf_mvb(x, 32, 2, 1) | \
|
||||
___bpf_mvb(x, 32, 3, 0)))
|
||||
|
||||
#define ___bpf_swab64(x) ((__u64)( \
|
||||
___bpf_mvb(x, 64, 0, 7) | \
|
||||
___bpf_mvb(x, 64, 1, 6) | \
|
||||
___bpf_mvb(x, 64, 2, 5) | \
|
||||
___bpf_mvb(x, 64, 3, 4) | \
|
||||
___bpf_mvb(x, 64, 4, 3) | \
|
||||
___bpf_mvb(x, 64, 5, 2) | \
|
||||
___bpf_mvb(x, 64, 6, 1) | \
|
||||
___bpf_mvb(x, 64, 7, 0)))
|
||||
|
||||
/* LLVM's BPF target selects the endianness of the CPU
|
||||
* it compiles on, or the user specifies (bpfel/bpfeb),
|
||||
* respectively. The used __BYTE_ORDER__ is defined by
|
||||
* the compiler, we cannot rely on __BYTE_ORDER from
|
||||
* libc headers, since it doesn't reflect the actual
|
||||
* requested byte order.
|
||||
*
|
||||
* Note, LLVM's BPF target has different __builtin_bswapX()
|
||||
* semantics. It does map to BPF_ALU | BPF_END | BPF_TO_BE
|
||||
* in bpfel and bpfeb case, which means below, that we map
|
||||
* to cpu_to_be16(). We could use it unconditionally in BPF
|
||||
* case, but better not rely on it, so that this header here
|
||||
* can be used from application and BPF program side, which
|
||||
* use different targets.
|
||||
*/
|
||||
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
|
||||
# define __bpf_ntohs(x) __builtin_bswap16(x)
|
||||
# define __bpf_htons(x) __builtin_bswap16(x)
|
||||
# define __bpf_constant_ntohs(x) ___bpf_swab16(x)
|
||||
# define __bpf_constant_htons(x) ___bpf_swab16(x)
|
||||
# define __bpf_ntohl(x) __builtin_bswap32(x)
|
||||
# define __bpf_htonl(x) __builtin_bswap32(x)
|
||||
# define __bpf_constant_ntohl(x) ___bpf_swab32(x)
|
||||
# define __bpf_constant_htonl(x) ___bpf_swab32(x)
|
||||
# define __bpf_be64_to_cpu(x) __builtin_bswap64(x)
|
||||
# define __bpf_cpu_to_be64(x) __builtin_bswap64(x)
|
||||
# define __bpf_constant_be64_to_cpu(x) ___bpf_swab64(x)
|
||||
# define __bpf_constant_cpu_to_be64(x) ___bpf_swab64(x)
|
||||
#elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
|
||||
# define __bpf_ntohs(x) (x)
|
||||
# define __bpf_htons(x) (x)
|
||||
# define __bpf_constant_ntohs(x) (x)
|
||||
# define __bpf_constant_htons(x) (x)
|
||||
# define __bpf_ntohl(x) (x)
|
||||
# define __bpf_htonl(x) (x)
|
||||
# define __bpf_constant_ntohl(x) (x)
|
||||
# define __bpf_constant_htonl(x) (x)
|
||||
# define __bpf_be64_to_cpu(x) (x)
|
||||
# define __bpf_cpu_to_be64(x) (x)
|
||||
# define __bpf_constant_be64_to_cpu(x) (x)
|
||||
# define __bpf_constant_cpu_to_be64(x) (x)
|
||||
#else
|
||||
# error "Fix your compiler's __BYTE_ORDER__?!"
|
||||
#endif
|
||||
|
||||
#define bpf_htons(x) \
|
||||
(__builtin_constant_p(x) ? \
|
||||
__bpf_constant_htons(x) : __bpf_htons(x))
|
||||
#define bpf_ntohs(x) \
|
||||
(__builtin_constant_p(x) ? \
|
||||
__bpf_constant_ntohs(x) : __bpf_ntohs(x))
|
||||
#define bpf_htonl(x) \
|
||||
(__builtin_constant_p(x) ? \
|
||||
__bpf_constant_htonl(x) : __bpf_htonl(x))
|
||||
#define bpf_ntohl(x) \
|
||||
(__builtin_constant_p(x) ? \
|
||||
__bpf_constant_ntohl(x) : __bpf_ntohl(x))
|
||||
#define bpf_cpu_to_be64(x) \
|
||||
(__builtin_constant_p(x) ? \
|
||||
__bpf_constant_cpu_to_be64(x) : __bpf_cpu_to_be64(x))
|
||||
#define bpf_be64_to_cpu(x) \
|
||||
(__builtin_constant_p(x) ? \
|
||||
__bpf_constant_be64_to_cpu(x) : __bpf_be64_to_cpu(x))
|
||||
|
||||
#endif /* __BPF_ENDIAN__ */
|
File diff suppressed because it is too large
Load Diff
@ -1,262 +0,0 @@
|
||||
/* SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) */
|
||||
#ifndef __BPF_HELPERS__
|
||||
#define __BPF_HELPERS__
|
||||
|
||||
/*
|
||||
* Note that bpf programs need to include either
|
||||
* vmlinux.h (auto-generated from BTF) or linux/types.h
|
||||
* in advance since bpf_helper_defs.h uses such types
|
||||
* as __u64.
|
||||
*/
|
||||
#include "bpf_helper_defs.h"
|
||||
|
||||
#define __uint(name, val) int (*name)[val]
|
||||
#define __type(name, val) typeof(val) *name
|
||||
#define __array(name, val) typeof(val) *name[]
|
||||
|
||||
/*
|
||||
* Helper macro to place programs, maps, license in
|
||||
* different sections in elf_bpf file. Section names
|
||||
* are interpreted by libbpf depending on the context (BPF programs, BPF maps,
|
||||
* extern variables, etc).
|
||||
* To allow use of SEC() with externs (e.g., for extern .maps declarations),
|
||||
* make sure __attribute__((unused)) doesn't trigger compilation warning.
|
||||
*/
|
||||
#define SEC(name) \
|
||||
_Pragma("GCC diagnostic push") \
|
||||
_Pragma("GCC diagnostic ignored \"-Wignored-attributes\"") \
|
||||
__attribute__((section(name), used)) \
|
||||
_Pragma("GCC diagnostic pop") \
|
||||
|
||||
/* Avoid 'linux/stddef.h' definition of '__always_inline'. */
|
||||
#undef __always_inline
|
||||
#define __always_inline inline __attribute__((always_inline))
|
||||
|
||||
#ifndef __noinline
|
||||
#define __noinline __attribute__((noinline))
|
||||
#endif
|
||||
#ifndef __weak
|
||||
#define __weak __attribute__((weak))
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Use __hidden attribute to mark a non-static BPF subprogram effectively
|
||||
* static for BPF verifier's verification algorithm purposes, allowing more
|
||||
* extensive and permissive BPF verification process, taking into account
|
||||
* subprogram's caller context.
|
||||
*/
|
||||
#define __hidden __attribute__((visibility("hidden")))
|
||||
|
||||
/* When utilizing vmlinux.h with BPF CO-RE, user BPF programs can't include
|
||||
* any system-level headers (such as stddef.h, linux/version.h, etc), and
|
||||
* commonly-used macros like NULL and KERNEL_VERSION aren't available through
|
||||
* vmlinux.h. This just adds unnecessary hurdles and forces users to re-define
|
||||
* them on their own. So as a convenience, provide such definitions here.
|
||||
*/
|
||||
#ifndef NULL
|
||||
#define NULL ((void *)0)
|
||||
#endif
|
||||
|
||||
#ifndef KERNEL_VERSION
|
||||
#define KERNEL_VERSION(a, b, c) (((a) << 16) + ((b) << 8) + ((c) > 255 ? 255 : (c)))
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Helper macros to manipulate data structures
|
||||
*/
|
||||
#ifndef offsetof
|
||||
#define offsetof(TYPE, MEMBER) ((unsigned long)&((TYPE *)0)->MEMBER)
|
||||
#endif
|
||||
#ifndef container_of
|
||||
#define container_of(ptr, type, member) \
|
||||
({ \
|
||||
void *__mptr = (void *)(ptr); \
|
||||
((type *)(__mptr - offsetof(type, member))); \
|
||||
})
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Helper macro to throw a compilation error if __bpf_unreachable() gets
|
||||
* built into the resulting code. This works given BPF back end does not
|
||||
* implement __builtin_trap(). This is useful to assert that certain paths
|
||||
* of the program code are never used and hence eliminated by the compiler.
|
||||
*
|
||||
* For example, consider a switch statement that covers known cases used by
|
||||
* the program. __bpf_unreachable() can then reside in the default case. If
|
||||
* the program gets extended such that a case is not covered in the switch
|
||||
* statement, then it will throw a build error due to the default case not
|
||||
* being compiled out.
|
||||
*/
|
||||
#ifndef __bpf_unreachable
|
||||
# define __bpf_unreachable() __builtin_trap()
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Helper function to perform a tail call with a constant/immediate map slot.
|
||||
*/
|
||||
#if __clang_major__ >= 8 && defined(__bpf__)
|
||||
static __always_inline void
|
||||
bpf_tail_call_static(void *ctx, const void *map, const __u32 slot)
|
||||
{
|
||||
if (!__builtin_constant_p(slot))
|
||||
__bpf_unreachable();
|
||||
|
||||
/*
|
||||
* Provide a hard guarantee that LLVM won't optimize setting r2 (map
|
||||
* pointer) and r3 (constant map index) from _different paths_ ending
|
||||
* up at the _same_ call insn as otherwise we won't be able to use the
|
||||
* jmpq/nopl retpoline-free patching by the x86-64 JIT in the kernel
|
||||
* given they mismatch. See also d2e4c1e6c294 ("bpf: Constant map key
|
||||
* tracking for prog array pokes") for details on verifier tracking.
|
||||
*
|
||||
* Note on clobber list: we need to stay in-line with BPF calling
|
||||
* convention, so even if we don't end up using r0, r4, r5, we need
|
||||
* to mark them as clobber so that LLVM doesn't end up using them
|
||||
* before / after the call.
|
||||
*/
|
||||
asm volatile("r1 = %[ctx]\n\t"
|
||||
"r2 = %[map]\n\t"
|
||||
"r3 = %[slot]\n\t"
|
||||
"call 12"
|
||||
:: [ctx]"r"(ctx), [map]"r"(map), [slot]"i"(slot)
|
||||
: "r0", "r1", "r2", "r3", "r4", "r5");
|
||||
}
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Helper structure used by eBPF C program
|
||||
* to describe BPF map attributes to libbpf loader
|
||||
*/
|
||||
struct bpf_map_def {
|
||||
unsigned int type;
|
||||
unsigned int key_size;
|
||||
unsigned int value_size;
|
||||
unsigned int max_entries;
|
||||
unsigned int map_flags;
|
||||
};
|
||||
|
||||
enum libbpf_pin_type {
|
||||
LIBBPF_PIN_NONE,
|
||||
/* PIN_BY_NAME: pin maps by name (in /sys/fs/bpf by default) */
|
||||
LIBBPF_PIN_BY_NAME,
|
||||
};
|
||||
|
||||
enum libbpf_tristate {
|
||||
TRI_NO = 0,
|
||||
TRI_YES = 1,
|
||||
TRI_MODULE = 2,
|
||||
};
|
||||
|
||||
#define __kconfig __attribute__((section(".kconfig")))
|
||||
#define __ksym __attribute__((section(".ksyms")))
|
||||
|
||||
#ifndef ___bpf_concat
|
||||
#define ___bpf_concat(a, b) a ## b
|
||||
#endif
|
||||
#ifndef ___bpf_apply
|
||||
#define ___bpf_apply(fn, n) ___bpf_concat(fn, n)
|
||||
#endif
|
||||
#ifndef ___bpf_nth
|
||||
#define ___bpf_nth(_, _1, _2, _3, _4, _5, _6, _7, _8, _9, _a, _b, _c, N, ...) N
|
||||
#endif
|
||||
#ifndef ___bpf_narg
|
||||
#define ___bpf_narg(...) \
|
||||
___bpf_nth(_, ##__VA_ARGS__, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0)
|
||||
#endif
|
||||
|
||||
#define ___bpf_fill0(arr, p, x) do {} while (0)
|
||||
#define ___bpf_fill1(arr, p, x) arr[p] = x
|
||||
#define ___bpf_fill2(arr, p, x, args...) arr[p] = x; ___bpf_fill1(arr, p + 1, args)
|
||||
#define ___bpf_fill3(arr, p, x, args...) arr[p] = x; ___bpf_fill2(arr, p + 1, args)
|
||||
#define ___bpf_fill4(arr, p, x, args...) arr[p] = x; ___bpf_fill3(arr, p + 1, args)
|
||||
#define ___bpf_fill5(arr, p, x, args...) arr[p] = x; ___bpf_fill4(arr, p + 1, args)
|
||||
#define ___bpf_fill6(arr, p, x, args...) arr[p] = x; ___bpf_fill5(arr, p + 1, args)
|
||||
#define ___bpf_fill7(arr, p, x, args...) arr[p] = x; ___bpf_fill6(arr, p + 1, args)
|
||||
#define ___bpf_fill8(arr, p, x, args...) arr[p] = x; ___bpf_fill7(arr, p + 1, args)
|
||||
#define ___bpf_fill9(arr, p, x, args...) arr[p] = x; ___bpf_fill8(arr, p + 1, args)
|
||||
#define ___bpf_fill10(arr, p, x, args...) arr[p] = x; ___bpf_fill9(arr, p + 1, args)
|
||||
#define ___bpf_fill11(arr, p, x, args...) arr[p] = x; ___bpf_fill10(arr, p + 1, args)
|
||||
#define ___bpf_fill12(arr, p, x, args...) arr[p] = x; ___bpf_fill11(arr, p + 1, args)
|
||||
#define ___bpf_fill(arr, args...) \
|
||||
___bpf_apply(___bpf_fill, ___bpf_narg(args))(arr, 0, args)
|
||||
|
||||
/*
|
||||
* BPF_SEQ_PRINTF to wrap bpf_seq_printf to-be-printed values
|
||||
* in a structure.
|
||||
*/
|
||||
#define BPF_SEQ_PRINTF(seq, fmt, args...) \
|
||||
({ \
|
||||
static const char ___fmt[] = fmt; \
|
||||
unsigned long long ___param[___bpf_narg(args)]; \
|
||||
\
|
||||
_Pragma("GCC diagnostic push") \
|
||||
_Pragma("GCC diagnostic ignored \"-Wint-conversion\"") \
|
||||
___bpf_fill(___param, args); \
|
||||
_Pragma("GCC diagnostic pop") \
|
||||
\
|
||||
bpf_seq_printf(seq, ___fmt, sizeof(___fmt), \
|
||||
___param, sizeof(___param)); \
|
||||
})
|
||||
|
||||
/*
|
||||
* BPF_SNPRINTF wraps the bpf_snprintf helper with variadic arguments instead of
|
||||
* an array of u64.
|
||||
*/
|
||||
#define BPF_SNPRINTF(out, out_size, fmt, args...) \
|
||||
({ \
|
||||
static const char ___fmt[] = fmt; \
|
||||
unsigned long long ___param[___bpf_narg(args)]; \
|
||||
\
|
||||
_Pragma("GCC diagnostic push") \
|
||||
_Pragma("GCC diagnostic ignored \"-Wint-conversion\"") \
|
||||
___bpf_fill(___param, args); \
|
||||
_Pragma("GCC diagnostic pop") \
|
||||
\
|
||||
bpf_snprintf(out, out_size, ___fmt, \
|
||||
___param, sizeof(___param)); \
|
||||
})
|
||||
|
||||
#ifdef BPF_NO_GLOBAL_DATA
|
||||
#define BPF_PRINTK_FMT_MOD
|
||||
#else
|
||||
#define BPF_PRINTK_FMT_MOD static const
|
||||
#endif
|
||||
|
||||
#define __bpf_printk(fmt, ...) \
|
||||
({ \
|
||||
BPF_PRINTK_FMT_MOD char ____fmt[] = fmt; \
|
||||
bpf_trace_printk(____fmt, sizeof(____fmt), \
|
||||
##__VA_ARGS__); \
|
||||
})
|
||||
|
||||
/*
|
||||
* __bpf_vprintk wraps the bpf_trace_vprintk helper with variadic arguments
|
||||
* instead of an array of u64.
|
||||
*/
|
||||
#define __bpf_vprintk(fmt, args...) \
|
||||
({ \
|
||||
static const char ___fmt[] = fmt; \
|
||||
unsigned long long ___param[___bpf_narg(args)]; \
|
||||
\
|
||||
_Pragma("GCC diagnostic push") \
|
||||
_Pragma("GCC diagnostic ignored \"-Wint-conversion\"") \
|
||||
___bpf_fill(___param, args); \
|
||||
_Pragma("GCC diagnostic pop") \
|
||||
\
|
||||
bpf_trace_vprintk(___fmt, sizeof(___fmt), \
|
||||
___param, sizeof(___param)); \
|
||||
})
|
||||
|
||||
/* Use __bpf_printk when bpf_printk call has 3 or fewer fmt args
|
||||
* Otherwise use __bpf_vprintk
|
||||
*/
|
||||
#define ___bpf_pick_printk(...) \
|
||||
___bpf_nth(_, ##__VA_ARGS__, __bpf_vprintk, __bpf_vprintk, __bpf_vprintk, \
|
||||
__bpf_vprintk, __bpf_vprintk, __bpf_vprintk, __bpf_vprintk, \
|
||||
__bpf_vprintk, __bpf_vprintk, __bpf_printk /*3*/, __bpf_printk /*2*/,\
|
||||
__bpf_printk /*1*/, __bpf_printk /*0*/)
|
||||
|
||||
/* Helper macro to print out debug messages */
|
||||
#define bpf_printk(fmt, args...) ___bpf_pick_printk(args)(fmt, ##args)
|
||||
|
||||
#endif
|
@ -1,342 +0,0 @@
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
//#include <linux/types.h>
|
||||
|
||||
#include <linux/bpf.h>
|
||||
#include <linux/if_ether.h>
|
||||
//#include <linux/if_packet.h>
|
||||
//#include <linux/if_vlan.h>
|
||||
#include <linux/ip.h>
|
||||
#include <linux/in.h>
|
||||
#include <linux/tcp.h>
|
||||
//#include <linux/udp.h>
|
||||
|
||||
#include <linux/pkt_cls.h>
|
||||
|
||||
#include "bpf_endian.h"
|
||||
#include "bpf_helpers.h"
|
||||
|
||||
#define IP_CSUM_OFF (ETH_HLEN + offsetof(struct iphdr, check))
|
||||
#define IP_DST_OFF (ETH_HLEN + offsetof(struct iphdr, daddr))
|
||||
#define IP_SRC_OFF (ETH_HLEN + offsetof(struct iphdr, saddr))
|
||||
#define IP_PROTO_OFF (ETH_HLEN + offsetof(struct iphdr, protocol))
|
||||
#define TCP_CSUM_OFF (ETH_HLEN + sizeof(struct iphdr) + offsetof(struct tcphdr, check))
|
||||
#define TCP_SRC_OFF (ETH_HLEN + sizeof(struct iphdr) + offsetof(struct tcphdr, source))
|
||||
#define TCP_DST_OFF (ETH_HLEN + sizeof(struct iphdr) + offsetof(struct tcphdr, dest))
|
||||
//#define UDP_CSUM_OFF (ETH_HLEN + sizeof(struct iphdr) + offsetof(struct udphdr, check))
|
||||
//#define UDP_SRC_OFF (ETH_HLEN + sizeof(struct iphdr) + offsetof(struct udphdr, source))
|
||||
//#define UDP_DST_OFF (ETH_HLEN + sizeof(struct iphdr) + offsetof(struct udphdr, dest))
|
||||
#define IS_PSEUDO 0x10
|
||||
|
||||
struct origin_info {
|
||||
__be32 ip;
|
||||
__be16 port;
|
||||
__u16 pad;
|
||||
};
|
||||
|
||||
struct origin_info *origin_info_unused __attribute__((unused));
|
||||
|
||||
struct redir_info {
|
||||
__be32 sip;
|
||||
__be32 dip;
|
||||
__be16 sport;
|
||||
__be16 dport;
|
||||
};
|
||||
|
||||
struct redir_info *redir_info_unused __attribute__((unused));
|
||||
|
||||
struct {
|
||||
__uint(type, BPF_MAP_TYPE_LRU_HASH);
|
||||
__type(key, struct redir_info);
|
||||
__type(value, struct origin_info);
|
||||
__uint(max_entries, 65535);
|
||||
__uint(pinning, LIBBPF_PIN_BY_NAME);
|
||||
} pair_original_dst_map SEC(".maps");
|
||||
|
||||
struct {
|
||||
__uint(type, BPF_MAP_TYPE_ARRAY);
|
||||
__type(key, __u32);
|
||||
__type(value, __u32);
|
||||
__uint(max_entries, 3);
|
||||
__uint(pinning, LIBBPF_PIN_BY_NAME);
|
||||
} redir_params_map SEC(".maps");
|
||||
|
||||
static __always_inline int rewrite_ip(struct __sk_buff *skb, __be32 new_ip, bool is_dest) {
|
||||
int ret, off = 0, flags = IS_PSEUDO;
|
||||
__be32 old_ip;
|
||||
|
||||
if (is_dest)
|
||||
ret = bpf_skb_load_bytes(skb, IP_DST_OFF, &old_ip, 4);
|
||||
else
|
||||
ret = bpf_skb_load_bytes(skb, IP_SRC_OFF, &old_ip, 4);
|
||||
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
off = TCP_CSUM_OFF;
|
||||
// __u8 proto;
|
||||
//
|
||||
// ret = bpf_skb_load_bytes(skb, IP_PROTO_OFF, &proto, 1);
|
||||
// if (ret < 0) {
|
||||
// return BPF_DROP;
|
||||
// }
|
||||
//
|
||||
// switch (proto) {
|
||||
// case IPPROTO_TCP:
|
||||
// off = TCP_CSUM_OFF;
|
||||
// break;
|
||||
//
|
||||
// case IPPROTO_UDP:
|
||||
// off = UDP_CSUM_OFF;
|
||||
// flags |= BPF_F_MARK_MANGLED_0;
|
||||
// break;
|
||||
//
|
||||
// case IPPROTO_ICMPV6:
|
||||
// off = offsetof(struct icmp6hdr, icmp6_cksum);
|
||||
// break;
|
||||
// }
|
||||
//
|
||||
// if (off) {
|
||||
ret = bpf_l4_csum_replace(skb, off, old_ip, new_ip, flags | sizeof(new_ip));
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
// }
|
||||
|
||||
ret = bpf_l3_csum_replace(skb, IP_CSUM_OFF, old_ip, new_ip, sizeof(new_ip));
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (is_dest)
|
||||
ret = bpf_skb_store_bytes(skb, IP_DST_OFF, &new_ip, sizeof(new_ip), 0);
|
||||
else
|
||||
ret = bpf_skb_store_bytes(skb, IP_SRC_OFF, &new_ip, sizeof(new_ip), 0);
|
||||
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static __always_inline int rewrite_port(struct __sk_buff *skb, __be16 new_port, bool is_dest) {
|
||||
int ret, off = 0;
|
||||
__be16 old_port;
|
||||
|
||||
if (is_dest)
|
||||
ret = bpf_skb_load_bytes(skb, TCP_DST_OFF, &old_port, 2);
|
||||
else
|
||||
ret = bpf_skb_load_bytes(skb, TCP_SRC_OFF, &old_port, 2);
|
||||
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
off = TCP_CSUM_OFF;
|
||||
|
||||
ret = bpf_l4_csum_replace(skb, off, old_port, new_port, sizeof(new_port));
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (is_dest)
|
||||
ret = bpf_skb_store_bytes(skb, TCP_DST_OFF, &new_port, sizeof(new_port), 0);
|
||||
else
|
||||
ret = bpf_skb_store_bytes(skb, TCP_SRC_OFF, &new_port, sizeof(new_port), 0);
|
||||
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static __always_inline bool is_lan_ip(__be32 addr) {
|
||||
if (addr == 0xffffffff)
|
||||
return true;
|
||||
|
||||
__u8 fist = (__u8)(addr & 0xff);
|
||||
|
||||
if (fist == 127 || fist == 10)
|
||||
return true;
|
||||
|
||||
__u8 second = (__u8)((addr >> 8) & 0xff);
|
||||
|
||||
if (fist == 172 && second >= 16 && second <= 31)
|
||||
return true;
|
||||
|
||||
if (fist == 192 && second == 168)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
SEC("tc_mihomo_auto_redir_ingress")
|
||||
int tc_redir_ingress_func(struct __sk_buff *skb) {
|
||||
void *data = (void *)(long)skb->data;
|
||||
void *data_end = (void *)(long)skb->data_end;
|
||||
struct ethhdr *eth = data;
|
||||
|
||||
if ((void *)(eth + 1) > data_end)
|
||||
return TC_ACT_OK;
|
||||
|
||||
if (eth->h_proto != bpf_htons(ETH_P_IP))
|
||||
return TC_ACT_OK;
|
||||
|
||||
struct iphdr *iph = (struct iphdr *)(eth + 1);
|
||||
if ((void *)(iph + 1) > data_end)
|
||||
return TC_ACT_OK;
|
||||
|
||||
__u32 key = 0, *route_index, *redir_ip, *redir_port;
|
||||
|
||||
route_index = bpf_map_lookup_elem(&redir_params_map, &key);
|
||||
if (!route_index)
|
||||
return TC_ACT_OK;
|
||||
|
||||
if (iph->protocol == IPPROTO_ICMP && *route_index != 0)
|
||||
return bpf_redirect(*route_index, 0);
|
||||
|
||||
if (iph->protocol != IPPROTO_TCP)
|
||||
return TC_ACT_OK;
|
||||
|
||||
struct tcphdr *tcph = (struct tcphdr *)(iph + 1);
|
||||
if ((void *)(tcph + 1) > data_end)
|
||||
return TC_ACT_SHOT;
|
||||
|
||||
key = 1;
|
||||
redir_ip = bpf_map_lookup_elem(&redir_params_map, &key);
|
||||
if (!redir_ip)
|
||||
return TC_ACT_OK;
|
||||
|
||||
key = 2;
|
||||
redir_port = bpf_map_lookup_elem(&redir_params_map, &key);
|
||||
if (!redir_port)
|
||||
return TC_ACT_OK;
|
||||
|
||||
__be32 new_ip = bpf_htonl(*redir_ip);
|
||||
__be16 new_port = bpf_htonl(*redir_port) >> 16;
|
||||
__be32 old_ip = iph->daddr;
|
||||
__be16 old_port = tcph->dest;
|
||||
|
||||
if (old_ip == new_ip || is_lan_ip(old_ip) || bpf_ntohs(old_port) == 53) {
|
||||
return TC_ACT_OK;
|
||||
}
|
||||
|
||||
struct redir_info p_key = {
|
||||
.sip = iph->saddr,
|
||||
.sport = tcph->source,
|
||||
.dip = new_ip,
|
||||
.dport = new_port,
|
||||
};
|
||||
|
||||
if (tcph->syn && !tcph->ack) {
|
||||
struct origin_info origin = {
|
||||
.ip = old_ip,
|
||||
.port = old_port,
|
||||
};
|
||||
|
||||
bpf_map_update_elem(&pair_original_dst_map, &p_key, &origin, BPF_NOEXIST);
|
||||
|
||||
if (rewrite_ip(skb, new_ip, true) < 0) {
|
||||
return TC_ACT_SHOT;
|
||||
}
|
||||
|
||||
if (rewrite_port(skb, new_port, true) < 0) {
|
||||
return TC_ACT_SHOT;
|
||||
}
|
||||
} else {
|
||||
struct origin_info *origin = bpf_map_lookup_elem(&pair_original_dst_map, &p_key);
|
||||
if (!origin) {
|
||||
return TC_ACT_OK;
|
||||
}
|
||||
|
||||
if (rewrite_ip(skb, new_ip, true) < 0) {
|
||||
return TC_ACT_SHOT;
|
||||
}
|
||||
|
||||
if (rewrite_port(skb, new_port, true) < 0) {
|
||||
return TC_ACT_SHOT;
|
||||
}
|
||||
}
|
||||
|
||||
return TC_ACT_OK;
|
||||
}
|
||||
|
||||
SEC("tc_mihomo_auto_redir_egress")
|
||||
int tc_redir_egress_func(struct __sk_buff *skb) {
|
||||
void *data = (void *)(long)skb->data;
|
||||
void *data_end = (void *)(long)skb->data_end;
|
||||
struct ethhdr *eth = data;
|
||||
|
||||
if ((void *)(eth + 1) > data_end)
|
||||
return TC_ACT_OK;
|
||||
|
||||
if (eth->h_proto != bpf_htons(ETH_P_IP))
|
||||
return TC_ACT_OK;
|
||||
|
||||
__u32 key = 0, *redir_ip, *redir_port; // *mihomo_mark
|
||||
|
||||
// mihomo_mark = bpf_map_lookup_elem(&redir_params_map, &key);
|
||||
// if (mihomo_mark && *mihomo_mark != 0 && *mihomo_mark == skb->mark)
|
||||
// return TC_ACT_OK;
|
||||
|
||||
struct iphdr *iph = (struct iphdr *)(eth + 1);
|
||||
if ((void *)(iph + 1) > data_end)
|
||||
return TC_ACT_OK;
|
||||
|
||||
if (iph->protocol != IPPROTO_TCP)
|
||||
return TC_ACT_OK;
|
||||
|
||||
struct tcphdr *tcph = (struct tcphdr *)(iph + 1);
|
||||
if ((void *)(tcph + 1) > data_end)
|
||||
return TC_ACT_SHOT;
|
||||
|
||||
key = 1;
|
||||
redir_ip = bpf_map_lookup_elem(&redir_params_map, &key);
|
||||
if (!redir_ip)
|
||||
return TC_ACT_OK;
|
||||
|
||||
key = 2;
|
||||
redir_port = bpf_map_lookup_elem(&redir_params_map, &key);
|
||||
if (!redir_port)
|
||||
return TC_ACT_OK;
|
||||
|
||||
__be32 new_ip = bpf_htonl(*redir_ip);
|
||||
__be16 new_port = bpf_htonl(*redir_port) >> 16;
|
||||
__be32 old_ip = iph->saddr;
|
||||
__be16 old_port = tcph->source;
|
||||
|
||||
if (old_ip != new_ip || old_port != new_port) {
|
||||
return TC_ACT_OK;
|
||||
}
|
||||
|
||||
struct redir_info p_key = {
|
||||
.sip = iph->daddr,
|
||||
.sport = tcph->dest,
|
||||
.dip = iph->saddr,
|
||||
.dport = tcph->source,
|
||||
};
|
||||
|
||||
struct origin_info *origin = bpf_map_lookup_elem(&pair_original_dst_map, &p_key);
|
||||
if (!origin) {
|
||||
return TC_ACT_OK;
|
||||
}
|
||||
|
||||
if (tcph->fin && tcph->ack) {
|
||||
bpf_map_delete_elem(&pair_original_dst_map, &p_key);
|
||||
}
|
||||
|
||||
if (rewrite_ip(skb, origin->ip, false) < 0) {
|
||||
return TC_ACT_SHOT;
|
||||
}
|
||||
|
||||
if (rewrite_port(skb, origin->port, false) < 0) {
|
||||
return TC_ACT_SHOT;
|
||||
}
|
||||
|
||||
return TC_ACT_OK;
|
||||
}
|
||||
|
||||
char _license[] SEC("license") = "GPL";
|
@ -1,103 +0,0 @@
|
||||
#include <stdbool.h>
|
||||
#include <linux/bpf.h>
|
||||
#include <linux/if_ether.h>
|
||||
#include <linux/ip.h>
|
||||
#include <linux/in.h>
|
||||
//#include <linux/tcp.h>
|
||||
//#include <linux/udp.h>
|
||||
#include <linux/pkt_cls.h>
|
||||
|
||||
#include "bpf_endian.h"
|
||||
#include "bpf_helpers.h"
|
||||
|
||||
struct {
|
||||
__uint(type, BPF_MAP_TYPE_ARRAY);
|
||||
__type(key, __u32);
|
||||
__type(value, __u32);
|
||||
__uint(max_entries, 2);
|
||||
__uint(pinning, LIBBPF_PIN_BY_NAME);
|
||||
} tc_params_map SEC(".maps");
|
||||
|
||||
static __always_inline bool is_lan_ip(__be32 addr) {
|
||||
if (addr == 0xffffffff)
|
||||
return true;
|
||||
|
||||
__u8 fist = (__u8)(addr & 0xff);
|
||||
|
||||
if (fist == 127 || fist == 10)
|
||||
return true;
|
||||
|
||||
__u8 second = (__u8)((addr >> 8) & 0xff);
|
||||
|
||||
if (fist == 172 && second >= 16 && second <= 31)
|
||||
return true;
|
||||
|
||||
if (fist == 192 && second == 168)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
SEC("tc_mihomo_redirect_to_tun")
|
||||
int tc_tun_func(struct __sk_buff *skb) {
|
||||
void *data = (void *)(long)skb->data;
|
||||
void *data_end = (void *)(long)skb->data_end;
|
||||
struct ethhdr *eth = data;
|
||||
|
||||
if ((void *)(eth + 1) > data_end)
|
||||
return TC_ACT_OK;
|
||||
|
||||
if (eth->h_proto == bpf_htons(ETH_P_ARP))
|
||||
return TC_ACT_OK;
|
||||
|
||||
__u32 key = 0, *mihomo_mark, *tun_ifindex;
|
||||
|
||||
mihomo_mark = bpf_map_lookup_elem(&tc_params_map, &key);
|
||||
if (!mihomo_mark)
|
||||
return TC_ACT_OK;
|
||||
|
||||
if (skb->mark == *mihomo_mark)
|
||||
return TC_ACT_OK;
|
||||
|
||||
if (eth->h_proto == bpf_htons(ETH_P_IP)) {
|
||||
struct iphdr *iph = (struct iphdr *)(eth + 1);
|
||||
if ((void *)(iph + 1) > data_end)
|
||||
return TC_ACT_OK;
|
||||
|
||||
if (iph->protocol == IPPROTO_ICMP)
|
||||
return TC_ACT_OK;
|
||||
|
||||
__be32 daddr = iph->daddr;
|
||||
|
||||
if (is_lan_ip(daddr))
|
||||
return TC_ACT_OK;
|
||||
|
||||
// if (iph->protocol == IPPROTO_TCP) {
|
||||
// struct tcphdr *tcph = (struct tcphdr *)(iph + 1);
|
||||
// if ((void *)(tcph + 1) > data_end)
|
||||
// return TC_ACT_OK;
|
||||
//
|
||||
// __u16 source = bpf_ntohs(tcph->source);
|
||||
// if (source == 22 || source == 80 || source == 443 || source == 8080 || source == 8443 || source == 9090 || (source >= 7890 && source <= 7895))
|
||||
// return TC_ACT_OK;
|
||||
// } else if (iph->protocol == IPPROTO_UDP) {
|
||||
// struct udphdr *udph = (struct udphdr *)(iph + 1);
|
||||
// if ((void *)(udph + 1) > data_end)
|
||||
// return TC_ACT_OK;
|
||||
//
|
||||
// __u16 source = bpf_ntohs(udph->source);
|
||||
// if (source == 53 || (source >= 135 && source <= 139))
|
||||
// return TC_ACT_OK;
|
||||
// }
|
||||
}
|
||||
|
||||
key = 1;
|
||||
tun_ifindex = bpf_map_lookup_elem(&tc_params_map, &key);
|
||||
if (!tun_ifindex)
|
||||
return TC_ACT_OK;
|
||||
|
||||
//return bpf_redirect(*tun_ifindex, BPF_F_INGRESS); // __bpf_rx_skb
|
||||
return bpf_redirect(*tun_ifindex, 0); // __bpf_tx_skb / __dev_xmit_skb
|
||||
}
|
||||
|
||||
char _license[] SEC("license") = "GPL";
|
@ -1,13 +0,0 @@
|
||||
package byteorder
|
||||
|
||||
import (
|
||||
"net"
|
||||
)
|
||||
|
||||
// NetIPv4ToHost32 converts an net.IP to a uint32 in host byte order. ip
|
||||
// must be a IPv4 address, otherwise the function will panic.
|
||||
func NetIPv4ToHost32(ip net.IP) uint32 {
|
||||
ipv4 := ip.To4()
|
||||
_ = ipv4[3] // Assert length of ipv4.
|
||||
return Native.Uint32(ipv4)
|
||||
}
|
@ -1,12 +0,0 @@
|
||||
//go:build arm64be || armbe || mips || mips64 || mips64p32 || ppc64 || s390 || s390x || sparc || sparc64
|
||||
|
||||
package byteorder
|
||||
|
||||
import "encoding/binary"
|
||||
|
||||
var Native binary.ByteOrder = binary.BigEndian
|
||||
|
||||
func HostToNetwork16(u uint16) uint16 { return u }
|
||||
func HostToNetwork32(u uint32) uint32 { return u }
|
||||
func NetworkToHost16(u uint16) uint16 { return u }
|
||||
func NetworkToHost32(u uint32) uint32 { return u }
|
@ -1,15 +0,0 @@
|
||||
//go:build 386 || amd64 || amd64p32 || arm || arm64 || mips64le || mips64p32le || mipsle || ppc64le || riscv64 || loong64
|
||||
|
||||
package byteorder
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"math/bits"
|
||||
)
|
||||
|
||||
var Native binary.ByteOrder = binary.LittleEndian
|
||||
|
||||
func HostToNetwork16(u uint16) uint16 { return bits.ReverseBytes16(u) }
|
||||
func HostToNetwork32(u uint32) uint32 { return bits.ReverseBytes32(u) }
|
||||
func NetworkToHost16(u uint16) uint16 { return bits.ReverseBytes16(u) }
|
||||
func NetworkToHost32(u uint32) uint32 { return bits.ReverseBytes32(u) }
|
@ -1,33 +0,0 @@
|
||||
package ebpf
|
||||
|
||||
import (
|
||||
"net/netip"
|
||||
|
||||
C "github.com/metacubex/mihomo/constant"
|
||||
"github.com/metacubex/mihomo/transport/socks5"
|
||||
)
|
||||
|
||||
type TcEBpfProgram struct {
|
||||
pros []C.EBpf
|
||||
rawNICs []string
|
||||
}
|
||||
|
||||
func (t *TcEBpfProgram) RawNICs() []string {
|
||||
return t.rawNICs
|
||||
}
|
||||
|
||||
func (t *TcEBpfProgram) Close() {
|
||||
for _, p := range t.pros {
|
||||
p.Close()
|
||||
}
|
||||
}
|
||||
|
||||
func (t *TcEBpfProgram) Lookup(srcAddrPort netip.AddrPort) (addr socks5.Addr, err error) {
|
||||
for _, p := range t.pros {
|
||||
addr, err = p.Lookup(srcAddrPort)
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
@ -1,137 +0,0 @@
|
||||
//go:build !android
|
||||
|
||||
package ebpf
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/netip"
|
||||
|
||||
"github.com/metacubex/mihomo/common/cmd"
|
||||
"github.com/metacubex/mihomo/component/dialer"
|
||||
"github.com/metacubex/mihomo/component/ebpf/redir"
|
||||
"github.com/metacubex/mihomo/component/ebpf/tc"
|
||||
C "github.com/metacubex/mihomo/constant"
|
||||
"github.com/sagernet/netlink"
|
||||
)
|
||||
|
||||
func GetAutoDetectInterface() (string, error) {
|
||||
routes, err := netlink.RouteList(nil, netlink.FAMILY_V4)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
for _, route := range routes {
|
||||
if route.Dst == nil {
|
||||
lk, err := netlink.LinkByIndex(route.LinkIndex)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if lk.Type() == "tuntap" {
|
||||
continue
|
||||
}
|
||||
|
||||
return lk.Attrs().Name, nil
|
||||
}
|
||||
}
|
||||
|
||||
return "", fmt.Errorf("interface not found")
|
||||
}
|
||||
|
||||
// NewTcEBpfProgram new redirect to tun ebpf program
|
||||
func NewTcEBpfProgram(ifaceNames []string, tunName string) (*TcEBpfProgram, error) {
|
||||
tunIface, err := netlink.LinkByName(tunName)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("lookup network iface %q: %w", tunName, err)
|
||||
}
|
||||
|
||||
tunIndex := uint32(tunIface.Attrs().Index)
|
||||
|
||||
dialer.DefaultRoutingMark.Store(C.MihomoTrafficMark)
|
||||
|
||||
ifMark := uint32(dialer.DefaultRoutingMark.Load())
|
||||
|
||||
var pros []C.EBpf
|
||||
for _, ifaceName := range ifaceNames {
|
||||
iface, err := netlink.LinkByName(ifaceName)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("lookup network iface %q: %w", ifaceName, err)
|
||||
}
|
||||
if iface.Attrs().OperState != netlink.OperUp {
|
||||
return nil, fmt.Errorf("network iface %q is down", ifaceName)
|
||||
}
|
||||
|
||||
attrs := iface.Attrs()
|
||||
index := attrs.Index
|
||||
|
||||
tcPro := tc.NewEBpfTc(ifaceName, index, ifMark, tunIndex)
|
||||
if err = tcPro.Start(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pros = append(pros, tcPro)
|
||||
}
|
||||
|
||||
systemSetting(ifaceNames...)
|
||||
|
||||
return &TcEBpfProgram{pros: pros, rawNICs: ifaceNames}, nil
|
||||
}
|
||||
|
||||
// NewRedirEBpfProgram new auto redirect ebpf program
|
||||
func NewRedirEBpfProgram(ifaceNames []string, redirPort uint16, defaultRouteInterfaceName string) (*TcEBpfProgram, error) {
|
||||
defaultRouteInterface, err := netlink.LinkByName(defaultRouteInterfaceName)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("lookup network iface %q: %w", defaultRouteInterfaceName, err)
|
||||
}
|
||||
|
||||
defaultRouteIndex := uint32(defaultRouteInterface.Attrs().Index)
|
||||
|
||||
var pros []C.EBpf
|
||||
for _, ifaceName := range ifaceNames {
|
||||
iface, err := netlink.LinkByName(ifaceName)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("lookup network iface %q: %w", ifaceName, err)
|
||||
}
|
||||
|
||||
attrs := iface.Attrs()
|
||||
index := attrs.Index
|
||||
|
||||
addrs, err := netlink.AddrList(iface, netlink.FAMILY_V4)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("lookup network iface %q address: %w", ifaceName, err)
|
||||
}
|
||||
|
||||
if len(addrs) == 0 {
|
||||
return nil, fmt.Errorf("network iface %q does not contain any ipv4 addresses", ifaceName)
|
||||
}
|
||||
|
||||
address, _ := netip.AddrFromSlice(addrs[0].IP)
|
||||
redirAddrPort := netip.AddrPortFrom(address, redirPort)
|
||||
|
||||
redirPro := redir.NewEBpfRedirect(ifaceName, index, 0, defaultRouteIndex, redirAddrPort)
|
||||
if err = redirPro.Start(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pros = append(pros, redirPro)
|
||||
}
|
||||
|
||||
systemSetting(ifaceNames...)
|
||||
|
||||
return &TcEBpfProgram{pros: pros, rawNICs: ifaceNames}, nil
|
||||
}
|
||||
|
||||
func systemSetting(ifaceNames ...string) {
|
||||
_, _ = cmd.ExecCmd("sysctl -w net.ipv4.ip_forward=1")
|
||||
_, _ = cmd.ExecCmd("sysctl -w net.ipv4.conf.all.forwarding=1")
|
||||
_, _ = cmd.ExecCmd("sysctl -w net.ipv4.conf.all.accept_local=1")
|
||||
_, _ = cmd.ExecCmd("sysctl -w net.ipv4.conf.all.accept_redirects=1")
|
||||
_, _ = cmd.ExecCmd("sysctl -w net.ipv4.conf.all.rp_filter=0")
|
||||
|
||||
for _, ifaceName := range ifaceNames {
|
||||
_, _ = cmd.ExecCmd(fmt.Sprintf("sysctl -w net.ipv4.conf.%s.forwarding=1", ifaceName))
|
||||
_, _ = cmd.ExecCmd(fmt.Sprintf("sysctl -w net.ipv4.conf.%s.accept_local=1", ifaceName))
|
||||
_, _ = cmd.ExecCmd(fmt.Sprintf("sysctl -w net.ipv4.conf.%s.accept_redirects=1", ifaceName))
|
||||
_, _ = cmd.ExecCmd(fmt.Sprintf("sysctl -w net.ipv4.conf.%s.rp_filter=0", ifaceName))
|
||||
}
|
||||
}
|
@ -1,21 +0,0 @@
|
||||
//go:build !linux || android
|
||||
|
||||
package ebpf
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// NewTcEBpfProgram new ebpf tc program
|
||||
func NewTcEBpfProgram(_ []string, _ string) (*TcEBpfProgram, error) {
|
||||
return nil, fmt.Errorf("system not supported")
|
||||
}
|
||||
|
||||
// NewRedirEBpfProgram new ebpf redirect program
|
||||
func NewRedirEBpfProgram(_ []string, _ uint16, _ string) (*TcEBpfProgram, error) {
|
||||
return nil, fmt.Errorf("system not supported")
|
||||
}
|
||||
|
||||
func GetAutoDetectInterface() (string, error) {
|
||||
return "", fmt.Errorf("system not supported")
|
||||
}
|
@ -1,216 +0,0 @@
|
||||
//go:build linux
|
||||
|
||||
package redir
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/netip"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/cilium/ebpf"
|
||||
"github.com/cilium/ebpf/rlimit"
|
||||
"github.com/sagernet/netlink"
|
||||
"golang.org/x/sys/unix"
|
||||
|
||||
"github.com/metacubex/mihomo/component/ebpf/byteorder"
|
||||
C "github.com/metacubex/mihomo/constant"
|
||||
"github.com/metacubex/mihomo/transport/socks5"
|
||||
)
|
||||
|
||||
//go:generate go run github.com/cilium/ebpf/cmd/bpf2go -cc $BPF_CLANG -cflags $BPF_CFLAGS bpf ../bpf/redir.c
|
||||
|
||||
const (
|
||||
mapKey1 uint32 = 0
|
||||
mapKey2 uint32 = 1
|
||||
mapKey3 uint32 = 2
|
||||
)
|
||||
|
||||
type EBpfRedirect struct {
|
||||
objs io.Closer
|
||||
originMap *ebpf.Map
|
||||
qdisc netlink.Qdisc
|
||||
filter netlink.Filter
|
||||
filterEgress netlink.Filter
|
||||
|
||||
ifName string
|
||||
ifIndex int
|
||||
ifMark uint32
|
||||
rtIndex uint32
|
||||
redirIp uint32
|
||||
redirPort uint16
|
||||
|
||||
bpfPath string
|
||||
}
|
||||
|
||||
func NewEBpfRedirect(ifName string, ifIndex int, ifMark uint32, routeIndex uint32, redirAddrPort netip.AddrPort) *EBpfRedirect {
|
||||
return &EBpfRedirect{
|
||||
ifName: ifName,
|
||||
ifIndex: ifIndex,
|
||||
ifMark: ifMark,
|
||||
rtIndex: routeIndex,
|
||||
redirIp: binary.BigEndian.Uint32(redirAddrPort.Addr().AsSlice()),
|
||||
redirPort: redirAddrPort.Port(),
|
||||
}
|
||||
}
|
||||
|
||||
func (e *EBpfRedirect) Start() error {
|
||||
if err := rlimit.RemoveMemlock(); err != nil {
|
||||
return fmt.Errorf("remove memory lock: %w", err)
|
||||
}
|
||||
|
||||
e.bpfPath = filepath.Join(C.BpfFSPath, e.ifName)
|
||||
if err := os.MkdirAll(e.bpfPath, os.ModePerm); err != nil {
|
||||
return fmt.Errorf("failed to create bpf fs subpath: %w", err)
|
||||
}
|
||||
|
||||
var objs bpfObjects
|
||||
if err := loadBpfObjects(&objs, &ebpf.CollectionOptions{
|
||||
Maps: ebpf.MapOptions{
|
||||
PinPath: e.bpfPath,
|
||||
},
|
||||
}); err != nil {
|
||||
e.Close()
|
||||
return fmt.Errorf("loading objects: %w", err)
|
||||
}
|
||||
|
||||
e.objs = &objs
|
||||
e.originMap = objs.bpfMaps.PairOriginalDstMap
|
||||
|
||||
if err := objs.bpfMaps.RedirParamsMap.Update(mapKey1, e.rtIndex, ebpf.UpdateAny); err != nil {
|
||||
e.Close()
|
||||
return fmt.Errorf("storing objects: %w", err)
|
||||
}
|
||||
|
||||
if err := objs.bpfMaps.RedirParamsMap.Update(mapKey2, e.redirIp, ebpf.UpdateAny); err != nil {
|
||||
e.Close()
|
||||
return fmt.Errorf("storing objects: %w", err)
|
||||
}
|
||||
|
||||
if err := objs.bpfMaps.RedirParamsMap.Update(mapKey3, uint32(e.redirPort), ebpf.UpdateAny); err != nil {
|
||||
e.Close()
|
||||
return fmt.Errorf("storing objects: %w", err)
|
||||
}
|
||||
|
||||
attrs := netlink.QdiscAttrs{
|
||||
LinkIndex: e.ifIndex,
|
||||
Handle: netlink.MakeHandle(0xffff, 0),
|
||||
Parent: netlink.HANDLE_CLSACT,
|
||||
}
|
||||
|
||||
qdisc := &netlink.GenericQdisc{
|
||||
QdiscAttrs: attrs,
|
||||
QdiscType: "clsact",
|
||||
}
|
||||
|
||||
e.qdisc = qdisc
|
||||
|
||||
if err := netlink.QdiscAdd(qdisc); err != nil {
|
||||
if os.IsExist(err) {
|
||||
_ = netlink.QdiscDel(qdisc)
|
||||
err = netlink.QdiscAdd(qdisc)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
e.Close()
|
||||
return fmt.Errorf("cannot add clsact qdisc: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
filterAttrs := netlink.FilterAttrs{
|
||||
LinkIndex: e.ifIndex,
|
||||
Parent: netlink.HANDLE_MIN_INGRESS,
|
||||
Handle: netlink.MakeHandle(0, 1),
|
||||
Protocol: unix.ETH_P_IP,
|
||||
Priority: 0,
|
||||
}
|
||||
|
||||
filter := &netlink.BpfFilter{
|
||||
FilterAttrs: filterAttrs,
|
||||
Fd: objs.bpfPrograms.TcRedirIngressFunc.FD(),
|
||||
Name: "mihomo-redir-ingress-" + e.ifName,
|
||||
DirectAction: true,
|
||||
}
|
||||
|
||||
if err := netlink.FilterAdd(filter); err != nil {
|
||||
e.Close()
|
||||
return fmt.Errorf("cannot attach ebpf object to filter ingress: %w", err)
|
||||
}
|
||||
|
||||
e.filter = filter
|
||||
|
||||
filterAttrsEgress := netlink.FilterAttrs{
|
||||
LinkIndex: e.ifIndex,
|
||||
Parent: netlink.HANDLE_MIN_EGRESS,
|
||||
Handle: netlink.MakeHandle(0, 1),
|
||||
Protocol: unix.ETH_P_IP,
|
||||
Priority: 0,
|
||||
}
|
||||
|
||||
filterEgress := &netlink.BpfFilter{
|
||||
FilterAttrs: filterAttrsEgress,
|
||||
Fd: objs.bpfPrograms.TcRedirEgressFunc.FD(),
|
||||
Name: "mihomo-redir-egress-" + e.ifName,
|
||||
DirectAction: true,
|
||||
}
|
||||
|
||||
if err := netlink.FilterAdd(filterEgress); err != nil {
|
||||
e.Close()
|
||||
return fmt.Errorf("cannot attach ebpf object to filter egress: %w", err)
|
||||
}
|
||||
|
||||
e.filterEgress = filterEgress
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *EBpfRedirect) Close() {
|
||||
if e.filter != nil {
|
||||
_ = netlink.FilterDel(e.filter)
|
||||
}
|
||||
if e.filterEgress != nil {
|
||||
_ = netlink.FilterDel(e.filterEgress)
|
||||
}
|
||||
if e.qdisc != nil {
|
||||
_ = netlink.QdiscDel(e.qdisc)
|
||||
}
|
||||
if e.objs != nil {
|
||||
_ = e.objs.Close()
|
||||
}
|
||||
_ = os.Remove(filepath.Join(e.bpfPath, "redir_params_map"))
|
||||
_ = os.Remove(filepath.Join(e.bpfPath, "pair_original_dst_map"))
|
||||
}
|
||||
|
||||
func (e *EBpfRedirect) Lookup(srcAddrPort netip.AddrPort) (socks5.Addr, error) {
|
||||
rAddr := srcAddrPort.Addr().Unmap()
|
||||
if rAddr.Is6() {
|
||||
return nil, fmt.Errorf("remote address is ipv6")
|
||||
}
|
||||
|
||||
srcIp := binary.BigEndian.Uint32(rAddr.AsSlice())
|
||||
scrPort := srcAddrPort.Port()
|
||||
|
||||
key := bpfRedirInfo{
|
||||
Sip: byteorder.HostToNetwork32(srcIp),
|
||||
Sport: byteorder.HostToNetwork16(scrPort),
|
||||
Dip: byteorder.HostToNetwork32(e.redirIp),
|
||||
Dport: byteorder.HostToNetwork16(e.redirPort),
|
||||
}
|
||||
|
||||
origin := bpfOriginInfo{}
|
||||
|
||||
err := e.originMap.Lookup(key, &origin)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
addr := make([]byte, net.IPv4len+3)
|
||||
addr[0] = socks5.AtypIPv4
|
||||
|
||||
binary.BigEndian.PutUint32(addr[1:1+net.IPv4len], byteorder.NetworkToHost32(origin.Ip)) // big end
|
||||
binary.BigEndian.PutUint16(addr[1+net.IPv4len:3+net.IPv4len], byteorder.NetworkToHost16(origin.Port)) // big end
|
||||
return addr, nil
|
||||
}
|
@ -1,139 +0,0 @@
|
||||
// Code generated by bpf2go; DO NOT EDIT.
|
||||
//go:build arm64be || armbe || mips || mips64 || mips64p32 || ppc64 || s390 || s390x || sparc || sparc64
|
||||
// +build arm64be armbe mips mips64 mips64p32 ppc64 s390 s390x sparc sparc64
|
||||
|
||||
package redir
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
_ "embed"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/cilium/ebpf"
|
||||
)
|
||||
|
||||
type bpfOriginInfo struct {
|
||||
Ip uint32
|
||||
Port uint16
|
||||
Pad uint16
|
||||
}
|
||||
|
||||
type bpfRedirInfo struct {
|
||||
Sip uint32
|
||||
Dip uint32
|
||||
Sport uint16
|
||||
Dport uint16
|
||||
}
|
||||
|
||||
// loadBpf returns the embedded CollectionSpec for bpf.
|
||||
func loadBpf() (*ebpf.CollectionSpec, error) {
|
||||
reader := bytes.NewReader(_BpfBytes)
|
||||
spec, err := ebpf.LoadCollectionSpecFromReader(reader)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("can't load bpf: %w", err)
|
||||
}
|
||||
|
||||
return spec, err
|
||||
}
|
||||
|
||||
// loadBpfObjects loads bpf and converts it into a struct.
|
||||
//
|
||||
// The following types are suitable as obj argument:
|
||||
//
|
||||
// *bpfObjects
|
||||
// *bpfPrograms
|
||||
// *bpfMaps
|
||||
//
|
||||
// See ebpf.CollectionSpec.LoadAndAssign documentation for details.
|
||||
func loadBpfObjects(obj interface{}, opts *ebpf.CollectionOptions) error {
|
||||
spec, err := loadBpf()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return spec.LoadAndAssign(obj, opts)
|
||||
}
|
||||
|
||||
// bpfSpecs contains maps and programs before they are loaded into the kernel.
|
||||
//
|
||||
// It can be passed ebpf.CollectionSpec.Assign.
|
||||
type bpfSpecs struct {
|
||||
bpfProgramSpecs
|
||||
bpfMapSpecs
|
||||
}
|
||||
|
||||
// bpfSpecs contains programs before they are loaded into the kernel.
|
||||
//
|
||||
// It can be passed ebpf.CollectionSpec.Assign.
|
||||
type bpfProgramSpecs struct {
|
||||
TcRedirEgressFunc *ebpf.ProgramSpec `ebpf:"tc_redir_egress_func"`
|
||||
TcRedirIngressFunc *ebpf.ProgramSpec `ebpf:"tc_redir_ingress_func"`
|
||||
}
|
||||
|
||||
// bpfMapSpecs contains maps before they are loaded into the kernel.
|
||||
//
|
||||
// It can be passed ebpf.CollectionSpec.Assign.
|
||||
type bpfMapSpecs struct {
|
||||
PairOriginalDstMap *ebpf.MapSpec `ebpf:"pair_original_dst_map"`
|
||||
RedirParamsMap *ebpf.MapSpec `ebpf:"redir_params_map"`
|
||||
}
|
||||
|
||||
// bpfObjects contains all objects after they have been loaded into the kernel.
|
||||
//
|
||||
// It can be passed to loadBpfObjects or ebpf.CollectionSpec.LoadAndAssign.
|
||||
type bpfObjects struct {
|
||||
bpfPrograms
|
||||
bpfMaps
|
||||
}
|
||||
|
||||
func (o *bpfObjects) Close() error {
|
||||
return _BpfClose(
|
||||
&o.bpfPrograms,
|
||||
&o.bpfMaps,
|
||||
)
|
||||
}
|
||||
|
||||
// bpfMaps contains all maps after they have been loaded into the kernel.
|
||||
//
|
||||
// It can be passed to loadBpfObjects or ebpf.CollectionSpec.LoadAndAssign.
|
||||
type bpfMaps struct {
|
||||
PairOriginalDstMap *ebpf.Map `ebpf:"pair_original_dst_map"`
|
||||
RedirParamsMap *ebpf.Map `ebpf:"redir_params_map"`
|
||||
}
|
||||
|
||||
func (m *bpfMaps) Close() error {
|
||||
return _BpfClose(
|
||||
m.PairOriginalDstMap,
|
||||
m.RedirParamsMap,
|
||||
)
|
||||
}
|
||||
|
||||
// bpfPrograms contains all programs after they have been loaded into the kernel.
|
||||
//
|
||||
// It can be passed to loadBpfObjects or ebpf.CollectionSpec.LoadAndAssign.
|
||||
type bpfPrograms struct {
|
||||
TcRedirEgressFunc *ebpf.Program `ebpf:"tc_redir_egress_func"`
|
||||
TcRedirIngressFunc *ebpf.Program `ebpf:"tc_redir_ingress_func"`
|
||||
}
|
||||
|
||||
func (p *bpfPrograms) Close() error {
|
||||
return _BpfClose(
|
||||
p.TcRedirEgressFunc,
|
||||
p.TcRedirIngressFunc,
|
||||
)
|
||||
}
|
||||
|
||||
func _BpfClose(closers ...io.Closer) error {
|
||||
for _, closer := range closers {
|
||||
if err := closer.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Do not access this directly.
|
||||
//
|
||||
//go:embed bpf_bpfeb.o
|
||||
var _BpfBytes []byte
|
Binary file not shown.
@ -1,139 +0,0 @@
|
||||
// Code generated by bpf2go; DO NOT EDIT.
|
||||
//go:build 386 || amd64 || amd64p32 || arm || arm64 || mips64le || mips64p32le || mipsle || ppc64le || riscv64 || loong64
|
||||
// +build 386 amd64 amd64p32 arm arm64 mips64le mips64p32le mipsle ppc64le riscv64 loong64
|
||||
|
||||
package redir
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
_ "embed"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/cilium/ebpf"
|
||||
)
|
||||
|
||||
type bpfOriginInfo struct {
|
||||
Ip uint32
|
||||
Port uint16
|
||||
Pad uint16
|
||||
}
|
||||
|
||||
type bpfRedirInfo struct {
|
||||
Sip uint32
|
||||
Dip uint32
|
||||
Sport uint16
|
||||
Dport uint16
|
||||
}
|
||||
|
||||
// loadBpf returns the embedded CollectionSpec for bpf.
|
||||
func loadBpf() (*ebpf.CollectionSpec, error) {
|
||||
reader := bytes.NewReader(_BpfBytes)
|
||||
spec, err := ebpf.LoadCollectionSpecFromReader(reader)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("can't load bpf: %w", err)
|
||||
}
|
||||
|
||||
return spec, err
|
||||
}
|
||||
|
||||
// loadBpfObjects loads bpf and converts it into a struct.
|
||||
//
|
||||
// The following types are suitable as obj argument:
|
||||
//
|
||||
// *bpfObjects
|
||||
// *bpfPrograms
|
||||
// *bpfMaps
|
||||
//
|
||||
// See ebpf.CollectionSpec.LoadAndAssign documentation for details.
|
||||
func loadBpfObjects(obj interface{}, opts *ebpf.CollectionOptions) error {
|
||||
spec, err := loadBpf()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return spec.LoadAndAssign(obj, opts)
|
||||
}
|
||||
|
||||
// bpfSpecs contains maps and programs before they are loaded into the kernel.
|
||||
//
|
||||
// It can be passed ebpf.CollectionSpec.Assign.
|
||||
type bpfSpecs struct {
|
||||
bpfProgramSpecs
|
||||
bpfMapSpecs
|
||||
}
|
||||
|
||||
// bpfSpecs contains programs before they are loaded into the kernel.
|
||||
//
|
||||
// It can be passed ebpf.CollectionSpec.Assign.
|
||||
type bpfProgramSpecs struct {
|
||||
TcRedirEgressFunc *ebpf.ProgramSpec `ebpf:"tc_redir_egress_func"`
|
||||
TcRedirIngressFunc *ebpf.ProgramSpec `ebpf:"tc_redir_ingress_func"`
|
||||
}
|
||||
|
||||
// bpfMapSpecs contains maps before they are loaded into the kernel.
|
||||
//
|
||||
// It can be passed ebpf.CollectionSpec.Assign.
|
||||
type bpfMapSpecs struct {
|
||||
PairOriginalDstMap *ebpf.MapSpec `ebpf:"pair_original_dst_map"`
|
||||
RedirParamsMap *ebpf.MapSpec `ebpf:"redir_params_map"`
|
||||
}
|
||||
|
||||
// bpfObjects contains all objects after they have been loaded into the kernel.
|
||||
//
|
||||
// It can be passed to loadBpfObjects or ebpf.CollectionSpec.LoadAndAssign.
|
||||
type bpfObjects struct {
|
||||
bpfPrograms
|
||||
bpfMaps
|
||||
}
|
||||
|
||||
func (o *bpfObjects) Close() error {
|
||||
return _BpfClose(
|
||||
&o.bpfPrograms,
|
||||
&o.bpfMaps,
|
||||
)
|
||||
}
|
||||
|
||||
// bpfMaps contains all maps after they have been loaded into the kernel.
|
||||
//
|
||||
// It can be passed to loadBpfObjects or ebpf.CollectionSpec.LoadAndAssign.
|
||||
type bpfMaps struct {
|
||||
PairOriginalDstMap *ebpf.Map `ebpf:"pair_original_dst_map"`
|
||||
RedirParamsMap *ebpf.Map `ebpf:"redir_params_map"`
|
||||
}
|
||||
|
||||
func (m *bpfMaps) Close() error {
|
||||
return _BpfClose(
|
||||
m.PairOriginalDstMap,
|
||||
m.RedirParamsMap,
|
||||
)
|
||||
}
|
||||
|
||||
// bpfPrograms contains all programs after they have been loaded into the kernel.
|
||||
//
|
||||
// It can be passed to loadBpfObjects or ebpf.CollectionSpec.LoadAndAssign.
|
||||
type bpfPrograms struct {
|
||||
TcRedirEgressFunc *ebpf.Program `ebpf:"tc_redir_egress_func"`
|
||||
TcRedirIngressFunc *ebpf.Program `ebpf:"tc_redir_ingress_func"`
|
||||
}
|
||||
|
||||
func (p *bpfPrograms) Close() error {
|
||||
return _BpfClose(
|
||||
p.TcRedirEgressFunc,
|
||||
p.TcRedirIngressFunc,
|
||||
)
|
||||
}
|
||||
|
||||
func _BpfClose(closers ...io.Closer) error {
|
||||
for _, closer := range closers {
|
||||
if err := closer.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Do not access this directly.
|
||||
//
|
||||
//go:embed bpf_bpfel.o
|
||||
var _BpfBytes []byte
|
Binary file not shown.
@ -1,120 +0,0 @@
|
||||
// Code generated by bpf2go; DO NOT EDIT.
|
||||
//go:build arm64be || armbe || mips || mips64 || mips64p32 || ppc64 || s390 || s390x || sparc || sparc64
|
||||
// +build arm64be armbe mips mips64 mips64p32 ppc64 s390 s390x sparc sparc64
|
||||
|
||||
package tc
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
_ "embed"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/cilium/ebpf"
|
||||
)
|
||||
|
||||
// loadBpf returns the embedded CollectionSpec for bpf.
|
||||
func loadBpf() (*ebpf.CollectionSpec, error) {
|
||||
reader := bytes.NewReader(_BpfBytes)
|
||||
spec, err := ebpf.LoadCollectionSpecFromReader(reader)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("can't load bpf: %w", err)
|
||||
}
|
||||
|
||||
return spec, err
|
||||
}
|
||||
|
||||
// loadBpfObjects loads bpf and converts it into a struct.
|
||||
//
|
||||
// The following types are suitable as obj argument:
|
||||
//
|
||||
// *bpfObjects
|
||||
// *bpfPrograms
|
||||
// *bpfMaps
|
||||
//
|
||||
// See ebpf.CollectionSpec.LoadAndAssign documentation for details.
|
||||
func loadBpfObjects(obj interface{}, opts *ebpf.CollectionOptions) error {
|
||||
spec, err := loadBpf()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return spec.LoadAndAssign(obj, opts)
|
||||
}
|
||||
|
||||
// bpfSpecs contains maps and programs before they are loaded into the kernel.
|
||||
//
|
||||
// It can be passed ebpf.CollectionSpec.Assign.
|
||||
type bpfSpecs struct {
|
||||
bpfProgramSpecs
|
||||
bpfMapSpecs
|
||||
}
|
||||
|
||||
// bpfSpecs contains programs before they are loaded into the kernel.
|
||||
//
|
||||
// It can be passed ebpf.CollectionSpec.Assign.
|
||||
type bpfProgramSpecs struct {
|
||||
TcTunFunc *ebpf.ProgramSpec `ebpf:"tc_tun_func"`
|
||||
}
|
||||
|
||||
// bpfMapSpecs contains maps before they are loaded into the kernel.
|
||||
//
|
||||
// It can be passed ebpf.CollectionSpec.Assign.
|
||||
type bpfMapSpecs struct {
|
||||
TcParamsMap *ebpf.MapSpec `ebpf:"tc_params_map"`
|
||||
}
|
||||
|
||||
// bpfObjects contains all objects after they have been loaded into the kernel.
|
||||
//
|
||||
// It can be passed to loadBpfObjects or ebpf.CollectionSpec.LoadAndAssign.
|
||||
type bpfObjects struct {
|
||||
bpfPrograms
|
||||
bpfMaps
|
||||
}
|
||||
|
||||
func (o *bpfObjects) Close() error {
|
||||
return _BpfClose(
|
||||
&o.bpfPrograms,
|
||||
&o.bpfMaps,
|
||||
)
|
||||
}
|
||||
|
||||
// bpfMaps contains all maps after they have been loaded into the kernel.
|
||||
//
|
||||
// It can be passed to loadBpfObjects or ebpf.CollectionSpec.LoadAndAssign.
|
||||
type bpfMaps struct {
|
||||
TcParamsMap *ebpf.Map `ebpf:"tc_params_map"`
|
||||
}
|
||||
|
||||
func (m *bpfMaps) Close() error {
|
||||
return _BpfClose(
|
||||
m.TcParamsMap,
|
||||
)
|
||||
}
|
||||
|
||||
// bpfPrograms contains all programs after they have been loaded into the kernel.
|
||||
//
|
||||
// It can be passed to loadBpfObjects or ebpf.CollectionSpec.LoadAndAssign.
|
||||
type bpfPrograms struct {
|
||||
TcTunFunc *ebpf.Program `ebpf:"tc_tun_func"`
|
||||
}
|
||||
|
||||
func (p *bpfPrograms) Close() error {
|
||||
return _BpfClose(
|
||||
p.TcTunFunc,
|
||||
)
|
||||
}
|
||||
|
||||
func _BpfClose(closers ...io.Closer) error {
|
||||
for _, closer := range closers {
|
||||
if err := closer.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Do not access this directly.
|
||||
//
|
||||
//go:embed bpf_bpfeb.o
|
||||
var _BpfBytes []byte
|
Binary file not shown.
@ -1,120 +0,0 @@
|
||||
//
|
||||
//go:build 386 || amd64 || amd64p32 || arm || arm64 || mips64le || mips64p32le || mipsle || ppc64le || riscv64 || loong64
|
||||
// +build 386 amd64 amd64p32 arm arm64 mips64le mips64p32le mipsle ppc64le riscv64 loong64
|
||||
|
||||
package tc
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
_ "embed"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/cilium/ebpf"
|
||||
)
|
||||
|
||||
// loadBpf returns the embedded CollectionSpec for bpf.
|
||||
func loadBpf() (*ebpf.CollectionSpec, error) {
|
||||
reader := bytes.NewReader(_BpfBytes)
|
||||
spec, err := ebpf.LoadCollectionSpecFromReader(reader)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("can't load bpf: %w", err)
|
||||
}
|
||||
|
||||
return spec, err
|
||||
}
|
||||
|
||||
// loadBpfObjects loads bpf and converts it into a struct.
|
||||
//
|
||||
// The following types are suitable as obj argument:
|
||||
//
|
||||
// *bpfObjects
|
||||
// *bpfPrograms
|
||||
// *bpfMaps
|
||||
//
|
||||
// See ebpf.CollectionSpec.LoadAndAssign documentation for details.
|
||||
func loadBpfObjects(obj interface{}, opts *ebpf.CollectionOptions) error {
|
||||
spec, err := loadBpf()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return spec.LoadAndAssign(obj, opts)
|
||||
}
|
||||
|
||||
// bpfSpecs contains maps and programs before they are loaded into the kernel.
|
||||
//
|
||||
// It can be passed ebpf.CollectionSpec.Assign.
|
||||
type bpfSpecs struct {
|
||||
bpfProgramSpecs
|
||||
bpfMapSpecs
|
||||
}
|
||||
|
||||
// bpfSpecs contains programs before they are loaded into the kernel.
|
||||
//
|
||||
// It can be passed ebpf.CollectionSpec.Assign.
|
||||
type bpfProgramSpecs struct {
|
||||
TcTunFunc *ebpf.ProgramSpec `ebpf:"tc_tun_func"`
|
||||
}
|
||||
|
||||
// bpfMapSpecs contains maps before they are loaded into the kernel.
|
||||
//
|
||||
// It can be passed ebpf.CollectionSpec.Assign.
|
||||
type bpfMapSpecs struct {
|
||||
TcParamsMap *ebpf.MapSpec `ebpf:"tc_params_map"`
|
||||
}
|
||||
|
||||
// bpfObjects contains all objects after they have been loaded into the kernel.
|
||||
//
|
||||
// It can be passed to loadBpfObjects or ebpf.CollectionSpec.LoadAndAssign.
|
||||
type bpfObjects struct {
|
||||
bpfPrograms
|
||||
bpfMaps
|
||||
}
|
||||
|
||||
func (o *bpfObjects) Close() error {
|
||||
return _BpfClose(
|
||||
&o.bpfPrograms,
|
||||
&o.bpfMaps,
|
||||
)
|
||||
}
|
||||
|
||||
// bpfMaps contains all maps after they have been loaded into the kernel.
|
||||
//
|
||||
// It can be passed to loadBpfObjects or ebpf.CollectionSpec.LoadAndAssign.
|
||||
type bpfMaps struct {
|
||||
TcParamsMap *ebpf.Map `ebpf:"tc_params_map"`
|
||||
}
|
||||
|
||||
func (m *bpfMaps) Close() error {
|
||||
return _BpfClose(
|
||||
m.TcParamsMap,
|
||||
)
|
||||
}
|
||||
|
||||
// bpfPrograms contains all programs after they have been loaded into the kernel.
|
||||
//
|
||||
// It can be passed to loadBpfObjects or ebpf.CollectionSpec.LoadAndAssign.
|
||||
type bpfPrograms struct {
|
||||
TcTunFunc *ebpf.Program `ebpf:"tc_tun_func"`
|
||||
}
|
||||
|
||||
func (p *bpfPrograms) Close() error {
|
||||
return _BpfClose(
|
||||
p.TcTunFunc,
|
||||
)
|
||||
}
|
||||
|
||||
func _BpfClose(closers ...io.Closer) error {
|
||||
for _, closer := range closers {
|
||||
if err := closer.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Do not access this directly.
|
||||
//
|
||||
//go:embed bpf_bpfel.o
|
||||
var _BpfBytes []byte
|
Binary file not shown.
@ -1,147 +0,0 @@
|
||||
//go:build linux
|
||||
|
||||
package tc
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/netip"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/cilium/ebpf"
|
||||
"github.com/cilium/ebpf/rlimit"
|
||||
"github.com/sagernet/netlink"
|
||||
"golang.org/x/sys/unix"
|
||||
|
||||
C "github.com/metacubex/mihomo/constant"
|
||||
"github.com/metacubex/mihomo/transport/socks5"
|
||||
)
|
||||
|
||||
//go:generate go run github.com/cilium/ebpf/cmd/bpf2go -cc $BPF_CLANG -cflags $BPF_CFLAGS bpf ../bpf/tc.c
|
||||
|
||||
const (
|
||||
mapKey1 uint32 = 0
|
||||
mapKey2 uint32 = 1
|
||||
)
|
||||
|
||||
type EBpfTC struct {
|
||||
objs io.Closer
|
||||
qdisc netlink.Qdisc
|
||||
filter netlink.Filter
|
||||
|
||||
ifName string
|
||||
ifIndex int
|
||||
ifMark uint32
|
||||
tunIfIndex uint32
|
||||
|
||||
bpfPath string
|
||||
}
|
||||
|
||||
func NewEBpfTc(ifName string, ifIndex int, ifMark uint32, tunIfIndex uint32) *EBpfTC {
|
||||
return &EBpfTC{
|
||||
ifName: ifName,
|
||||
ifIndex: ifIndex,
|
||||
ifMark: ifMark,
|
||||
tunIfIndex: tunIfIndex,
|
||||
}
|
||||
}
|
||||
|
||||
func (e *EBpfTC) Start() error {
|
||||
if err := rlimit.RemoveMemlock(); err != nil {
|
||||
return fmt.Errorf("remove memory lock: %w", err)
|
||||
}
|
||||
|
||||
e.bpfPath = filepath.Join(C.BpfFSPath, e.ifName)
|
||||
if err := os.MkdirAll(e.bpfPath, os.ModePerm); err != nil {
|
||||
return fmt.Errorf("failed to create bpf fs subpath: %w", err)
|
||||
}
|
||||
|
||||
var objs bpfObjects
|
||||
if err := loadBpfObjects(&objs, &ebpf.CollectionOptions{
|
||||
Maps: ebpf.MapOptions{
|
||||
PinPath: e.bpfPath,
|
||||
},
|
||||
}); err != nil {
|
||||
e.Close()
|
||||
return fmt.Errorf("loading objects: %w", err)
|
||||
}
|
||||
|
||||
e.objs = &objs
|
||||
|
||||
if err := objs.bpfMaps.TcParamsMap.Update(mapKey1, e.ifMark, ebpf.UpdateAny); err != nil {
|
||||
e.Close()
|
||||
return fmt.Errorf("storing objects: %w", err)
|
||||
}
|
||||
|
||||
if err := objs.bpfMaps.TcParamsMap.Update(mapKey2, e.tunIfIndex, ebpf.UpdateAny); err != nil {
|
||||
e.Close()
|
||||
return fmt.Errorf("storing objects: %w", err)
|
||||
}
|
||||
|
||||
attrs := netlink.QdiscAttrs{
|
||||
LinkIndex: e.ifIndex,
|
||||
Handle: netlink.MakeHandle(0xffff, 0),
|
||||
Parent: netlink.HANDLE_CLSACT,
|
||||
}
|
||||
|
||||
qdisc := &netlink.GenericQdisc{
|
||||
QdiscAttrs: attrs,
|
||||
QdiscType: "clsact",
|
||||
}
|
||||
|
||||
e.qdisc = qdisc
|
||||
|
||||
if err := netlink.QdiscAdd(qdisc); err != nil {
|
||||
if os.IsExist(err) {
|
||||
_ = netlink.QdiscDel(qdisc)
|
||||
err = netlink.QdiscAdd(qdisc)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
e.Close()
|
||||
return fmt.Errorf("cannot add clsact qdisc: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
filterAttrs := netlink.FilterAttrs{
|
||||
LinkIndex: e.ifIndex,
|
||||
Parent: netlink.HANDLE_MIN_EGRESS,
|
||||
Handle: netlink.MakeHandle(0, 1),
|
||||
Protocol: unix.ETH_P_ALL,
|
||||
Priority: 1,
|
||||
}
|
||||
|
||||
filter := &netlink.BpfFilter{
|
||||
FilterAttrs: filterAttrs,
|
||||
Fd: objs.bpfPrograms.TcTunFunc.FD(),
|
||||
Name: "mihomo-tc-" + e.ifName,
|
||||
DirectAction: true,
|
||||
}
|
||||
|
||||
if err := netlink.FilterAdd(filter); err != nil {
|
||||
e.Close()
|
||||
return fmt.Errorf("cannot attach ebpf object to filter: %w", err)
|
||||
}
|
||||
|
||||
e.filter = filter
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *EBpfTC) Close() {
|
||||
if e.filter != nil {
|
||||
_ = netlink.FilterDel(e.filter)
|
||||
}
|
||||
if e.qdisc != nil {
|
||||
_ = netlink.QdiscDel(e.qdisc)
|
||||
}
|
||||
if e.objs != nil {
|
||||
_ = e.objs.Close()
|
||||
}
|
||||
_ = os.Remove(filepath.Join(e.bpfPath, "tc_params_map"))
|
||||
}
|
||||
|
||||
func (e *EBpfTC) Lookup(_ netip.AddrPort) (socks5.Addr, error) {
|
||||
return nil, fmt.Errorf("not supported")
|
||||
}
|
@ -8,7 +8,7 @@ import (
|
||||
|
||||
"github.com/metacubex/mihomo/common/nnip"
|
||||
"github.com/metacubex/mihomo/component/profile/cachefile"
|
||||
"github.com/metacubex/mihomo/component/trie"
|
||||
C "github.com/metacubex/mihomo/constant"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -35,7 +35,7 @@ type Pool struct {
|
||||
offset netip.Addr
|
||||
cycle bool
|
||||
mux sync.Mutex
|
||||
host *trie.DomainTrie[struct{}]
|
||||
host []C.Rule
|
||||
ipnet netip.Prefix
|
||||
store store
|
||||
}
|
||||
@ -66,10 +66,12 @@ func (p *Pool) LookBack(ip netip.Addr) (string, bool) {
|
||||
|
||||
// ShouldSkipped return if domain should be skipped
|
||||
func (p *Pool) ShouldSkipped(domain string) bool {
|
||||
if p.host == nil {
|
||||
return false
|
||||
for _, rule := range p.host {
|
||||
if match, _ := rule.Match(&C.Metadata{Host: domain}); match {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return p.host.Search(domain) != nil
|
||||
return false
|
||||
}
|
||||
|
||||
// Exist returns if given ip exists in fake-ip pool
|
||||
@ -154,7 +156,7 @@ func (p *Pool) restoreState() {
|
||||
|
||||
type Options struct {
|
||||
IPNet netip.Prefix
|
||||
Host *trie.DomainTrie[struct{}]
|
||||
Host []C.Rule
|
||||
|
||||
// Size sets the maximum number of entries in memory
|
||||
// and does not work if Persistence is true
|
||||
|
@ -9,6 +9,8 @@ import (
|
||||
|
||||
"github.com/metacubex/mihomo/component/profile/cachefile"
|
||||
"github.com/metacubex/mihomo/component/trie"
|
||||
C "github.com/metacubex/mihomo/constant"
|
||||
RP "github.com/metacubex/mihomo/rules/provider"
|
||||
|
||||
"github.com/sagernet/bbolt"
|
||||
"github.com/stretchr/testify/assert"
|
||||
@ -150,11 +152,12 @@ func TestPool_CycleUsed(t *testing.T) {
|
||||
func TestPool_Skip(t *testing.T) {
|
||||
ipnet := netip.MustParsePrefix("192.168.0.1/29")
|
||||
tree := trie.New[struct{}]()
|
||||
tree.Insert("example.com", struct{}{})
|
||||
assert.NoError(t, tree.Insert("example.com", struct{}{}))
|
||||
assert.False(t, tree.IsEmpty())
|
||||
pools, tempfile, err := createPools(Options{
|
||||
IPNet: ipnet,
|
||||
Size: 10,
|
||||
Host: tree,
|
||||
Host: []C.Rule{RP.NewDomainSet(tree.NewDomainSet(), "")},
|
||||
})
|
||||
assert.Nil(t, err)
|
||||
defer os.Remove(tempfile)
|
||||
|
@ -7,7 +7,7 @@ import (
|
||||
)
|
||||
|
||||
type AttributeList struct {
|
||||
matcher []AttributeMatcher
|
||||
matcher []BooleanMatcher
|
||||
}
|
||||
|
||||
func (al *AttributeList) Match(domain *router.Domain) bool {
|
||||
@ -23,6 +23,14 @@ func (al *AttributeList) IsEmpty() bool {
|
||||
return len(al.matcher) == 0
|
||||
}
|
||||
|
||||
func (al *AttributeList) String() string {
|
||||
matcher := make([]string, len(al.matcher))
|
||||
for i, match := range al.matcher {
|
||||
matcher[i] = string(match)
|
||||
}
|
||||
return strings.Join(matcher, ",")
|
||||
}
|
||||
|
||||
func parseAttrs(attrs []string) *AttributeList {
|
||||
al := new(AttributeList)
|
||||
for _, attr := range attrs {
|
||||
|
@ -33,12 +33,13 @@ func domainToMatcher(domain *Domain) (strmatcher.Matcher, error) {
|
||||
|
||||
type DomainMatcher interface {
|
||||
ApplyDomain(string) bool
|
||||
Count() int
|
||||
}
|
||||
|
||||
type succinctDomainMatcher struct {
|
||||
set *trie.DomainSet
|
||||
otherMatchers []strmatcher.Matcher
|
||||
not bool
|
||||
count int
|
||||
}
|
||||
|
||||
func (m *succinctDomainMatcher) ApplyDomain(domain string) bool {
|
||||
@ -51,16 +52,17 @@ func (m *succinctDomainMatcher) ApplyDomain(domain string) bool {
|
||||
}
|
||||
}
|
||||
}
|
||||
if m.not {
|
||||
isMatched = !isMatched
|
||||
}
|
||||
return isMatched
|
||||
}
|
||||
|
||||
func NewSuccinctMatcherGroup(domains []*Domain, not bool) (DomainMatcher, error) {
|
||||
func (m *succinctDomainMatcher) Count() int {
|
||||
return m.count
|
||||
}
|
||||
|
||||
func NewSuccinctMatcherGroup(domains []*Domain) (DomainMatcher, error) {
|
||||
t := trie.New[struct{}]()
|
||||
m := &succinctDomainMatcher{
|
||||
not: not,
|
||||
count: len(domains),
|
||||
}
|
||||
for _, d := range domains {
|
||||
switch d.Type {
|
||||
@ -90,10 +92,10 @@ func NewSuccinctMatcherGroup(domains []*Domain, not bool) (DomainMatcher, error)
|
||||
|
||||
type v2rayDomainMatcher struct {
|
||||
matchers strmatcher.IndexMatcher
|
||||
not bool
|
||||
count int
|
||||
}
|
||||
|
||||
func NewMphMatcherGroup(domains []*Domain, not bool) (DomainMatcher, error) {
|
||||
func NewMphMatcherGroup(domains []*Domain) (DomainMatcher, error) {
|
||||
g := strmatcher.NewMphMatcherGroup()
|
||||
for _, d := range domains {
|
||||
matcherType, f := matcherTypeMap[d.Type]
|
||||
@ -108,119 +110,80 @@ func NewMphMatcherGroup(domains []*Domain, not bool) (DomainMatcher, error) {
|
||||
g.Build()
|
||||
return &v2rayDomainMatcher{
|
||||
matchers: g,
|
||||
not: not,
|
||||
count: len(domains),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (m *v2rayDomainMatcher) ApplyDomain(domain string) bool {
|
||||
isMatched := len(m.matchers.Match(strings.ToLower(domain))) > 0
|
||||
if m.not {
|
||||
isMatched = !isMatched
|
||||
}
|
||||
return isMatched
|
||||
return len(m.matchers.Match(strings.ToLower(domain))) > 0
|
||||
}
|
||||
|
||||
type GeoIPMatcher struct {
|
||||
countryCode string
|
||||
reverseMatch bool
|
||||
cidrSet *cidr.IpCidrSet
|
||||
func (m *v2rayDomainMatcher) Count() int {
|
||||
return m.count
|
||||
}
|
||||
|
||||
func (m *GeoIPMatcher) Init(cidrs []*CIDR) error {
|
||||
for _, cidr := range cidrs {
|
||||
addr, ok := netip.AddrFromSlice(cidr.Ip)
|
||||
if !ok {
|
||||
return fmt.Errorf("error when loading GeoIP: invalid IP: %s", cidr.Ip)
|
||||
}
|
||||
err := m.cidrSet.AddIpCidr(netip.PrefixFrom(addr, int(cidr.Prefix)))
|
||||
if err != nil {
|
||||
return fmt.Errorf("error when loading GeoIP: %w", err)
|
||||
}
|
||||
}
|
||||
return m.cidrSet.Merge()
|
||||
type notDomainMatcher struct {
|
||||
DomainMatcher
|
||||
}
|
||||
|
||||
func (m *GeoIPMatcher) SetReverseMatch(isReverseMatch bool) {
|
||||
m.reverseMatch = isReverseMatch
|
||||
func (m notDomainMatcher) ApplyDomain(domain string) bool {
|
||||
return !m.DomainMatcher.ApplyDomain(domain)
|
||||
}
|
||||
|
||||
func NewNotDomainMatcherGroup(matcher DomainMatcher) DomainMatcher {
|
||||
return notDomainMatcher{matcher}
|
||||
}
|
||||
|
||||
type IPMatcher interface {
|
||||
Match(ip netip.Addr) bool
|
||||
Count() int
|
||||
}
|
||||
|
||||
type geoIPMatcher struct {
|
||||
cidrSet *cidr.IpCidrSet
|
||||
count int
|
||||
}
|
||||
|
||||
// Match returns true if the given ip is included by the GeoIP.
|
||||
func (m *GeoIPMatcher) Match(ip netip.Addr) bool {
|
||||
match := m.cidrSet.IsContain(ip)
|
||||
if m.reverseMatch {
|
||||
return !match
|
||||
func (m *geoIPMatcher) Match(ip netip.Addr) bool {
|
||||
return m.cidrSet.IsContain(ip)
|
||||
}
|
||||
|
||||
func (m *geoIPMatcher) Count() int {
|
||||
return m.count
|
||||
}
|
||||
|
||||
func NewGeoIPMatcher(cidrList []*CIDR) (IPMatcher, error) {
|
||||
m := &geoIPMatcher{
|
||||
cidrSet: cidr.NewIpCidrSet(),
|
||||
count: len(cidrList),
|
||||
}
|
||||
return match
|
||||
}
|
||||
|
||||
// GeoIPMatcherContainer is a container for GeoIPMatchers. It keeps unique copies of GeoIPMatcher by country code.
|
||||
type GeoIPMatcherContainer struct {
|
||||
matchers []*GeoIPMatcher
|
||||
}
|
||||
|
||||
// Add adds a new GeoIP set into the container.
|
||||
// If the country code of GeoIP is not empty, GeoIPMatcherContainer will try to find an existing one, instead of adding a new one.
|
||||
func (c *GeoIPMatcherContainer) Add(geoip *GeoIP) (*GeoIPMatcher, error) {
|
||||
if len(geoip.CountryCode) > 0 {
|
||||
for _, m := range c.matchers {
|
||||
if m.countryCode == geoip.CountryCode && m.reverseMatch == geoip.ReverseMatch {
|
||||
return m, nil
|
||||
}
|
||||
for _, cidr := range cidrList {
|
||||
addr, ok := netip.AddrFromSlice(cidr.Ip)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("error when loading GeoIP: invalid IP: %s", cidr.Ip)
|
||||
}
|
||||
err := m.cidrSet.AddIpCidr(netip.PrefixFrom(addr, int(cidr.Prefix)))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error when loading GeoIP: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
m := &GeoIPMatcher{
|
||||
countryCode: geoip.CountryCode,
|
||||
reverseMatch: geoip.ReverseMatch,
|
||||
cidrSet: cidr.NewIpCidrSet(),
|
||||
}
|
||||
if err := m.Init(geoip.Cidr); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(geoip.CountryCode) > 0 {
|
||||
c.matchers = append(c.matchers, m)
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
var globalGeoIPContainer GeoIPMatcherContainer
|
||||
|
||||
type MultiGeoIPMatcher struct {
|
||||
matchers []*GeoIPMatcher
|
||||
}
|
||||
|
||||
func NewGeoIPMatcher(geoip *GeoIP) (*GeoIPMatcher, error) {
|
||||
matcher, err := globalGeoIPContainer.Add(geoip)
|
||||
err := m.cidrSet.Merge()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return matcher, nil
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func (m *MultiGeoIPMatcher) ApplyIp(ip netip.Addr) bool {
|
||||
for _, matcher := range m.matchers {
|
||||
if matcher.Match(ip) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
type notIPMatcher struct {
|
||||
IPMatcher
|
||||
}
|
||||
|
||||
func NewMultiGeoIPMatcher(geoips []*GeoIP) (*MultiGeoIPMatcher, error) {
|
||||
var matchers []*GeoIPMatcher
|
||||
for _, geoip := range geoips {
|
||||
matcher, err := globalGeoIPContainer.Add(geoip)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
matchers = append(matchers, matcher)
|
||||
}
|
||||
|
||||
matcher := &MultiGeoIPMatcher{
|
||||
matchers: matchers,
|
||||
}
|
||||
|
||||
return matcher, nil
|
||||
func (m notIPMatcher) Match(ip netip.Addr) bool {
|
||||
return !m.IPMatcher.Match(ip)
|
||||
}
|
||||
|
||||
func NewNotIpMatcherGroup(matcher IPMatcher) IPMatcher {
|
||||
return notIPMatcher{matcher}
|
||||
}
|
||||
|
@ -5,8 +5,7 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/sync/singleflight"
|
||||
|
||||
"github.com/metacubex/mihomo/common/singleflight"
|
||||
"github.com/metacubex/mihomo/component/geodata/router"
|
||||
C "github.com/metacubex/mihomo/constant"
|
||||
"github.com/metacubex/mihomo/log"
|
||||
@ -71,21 +70,22 @@ func SetSiteMatcher(newMatcher string) {
|
||||
func Verify(name string) error {
|
||||
switch name {
|
||||
case C.GeositeName:
|
||||
_, _, err := LoadGeoSiteMatcher("CN")
|
||||
_, err := LoadGeoSiteMatcher("CN")
|
||||
return err
|
||||
case C.GeoipName:
|
||||
_, _, err := LoadGeoIPMatcher("CN")
|
||||
_, err := LoadGeoIPMatcher("CN")
|
||||
return err
|
||||
default:
|
||||
return fmt.Errorf("not support name")
|
||||
}
|
||||
}
|
||||
|
||||
var loadGeoSiteMatcherSF = singleflight.Group{}
|
||||
var loadGeoSiteMatcherListSF = singleflight.Group[[]*router.Domain]{StoreResult: true}
|
||||
var loadGeoSiteMatcherSF = singleflight.Group[router.DomainMatcher]{StoreResult: true}
|
||||
|
||||
func LoadGeoSiteMatcher(countryCode string) (router.DomainMatcher, int, error) {
|
||||
func LoadGeoSiteMatcher(countryCode string) (router.DomainMatcher, error) {
|
||||
if countryCode == "" {
|
||||
return nil, 0, fmt.Errorf("country code could not be empty")
|
||||
return nil, fmt.Errorf("country code could not be empty")
|
||||
}
|
||||
|
||||
not := false
|
||||
@ -97,73 +97,84 @@ func LoadGeoSiteMatcher(countryCode string) (router.DomainMatcher, int, error) {
|
||||
|
||||
parts := strings.Split(countryCode, "@")
|
||||
if len(parts) == 0 {
|
||||
return nil, 0, errors.New("empty rule")
|
||||
return nil, errors.New("empty rule")
|
||||
}
|
||||
listName := strings.TrimSpace(parts[0])
|
||||
attrVal := parts[1:]
|
||||
attrs := parseAttrs(attrVal)
|
||||
|
||||
if listName == "" {
|
||||
return nil, 0, fmt.Errorf("empty listname in rule: %s", countryCode)
|
||||
return nil, fmt.Errorf("empty listname in rule: %s", countryCode)
|
||||
}
|
||||
|
||||
v, err, shared := loadGeoSiteMatcherSF.Do(listName, func() (interface{}, error) {
|
||||
geoLoader, err := GetGeoDataLoader(geoLoaderName)
|
||||
matcherName := listName
|
||||
if !attrs.IsEmpty() {
|
||||
matcherName += "@" + attrs.String()
|
||||
}
|
||||
matcher, err, shared := loadGeoSiteMatcherSF.Do(matcherName, func() (router.DomainMatcher, error) {
|
||||
log.Infoln("Load GeoSite rule: %s", matcherName)
|
||||
domains, err, shared := loadGeoSiteMatcherListSF.Do(listName, func() ([]*router.Domain, error) {
|
||||
geoLoader, err := GetGeoDataLoader(geoLoaderName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return geoLoader.LoadGeoSite(listName)
|
||||
})
|
||||
if err != nil {
|
||||
if !shared {
|
||||
loadGeoSiteMatcherListSF.Forget(listName) // don't store the error result
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return geoLoader.LoadGeoSite(listName)
|
||||
|
||||
if attrs.IsEmpty() {
|
||||
if strings.Contains(countryCode, "@") {
|
||||
log.Warnln("empty attribute list: %s", countryCode)
|
||||
}
|
||||
} else {
|
||||
filteredDomains := make([]*router.Domain, 0, len(domains))
|
||||
hasAttrMatched := false
|
||||
for _, domain := range domains {
|
||||
if attrs.Match(domain) {
|
||||
hasAttrMatched = true
|
||||
filteredDomains = append(filteredDomains, domain)
|
||||
}
|
||||
}
|
||||
if !hasAttrMatched {
|
||||
log.Warnln("attribute match no rule: geosite: %s", countryCode)
|
||||
}
|
||||
domains = filteredDomains
|
||||
}
|
||||
|
||||
/**
|
||||
linear: linear algorithm
|
||||
matcher, err := router.NewDomainMatcher(domains)
|
||||
mph:minimal perfect hash algorithm
|
||||
*/
|
||||
if geoSiteMatcher == "mph" {
|
||||
return router.NewMphMatcherGroup(domains)
|
||||
} else {
|
||||
return router.NewSuccinctMatcherGroup(domains)
|
||||
}
|
||||
})
|
||||
if err != nil {
|
||||
if !shared {
|
||||
loadGeoSiteMatcherSF.Forget(listName) // don't store the error result
|
||||
loadGeoSiteMatcherSF.Forget(matcherName) // don't store the error result
|
||||
}
|
||||
return nil, 0, err
|
||||
return nil, err
|
||||
}
|
||||
domains := v.([]*router.Domain)
|
||||
|
||||
attrs := parseAttrs(attrVal)
|
||||
if attrs.IsEmpty() {
|
||||
if strings.Contains(countryCode, "@") {
|
||||
log.Warnln("empty attribute list: %s", countryCode)
|
||||
}
|
||||
} else {
|
||||
filteredDomains := make([]*router.Domain, 0, len(domains))
|
||||
hasAttrMatched := false
|
||||
for _, domain := range domains {
|
||||
if attrs.Match(domain) {
|
||||
hasAttrMatched = true
|
||||
filteredDomains = append(filteredDomains, domain)
|
||||
}
|
||||
}
|
||||
if !hasAttrMatched {
|
||||
log.Warnln("attribute match no rule: geosite: %s", countryCode)
|
||||
}
|
||||
domains = filteredDomains
|
||||
if not {
|
||||
matcher = router.NewNotDomainMatcherGroup(matcher)
|
||||
}
|
||||
|
||||
/**
|
||||
linear: linear algorithm
|
||||
matcher, err := router.NewDomainMatcher(domains)
|
||||
mph:minimal perfect hash algorithm
|
||||
*/
|
||||
var matcher router.DomainMatcher
|
||||
if geoSiteMatcher == "mph" {
|
||||
matcher, err = router.NewMphMatcherGroup(domains, not)
|
||||
} else {
|
||||
matcher, err = router.NewSuccinctMatcherGroup(domains, not)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
return matcher, len(domains), nil
|
||||
return matcher, nil
|
||||
}
|
||||
|
||||
var loadGeoIPMatcherSF = singleflight.Group{}
|
||||
var loadGeoIPMatcherSF = singleflight.Group[router.IPMatcher]{StoreResult: true}
|
||||
|
||||
func LoadGeoIPMatcher(country string) (*router.GeoIPMatcher, int, error) {
|
||||
func LoadGeoIPMatcher(country string) (router.IPMatcher, error) {
|
||||
if len(country) == 0 {
|
||||
return nil, 0, fmt.Errorf("country code could not be empty")
|
||||
return nil, fmt.Errorf("country code could not be empty")
|
||||
}
|
||||
|
||||
not := false
|
||||
@ -173,35 +184,33 @@ func LoadGeoIPMatcher(country string) (*router.GeoIPMatcher, int, error) {
|
||||
}
|
||||
country = strings.ToLower(country)
|
||||
|
||||
v, err, shared := loadGeoIPMatcherSF.Do(country, func() (interface{}, error) {
|
||||
matcher, err, shared := loadGeoIPMatcherSF.Do(country, func() (router.IPMatcher, error) {
|
||||
log.Infoln("Load GeoIP rule: %s", country)
|
||||
geoLoader, err := GetGeoDataLoader(geoLoaderName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return geoLoader.LoadGeoIP(country)
|
||||
cidrList, err := geoLoader.LoadGeoIP(country)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return router.NewGeoIPMatcher(cidrList)
|
||||
})
|
||||
if err != nil {
|
||||
if !shared {
|
||||
loadGeoIPMatcherSF.Forget(country) // don't store the error result
|
||||
log.Warnln("Load GeoIP rule: %s", country)
|
||||
}
|
||||
return nil, 0, err
|
||||
return nil, err
|
||||
}
|
||||
records := v.([]*router.CIDR)
|
||||
|
||||
geoIP := &router.GeoIP{
|
||||
CountryCode: country,
|
||||
Cidr: records,
|
||||
ReverseMatch: not,
|
||||
if not {
|
||||
matcher = router.NewNotIpMatcherGroup(matcher)
|
||||
}
|
||||
|
||||
matcher, err := router.NewGeoIPMatcher(geoIP)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
return matcher, len(records), nil
|
||||
return matcher, nil
|
||||
}
|
||||
|
||||
func ClearCache() {
|
||||
loadGeoSiteMatcherSF = singleflight.Group{}
|
||||
loadGeoIPMatcherSF = singleflight.Group{}
|
||||
loadGeoSiteMatcherListSF.Reset()
|
||||
loadGeoSiteMatcherSF.Reset()
|
||||
loadGeoIPMatcherSF.Reset()
|
||||
}
|
||||
|
@ -9,7 +9,6 @@ import (
|
||||
|
||||
"github.com/metacubex/mihomo/common/lru"
|
||||
N "github.com/metacubex/mihomo/common/net"
|
||||
"github.com/metacubex/mihomo/component/trie"
|
||||
C "github.com/metacubex/mihomo/constant"
|
||||
"github.com/metacubex/mihomo/constant/sniffer"
|
||||
"github.com/metacubex/mihomo/log"
|
||||
@ -26,17 +25,26 @@ var Dispatcher *SnifferDispatcher
|
||||
type SnifferDispatcher struct {
|
||||
enable bool
|
||||
sniffers map[sniffer.Sniffer]SnifferConfig
|
||||
forceDomain *trie.DomainSet
|
||||
skipSNI *trie.DomainSet
|
||||
forceDomain []C.Rule
|
||||
skipDomain []C.Rule
|
||||
skipList *lru.LruCache[string, uint8]
|
||||
forceDnsMapping bool
|
||||
parsePureIp bool
|
||||
}
|
||||
|
||||
func (sd *SnifferDispatcher) shouldOverride(metadata *C.Metadata) bool {
|
||||
return (metadata.Host == "" && sd.parsePureIp) ||
|
||||
sd.forceDomain.Has(metadata.Host) ||
|
||||
(metadata.DNSMode == C.DNSMapping && sd.forceDnsMapping)
|
||||
if metadata.Host == "" && sd.parsePureIp {
|
||||
return true
|
||||
}
|
||||
if metadata.DNSMode == C.DNSMapping && sd.forceDnsMapping {
|
||||
return true
|
||||
}
|
||||
for _, rule := range sd.forceDomain {
|
||||
if ok, _ := rule.Match(&C.Metadata{Host: metadata.Host}); ok {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (sd *SnifferDispatcher) UDPSniff(packet C.PacketAdapter) bool {
|
||||
@ -94,9 +102,11 @@ func (sd *SnifferDispatcher) TCPSniff(conn *N.BufferedConn, metadata *C.Metadata
|
||||
log.Debugln("[Sniffer] All sniffing sniff failed with from [%s:%d] to [%s:%d]", metadata.SrcIP, metadata.SrcPort, metadata.String(), metadata.DstPort)
|
||||
return false
|
||||
} else {
|
||||
if sd.skipSNI.Has(host) {
|
||||
log.Debugln("[Sniffer] Skip sni[%s]", host)
|
||||
return false
|
||||
for _, rule := range sd.skipDomain {
|
||||
if ok, _ := rule.Match(&C.Metadata{Host: host}); ok {
|
||||
log.Debugln("[Sniffer] Skip sni[%s]", host)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
sd.skipList.Delete(dst)
|
||||
@ -187,12 +197,12 @@ func NewCloseSnifferDispatcher() (*SnifferDispatcher, error) {
|
||||
}
|
||||
|
||||
func NewSnifferDispatcher(snifferConfig map[sniffer.Type]SnifferConfig,
|
||||
forceDomain *trie.DomainSet, skipSNI *trie.DomainSet,
|
||||
forceDomain []C.Rule, skipDomain []C.Rule,
|
||||
forceDnsMapping bool, parsePureIp bool) (*SnifferDispatcher, error) {
|
||||
dispatcher := SnifferDispatcher{
|
||||
enable: true,
|
||||
forceDomain: forceDomain,
|
||||
skipSNI: skipSNI,
|
||||
skipDomain: skipDomain,
|
||||
skipList: lru.New(lru.WithSize[string, uint8](128), lru.WithAge[string, uint8](600)),
|
||||
forceDnsMapping: forceDnsMapping,
|
||||
parsePureIp: parsePureIp,
|
||||
|
@ -126,7 +126,7 @@ func (t *DomainTrie[T]) Optimize() {
|
||||
func (t *DomainTrie[T]) Foreach(fn func(domain string, data T) bool) {
|
||||
for key, data := range t.root.getChildren() {
|
||||
recursion([]string{key}, data, fn)
|
||||
if data != nil && data.inited {
|
||||
if !data.isEmpty() {
|
||||
if !fn(joinDomain([]string{key}), data.data) {
|
||||
return
|
||||
}
|
||||
@ -134,10 +134,17 @@ func (t *DomainTrie[T]) Foreach(fn func(domain string, data T) bool) {
|
||||
}
|
||||
}
|
||||
|
||||
func (t *DomainTrie[T]) IsEmpty() bool {
|
||||
if t == nil || t.root == nil {
|
||||
return true
|
||||
}
|
||||
return len(t.root.getChildren()) == 0
|
||||
}
|
||||
|
||||
func recursion[T any](items []string, node *Node[T], fn func(domain string, data T) bool) bool {
|
||||
for key, data := range node.getChildren() {
|
||||
newItems := append([]string{key}, items...)
|
||||
if data != nil && data.inited {
|
||||
if !data.isEmpty() {
|
||||
domain := joinDomain(newItems)
|
||||
if domain[0] == domainStepByte {
|
||||
domain = complexWildcard + domain
|
||||
|
@ -40,6 +40,7 @@ func TestDomainSet(t *testing.T) {
|
||||
for _, domain := range domainSet {
|
||||
assert.NoError(t, tree.Insert(domain, struct{}{}))
|
||||
}
|
||||
assert.False(t, tree.IsEmpty())
|
||||
set := tree.NewDomainSet()
|
||||
assert.NotNil(t, set)
|
||||
assert.True(t, set.Has("test.cn"))
|
||||
@ -68,6 +69,7 @@ func TestDomainSetComplexWildcard(t *testing.T) {
|
||||
for _, domain := range domainSet {
|
||||
assert.NoError(t, tree.Insert(domain, struct{}{}))
|
||||
}
|
||||
assert.False(t, tree.IsEmpty())
|
||||
set := tree.NewDomainSet()
|
||||
assert.NotNil(t, set)
|
||||
assert.False(t, set.Has("google.com"))
|
||||
@ -90,6 +92,7 @@ func TestDomainSetWildcard(t *testing.T) {
|
||||
for _, domain := range domainSet {
|
||||
assert.NoError(t, tree.Insert(domain, struct{}{}))
|
||||
}
|
||||
assert.False(t, tree.IsEmpty())
|
||||
set := tree.NewDomainSet()
|
||||
assert.NotNil(t, set)
|
||||
assert.True(t, set.Has("www.baidu.com"))
|
||||
|
@ -230,7 +230,7 @@ func clean() {
|
||||
|
||||
// MaxPackageFileSize is a maximum package file length in bytes. The largest
|
||||
// package whose size is limited by this constant currently has the size of
|
||||
// approximately 9 MiB.
|
||||
// approximately 32 MiB.
|
||||
const MaxPackageFileSize = 32 * 1024 * 1024
|
||||
|
||||
// Download package file and save it to disk
|
||||
|
@ -137,7 +137,7 @@ func getUpdateTime() (err error, time time.Time) {
|
||||
return nil, fileInfo.ModTime()
|
||||
}
|
||||
|
||||
func RegisterGeoUpdater(onSuccess func()) {
|
||||
func RegisterGeoUpdater() {
|
||||
if C.GeoUpdateInterval <= 0 {
|
||||
log.Errorln("[GEO] Invalid update interval: %d", C.GeoUpdateInterval)
|
||||
return
|
||||
@ -159,8 +159,6 @@ func RegisterGeoUpdater(onSuccess func()) {
|
||||
if err := UpdateGeoDatabases(); err != nil {
|
||||
log.Errorln("[GEO] Failed to update GEO database: %s", err.Error())
|
||||
return
|
||||
} else {
|
||||
onSuccess()
|
||||
}
|
||||
}
|
||||
|
||||
@ -168,8 +166,6 @@ func RegisterGeoUpdater(onSuccess func()) {
|
||||
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()
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
@ -29,11 +29,6 @@ func UpdateUI() error {
|
||||
xdMutex.Lock()
|
||||
defer xdMutex.Unlock()
|
||||
|
||||
err := prepare_ui()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
data, err := downloadForBytes(ExternalUIURL)
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't download file: %w", err)
|
||||
@ -64,7 +59,7 @@ func UpdateUI() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func prepare_ui() error {
|
||||
func PrepareUIPath() error {
|
||||
if ExternalUIPath == "" || ExternalUIURL == "" {
|
||||
return ErrIncompleteConf
|
||||
}
|
||||
@ -79,7 +74,6 @@ func prepare_ui() error {
|
||||
} else {
|
||||
ExternalUIFolder = ExternalUIPath
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
387
config/config.go
387
config/config.go
@ -20,9 +20,9 @@ import (
|
||||
N "github.com/metacubex/mihomo/common/net"
|
||||
"github.com/metacubex/mihomo/common/utils"
|
||||
"github.com/metacubex/mihomo/component/auth"
|
||||
"github.com/metacubex/mihomo/component/cidr"
|
||||
"github.com/metacubex/mihomo/component/fakeip"
|
||||
"github.com/metacubex/mihomo/component/geodata"
|
||||
"github.com/metacubex/mihomo/component/geodata/router"
|
||||
P "github.com/metacubex/mihomo/component/process"
|
||||
"github.com/metacubex/mihomo/component/resolver"
|
||||
SNIFF "github.com/metacubex/mihomo/component/sniffer"
|
||||
@ -38,6 +38,7 @@ import (
|
||||
LC "github.com/metacubex/mihomo/listener/config"
|
||||
"github.com/metacubex/mihomo/log"
|
||||
R "github.com/metacubex/mihomo/rules"
|
||||
RC "github.com/metacubex/mihomo/rules/common"
|
||||
RP "github.com/metacubex/mihomo/rules/provider"
|
||||
T "github.com/metacubex/mihomo/tunnel"
|
||||
|
||||
@ -65,7 +66,6 @@ type General struct {
|
||||
TCPConcurrent bool `json:"tcp-concurrent"`
|
||||
FindProcessMode P.FindProcessMode `json:"find-process-mode"`
|
||||
Sniffing bool `json:"sniffing"`
|
||||
EBpf EBpf `json:"-"`
|
||||
GlobalClientFingerprint string `json:"global-client-fingerprint"`
|
||||
GlobalUA string `json:"global-ua"`
|
||||
}
|
||||
@ -113,33 +113,25 @@ type NTP struct {
|
||||
|
||||
// DNS config
|
||||
type DNS struct {
|
||||
Enable bool `yaml:"enable"`
|
||||
PreferH3 bool `yaml:"prefer-h3"`
|
||||
IPv6 bool `yaml:"ipv6"`
|
||||
IPv6Timeout uint `yaml:"ipv6-timeout"`
|
||||
UseSystemHosts bool `yaml:"use-system-hosts"`
|
||||
NameServer []dns.NameServer `yaml:"nameserver"`
|
||||
Fallback []dns.NameServer `yaml:"fallback"`
|
||||
FallbackFilter FallbackFilter `yaml:"fallback-filter"`
|
||||
Listen string `yaml:"listen"`
|
||||
EnhancedMode C.DNSMode `yaml:"enhanced-mode"`
|
||||
DefaultNameserver []dns.NameServer `yaml:"default-nameserver"`
|
||||
CacheAlgorithm string `yaml:"cache-algorithm"`
|
||||
Enable bool
|
||||
PreferH3 bool
|
||||
IPv6 bool
|
||||
IPv6Timeout uint
|
||||
UseSystemHosts bool
|
||||
NameServer []dns.NameServer
|
||||
Fallback []dns.NameServer
|
||||
FallbackIPFilter []C.Rule
|
||||
FallbackDomainFilter []C.Rule
|
||||
Listen string
|
||||
EnhancedMode C.DNSMode
|
||||
DefaultNameserver []dns.NameServer
|
||||
CacheAlgorithm string
|
||||
FakeIPRange *fakeip.Pool
|
||||
Hosts *trie.DomainTrie[resolver.HostValue]
|
||||
NameServerPolicy *orderedmap.OrderedMap[string, []dns.NameServer]
|
||||
NameServerPolicy []dns.Policy
|
||||
ProxyServerNameserver []dns.NameServer
|
||||
}
|
||||
|
||||
// FallbackFilter config
|
||||
type FallbackFilter struct {
|
||||
GeoIP bool `yaml:"geoip"`
|
||||
GeoIPCode string `yaml:"geoip-code"`
|
||||
IPCIDR []netip.Prefix `yaml:"ipcidr"`
|
||||
Domain []string `yaml:"domain"`
|
||||
GeoSite []router.DomainMatcher `yaml:"geosite"`
|
||||
}
|
||||
|
||||
// Profile config
|
||||
type Profile struct {
|
||||
StoreSelected bool `yaml:"store-selected"`
|
||||
@ -163,8 +155,8 @@ type IPTables struct {
|
||||
type Sniffer struct {
|
||||
Enable bool
|
||||
Sniffers map[snifferTypes.Type]SNIFF.SnifferConfig
|
||||
ForceDomain *trie.DomainSet
|
||||
SkipDomain *trie.DomainSet
|
||||
ForceDomain []C.Rule
|
||||
SkipDomain []C.Rule
|
||||
ForceDnsMapping bool
|
||||
ParsePureIp bool
|
||||
}
|
||||
@ -338,7 +330,9 @@ type RawConfig struct {
|
||||
FindProcessMode P.FindProcessMode `yaml:"find-process-mode" json:"find-process-mode"`
|
||||
GlobalClientFingerprint string `yaml:"global-client-fingerprint"`
|
||||
GlobalUA string `yaml:"global-ua"`
|
||||
KeepAliveIdle int `yaml:"keep-alive-idle"`
|
||||
KeepAliveInterval int `yaml:"keep-alive-interval"`
|
||||
DisableKeepAlive bool `yaml:"disable-keep-alive"`
|
||||
|
||||
Sniffer RawSniffer `yaml:"sniffer" json:"sniffer"`
|
||||
ProxyProvider map[string]map[string]any `yaml:"proxy-providers"`
|
||||
@ -348,7 +342,6 @@ type RawConfig struct {
|
||||
DNS RawDNS `yaml:"dns" json:"dns"`
|
||||
Tun RawTun `yaml:"tun"`
|
||||
TuicServer RawTuicServer `yaml:"tuic-server"`
|
||||
EBpf EBpf `yaml:"ebpf"`
|
||||
IPTables IPTables `yaml:"iptables"`
|
||||
Experimental Experimental `yaml:"experimental"`
|
||||
Profile Profile `yaml:"profile"`
|
||||
@ -387,12 +380,6 @@ type RawSniffingConfig struct {
|
||||
OverrideDest *bool `yaml:"override-destination" json:"override-destination"`
|
||||
}
|
||||
|
||||
// EBpf config
|
||||
type EBpf struct {
|
||||
RedirectToTun []string `yaml:"redirect-to-tun" json:"redirect-to-tun"`
|
||||
AutoRedir []string `yaml:"auto-redir" json:"auto-redir"`
|
||||
}
|
||||
|
||||
var (
|
||||
GroupsList = list.New()
|
||||
ProxiesList = list.New()
|
||||
@ -453,10 +440,6 @@ func UnmarshalRawConfig(buf []byte) (*RawConfig, error) {
|
||||
ALPN: []string{"h3"},
|
||||
MaxUdpRelayPacketSize: 1500,
|
||||
},
|
||||
EBpf: EBpf{
|
||||
RedirectToTun: []string{},
|
||||
AutoRedir: []string{},
|
||||
},
|
||||
IPTables: IPTables{
|
||||
Enable: false,
|
||||
InboundInterface: "lo",
|
||||
@ -624,7 +607,7 @@ func ParseRawConfig(rawCfg *RawConfig) (*Config, error) {
|
||||
}
|
||||
}
|
||||
|
||||
config.Sniffer, err = parseSniffer(rawCfg.Sniffer)
|
||||
config.Sniffer, err = parseSniffer(rawCfg.Sniffer, ruleProviders)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -649,9 +632,14 @@ func parseGeneral(cfg *RawConfig) (*General, error) {
|
||||
C.ASNUrl = cfg.GeoXUrl.ASN
|
||||
C.GeodataMode = cfg.GeodataMode
|
||||
C.UA = cfg.GlobalUA
|
||||
|
||||
if cfg.KeepAliveIdle != 0 {
|
||||
N.KeepAliveIdle = time.Duration(cfg.KeepAliveIdle) * time.Second
|
||||
}
|
||||
if cfg.KeepAliveInterval != 0 {
|
||||
N.KeepAliveInterval = time.Duration(cfg.KeepAliveInterval) * time.Second
|
||||
}
|
||||
N.DisableKeepAlive = cfg.DisableKeepAlive
|
||||
|
||||
updater.ExternalUIPath = cfg.ExternalUI
|
||||
// checkout externalUI exist
|
||||
@ -677,6 +665,11 @@ func parseGeneral(cfg *RawConfig) (*General, error) {
|
||||
updater.ExternalUIURL = cfg.ExternalUIURL
|
||||
}
|
||||
|
||||
err := updater.PrepareUIPath()
|
||||
if err != nil {
|
||||
log.Errorln("PrepareUIPath error: %s", err)
|
||||
}
|
||||
|
||||
return &General{
|
||||
Inbound: Inbound{
|
||||
Port: cfg.Port,
|
||||
@ -715,7 +708,6 @@ func parseGeneral(cfg *RawConfig) (*General, error) {
|
||||
GeodataLoader: cfg.GeodataLoader,
|
||||
TCPConcurrent: cfg.TCPConcurrent,
|
||||
FindProcessMode: cfg.FindProcessMode,
|
||||
EBpf: cfg.EBpf,
|
||||
GlobalClientFingerprint: cfg.GlobalClientFingerprint,
|
||||
GlobalUA: cfg.GlobalUA,
|
||||
}, nil
|
||||
@ -1192,49 +1184,13 @@ func parsePureDNSServer(server string) string {
|
||||
}
|
||||
}
|
||||
|
||||
func parseNameServerPolicy(nsPolicy *orderedmap.OrderedMap[string, any], ruleProviders map[string]providerTypes.RuleProvider, respectRules bool, preferH3 bool) (*orderedmap.OrderedMap[string, []dns.NameServer], error) {
|
||||
policy := orderedmap.New[string, []dns.NameServer]()
|
||||
updatedPolicy := orderedmap.New[string, any]()
|
||||
func parseNameServerPolicy(nsPolicy *orderedmap.OrderedMap[string, any], rules []C.Rule, ruleProviders map[string]providerTypes.RuleProvider, respectRules bool, preferH3 bool) ([]dns.Policy, error) {
|
||||
var policy []dns.Policy
|
||||
re := regexp.MustCompile(`[a-zA-Z0-9\-]+\.[a-zA-Z]{2,}(\.[a-zA-Z]{2,})?`)
|
||||
|
||||
for pair := nsPolicy.Oldest(); pair != nil; pair = pair.Next() {
|
||||
k, v := pair.Key, pair.Value
|
||||
if strings.Contains(strings.ToLower(k), ",") {
|
||||
if strings.Contains(k, "geosite:") {
|
||||
subkeys := strings.Split(k, ":")
|
||||
subkeys = subkeys[1:]
|
||||
subkeys = strings.Split(subkeys[0], ",")
|
||||
for _, subkey := range subkeys {
|
||||
newKey := "geosite:" + subkey
|
||||
updatedPolicy.Store(newKey, v)
|
||||
}
|
||||
} else if strings.Contains(strings.ToLower(k), "rule-set:") {
|
||||
subkeys := strings.Split(k, ":")
|
||||
subkeys = subkeys[1:]
|
||||
subkeys = strings.Split(subkeys[0], ",")
|
||||
for _, subkey := range subkeys {
|
||||
newKey := "rule-set:" + subkey
|
||||
updatedPolicy.Store(newKey, v)
|
||||
}
|
||||
} else if re.MatchString(k) {
|
||||
subkeys := strings.Split(k, ",")
|
||||
for _, subkey := range subkeys {
|
||||
updatedPolicy.Store(subkey, v)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if strings.Contains(strings.ToLower(k), "geosite:") {
|
||||
updatedPolicy.Store("geosite:"+k[8:], v)
|
||||
} else if strings.Contains(strings.ToLower(k), "rule-set:") {
|
||||
updatedPolicy.Store("rule-set:"+k[9:], v)
|
||||
}
|
||||
updatedPolicy.Store(k, v)
|
||||
}
|
||||
}
|
||||
|
||||
for pair := updatedPolicy.Oldest(); pair != nil; pair = pair.Next() {
|
||||
domain, server := pair.Key, pair.Value
|
||||
servers, err := utils.ToStringSlice(server)
|
||||
servers, err := utils.ToStringSlice(v)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -1242,75 +1198,65 @@ func parseNameServerPolicy(nsPolicy *orderedmap.OrderedMap[string, any], rulePro
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if _, valid := trie.ValidAndSplitDomain(domain); !valid {
|
||||
return nil, fmt.Errorf("DNS ResoverRule invalid domain: %s", domain)
|
||||
if strings.Contains(strings.ToLower(k), ",") {
|
||||
if strings.Contains(k, "geosite:") {
|
||||
subkeys := strings.Split(k, ":")
|
||||
subkeys = subkeys[1:]
|
||||
subkeys = strings.Split(subkeys[0], ",")
|
||||
for _, subkey := range subkeys {
|
||||
newKey := "geosite:" + subkey
|
||||
policy = append(policy, dns.Policy{Domain: newKey, NameServers: nameservers})
|
||||
}
|
||||
} else if strings.Contains(strings.ToLower(k), "rule-set:") {
|
||||
subkeys := strings.Split(k, ":")
|
||||
subkeys = subkeys[1:]
|
||||
subkeys = strings.Split(subkeys[0], ",")
|
||||
for _, subkey := range subkeys {
|
||||
newKey := "rule-set:" + subkey
|
||||
policy = append(policy, dns.Policy{Domain: newKey, NameServers: nameservers})
|
||||
}
|
||||
} else if re.MatchString(k) {
|
||||
subkeys := strings.Split(k, ",")
|
||||
for _, subkey := range subkeys {
|
||||
policy = append(policy, dns.Policy{Domain: subkey, NameServers: nameservers})
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if strings.Contains(strings.ToLower(k), "geosite:") {
|
||||
policy = append(policy, dns.Policy{Domain: "geosite:" + k[8:], NameServers: nameservers})
|
||||
} else if strings.Contains(strings.ToLower(k), "rule-set:") {
|
||||
policy = append(policy, dns.Policy{Domain: "rule-set:" + k[9:], NameServers: nameservers})
|
||||
} else {
|
||||
policy = append(policy, dns.Policy{Domain: k, NameServers: nameservers})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for idx, p := range policy {
|
||||
domain, nameservers := p.Domain, p.NameServers
|
||||
|
||||
if strings.HasPrefix(domain, "rule-set:") {
|
||||
domainSetName := domain[9:]
|
||||
if provider, ok := ruleProviders[domainSetName]; !ok {
|
||||
return nil, fmt.Errorf("not found rule-set: %s", domainSetName)
|
||||
} else {
|
||||
switch provider.Behavior() {
|
||||
case providerTypes.IPCIDR:
|
||||
return nil, fmt.Errorf("rule provider type error, except domain,actual %s", provider.Behavior())
|
||||
case providerTypes.Classical:
|
||||
log.Warnln("%s provider is %s, only matching it contain domain rule", provider.Name(), provider.Behavior())
|
||||
}
|
||||
}
|
||||
}
|
||||
policy.Store(domain, nameservers)
|
||||
}
|
||||
|
||||
return policy, nil
|
||||
}
|
||||
|
||||
func parseFallbackIPCIDR(ips []string) ([]netip.Prefix, error) {
|
||||
var ipNets []netip.Prefix
|
||||
|
||||
for idx, ip := range ips {
|
||||
ipnet, err := netip.ParsePrefix(ip)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("DNS FallbackIP[%d] format error: %s", idx, err.Error())
|
||||
}
|
||||
ipNets = append(ipNets, ipnet)
|
||||
}
|
||||
|
||||
return ipNets, nil
|
||||
}
|
||||
|
||||
func parseFallbackGeoSite(countries []string, rules []C.Rule) ([]router.DomainMatcher, error) {
|
||||
var sites []router.DomainMatcher
|
||||
if len(countries) > 0 {
|
||||
if err := geodata.InitGeoSite(); err != nil {
|
||||
return nil, fmt.Errorf("can't initial GeoSite: %s", err)
|
||||
}
|
||||
log.Warnln("replace fallback-filter.geosite with nameserver-policy, it will be removed in the future")
|
||||
}
|
||||
|
||||
for _, country := range countries {
|
||||
found := false
|
||||
for _, rule := range rules {
|
||||
if rule.RuleType() == C.GEOSITE {
|
||||
if strings.EqualFold(country, rule.Payload()) {
|
||||
found = true
|
||||
sites = append(sites, rule.(C.RuleGeoSite).GetDomainMatcher())
|
||||
log.Infoln("Start initial GeoSite dns fallback filter from rule `%s`", country)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
matcher, recordsCount, err := geodata.LoadGeoSiteMatcher(country)
|
||||
rule, err := parseDomainRuleSet(domainSetName, "dns.nameserver-policy", ruleProviders)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sites = append(sites, matcher)
|
||||
|
||||
log.Infoln("Start initial GeoSite dns fallback filter `%s`, records: %d", country, recordsCount)
|
||||
policy[idx] = dns.Policy{Rule: rule, NameServers: nameservers}
|
||||
} else if strings.HasPrefix(domain, "geosite:") {
|
||||
country := domain[8:]
|
||||
rule, err := RC.NewGEOSITE(country, "dns.nameserver-policy")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
policy[idx] = dns.Policy{Rule: rule, NameServers: nameservers}
|
||||
} else {
|
||||
if _, valid := trie.ValidAndSplitDomain(domain); !valid {
|
||||
return nil, fmt.Errorf("DNS ResoverRule invalid domain: %s", domain)
|
||||
}
|
||||
}
|
||||
}
|
||||
return sites, nil
|
||||
|
||||
return policy, nil
|
||||
}
|
||||
|
||||
func paresNTP(rawCfg *RawConfig) *NTP {
|
||||
@ -1344,10 +1290,6 @@ func parseDNS(rawCfg *RawConfig, hosts *trie.DomainTrie[resolver.HostValue], rul
|
||||
IPv6: cfg.IPv6,
|
||||
UseSystemHosts: cfg.UseSystemHosts,
|
||||
EnhancedMode: cfg.EnhancedMode,
|
||||
FallbackFilter: FallbackFilter{
|
||||
IPCIDR: []netip.Prefix{},
|
||||
GeoSite: []router.DomainMatcher{},
|
||||
},
|
||||
}
|
||||
var err error
|
||||
if dnsCfg.NameServer, err = parseNameServer(cfg.NameServer, cfg.RespectRules, cfg.PreferH3); err != nil {
|
||||
@ -1358,7 +1300,7 @@ func parseDNS(rawCfg *RawConfig, hosts *trie.DomainTrie[resolver.HostValue], rul
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if dnsCfg.NameServerPolicy, err = parseNameServerPolicy(cfg.NameServerPolicy, ruleProviders, cfg.RespectRules, cfg.PreferH3); err != nil {
|
||||
if dnsCfg.NameServerPolicy, err = parseNameServerPolicy(cfg.NameServerPolicy, rules, ruleProviders, cfg.RespectRules, cfg.PreferH3); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@ -1395,27 +1337,21 @@ func parseDNS(rawCfg *RawConfig, hosts *trie.DomainTrie[resolver.HostValue], rul
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var host *trie.DomainTrie[struct{}]
|
||||
// fake ip skip host filter
|
||||
if len(cfg.FakeIPFilter) != 0 {
|
||||
host = trie.New[struct{}]()
|
||||
for _, domain := range cfg.FakeIPFilter {
|
||||
_ = host.Insert(domain, struct{}{})
|
||||
}
|
||||
host.Optimize()
|
||||
}
|
||||
|
||||
var fakeIPTrie *trie.DomainTrie[struct{}]
|
||||
if len(dnsCfg.Fallback) != 0 {
|
||||
if host == nil {
|
||||
host = trie.New[struct{}]()
|
||||
}
|
||||
fakeIPTrie = trie.New[struct{}]()
|
||||
for _, fb := range dnsCfg.Fallback {
|
||||
if net.ParseIP(fb.Addr) != nil {
|
||||
continue
|
||||
}
|
||||
_ = host.Insert(fb.Addr, struct{}{})
|
||||
_ = fakeIPTrie.Insert(fb.Addr, struct{}{})
|
||||
}
|
||||
host.Optimize()
|
||||
}
|
||||
|
||||
// fake ip skip host filter
|
||||
host, err := parseDomain(cfg.FakeIPFilter, fakeIPTrie, "dns.fake-ip-filter", ruleProviders)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pool, err := fakeip.New(fakeip.Options{
|
||||
@ -1431,18 +1367,51 @@ func parseDNS(rawCfg *RawConfig, hosts *trie.DomainTrie[resolver.HostValue], rul
|
||||
dnsCfg.FakeIPRange = pool
|
||||
}
|
||||
|
||||
var rule C.Rule
|
||||
if len(cfg.Fallback) != 0 {
|
||||
dnsCfg.FallbackFilter.GeoIP = cfg.FallbackFilter.GeoIP
|
||||
dnsCfg.FallbackFilter.GeoIPCode = cfg.FallbackFilter.GeoIPCode
|
||||
if fallbackip, err := parseFallbackIPCIDR(cfg.FallbackFilter.IPCIDR); err == nil {
|
||||
dnsCfg.FallbackFilter.IPCIDR = fallbackip
|
||||
if cfg.FallbackFilter.GeoIP {
|
||||
rule, err = RC.NewGEOIP(cfg.FallbackFilter.GeoIPCode, "", false, true)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("load GeoIP dns fallback filter error, %w", err)
|
||||
}
|
||||
dnsCfg.FallbackIPFilter = append(dnsCfg.FallbackIPFilter, rule)
|
||||
}
|
||||
dnsCfg.FallbackFilter.Domain = cfg.FallbackFilter.Domain
|
||||
fallbackGeoSite, err := parseFallbackGeoSite(cfg.FallbackFilter.GeoSite, rules)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("load GeoSite dns fallback filter error, %w", err)
|
||||
if len(cfg.FallbackFilter.IPCIDR) > 0 {
|
||||
cidrSet := cidr.NewIpCidrSet()
|
||||
for idx, ipcidr := range cfg.FallbackFilter.IPCIDR {
|
||||
err = cidrSet.AddIpCidrForString(ipcidr)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("DNS FallbackIP[%d] format error: %w", idx, err)
|
||||
}
|
||||
}
|
||||
err = cidrSet.Merge()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rule = RP.NewIpCidrSet(cidrSet, "dns.fallback-filter.ipcidr")
|
||||
dnsCfg.FallbackIPFilter = append(dnsCfg.FallbackIPFilter, rule)
|
||||
}
|
||||
if len(cfg.FallbackFilter.Domain) > 0 {
|
||||
domainTrie := trie.New[struct{}]()
|
||||
for idx, domain := range cfg.FallbackFilter.Domain {
|
||||
err = domainTrie.Insert(domain, struct{}{})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("DNS FallbackDomain[%d] format error: %w", idx, err)
|
||||
}
|
||||
}
|
||||
rule = RP.NewDomainSet(domainTrie.NewDomainSet(), "dns.fallback-filter.domain")
|
||||
dnsCfg.FallbackIPFilter = append(dnsCfg.FallbackIPFilter, rule)
|
||||
}
|
||||
if len(cfg.FallbackFilter.GeoSite) > 0 {
|
||||
log.Warnln("replace fallback-filter.geosite with nameserver-policy, it will be removed in the future")
|
||||
for idx, geoSite := range cfg.FallbackFilter.GeoSite {
|
||||
rule, err = RC.NewGEOSITE(geoSite, "dns.fallback-filter.geosite")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("DNS FallbackGeosite[%d] format error: %w", idx, err)
|
||||
}
|
||||
dnsCfg.FallbackIPFilter = append(dnsCfg.FallbackIPFilter, rule)
|
||||
}
|
||||
}
|
||||
dnsCfg.FallbackFilter.GeoSite = fallbackGeoSite
|
||||
}
|
||||
|
||||
if cfg.UseHosts {
|
||||
@ -1542,7 +1511,7 @@ func parseTuicServer(rawTuic RawTuicServer, general *General) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func parseSniffer(snifferRaw RawSniffer) (*Sniffer, error) {
|
||||
func parseSniffer(snifferRaw RawSniffer, ruleProviders map[string]providerTypes.RuleProvider) (*Sniffer, error) {
|
||||
sniffer := &Sniffer{
|
||||
Enable: snifferRaw.Enable,
|
||||
ForceDnsMapping: snifferRaw.ForceDnsMapping,
|
||||
@ -1605,23 +1574,75 @@ func parseSniffer(snifferRaw RawSniffer) (*Sniffer, error) {
|
||||
|
||||
sniffer.Sniffers = loadSniffer
|
||||
|
||||
forceDomainTrie := trie.New[struct{}]()
|
||||
for _, domain := range snifferRaw.ForceDomain {
|
||||
err := forceDomainTrie.Insert(domain, struct{}{})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error domian[%s] in force-domain, error:%v", domain, err)
|
||||
}
|
||||
forceDomain, err := parseDomain(snifferRaw.ForceDomain, nil, "sniffer.force-domain", ruleProviders)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error in force-domain, error:%w", err)
|
||||
}
|
||||
sniffer.ForceDomain = forceDomainTrie.NewDomainSet()
|
||||
sniffer.ForceDomain = forceDomain
|
||||
|
||||
skipDomainTrie := trie.New[struct{}]()
|
||||
for _, domain := range snifferRaw.SkipDomain {
|
||||
err := skipDomainTrie.Insert(domain, struct{}{})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error domian[%s] in force-domain, error:%v", domain, err)
|
||||
}
|
||||
skipDomain, err := parseDomain(snifferRaw.SkipDomain, nil, "sniffer.skip-domain", ruleProviders)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error in skip-domain, error:%w", err)
|
||||
}
|
||||
sniffer.SkipDomain = skipDomainTrie.NewDomainSet()
|
||||
sniffer.SkipDomain = skipDomain
|
||||
|
||||
return sniffer, nil
|
||||
}
|
||||
|
||||
func parseDomain(domains []string, domainTrie *trie.DomainTrie[struct{}], adapterName string, ruleProviders map[string]providerTypes.RuleProvider) (domainRules []C.Rule, err error) {
|
||||
var rule C.Rule
|
||||
for _, domain := range domains {
|
||||
domainLower := strings.ToLower(domain)
|
||||
if strings.Contains(domainLower, "geosite:") {
|
||||
subkeys := strings.Split(domain, ":")
|
||||
subkeys = subkeys[1:]
|
||||
subkeys = strings.Split(subkeys[0], ",")
|
||||
for _, country := range subkeys {
|
||||
rule, err = RC.NewGEOSITE(country, adapterName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
domainRules = append(domainRules, rule)
|
||||
}
|
||||
} else if strings.Contains(domainLower, "rule-set:") {
|
||||
subkeys := strings.Split(domain, ":")
|
||||
subkeys = subkeys[1:]
|
||||
subkeys = strings.Split(subkeys[0], ",")
|
||||
for _, domainSetName := range subkeys {
|
||||
rule, err = parseDomainRuleSet(domainSetName, adapterName, ruleProviders)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
domainRules = append(domainRules, rule)
|
||||
}
|
||||
} else {
|
||||
if domainTrie == nil {
|
||||
domainTrie = trie.New[struct{}]()
|
||||
}
|
||||
err = domainTrie.Insert(domain, struct{}{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
if !domainTrie.IsEmpty() {
|
||||
rule = RP.NewDomainSet(domainTrie.NewDomainSet(), adapterName)
|
||||
domainRules = append(domainRules, rule)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func parseDomainRuleSet(domainSetName string, adapterName string, ruleProviders map[string]providerTypes.RuleProvider) (C.Rule, error) {
|
||||
if rp, ok := ruleProviders[domainSetName]; !ok {
|
||||
return nil, fmt.Errorf("not found rule-set: %s", domainSetName)
|
||||
} else {
|
||||
switch rp.Behavior() {
|
||||
case providerTypes.IPCIDR:
|
||||
return nil, fmt.Errorf("rule provider type error, except domain,actual %s", rp.Behavior())
|
||||
case providerTypes.Classical:
|
||||
log.Warnln("%s provider is %s, only matching it contain domain rule", rp.Name(), rp.Behavior())
|
||||
default:
|
||||
}
|
||||
}
|
||||
return RP.NewRuleSet(domainSetName, adapterName, true)
|
||||
}
|
||||
|
@ -1,20 +0,0 @@
|
||||
package constant
|
||||
|
||||
import (
|
||||
"net/netip"
|
||||
|
||||
"github.com/metacubex/mihomo/transport/socks5"
|
||||
)
|
||||
|
||||
const (
|
||||
BpfFSPath = "/sys/fs/bpf/mihomo"
|
||||
|
||||
TcpAutoRedirPort = 't'<<8 | 'r'<<0
|
||||
MihomoTrafficMark = 'c'<<24 | 'l'<<16 | 't'<<8 | 'm'<<0
|
||||
)
|
||||
|
||||
type EBpf interface {
|
||||
Start() error
|
||||
Close()
|
||||
Lookup(srcAddrPort netip.AddrPort) (socks5.Addr, error)
|
||||
}
|
@ -27,6 +27,8 @@ const (
|
||||
ProcessNameRegex
|
||||
ProcessPathRegex
|
||||
RuleSet
|
||||
DomainSet
|
||||
IpCidrSet
|
||||
Network
|
||||
Uid
|
||||
SubRules
|
||||
@ -90,6 +92,10 @@ func (rt RuleType) String() string {
|
||||
return "Match"
|
||||
case RuleSet:
|
||||
return "RuleSet"
|
||||
case DomainSet:
|
||||
return "DomainSet"
|
||||
case IpCidrSet:
|
||||
return "IpCidrSet"
|
||||
case Network:
|
||||
return "Network"
|
||||
case DSCP:
|
||||
@ -118,3 +124,8 @@ type Rule interface {
|
||||
ShouldFindProcess() bool
|
||||
ProviderNames() []string
|
||||
}
|
||||
|
||||
type RuleGroup interface {
|
||||
Rule
|
||||
GetRecodeSize() int
|
||||
}
|
||||
|
@ -1,17 +0,0 @@
|
||||
package constant
|
||||
|
||||
import (
|
||||
"github.com/metacubex/mihomo/component/geodata/router"
|
||||
)
|
||||
|
||||
type RuleGeoSite interface {
|
||||
GetDomainMatcher() router.DomainMatcher
|
||||
}
|
||||
|
||||
type RuleGeoIP interface {
|
||||
GetIPMatcher() *router.GeoIPMatcher
|
||||
}
|
||||
|
||||
type RuleGroup interface {
|
||||
GetRecodeSize() int
|
||||
}
|
102
dns/filters.go
102
dns/filters.go
@ -1,102 +0,0 @@
|
||||
package dns
|
||||
|
||||
import (
|
||||
"net/netip"
|
||||
"strings"
|
||||
|
||||
"github.com/metacubex/mihomo/component/geodata"
|
||||
"github.com/metacubex/mihomo/component/geodata/router"
|
||||
"github.com/metacubex/mihomo/component/mmdb"
|
||||
"github.com/metacubex/mihomo/component/trie"
|
||||
C "github.com/metacubex/mihomo/constant"
|
||||
"github.com/metacubex/mihomo/log"
|
||||
)
|
||||
|
||||
type fallbackIPFilter interface {
|
||||
Match(netip.Addr) bool
|
||||
}
|
||||
|
||||
type geoipFilter struct {
|
||||
code string
|
||||
}
|
||||
|
||||
var geoIPMatcher *router.GeoIPMatcher
|
||||
|
||||
func (gf *geoipFilter) Match(ip netip.Addr) bool {
|
||||
if !C.GeodataMode {
|
||||
codes := mmdb.IPInstance().LookupCode(ip.AsSlice())
|
||||
for _, code := range codes {
|
||||
if !strings.EqualFold(code, gf.code) && !ip.IsPrivate() {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
if geoIPMatcher == nil {
|
||||
var err error
|
||||
geoIPMatcher, _, err = geodata.LoadGeoIPMatcher("CN")
|
||||
if err != nil {
|
||||
log.Errorln("[GeoIPFilter] LoadGeoIPMatcher error: %s", err.Error())
|
||||
return false
|
||||
}
|
||||
}
|
||||
return !geoIPMatcher.Match(ip)
|
||||
}
|
||||
|
||||
type ipnetFilter struct {
|
||||
ipnet netip.Prefix
|
||||
}
|
||||
|
||||
func (inf *ipnetFilter) Match(ip netip.Addr) bool {
|
||||
return inf.ipnet.Contains(ip)
|
||||
}
|
||||
|
||||
type fallbackDomainFilter interface {
|
||||
Match(domain string) bool
|
||||
}
|
||||
|
||||
type domainFilter struct {
|
||||
tree *trie.DomainTrie[struct{}]
|
||||
}
|
||||
|
||||
func NewDomainFilter(domains []string) *domainFilter {
|
||||
df := domainFilter{tree: trie.New[struct{}]()}
|
||||
for _, domain := range domains {
|
||||
_ = df.tree.Insert(domain, struct{}{})
|
||||
}
|
||||
df.tree.Optimize()
|
||||
return &df
|
||||
}
|
||||
|
||||
func (df *domainFilter) Match(domain string) bool {
|
||||
return df.tree.Search(domain) != nil
|
||||
}
|
||||
|
||||
type geoSiteFilter struct {
|
||||
matchers []router.DomainMatcher
|
||||
}
|
||||
|
||||
func NewGeoSite(group string) (fallbackDomainFilter, error) {
|
||||
if err := geodata.InitGeoSite(); err != nil {
|
||||
log.Errorln("can't initial GeoSite: %s", err)
|
||||
return nil, err
|
||||
}
|
||||
matcher, _, err := geodata.LoadGeoSiteMatcher(group)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
filter := &geoSiteFilter{
|
||||
matchers: []router.DomainMatcher{matcher},
|
||||
}
|
||||
return filter, nil
|
||||
}
|
||||
|
||||
func (gsf *geoSiteFilter) Match(domain string) bool {
|
||||
for _, matcher := range gsf.matchers {
|
||||
if matcher.ApplyDomain(domain) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
@ -3,7 +3,6 @@ package dns
|
||||
import (
|
||||
"github.com/metacubex/mihomo/component/trie"
|
||||
C "github.com/metacubex/mihomo/constant"
|
||||
"github.com/metacubex/mihomo/constant/provider"
|
||||
)
|
||||
|
||||
type dnsPolicy interface {
|
||||
@ -22,32 +21,14 @@ func (p domainTriePolicy) Match(domain string) []dnsClient {
|
||||
return nil
|
||||
}
|
||||
|
||||
type geositePolicy struct {
|
||||
matcher fallbackDomainFilter
|
||||
inverse bool
|
||||
type domainRulePolicy struct {
|
||||
rule C.Rule
|
||||
dnsClients []dnsClient
|
||||
}
|
||||
|
||||
func (p geositePolicy) Match(domain string) []dnsClient {
|
||||
matched := p.matcher.Match(domain)
|
||||
if matched != p.inverse {
|
||||
func (p domainRulePolicy) Match(domain string) []dnsClient {
|
||||
if ok, _ := p.rule.Match(&C.Metadata{Host: domain}); ok {
|
||||
return p.dnsClients
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type domainSetPolicy struct {
|
||||
tunnel provider.Tunnel
|
||||
name string
|
||||
dnsClients []dnsClient
|
||||
}
|
||||
|
||||
func (p domainSetPolicy) Match(domain string) []dnsClient {
|
||||
if ruleProvider, ok := p.tunnel.RuleProviders()[p.name]; ok {
|
||||
metadata := &C.Metadata{Host: domain}
|
||||
if ok := ruleProvider.Match(metadata); ok {
|
||||
return p.dnsClients
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
140
dns/resolver.go
140
dns/resolver.go
@ -4,13 +4,12 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"net/netip"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/metacubex/mihomo/common/arc"
|
||||
"github.com/metacubex/mihomo/common/lru"
|
||||
"github.com/metacubex/mihomo/common/singleflight"
|
||||
"github.com/metacubex/mihomo/component/fakeip"
|
||||
"github.com/metacubex/mihomo/component/geodata/router"
|
||||
"github.com/metacubex/mihomo/component/resolver"
|
||||
"github.com/metacubex/mihomo/component/trie"
|
||||
C "github.com/metacubex/mihomo/constant"
|
||||
@ -19,9 +18,7 @@ import (
|
||||
|
||||
D "github.com/miekg/dns"
|
||||
"github.com/samber/lo"
|
||||
orderedmap "github.com/wk8/go-ordered-map/v2"
|
||||
"golang.org/x/exp/maps"
|
||||
"golang.org/x/sync/singleflight"
|
||||
)
|
||||
|
||||
type dnsClient interface {
|
||||
@ -45,9 +42,9 @@ type Resolver struct {
|
||||
hosts *trie.DomainTrie[resolver.HostValue]
|
||||
main []dnsClient
|
||||
fallback []dnsClient
|
||||
fallbackDomainFilters []fallbackDomainFilter
|
||||
fallbackIPFilters []fallbackIPFilter
|
||||
group singleflight.Group
|
||||
fallbackDomainFilters []C.Rule
|
||||
fallbackIPFilters []C.Rule
|
||||
group singleflight.Group[*D.Msg]
|
||||
cache dnsCache
|
||||
policy []dnsPolicy
|
||||
proxyServer []dnsClient
|
||||
@ -122,7 +119,7 @@ func (r *Resolver) LookupIPv6(ctx context.Context, host string) ([]netip.Addr, e
|
||||
|
||||
func (r *Resolver) shouldIPFallback(ip netip.Addr) bool {
|
||||
for _, filter := range r.fallbackIPFilters {
|
||||
if filter.Match(ip) {
|
||||
if ok, _ := filter.Match(&C.Metadata{DstIP: ip}); ok {
|
||||
return true
|
||||
}
|
||||
}
|
||||
@ -172,19 +169,20 @@ func (r *Resolver) exchangeWithoutCache(ctx context.Context, m *D.Msg) (msg *D.M
|
||||
|
||||
retryNum := 0
|
||||
retryMax := 3
|
||||
fn := func() (result any, err error) {
|
||||
fn := func() (result *D.Msg, err error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), resolver.DefaultDNSTimeout) // reset timeout in singleflight
|
||||
defer cancel()
|
||||
cache := false
|
||||
|
||||
defer func() {
|
||||
if err != nil {
|
||||
result = retryNum
|
||||
result = &D.Msg{}
|
||||
result.Opcode = retryNum
|
||||
retryNum++
|
||||
return
|
||||
}
|
||||
|
||||
msg := result.(*D.Msg)
|
||||
msg := result
|
||||
|
||||
if cache {
|
||||
// OPT RRs MUST NOT be cached, forwarded, or stored in or loaded from master files.
|
||||
@ -211,7 +209,7 @@ func (r *Resolver) exchangeWithoutCache(ctx context.Context, m *D.Msg) (msg *D.M
|
||||
|
||||
ch := r.group.DoChan(q.String(), fn)
|
||||
|
||||
var result singleflight.Result
|
||||
var result singleflight.Result[*D.Msg]
|
||||
|
||||
select {
|
||||
case result = <-ch:
|
||||
@ -224,7 +222,7 @@ func (r *Resolver) exchangeWithoutCache(ctx context.Context, m *D.Msg) (msg *D.M
|
||||
go func() { // start a retrying monitor in background
|
||||
result := <-ch
|
||||
ret, err, shared := result.Val, result.Err, result.Shared
|
||||
if err != nil && !shared && ret.(int) < retryMax { // retry
|
||||
if err != nil && !shared && ret.Opcode < retryMax { // retry
|
||||
r.group.DoChan(q.String(), fn)
|
||||
}
|
||||
}()
|
||||
@ -233,12 +231,12 @@ func (r *Resolver) exchangeWithoutCache(ctx context.Context, m *D.Msg) (msg *D.M
|
||||
}
|
||||
|
||||
ret, err, shared := result.Val, result.Err, result.Shared
|
||||
if err != nil && !shared && ret.(int) < retryMax { // retry
|
||||
if err != nil && !shared && ret.Opcode < retryMax { // retry
|
||||
r.group.DoChan(q.String(), fn)
|
||||
}
|
||||
|
||||
if err == nil {
|
||||
msg = ret.(*D.Msg)
|
||||
msg = ret
|
||||
if shared {
|
||||
msg = msg.Copy()
|
||||
}
|
||||
@ -277,7 +275,7 @@ func (r *Resolver) shouldOnlyQueryFallback(m *D.Msg) bool {
|
||||
}
|
||||
|
||||
for _, df := range r.fallbackDomainFilters {
|
||||
if df.Match(domain) {
|
||||
if ok, _ := df.Match(&C.Metadata{Host: domain}); ok {
|
||||
return true
|
||||
}
|
||||
}
|
||||
@ -398,27 +396,26 @@ func (ns NameServer) Equal(ns2 NameServer) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
type FallbackFilter struct {
|
||||
GeoIP bool
|
||||
GeoIPCode string
|
||||
IPCIDR []netip.Prefix
|
||||
Domain []string
|
||||
GeoSite []router.DomainMatcher
|
||||
type Policy struct {
|
||||
Domain string
|
||||
Rule C.Rule
|
||||
NameServers []NameServer
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
Main, Fallback []NameServer
|
||||
Default []NameServer
|
||||
ProxyServer []NameServer
|
||||
IPv6 bool
|
||||
IPv6Timeout uint
|
||||
EnhancedMode C.DNSMode
|
||||
FallbackFilter FallbackFilter
|
||||
Pool *fakeip.Pool
|
||||
Hosts *trie.DomainTrie[resolver.HostValue]
|
||||
Policy *orderedmap.OrderedMap[string, []NameServer]
|
||||
Tunnel provider.Tunnel
|
||||
CacheAlgorithm string
|
||||
Main, Fallback []NameServer
|
||||
Default []NameServer
|
||||
ProxyServer []NameServer
|
||||
IPv6 bool
|
||||
IPv6Timeout uint
|
||||
EnhancedMode C.DNSMode
|
||||
FallbackIPFilter []C.Rule
|
||||
FallbackDomainFilter []C.Rule
|
||||
Pool *fakeip.Pool
|
||||
Hosts *trie.DomainTrie[resolver.HostValue]
|
||||
Policy []Policy
|
||||
Tunnel provider.Tunnel
|
||||
CacheAlgorithm string
|
||||
}
|
||||
|
||||
func NewResolver(config Config) *Resolver {
|
||||
@ -482,7 +479,7 @@ func NewResolver(config Config) *Resolver {
|
||||
r.proxyServer = cacheTransform(config.ProxyServer)
|
||||
}
|
||||
|
||||
if config.Policy.Len() != 0 {
|
||||
if len(config.Policy) != 0 {
|
||||
r.policy = make([]dnsPolicy, 0)
|
||||
|
||||
var triePolicy *trie.DomainTrie[[]dnsClient]
|
||||
@ -497,75 +494,20 @@ func NewResolver(config Config) *Resolver {
|
||||
}
|
||||
}
|
||||
|
||||
for pair := config.Policy.Oldest(); pair != nil; pair = pair.Next() {
|
||||
domain, nameserver := pair.Key, pair.Value
|
||||
|
||||
if temp := strings.Split(domain, ":"); len(temp) == 2 {
|
||||
prefix := temp[0]
|
||||
key := temp[1]
|
||||
switch prefix {
|
||||
case "rule-set":
|
||||
if _, ok := config.Tunnel.RuleProviders()[key]; ok {
|
||||
log.Debugln("Adding rule-set policy: %s ", key)
|
||||
insertPolicy(domainSetPolicy{
|
||||
tunnel: config.Tunnel,
|
||||
name: key,
|
||||
dnsClients: cacheTransform(nameserver),
|
||||
})
|
||||
continue
|
||||
} else {
|
||||
log.Warnln("Can't found ruleset policy: %s", key)
|
||||
}
|
||||
case "geosite":
|
||||
inverse := false
|
||||
if strings.HasPrefix(key, "!") {
|
||||
inverse = true
|
||||
key = key[1:]
|
||||
}
|
||||
log.Debugln("Adding geosite policy: %s inversed %t", key, inverse)
|
||||
matcher, err := NewGeoSite(key)
|
||||
if err != nil {
|
||||
log.Warnln("adding geosite policy %s error: %s", key, err)
|
||||
continue
|
||||
}
|
||||
insertPolicy(geositePolicy{
|
||||
matcher: matcher,
|
||||
inverse: inverse,
|
||||
dnsClients: cacheTransform(nameserver),
|
||||
})
|
||||
continue // skip triePolicy new
|
||||
for _, policy := range config.Policy {
|
||||
if policy.Rule != nil {
|
||||
insertPolicy(domainRulePolicy{rule: policy.Rule, dnsClients: cacheTransform(policy.NameServers)})
|
||||
} else {
|
||||
if triePolicy == nil {
|
||||
triePolicy = trie.New[[]dnsClient]()
|
||||
}
|
||||
_ = triePolicy.Insert(policy.Domain, cacheTransform(policy.NameServers))
|
||||
}
|
||||
if triePolicy == nil {
|
||||
triePolicy = trie.New[[]dnsClient]()
|
||||
}
|
||||
_ = triePolicy.Insert(domain, cacheTransform(nameserver))
|
||||
}
|
||||
insertPolicy(nil)
|
||||
}
|
||||
|
||||
fallbackIPFilters := []fallbackIPFilter{}
|
||||
if config.FallbackFilter.GeoIP {
|
||||
fallbackIPFilters = append(fallbackIPFilters, &geoipFilter{
|
||||
code: config.FallbackFilter.GeoIPCode,
|
||||
})
|
||||
}
|
||||
for _, ipnet := range config.FallbackFilter.IPCIDR {
|
||||
fallbackIPFilters = append(fallbackIPFilters, &ipnetFilter{ipnet: ipnet})
|
||||
}
|
||||
r.fallbackIPFilters = fallbackIPFilters
|
||||
|
||||
fallbackDomainFilters := []fallbackDomainFilter{}
|
||||
if len(config.FallbackFilter.Domain) != 0 {
|
||||
fallbackDomainFilters = append(fallbackDomainFilters, NewDomainFilter(config.FallbackFilter.Domain))
|
||||
}
|
||||
|
||||
if len(config.FallbackFilter.GeoSite) != 0 {
|
||||
fallbackDomainFilters = append(fallbackDomainFilters, &geoSiteFilter{
|
||||
matchers: config.FallbackFilter.GeoSite,
|
||||
})
|
||||
}
|
||||
r.fallbackDomainFilters = fallbackDomainFilters
|
||||
r.fallbackIPFilters = config.FallbackIPFilter
|
||||
r.fallbackDomainFilters = config.FallbackDomainFilter
|
||||
|
||||
return r
|
||||
}
|
||||
|
@ -82,7 +82,9 @@ external-doh-server: /dns-query
|
||||
global-client-fingerprint: chrome
|
||||
|
||||
# TCP keep alive interval
|
||||
keep-alive-interval: 15
|
||||
# disable-keep-alive: false #目前在android端强制为true
|
||||
# keep-alive-idle: 15
|
||||
# keep-alive-interval: 15
|
||||
|
||||
# routing-mark:6666 # 配置 fwmark 仅用于 Linux
|
||||
experimental:
|
||||
@ -164,13 +166,6 @@ tun:
|
||||
# exclude-package: # 排除被路由的 Android 应用包名
|
||||
# - com.android.captiveportallogin
|
||||
|
||||
#ebpf 配置
|
||||
ebpf:
|
||||
auto-redir: # redirect 模式,仅支持 TCP
|
||||
- eth0
|
||||
redirect-to-tun: # UDP+TCP 使用该功能请勿启用 auto-route
|
||||
- eth0
|
||||
|
||||
# 嗅探域名 可选配置
|
||||
sniffer:
|
||||
enable: false
|
||||
@ -241,6 +236,16 @@ dns:
|
||||
|
||||
fake-ip-range: 198.18.0.1/16 # fake-ip 池设置
|
||||
|
||||
# 配置不使用 fake-ip 的域名
|
||||
fake-ip-filter:
|
||||
- '*.lan'
|
||||
- localhost.ptlogin2.qq.com
|
||||
# fakeip-filter 为 rule-providers 中的名为 fakeip-filter 规则订阅,
|
||||
# 且 behavior 必须为 domain/classical,当为 classical 时仅会生效域名类规则
|
||||
- rule-set:fakeip-filter
|
||||
# fakeip-filter 为 geosite 中名为 fakeip-filter 的分类(需要自行保证该分类存在)
|
||||
- geosite:fakeip-filter
|
||||
|
||||
# use-hosts: true # 查询 hosts
|
||||
|
||||
# 配置后面的nameserver、fallback和nameserver-policy向dns服务器的连接过程是否遵守遵守rules规则
|
||||
@ -250,11 +255,6 @@ dns:
|
||||
# 此外,这三者配置中的dns服务器如果出现域名会采用default-nameserver配置项解析,也请确保正确配置default-nameserver
|
||||
respect-rules: false
|
||||
|
||||
# 配置不使用 fake-ip 的域名
|
||||
# fake-ip-filter:
|
||||
# - '*.lan'
|
||||
# - localhost.ptlogin2.qq.com
|
||||
|
||||
# DNS 主要域名配置
|
||||
# 支持 UDP,TCP,DoT,DoH,DoQ
|
||||
# 这部分为主要 DNS 配置,影响所有直连,确保使用对大陆解析精准的 DNS
|
||||
|
4
go.mod
4
go.mod
@ -5,7 +5,6 @@ go 1.20
|
||||
require (
|
||||
github.com/3andne/restls-client-go v0.1.6
|
||||
github.com/bahlo/generic-list-go v0.2.0
|
||||
github.com/cilium/ebpf v0.12.3
|
||||
github.com/coreos/go-iptables v0.7.0
|
||||
github.com/dlclark/regexp2 v1.11.4
|
||||
github.com/go-chi/chi/v5 v5.1.0
|
||||
@ -53,7 +52,6 @@ require (
|
||||
golang.org/x/crypto v0.26.0
|
||||
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56
|
||||
golang.org/x/net v0.28.0
|
||||
golang.org/x/sync v0.8.0
|
||||
golang.org/x/sys v0.23.0
|
||||
google.golang.org/protobuf v1.34.2
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
@ -83,6 +81,7 @@ require (
|
||||
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 // indirect
|
||||
github.com/hashicorp/yamux v0.1.1 // indirect
|
||||
github.com/josharian/native v1.1.0 // indirect
|
||||
github.com/kr/text v0.2.0 // indirect
|
||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
|
||||
github.com/mailru/easyjson v0.7.7 // indirect
|
||||
github.com/mdlayher/socket v0.4.1 // indirect
|
||||
@ -108,6 +107,7 @@ require (
|
||||
gitlab.com/yawning/bsaes.git v0.0.0-20190805113838-0a714cd429ec // indirect
|
||||
go.uber.org/mock v0.4.0 // indirect
|
||||
golang.org/x/mod v0.19.0 // indirect
|
||||
golang.org/x/sync v0.8.0 // indirect
|
||||
golang.org/x/text v0.17.0 // indirect
|
||||
golang.org/x/time v0.5.0 // indirect
|
||||
golang.org/x/tools v0.23.0 // indirect
|
||||
|
8
go.sum
8
go.sum
@ -17,12 +17,11 @@ github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx2
|
||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||
github.com/cilium/ebpf v0.12.3 h1:8ht6F9MquybnY97at+VDZb3eQQr8ev79RueWeVaEcG4=
|
||||
github.com/cilium/ebpf v0.12.3/go.mod h1:TctK1ivibvI3znr66ljgi4hqOT8EYQjz1KWBfb1UVgM=
|
||||
github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU=
|
||||
github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA=
|
||||
github.com/coreos/go-iptables v0.7.0 h1:XWM3V+MPRr5/q51NuWSgU0fqMad64Zyxs8ZUoMsamr8=
|
||||
github.com/coreos/go-iptables v0.7.0/go.mod h1:Qe8Bv2Xik5FyTXwgIbLAnv2sWSBmvWdFETJConOQ//Q=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
@ -37,7 +36,6 @@ github.com/ericlagergren/siv v0.0.0-20220507050439-0b757b3aa5f1 h1:tlDMEdcPRQKBE
|
||||
github.com/ericlagergren/siv v0.0.0-20220507050439-0b757b3aa5f1/go.mod h1:4RfsapbGx2j/vU5xC/5/9qB3kn9Awp1YDiEnN43QrJ4=
|
||||
github.com/ericlagergren/subtle v0.0.0-20220507045147-890d697da010 h1:fuGucgPk5dN6wzfnxl3D0D3rVLw4v2SbBT9jb4VnxzA=
|
||||
github.com/ericlagergren/subtle v0.0.0-20220507045147-890d697da010/go.mod h1:JtBcj7sBuTTRupn7c2bFspMDIObMJsVK8TeUvpShPok=
|
||||
github.com/frankban/quicktest v1.14.5 h1:dfYrrRyLtiqT9GyKXgdh+k4inNeTvmGbuSgZ3lx3GhA=
|
||||
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
|
||||
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
|
||||
github.com/gaukas/godicttls v0.0.4 h1:NlRaXb3J6hAnTmWdsEKb9bcSBD6BvcIjdGdeb0zfXbk=
|
||||
@ -85,8 +83,9 @@ github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2
|
||||
github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
|
||||
github.com/klauspost/cpuid/v2 v2.2.8 h1:+StwCXwm9PdpiEkPyzBXIy+M9KUb4ODm0Zarf1kS5BM=
|
||||
github.com/klauspost/cpuid/v2 v2.2.8/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4=
|
||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
|
||||
github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40 h1:EnfXoSqDfSNJv0VBNqY/88RNnhSGYkrHaO0mmFGbVsc=
|
||||
@ -155,7 +154,6 @@ github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo=
|
||||
github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A=
|
||||
github.com/quic-go/qtls-go1-20 v0.4.1 h1:D33340mCNDAIKBqXuAvexTNMUByrYmFYVfKfDN5nfFs=
|
||||
github.com/quic-go/qtls-go1-20 v0.4.1/go.mod h1:X9Nh97ZL80Z+bX/gUXMbipO6OxdiDi58b/fMC9mAL+k=
|
||||
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
||||
github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a h1:+NkI2670SQpQWvkkD2QgdTuzQG263YZ+2emfpeyGqW0=
|
||||
github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a/go.mod h1:63s7jpZqcDAIpj8oI/1v4Izok+npJOHACFCU6+huCkM=
|
||||
github.com/sagernet/fswatch v0.1.1 h1:YqID+93B7VRfqIH3PArW/XpJv5H4OLEVWDfProGoRQs=
|
||||
|
@ -23,9 +23,9 @@ import (
|
||||
"github.com/metacubex/mihomo/component/resolver"
|
||||
SNI "github.com/metacubex/mihomo/component/sniffer"
|
||||
"github.com/metacubex/mihomo/component/trie"
|
||||
"github.com/metacubex/mihomo/component/updater"
|
||||
"github.com/metacubex/mihomo/config"
|
||||
C "github.com/metacubex/mihomo/constant"
|
||||
"github.com/metacubex/mihomo/constant/features"
|
||||
"github.com/metacubex/mihomo/constant/provider"
|
||||
"github.com/metacubex/mihomo/dns"
|
||||
"github.com/metacubex/mihomo/listener"
|
||||
@ -113,6 +113,7 @@ func ApplyConfig(cfg *config.Config, force bool) {
|
||||
runtime.GC()
|
||||
tunnel.OnRunning()
|
||||
hcCompatibleProvider(cfg.Providers)
|
||||
initExternalUI()
|
||||
|
||||
log.SetLevel(cfg.General.LogLevel)
|
||||
}
|
||||
@ -180,9 +181,6 @@ func updateListeners(general *config.General, listeners map[string]C.InboundList
|
||||
listener.ReCreateHTTP(general.Port, tunnel.Tunnel)
|
||||
listener.ReCreateSocks(general.SocksPort, tunnel.Tunnel)
|
||||
listener.ReCreateRedir(general.RedirPort, tunnel.Tunnel)
|
||||
if !features.CMFA {
|
||||
listener.ReCreateAutoRedir(general.EBpf.AutoRedir, tunnel.Tunnel)
|
||||
}
|
||||
listener.ReCreateTProxy(general.TProxyPort, tunnel.Tunnel)
|
||||
listener.ReCreateMixed(general.MixedPort, tunnel.Tunnel)
|
||||
listener.ReCreateShadowSocks(general.ShadowSocksConfig, tunnel.Tunnel)
|
||||
@ -220,25 +218,20 @@ func updateDNS(c *config.DNS, generalIPv6 bool) {
|
||||
return
|
||||
}
|
||||
cfg := dns.Config{
|
||||
Main: c.NameServer,
|
||||
Fallback: c.Fallback,
|
||||
IPv6: c.IPv6 && generalIPv6,
|
||||
IPv6Timeout: c.IPv6Timeout,
|
||||
EnhancedMode: c.EnhancedMode,
|
||||
Pool: c.FakeIPRange,
|
||||
Hosts: c.Hosts,
|
||||
FallbackFilter: dns.FallbackFilter{
|
||||
GeoIP: c.FallbackFilter.GeoIP,
|
||||
GeoIPCode: c.FallbackFilter.GeoIPCode,
|
||||
IPCIDR: c.FallbackFilter.IPCIDR,
|
||||
Domain: c.FallbackFilter.Domain,
|
||||
GeoSite: c.FallbackFilter.GeoSite,
|
||||
},
|
||||
Default: c.DefaultNameserver,
|
||||
Policy: c.NameServerPolicy,
|
||||
ProxyServer: c.ProxyServerNameserver,
|
||||
Tunnel: tunnel.Tunnel,
|
||||
CacheAlgorithm: c.CacheAlgorithm,
|
||||
Main: c.NameServer,
|
||||
Fallback: c.Fallback,
|
||||
IPv6: c.IPv6 && generalIPv6,
|
||||
IPv6Timeout: c.IPv6Timeout,
|
||||
EnhancedMode: c.EnhancedMode,
|
||||
Pool: c.FakeIPRange,
|
||||
Hosts: c.Hosts,
|
||||
FallbackIPFilter: c.FallbackIPFilter,
|
||||
FallbackDomainFilter: c.FallbackDomainFilter,
|
||||
Default: c.DefaultNameserver,
|
||||
Policy: c.NameServerPolicy,
|
||||
ProxyServer: c.ProxyServerNameserver,
|
||||
Tunnel: tunnel.Tunnel,
|
||||
CacheAlgorithm: c.CacheAlgorithm,
|
||||
}
|
||||
|
||||
r := dns.NewResolver(cfg)
|
||||
@ -355,7 +348,6 @@ func updateTun(general *config.General) {
|
||||
return
|
||||
}
|
||||
listener.ReCreateTun(general.Tun, tunnel.Tunnel)
|
||||
listener.ReCreateRedirToTun(general.EBpf.RedirectToTun)
|
||||
}
|
||||
|
||||
func updateSniffer(sniffer *config.Sniffer) {
|
||||
@ -385,6 +377,18 @@ func updateTunnels(tunnels []LC.Tunnel) {
|
||||
listener.PatchTunnel(tunnels, tunnel.Tunnel)
|
||||
}
|
||||
|
||||
func initExternalUI() {
|
||||
if updater.ExternalUIFolder != "" {
|
||||
dirEntries, _ := os.ReadDir(updater.ExternalUIFolder)
|
||||
if len(dirEntries) > 0 {
|
||||
log.Infoln("UI already exists")
|
||||
} else {
|
||||
log.Infoln("UI not exists, downloading")
|
||||
updater.UpdateUI()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func updateGeneral(general *config.General) {
|
||||
tunnel.SetMode(general.Mode)
|
||||
tunnel.SetFindProcessMode(general.FindProcessMode)
|
||||
|
@ -402,23 +402,11 @@ func updateConfigs(w http.ResponseWriter, r *http.Request) {
|
||||
func updateGeoDatabases(w http.ResponseWriter, r *http.Request) {
|
||||
err := updater.UpdateGeoDatabases()
|
||||
if err != nil {
|
||||
log.Errorln("[REST-API] update GEO databases failed: %v", err)
|
||||
log.Errorln("[GEO] update GEO databases failed: %v", err)
|
||||
render.Status(r, http.StatusInternalServerError)
|
||||
render.JSON(w, r, newError(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
cfg, err := executor.ParseWithPath(C.Path.Config())
|
||||
if err != nil {
|
||||
log.Errorln("[REST-API] update GEO databases failed: %v", err)
|
||||
render.Status(r, http.StatusInternalServerError)
|
||||
render.JSON(w, r, newError("Error parsing configuration"))
|
||||
return
|
||||
}
|
||||
|
||||
log.Warnln("[GEO] update GEO databases success, applying config")
|
||||
|
||||
executor.ApplyConfig(cfg, false)
|
||||
|
||||
render.NoContent(w, r)
|
||||
}
|
||||
|
@ -1,95 +0,0 @@
|
||||
package autoredir
|
||||
|
||||
import (
|
||||
"net"
|
||||
"net/netip"
|
||||
|
||||
"github.com/metacubex/mihomo/adapter/inbound"
|
||||
N "github.com/metacubex/mihomo/common/net"
|
||||
C "github.com/metacubex/mihomo/constant"
|
||||
"github.com/metacubex/mihomo/log"
|
||||
"github.com/metacubex/mihomo/transport/socks5"
|
||||
)
|
||||
|
||||
type Listener struct {
|
||||
listener net.Listener
|
||||
addr string
|
||||
closed bool
|
||||
additions []inbound.Addition
|
||||
lookupFunc func(netip.AddrPort) (socks5.Addr, error)
|
||||
}
|
||||
|
||||
// RawAddress implements C.Listener
|
||||
func (l *Listener) RawAddress() string {
|
||||
return l.addr
|
||||
}
|
||||
|
||||
// Address implements C.Listener
|
||||
func (l *Listener) Address() string {
|
||||
return l.listener.Addr().String()
|
||||
}
|
||||
|
||||
// Close implements C.Listener
|
||||
func (l *Listener) Close() error {
|
||||
l.closed = true
|
||||
return l.listener.Close()
|
||||
}
|
||||
|
||||
func (l *Listener) TCPAddr() netip.AddrPort {
|
||||
return l.listener.Addr().(*net.TCPAddr).AddrPort()
|
||||
}
|
||||
|
||||
func (l *Listener) SetLookupFunc(lookupFunc func(netip.AddrPort) (socks5.Addr, error)) {
|
||||
l.lookupFunc = lookupFunc
|
||||
}
|
||||
|
||||
func (l *Listener) handleRedir(conn net.Conn, tunnel C.Tunnel) {
|
||||
if l.lookupFunc == nil {
|
||||
log.Errorln("[Auto Redirect] lookup function is nil")
|
||||
return
|
||||
}
|
||||
|
||||
target, err := l.lookupFunc(conn.RemoteAddr().(*net.TCPAddr).AddrPort())
|
||||
if err != nil {
|
||||
log.Warnln("[Auto Redirect] %v", err)
|
||||
_ = conn.Close()
|
||||
return
|
||||
}
|
||||
|
||||
N.TCPKeepAlive(conn)
|
||||
|
||||
tunnel.HandleTCPConn(inbound.NewSocket(target, conn, C.REDIR, l.additions...))
|
||||
}
|
||||
|
||||
func New(addr string, tunnel C.Tunnel, additions ...inbound.Addition) (*Listener, error) {
|
||||
if len(additions) == 0 {
|
||||
additions = []inbound.Addition{
|
||||
inbound.WithInName("DEFAULT-REDIR"),
|
||||
inbound.WithSpecialRules(""),
|
||||
}
|
||||
}
|
||||
l, err := net.Listen("tcp", addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rl := &Listener{
|
||||
listener: l,
|
||||
addr: addr,
|
||||
additions: additions,
|
||||
}
|
||||
|
||||
go func() {
|
||||
for {
|
||||
c, err := l.Accept()
|
||||
if err != nil {
|
||||
if rl.closed {
|
||||
break
|
||||
}
|
||||
continue
|
||||
}
|
||||
go rl.handleRedir(c, tunnel)
|
||||
}
|
||||
}()
|
||||
|
||||
return rl, nil
|
||||
}
|
@ -4,9 +4,9 @@ import (
|
||||
"net"
|
||||
|
||||
"github.com/metacubex/mihomo/adapter/inbound"
|
||||
N "github.com/metacubex/mihomo/common/net"
|
||||
"github.com/metacubex/mihomo/component/auth"
|
||||
C "github.com/metacubex/mihomo/constant"
|
||||
"github.com/metacubex/mihomo/constant/features"
|
||||
authStore "github.com/metacubex/mihomo/listener/auth"
|
||||
)
|
||||
|
||||
@ -74,11 +74,7 @@ func NewWithAuthenticator(addr string, tunnel C.Tunnel, authenticator auth.Authe
|
||||
}
|
||||
continue
|
||||
}
|
||||
if features.CMFA {
|
||||
if t, ok := conn.(*net.TCPConn); ok {
|
||||
t.SetKeepAlive(false)
|
||||
}
|
||||
}
|
||||
N.TCPKeepAlive(conn)
|
||||
if isDefault { // only apply on default listener
|
||||
if !inbound.IsRemoteAddrDisAllowed(conn.RemoteAddr()) {
|
||||
_ = conn.Close()
|
||||
|
@ -9,9 +9,7 @@ import (
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/metacubex/mihomo/component/ebpf"
|
||||
C "github.com/metacubex/mihomo/constant"
|
||||
"github.com/metacubex/mihomo/listener/autoredir"
|
||||
LC "github.com/metacubex/mihomo/listener/config"
|
||||
"github.com/metacubex/mihomo/listener/http"
|
||||
"github.com/metacubex/mihomo/listener/mixed"
|
||||
@ -49,9 +47,6 @@ var (
|
||||
shadowSocksListener C.MultiAddrListener
|
||||
vmessListener *sing_vmess.Listener
|
||||
tuicListener *tuic.Listener
|
||||
autoRedirListener *autoredir.Listener
|
||||
autoRedirProgram *ebpf.TcEBpfProgram
|
||||
tcProgram *ebpf.TcEBpfProgram
|
||||
|
||||
// lock for recreate function
|
||||
socksMux sync.Mutex
|
||||
@ -540,95 +535,6 @@ func ReCreateTun(tunConf LC.Tun, tunnel C.Tunnel) {
|
||||
log.Infoln("[TUN] Tun adapter listening at: %s", tunLister.Address())
|
||||
}
|
||||
|
||||
func ReCreateRedirToTun(ifaceNames []string) {
|
||||
tcMux.Lock()
|
||||
defer tcMux.Unlock()
|
||||
|
||||
nicArr := ifaceNames
|
||||
slices.Sort(nicArr)
|
||||
nicArr = slices.Compact(nicArr)
|
||||
|
||||
if tcProgram != nil {
|
||||
tcProgram.Close()
|
||||
tcProgram = nil
|
||||
}
|
||||
|
||||
if len(nicArr) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
tunConf := GetTunConf()
|
||||
|
||||
if !tunConf.Enable {
|
||||
return
|
||||
}
|
||||
|
||||
program, err := ebpf.NewTcEBpfProgram(nicArr, tunConf.Device)
|
||||
if err != nil {
|
||||
log.Errorln("Attached tc ebpf program error: %v", err)
|
||||
return
|
||||
}
|
||||
tcProgram = program
|
||||
|
||||
log.Infoln("Attached tc ebpf program to interfaces %v", tcProgram.RawNICs())
|
||||
}
|
||||
|
||||
func ReCreateAutoRedir(ifaceNames []string, tunnel C.Tunnel) {
|
||||
autoRedirMux.Lock()
|
||||
defer autoRedirMux.Unlock()
|
||||
|
||||
var err error
|
||||
defer func() {
|
||||
if err != nil {
|
||||
if autoRedirListener != nil {
|
||||
_ = autoRedirListener.Close()
|
||||
autoRedirListener = nil
|
||||
}
|
||||
if autoRedirProgram != nil {
|
||||
autoRedirProgram.Close()
|
||||
autoRedirProgram = nil
|
||||
}
|
||||
log.Errorln("Start auto redirect server error: %s", err.Error())
|
||||
}
|
||||
}()
|
||||
|
||||
nicArr := ifaceNames
|
||||
slices.Sort(nicArr)
|
||||
nicArr = slices.Compact(nicArr)
|
||||
|
||||
if autoRedirListener != nil && autoRedirProgram != nil {
|
||||
_ = autoRedirListener.Close()
|
||||
autoRedirProgram.Close()
|
||||
autoRedirListener = nil
|
||||
autoRedirProgram = nil
|
||||
}
|
||||
|
||||
if len(nicArr) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
defaultRouteInterfaceName, err := ebpf.GetAutoDetectInterface()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
addr := genAddr("*", C.TcpAutoRedirPort, true)
|
||||
|
||||
autoRedirListener, err = autoredir.New(addr, tunnel)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
autoRedirProgram, err = ebpf.NewRedirEBpfProgram(nicArr, autoRedirListener.TCPAddr().Port(), defaultRouteInterfaceName)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
autoRedirListener.SetLookupFunc(autoRedirProgram.Lookup)
|
||||
|
||||
log.Infoln("Auto redirect proxy listening at: %s, attached tc ebpf program to interfaces %v", autoRedirListener.Address(), autoRedirProgram.RawNICs())
|
||||
}
|
||||
|
||||
func PatchTunnel(tunnels []LC.Tunnel, tunnel C.Tunnel) {
|
||||
tunnelMux.Lock()
|
||||
defer tunnelMux.Unlock()
|
||||
|
12
main.go
12
main.go
@ -120,17 +120,7 @@ func main() {
|
||||
}
|
||||
|
||||
if C.GeoAutoUpdate {
|
||||
updater.RegisterGeoUpdater(func() {
|
||||
cfg, err := executor.ParseWithPath(C.Path.Config())
|
||||
if err != nil {
|
||||
log.Errorln("[GEO] update GEO databases failed: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
log.Warnln("[GEO] update GEO databases success, applying config")
|
||||
|
||||
executor.ApplyConfig(cfg, false)
|
||||
})
|
||||
updater.RegisterGeoUpdater()
|
||||
}
|
||||
|
||||
defer executor.Shutdown()
|
||||
|
@ -1,6 +1,7 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
@ -14,12 +15,11 @@ import (
|
||||
|
||||
type GEOIP struct {
|
||||
*Base
|
||||
country string
|
||||
adapter string
|
||||
noResolveIP bool
|
||||
isSourceIP bool
|
||||
geoIPMatcher *router.GeoIPMatcher
|
||||
recodeSize int
|
||||
country string
|
||||
adapter string
|
||||
noResolveIP bool
|
||||
isSourceIP bool
|
||||
geodata bool
|
||||
}
|
||||
|
||||
var _ C.Rule = (*GEOIP)(nil)
|
||||
@ -78,7 +78,11 @@ func (g *GEOIP) Match(metadata *C.Metadata) (bool, string) {
|
||||
return false, g.adapter
|
||||
}
|
||||
|
||||
match := g.geoIPMatcher.Match(ip)
|
||||
matcher, err := g.GetIPMatcher()
|
||||
if err != nil {
|
||||
return false, ""
|
||||
}
|
||||
match := matcher.Match(ip)
|
||||
if match && !g.isSourceIP {
|
||||
metadata.DstGeoIP = append(metadata.DstGeoIP, g.country)
|
||||
}
|
||||
@ -101,12 +105,22 @@ func (g *GEOIP) GetCountry() string {
|
||||
return g.country
|
||||
}
|
||||
|
||||
func (g *GEOIP) GetIPMatcher() *router.GeoIPMatcher {
|
||||
return g.geoIPMatcher
|
||||
func (g *GEOIP) GetIPMatcher() (router.IPMatcher, error) {
|
||||
if g.geodata {
|
||||
geoIPMatcher, err := geodata.LoadGeoIPMatcher(g.country)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("[GeoIP] %w", err)
|
||||
}
|
||||
return geoIPMatcher, nil
|
||||
}
|
||||
return nil, errors.New("geoip country not set")
|
||||
}
|
||||
|
||||
func (g *GEOIP) GetRecodeSize() int {
|
||||
return g.recodeSize
|
||||
if matcher, err := g.GetIPMatcher(); err == nil {
|
||||
return matcher.Count()
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func NewGEOIP(country string, adapter string, isSrc, noResolveIP bool) (*GEOIP, error) {
|
||||
@ -116,31 +130,23 @@ func NewGEOIP(country string, adapter string, isSrc, noResolveIP bool) (*GEOIP,
|
||||
}
|
||||
country = strings.ToLower(country)
|
||||
|
||||
geoip := &GEOIP{
|
||||
Base: &Base{},
|
||||
country: country,
|
||||
adapter: adapter,
|
||||
noResolveIP: noResolveIP,
|
||||
isSourceIP: isSrc,
|
||||
}
|
||||
if !C.GeodataMode || country == "lan" {
|
||||
geoip := &GEOIP{
|
||||
Base: &Base{},
|
||||
country: country,
|
||||
adapter: adapter,
|
||||
noResolveIP: noResolveIP,
|
||||
isSourceIP: isSrc,
|
||||
}
|
||||
return geoip, nil
|
||||
}
|
||||
|
||||
geoIPMatcher, size, err := geodata.LoadGeoIPMatcher(country)
|
||||
geoip.geodata = true
|
||||
geoIPMatcher, err := geoip.GetIPMatcher() // test load
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("[GeoIP] %w", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
log.Infoln("Start initial GeoIP rule %s => %s, records: %d", country, adapter, size)
|
||||
geoip := &GEOIP{
|
||||
Base: &Base{},
|
||||
country: country,
|
||||
adapter: adapter,
|
||||
noResolveIP: noResolveIP,
|
||||
isSourceIP: isSrc,
|
||||
geoIPMatcher: geoIPMatcher,
|
||||
recodeSize: size,
|
||||
}
|
||||
log.Infoln("Finished initial GeoIP rule %s => %s, records: %d", country, adapter, geoIPMatcher.Count())
|
||||
return geoip, nil
|
||||
}
|
||||
|
@ -15,7 +15,6 @@ type GEOSITE struct {
|
||||
*Base
|
||||
country string
|
||||
adapter string
|
||||
matcher router.DomainMatcher
|
||||
recodeSize int
|
||||
}
|
||||
|
||||
@ -28,7 +27,11 @@ func (gs *GEOSITE) Match(metadata *C.Metadata) (bool, string) {
|
||||
if len(domain) == 0 {
|
||||
return false, ""
|
||||
}
|
||||
return gs.matcher.ApplyDomain(domain), gs.adapter
|
||||
matcher, err := gs.GetDomainMatcher()
|
||||
if err != nil {
|
||||
return false, ""
|
||||
}
|
||||
return matcher.ApplyDomain(domain), gs.adapter
|
||||
}
|
||||
|
||||
func (gs *GEOSITE) Adapter() string {
|
||||
@ -39,12 +42,19 @@ func (gs *GEOSITE) Payload() string {
|
||||
return gs.country
|
||||
}
|
||||
|
||||
func (gs *GEOSITE) GetDomainMatcher() router.DomainMatcher {
|
||||
return gs.matcher
|
||||
func (gs *GEOSITE) GetDomainMatcher() (router.DomainMatcher, error) {
|
||||
matcher, err := geodata.LoadGeoSiteMatcher(gs.country)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("load GeoSite data error, %w", err)
|
||||
}
|
||||
return matcher, nil
|
||||
}
|
||||
|
||||
func (gs *GEOSITE) GetRecodeSize() int {
|
||||
return gs.recodeSize
|
||||
if matcher, err := gs.GetDomainMatcher(); err == nil {
|
||||
return matcher.Count()
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func NewGEOSITE(country string, adapter string) (*GEOSITE, error) {
|
||||
@ -53,21 +63,19 @@ func NewGEOSITE(country string, adapter string) (*GEOSITE, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
matcher, size, err := geodata.LoadGeoSiteMatcher(country)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("load GeoSite data error, %s", err.Error())
|
||||
}
|
||||
|
||||
log.Infoln("Start initial GeoSite rule %s => %s, records: %d", country, adapter, size)
|
||||
|
||||
geoSite := &GEOSITE{
|
||||
Base: &Base{},
|
||||
country: country,
|
||||
adapter: adapter,
|
||||
matcher: matcher,
|
||||
recodeSize: size,
|
||||
Base: &Base{},
|
||||
country: country,
|
||||
adapter: adapter,
|
||||
}
|
||||
|
||||
matcher, err := geoSite.GetDomainMatcher() // test load
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
log.Infoln("Finished initial GeoSite rule %s => %s, records: %d", country, adapter, matcher.Count())
|
||||
|
||||
return geoSite, nil
|
||||
}
|
||||
|
||||
|
40
rules/provider/domain_set.go
Normal file
40
rules/provider/domain_set.go
Normal file
@ -0,0 +1,40 @@
|
||||
package provider
|
||||
|
||||
import (
|
||||
"github.com/metacubex/mihomo/component/trie"
|
||||
C "github.com/metacubex/mihomo/constant"
|
||||
)
|
||||
|
||||
type DomainSet struct {
|
||||
*domainStrategy
|
||||
adapter string
|
||||
}
|
||||
|
||||
func (d *DomainSet) ProviderNames() []string {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *DomainSet) RuleType() C.RuleType {
|
||||
return C.DomainSet
|
||||
}
|
||||
|
||||
func (d *DomainSet) Match(metadata *C.Metadata) (bool, string) {
|
||||
return d.domainStrategy.Match(metadata), d.adapter
|
||||
}
|
||||
|
||||
func (d *DomainSet) Adapter() string {
|
||||
return d.adapter
|
||||
}
|
||||
|
||||
func (d *DomainSet) Payload() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func NewDomainSet(domainSet *trie.DomainSet, adapter string) *DomainSet {
|
||||
return &DomainSet{
|
||||
domainStrategy: &domainStrategy{domainSet: domainSet},
|
||||
adapter: adapter,
|
||||
}
|
||||
}
|
||||
|
||||
var _ C.Rule = (*DomainSet)(nil)
|
40
rules/provider/ipcidr_set.go
Normal file
40
rules/provider/ipcidr_set.go
Normal file
@ -0,0 +1,40 @@
|
||||
package provider
|
||||
|
||||
import (
|
||||
"github.com/metacubex/mihomo/component/cidr"
|
||||
C "github.com/metacubex/mihomo/constant"
|
||||
)
|
||||
|
||||
type IpCidrSet struct {
|
||||
*ipcidrStrategy
|
||||
adapter string
|
||||
}
|
||||
|
||||
func (d *IpCidrSet) ProviderNames() []string {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *IpCidrSet) RuleType() C.RuleType {
|
||||
return C.IpCidrSet
|
||||
}
|
||||
|
||||
func (d *IpCidrSet) Match(metadata *C.Metadata) (bool, string) {
|
||||
return d.ipcidrStrategy.Match(metadata), d.adapter
|
||||
}
|
||||
|
||||
func (d *IpCidrSet) Adapter() string {
|
||||
return d.adapter
|
||||
}
|
||||
|
||||
func (d *IpCidrSet) Payload() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func NewIpCidrSet(cidrSet *cidr.IpCidrSet, adapter string) *IpCidrSet {
|
||||
return &IpCidrSet{
|
||||
ipcidrStrategy: &ipcidrStrategy{cidrSet: cidrSet},
|
||||
adapter: adapter,
|
||||
}
|
||||
}
|
||||
|
||||
var _ C.Rule = (*IpCidrSet)(nil)
|
@ -22,7 +22,6 @@ require (
|
||||
github.com/andybalholm/brotli v1.0.5 // indirect
|
||||
github.com/bahlo/generic-list-go v0.2.0 // indirect
|
||||
github.com/buger/jsonparser v1.1.1 // indirect
|
||||
github.com/cilium/ebpf v0.12.3 // indirect
|
||||
github.com/coreos/go-iptables v0.7.0 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/distribution/reference v0.5.0 // indirect
|
||||
|
@ -19,8 +19,6 @@ github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx2
|
||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||
github.com/cilium/ebpf v0.12.3 h1:8ht6F9MquybnY97at+VDZb3eQQr8ev79RueWeVaEcG4=
|
||||
github.com/cilium/ebpf v0.12.3/go.mod h1:TctK1ivibvI3znr66ljgi4hqOT8EYQjz1KWBfb1UVgM=
|
||||
github.com/coreos/go-iptables v0.7.0 h1:XWM3V+MPRr5/q51NuWSgU0fqMad64Zyxs8ZUoMsamr8=
|
||||
github.com/coreos/go-iptables v0.7.0/go.mod h1:Qe8Bv2Xik5FyTXwgIbLAnv2sWSBmvWdFETJConOQ//Q=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
|
Loading…
Reference in New Issue
Block a user