Compare commits

..

39 Commits

Author SHA1 Message Date
github-actions[bot]
0128a0bb1f Merge branch 'Alpha' into Meta 2024-07-28 05:50:11 +00:00
github-actions[bot]
4277dc6eab Merge branch 'Alpha' into Meta 2024-07-01 15:05:18 +00:00
github-actions[bot]
48e481d0a2 Merge branch 'Alpha' into Meta 2024-05-19 08:50:40 +00:00
Larvan2
81947304bc Merge branch 'refs/heads/Alpha' into Meta 2024-04-29 14:22:39 +08:00
Larvan2
e27d7c010f Merge branch 'Alpha' into Meta 2024-03-29 19:50:16 +08:00
Larvan2
582e94ff4d Merge branch 'Alpha' into Meta 2024-03-27 18:43:06 +08:00
Larvan2
a034421a58 Merge branch 'Alpha' into Meta 2024-02-02 20:15:27 +08:00
Larvan2
dc2108c174 Merge branch 'Alpha' into Meta 2024-01-02 15:21:37 +08:00
wwqgtxx
bda71dbfa1 Merge branch 'Alpha' into Meta 2023-12-03 08:44:30 +08:00
wwqgtxx
253b023442 Merge branch 'Alpha' into Meta 2023-11-03 21:59:44 +08:00
wwqgtxx
8293b7fdae Merge branch 'Beta' into Meta
# Conflicts:
#	.github/workflows/docker.yaml
#	.github/workflows/prerelease.yml
2023-02-19 01:25:34 +08:00
wwqgtxx
0ba415866e Merge branch 'Alpha' into Beta 2023-02-19 01:25:01 +08:00
Larvan2
53b41ca166 Chore: Add action for deleting old workflow 2023-01-30 18:17:22 +08:00
metacubex
8a75f78e63 chore: adjust Dockerfile 2023-01-12 02:24:12 +08:00
metacubex
d9692c6366 Merge branch 'Beta' into Meta 2023-01-12 02:15:14 +08:00
metacubex
f4b0062dfc Merge branch 'Alpha' into Beta 2023-01-12 02:14:49 +08:00
metacubex
b9ffc82e53 Merge branch 'Beta' into Meta 2023-01-12 01:33:56 +08:00
metacubex
78aaea6a45 Merge branch 'Alpha' into Beta 2023-01-12 01:33:16 +08:00
cubemaze
3645fbf161 Merge pull request #327 from Rasphino/Meta
Update flake.nix hash
2023-01-08 00:29:29 +08:00
Rasphino
a1d0f22132 fix: update flake.nix hash 2023-01-07 23:38:32 +08:00
metacubex
fa73b0f4bf Merge remote-tracking branch 'origin/Beta' into Meta 2023-01-01 19:41:36 +08:00
metacubex
3b76a8b839 Merge remote-tracking branch 'origin/Alpha' into Beta 2023-01-01 19:40:36 +08:00
cubemaze
667f42dcdc Merge pull request #282 from tdjnodj/Meta
Update README.md
2022-12-03 17:23:51 +08:00
tdjnodj
dfbe09860f Update README.md 2022-12-03 17:17:08 +08:00
metacubex
9e20f9c26a chore: update dependencies 2022-11-28 20:33:10 +08:00
Skimmle
f968d0cb82 chore: update github action 2022-11-26 20:16:12 +08:00
metacubex
2ad84f4379 Merge branch 'Beta' into Meta 2022-11-02 18:08:22 +08:00
metacubex
c7aa16426f Merge branch 'Alpha' into Beta 2022-11-02 18:07:29 +08:00
Skyxim
5987f8e3b5 Merge branch 'Beta' into Meta 2022-08-29 13:08:29 +08:00
Skyxim
3a8eb72de2 Merge branch 'Alpha' into Beta 2022-08-29 13:08:22 +08:00
zhudan
33abbdfd24 Merge pull request #174 from MetaCubeX/Alpha
Alpha
2022-08-29 11:24:07 +08:00
zhudan
0703d6cbff Merge pull request #173 from MetaCubeX/Alpha
Alpha
2022-08-29 11:22:14 +08:00
Skyxim
10d2d14938 Merge branch 'Beta' into Meta
# Conflicts:
#	rules/provider/classical_strategy.go
2022-07-02 10:41:41 +08:00
wwqgtxx
691cf1d8d6 Merge pull request #94 from bash99/Meta
Update README.md
2022-06-15 19:15:51 +08:00
bash99
d1decb8e58 Update README.md
add permissions for systemctl services
clash-dashboard change to updated one
2022-06-15 14:00:05 +08:00
Skyxim
7d04904109 fix: leak dns when domain in hosts list 2022-06-11 18:51:26 +08:00
Skyxim
a5acd3aa97 refactor: clear linkname,reduce cycle dependencies,transport init geosite function 2022-06-11 18:51:22 +08:00
Skyxim
eea9a12560 fix: 规则匹配默认策略组返回错误 2022-06-09 14:18:35 +08:00
adlyq
0a4570b55c fix: group filter touch provider 2022-06-09 14:18:29 +08:00
204 changed files with 10076 additions and 5321 deletions

View File

@@ -0,0 +1,54 @@
diff --git a/src/syscall/exec_windows.go b/src/syscall/exec_windows.go
index 06e684c7116b4..b311a5c74684b 100644
--- a/src/syscall/exec_windows.go
+++ b/src/syscall/exec_windows.go
@@ -319,17 +319,6 @@ func StartProcess(argv0 string, argv []string, attr *ProcAttr) (pid int, handle
}
}
- var maj, min, build uint32
- rtlGetNtVersionNumbers(&maj, &min, &build)
- isWin7 := maj < 6 || (maj == 6 && min <= 1)
- // NT kernel handles are divisible by 4, with the bottom 3 bits left as
- // a tag. The fully set tag correlates with the types of handles we're
- // concerned about here. Except, the kernel will interpret some
- // special handle values, like -1, -2, and so forth, so kernelbase.dll
- // checks to see that those bottom three bits are checked, but that top
- // bit is not checked.
- isLegacyWin7ConsoleHandle := func(handle Handle) bool { return isWin7 && handle&0x10000003 == 3 }
-
p, _ := GetCurrentProcess()
parentProcess := p
if sys.ParentProcess != 0 {
@@ -338,15 +327,7 @@ func StartProcess(argv0 string, argv []string, attr *ProcAttr) (pid int, handle
fd := make([]Handle, len(attr.Files))
for i := range attr.Files {
if attr.Files[i] > 0 {
- destinationProcessHandle := parentProcess
-
- // On Windows 7, console handles aren't real handles, and can only be duplicated
- // into the current process, not a parent one, which amounts to the same thing.
- if parentProcess != p && isLegacyWin7ConsoleHandle(Handle(attr.Files[i])) {
- destinationProcessHandle = p
- }
-
- err := DuplicateHandle(p, Handle(attr.Files[i]), destinationProcessHandle, &fd[i], 0, true, DUPLICATE_SAME_ACCESS)
+ err := DuplicateHandle(p, Handle(attr.Files[i]), parentProcess, &fd[i], 0, true, DUPLICATE_SAME_ACCESS)
if err != nil {
return 0, 0, err
}
@@ -377,14 +358,6 @@ func StartProcess(argv0 string, argv []string, attr *ProcAttr) (pid int, handle
fd = append(fd, sys.AdditionalInheritedHandles...)
- // On Windows 7, console handles aren't real handles, so don't pass them
- // through to PROC_THREAD_ATTRIBUTE_HANDLE_LIST.
- for i := range fd {
- if isLegacyWin7ConsoleHandle(fd[i]) {
- fd[i] = 0
- }
- }
-
// The presence of a NULL handle in the list is enough to cause PROC_THREAD_ATTRIBUTE_HANDLE_LIST
// to treat the entire list as empty, so remove NULL handles.
j := 0

View File

@@ -0,0 +1,158 @@
diff --git a/src/crypto/rand/rand.go b/src/crypto/rand/rand.go
index 62738e2cb1a7d..d0dcc7cc71fc0 100644
--- a/src/crypto/rand/rand.go
+++ b/src/crypto/rand/rand.go
@@ -15,7 +15,7 @@ import "io"
// available, /dev/urandom otherwise.
// On OpenBSD and macOS, Reader uses getentropy(2).
// On other Unix-like systems, Reader reads from /dev/urandom.
-// On Windows systems, Reader uses the RtlGenRandom API.
+// On Windows systems, Reader uses the ProcessPrng API.
// On JS/Wasm, Reader uses the Web Crypto API.
// On WASIP1/Wasm, Reader uses random_get from wasi_snapshot_preview1.
var Reader io.Reader
diff --git a/src/crypto/rand/rand_windows.go b/src/crypto/rand/rand_windows.go
index 6c0655c72b692..7380f1f0f1e6e 100644
--- a/src/crypto/rand/rand_windows.go
+++ b/src/crypto/rand/rand_windows.go
@@ -15,11 +15,8 @@ func init() { Reader = &rngReader{} }
type rngReader struct{}
-func (r *rngReader) Read(b []byte) (n int, err error) {
- // RtlGenRandom only returns 1<<32-1 bytes at a time. We only read at
- // most 1<<31-1 bytes at a time so that this works the same on 32-bit
- // and 64-bit systems.
- if err := batched(windows.RtlGenRandom, 1<<31-1)(b); err != nil {
+func (r *rngReader) Read(b []byte) (int, error) {
+ if err := windows.ProcessPrng(b); err != nil {
return 0, err
}
return len(b), nil
diff --git a/src/internal/syscall/windows/syscall_windows.go b/src/internal/syscall/windows/syscall_windows.go
index ab4ad2ec64108..5854ca60b5cef 100644
--- a/src/internal/syscall/windows/syscall_windows.go
+++ b/src/internal/syscall/windows/syscall_windows.go
@@ -373,7 +373,7 @@ func ErrorLoadingGetTempPath2() error {
//sys DestroyEnvironmentBlock(block *uint16) (err error) = userenv.DestroyEnvironmentBlock
//sys CreateEvent(eventAttrs *SecurityAttributes, manualReset uint32, initialState uint32, name *uint16) (handle syscall.Handle, err error) = kernel32.CreateEventW
-//sys RtlGenRandom(buf []byte) (err error) = advapi32.SystemFunction036
+//sys ProcessPrng(buf []byte) (err error) = bcryptprimitives.ProcessPrng
type FILE_ID_BOTH_DIR_INFO struct {
NextEntryOffset uint32
diff --git a/src/internal/syscall/windows/zsyscall_windows.go b/src/internal/syscall/windows/zsyscall_windows.go
index e3f6d8d2a2208..5a587ad4f146c 100644
--- a/src/internal/syscall/windows/zsyscall_windows.go
+++ b/src/internal/syscall/windows/zsyscall_windows.go
@@ -37,13 +37,14 @@ func errnoErr(e syscall.Errno) error {
}
var (
- modadvapi32 = syscall.NewLazyDLL(sysdll.Add("advapi32.dll"))
- modiphlpapi = syscall.NewLazyDLL(sysdll.Add("iphlpapi.dll"))
- modkernel32 = syscall.NewLazyDLL(sysdll.Add("kernel32.dll"))
- modnetapi32 = syscall.NewLazyDLL(sysdll.Add("netapi32.dll"))
- modpsapi = syscall.NewLazyDLL(sysdll.Add("psapi.dll"))
- moduserenv = syscall.NewLazyDLL(sysdll.Add("userenv.dll"))
- modws2_32 = syscall.NewLazyDLL(sysdll.Add("ws2_32.dll"))
+ modadvapi32 = syscall.NewLazyDLL(sysdll.Add("advapi32.dll"))
+ modbcryptprimitives = syscall.NewLazyDLL(sysdll.Add("bcryptprimitives.dll"))
+ modiphlpapi = syscall.NewLazyDLL(sysdll.Add("iphlpapi.dll"))
+ modkernel32 = syscall.NewLazyDLL(sysdll.Add("kernel32.dll"))
+ modnetapi32 = syscall.NewLazyDLL(sysdll.Add("netapi32.dll"))
+ modpsapi = syscall.NewLazyDLL(sysdll.Add("psapi.dll"))
+ moduserenv = syscall.NewLazyDLL(sysdll.Add("userenv.dll"))
+ modws2_32 = syscall.NewLazyDLL(sysdll.Add("ws2_32.dll"))
procAdjustTokenPrivileges = modadvapi32.NewProc("AdjustTokenPrivileges")
procDuplicateTokenEx = modadvapi32.NewProc("DuplicateTokenEx")
@@ -55,7 +56,7 @@ var (
procQueryServiceStatus = modadvapi32.NewProc("QueryServiceStatus")
procRevertToSelf = modadvapi32.NewProc("RevertToSelf")
procSetTokenInformation = modadvapi32.NewProc("SetTokenInformation")
- procSystemFunction036 = modadvapi32.NewProc("SystemFunction036")
+ procProcessPrng = modbcryptprimitives.NewProc("ProcessPrng")
procGetAdaptersAddresses = modiphlpapi.NewProc("GetAdaptersAddresses")
procCreateEventW = modkernel32.NewProc("CreateEventW")
procGetACP = modkernel32.NewProc("GetACP")
@@ -179,12 +180,12 @@ func SetTokenInformation(tokenHandle syscall.Token, tokenInformationClass uint32
return
}
-func RtlGenRandom(buf []byte) (err error) {
+func ProcessPrng(buf []byte) (err error) {
var _p0 *byte
if len(buf) > 0 {
_p0 = &buf[0]
}
- r1, _, e1 := syscall.Syscall(procSystemFunction036.Addr(), 2, uintptr(unsafe.Pointer(_p0)), uintptr(len(buf)), 0)
+ r1, _, e1 := syscall.Syscall(procProcessPrng.Addr(), 2, uintptr(unsafe.Pointer(_p0)), uintptr(len(buf)), 0)
if r1 == 0 {
err = errnoErr(e1)
}
diff --git a/src/runtime/os_windows.go b/src/runtime/os_windows.go
index 8ca8d7790909e..3772a864b2ff4 100644
--- a/src/runtime/os_windows.go
+++ b/src/runtime/os_windows.go
@@ -127,15 +127,8 @@ var (
_WriteFile,
_ stdFunction
- // Use RtlGenRandom to generate cryptographically random data.
- // This approach has been recommended by Microsoft (see issue
- // 15589 for details).
- // The RtlGenRandom is not listed in advapi32.dll, instead
- // RtlGenRandom function can be found by searching for SystemFunction036.
- // Also some versions of Mingw cannot link to SystemFunction036
- // when building executable as Cgo. So load SystemFunction036
- // manually during runtime startup.
- _RtlGenRandom stdFunction
+ // Use ProcessPrng to generate cryptographically random data.
+ _ProcessPrng stdFunction
// Load ntdll.dll manually during startup, otherwise Mingw
// links wrong printf function to cgo executable (see issue
@@ -151,11 +144,11 @@ var (
)
var (
- advapi32dll = [...]uint16{'a', 'd', 'v', 'a', 'p', 'i', '3', '2', '.', 'd', 'l', 'l', 0}
- ntdlldll = [...]uint16{'n', 't', 'd', 'l', 'l', '.', 'd', 'l', 'l', 0}
- powrprofdll = [...]uint16{'p', 'o', 'w', 'r', 'p', 'r', 'o', 'f', '.', 'd', 'l', 'l', 0}
- winmmdll = [...]uint16{'w', 'i', 'n', 'm', 'm', '.', 'd', 'l', 'l', 0}
- ws2_32dll = [...]uint16{'w', 's', '2', '_', '3', '2', '.', 'd', 'l', 'l', 0}
+ bcryptprimitivesdll = [...]uint16{'b', 'c', 'r', 'y', 'p', 't', 'p', 'r', 'i', 'm', 'i', 't', 'i', 'v', 'e', 's', '.', 'd', 'l', 'l', 0}
+ ntdlldll = [...]uint16{'n', 't', 'd', 'l', 'l', '.', 'd', 'l', 'l', 0}
+ powrprofdll = [...]uint16{'p', 'o', 'w', 'r', 'p', 'r', 'o', 'f', '.', 'd', 'l', 'l', 0}
+ winmmdll = [...]uint16{'w', 'i', 'n', 'm', 'm', '.', 'd', 'l', 'l', 0}
+ ws2_32dll = [...]uint16{'w', 's', '2', '_', '3', '2', '.', 'd', 'l', 'l', 0}
)
// Function to be called by windows CreateThread
@@ -251,11 +244,11 @@ func windowsLoadSystemLib(name []uint16) uintptr {
}
func loadOptionalSyscalls() {
- a32 := windowsLoadSystemLib(advapi32dll[:])
- if a32 == 0 {
- throw("advapi32.dll not found")
+ bcryptPrimitives := windowsLoadSystemLib(bcryptprimitivesdll[:])
+ if bcryptPrimitives == 0 {
+ throw("bcryptprimitives.dll not found")
}
- _RtlGenRandom = windowsFindfunc(a32, []byte("SystemFunction036\000"))
+ _ProcessPrng = windowsFindfunc(bcryptPrimitives, []byte("ProcessPrng\000"))
n32 := windowsLoadSystemLib(ntdlldll[:])
if n32 == 0 {
@@ -531,7 +524,7 @@ func osinit() {
//go:nosplit
func readRandom(r []byte) int {
n := 0
- if stdcall2(_RtlGenRandom, uintptr(unsafe.Pointer(&r[0])), uintptr(len(r)))&0xff != 0 {
+ if stdcall2(_ProcessPrng, uintptr(unsafe.Pointer(&r[0])), uintptr(len(r)))&0xff != 0 {
n = len(r)
}
return n

View File

@@ -0,0 +1,162 @@
diff --git a/src/net/hook_windows.go b/src/net/hook_windows.go
index ab8656cbbf343..28c49cc6de7e7 100644
--- a/src/net/hook_windows.go
+++ b/src/net/hook_windows.go
@@ -14,7 +14,6 @@ var (
testHookDialChannel = func() { time.Sleep(time.Millisecond) } // see golang.org/issue/5349
// Placeholders for socket system calls.
- socketFunc func(int, int, int) (syscall.Handle, error) = syscall.Socket
wsaSocketFunc func(int32, int32, int32, *syscall.WSAProtocolInfo, uint32, uint32) (syscall.Handle, error) = windows.WSASocket
connectFunc func(syscall.Handle, syscall.Sockaddr) error = syscall.Connect
listenFunc func(syscall.Handle, int) error = syscall.Listen
diff --git a/src/net/internal/socktest/main_test.go b/src/net/internal/socktest/main_test.go
index 0197feb3f199a..967ce6795aedb 100644
--- a/src/net/internal/socktest/main_test.go
+++ b/src/net/internal/socktest/main_test.go
@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
-//go:build !js && !plan9 && !wasip1
+//go:build !js && !plan9 && !wasip1 && !windows
package socktest_test
diff --git a/src/net/internal/socktest/main_windows_test.go b/src/net/internal/socktest/main_windows_test.go
deleted file mode 100644
index df1cb97784b51..0000000000000
--- a/src/net/internal/socktest/main_windows_test.go
+++ /dev/null
@@ -1,22 +0,0 @@
-// Copyright 2015 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-package socktest_test
-
-import "syscall"
-
-var (
- socketFunc func(int, int, int) (syscall.Handle, error)
- closeFunc func(syscall.Handle) error
-)
-
-func installTestHooks() {
- socketFunc = sw.Socket
- closeFunc = sw.Closesocket
-}
-
-func uninstallTestHooks() {
- socketFunc = syscall.Socket
- closeFunc = syscall.Closesocket
-}
diff --git a/src/net/internal/socktest/sys_windows.go b/src/net/internal/socktest/sys_windows.go
index 8c1c862f33c9b..1c42e5c7f34b7 100644
--- a/src/net/internal/socktest/sys_windows.go
+++ b/src/net/internal/socktest/sys_windows.go
@@ -9,38 +9,6 @@ import (
"syscall"
)
-// Socket wraps syscall.Socket.
-func (sw *Switch) Socket(family, sotype, proto int) (s syscall.Handle, err error) {
- sw.once.Do(sw.init)
-
- so := &Status{Cookie: cookie(family, sotype, proto)}
- sw.fmu.RLock()
- f, _ := sw.fltab[FilterSocket]
- sw.fmu.RUnlock()
-
- af, err := f.apply(so)
- if err != nil {
- return syscall.InvalidHandle, err
- }
- s, so.Err = syscall.Socket(family, sotype, proto)
- if err = af.apply(so); err != nil {
- if so.Err == nil {
- syscall.Closesocket(s)
- }
- return syscall.InvalidHandle, err
- }
-
- sw.smu.Lock()
- defer sw.smu.Unlock()
- if so.Err != nil {
- sw.stats.getLocked(so.Cookie).OpenFailed++
- return syscall.InvalidHandle, so.Err
- }
- nso := sw.addLocked(s, family, sotype, proto)
- sw.stats.getLocked(nso.Cookie).Opened++
- return s, nil
-}
-
// WSASocket wraps [syscall.WSASocket].
func (sw *Switch) WSASocket(family, sotype, proto int32, protinfo *syscall.WSAProtocolInfo, group uint32, flags uint32) (s syscall.Handle, err error) {
sw.once.Do(sw.init)
diff --git a/src/net/main_windows_test.go b/src/net/main_windows_test.go
index 07f21b72eb1fc..bc024c0bbd82d 100644
--- a/src/net/main_windows_test.go
+++ b/src/net/main_windows_test.go
@@ -8,7 +8,6 @@ import "internal/poll"
var (
// Placeholders for saving original socket system calls.
- origSocket = socketFunc
origWSASocket = wsaSocketFunc
origClosesocket = poll.CloseFunc
origConnect = connectFunc
@@ -18,7 +17,6 @@ var (
)
func installTestHooks() {
- socketFunc = sw.Socket
wsaSocketFunc = sw.WSASocket
poll.CloseFunc = sw.Closesocket
connectFunc = sw.Connect
@@ -28,7 +26,6 @@ func installTestHooks() {
}
func uninstallTestHooks() {
- socketFunc = origSocket
wsaSocketFunc = origWSASocket
poll.CloseFunc = origClosesocket
connectFunc = origConnect
diff --git a/src/net/sock_windows.go b/src/net/sock_windows.go
index fa11c7af2e727..5540135a2c43e 100644
--- a/src/net/sock_windows.go
+++ b/src/net/sock_windows.go
@@ -19,21 +19,6 @@ func maxListenerBacklog() int {
func sysSocket(family, sotype, proto int) (syscall.Handle, error) {
s, err := wsaSocketFunc(int32(family), int32(sotype), int32(proto),
nil, 0, windows.WSA_FLAG_OVERLAPPED|windows.WSA_FLAG_NO_HANDLE_INHERIT)
- if err == nil {
- return s, nil
- }
- // WSA_FLAG_NO_HANDLE_INHERIT flag is not supported on some
- // old versions of Windows, see
- // https://msdn.microsoft.com/en-us/library/windows/desktop/ms742212(v=vs.85).aspx
- // for details. Just use syscall.Socket, if windows.WSASocket failed.
-
- // See ../syscall/exec_unix.go for description of ForkLock.
- syscall.ForkLock.RLock()
- s, err = socketFunc(family, sotype, proto)
- if err == nil {
- syscall.CloseOnExec(s)
- }
- syscall.ForkLock.RUnlock()
if err != nil {
return syscall.InvalidHandle, os.NewSyscallError("socket", err)
}
diff --git a/src/syscall/exec_windows.go b/src/syscall/exec_windows.go
index 0a93bc0a80d4e..06e684c7116b4 100644
--- a/src/syscall/exec_windows.go
+++ b/src/syscall/exec_windows.go
@@ -14,6 +14,7 @@ import (
"unsafe"
)
+// ForkLock is not used on Windows.
var ForkLock sync.RWMutex
// EscapeArg rewrites command line argument s as prescribed

16
.github/workflows/Delete.yml vendored Normal file
View File

@@ -0,0 +1,16 @@
name: Delete old workflow runs
on:
schedule:
- cron: '0 0 1 * *'
# Run monthly, at 00:00 on the 1st day of month.
jobs:
del_runs:
runs-on: ubuntu-latest
steps:
- name: Delete workflow runs
uses: GitRML/delete-workflow-runs@main
with:
token: ${{ secrets.AUTH_PAT }}
repository: ${{ github.repository }}
retain_days: 30

View File

@@ -40,10 +40,10 @@ jobs:
- { goos: linux, goarch: arm, goarm: '5', output: armv5 } - { goos: linux, goarch: arm, goarm: '5', output: armv5 }
- { goos: linux, goarch: arm, goarm: '6', output: armv6 } - { goos: linux, goarch: arm, goarm: '6', output: armv6 }
- { goos: linux, goarch: arm, goarm: '7', output: armv7 } - { goos: linux, goarch: arm, goarm: '7', output: armv7 }
- { goos: linux, goarch: mips, gomips: hardfloat, output: mips-hardfloat } - { goos: linux, goarch: mips, mips: hardfloat, output: mips-hardfloat }
- { goos: linux, goarch: mips, gomips: softfloat, output: mips-softfloat } - { goos: linux, goarch: mips, mips: softfloat, output: mips-softfloat }
- { goos: linux, goarch: mipsle, gomips: hardfloat, output: mipsle-hardfloat } - { goos: linux, goarch: mipsle, mips: hardfloat, output: mipsle-hardfloat }
- { goos: linux, goarch: mipsle, gomips: softfloat, output: mipsle-softfloat } - { goos: linux, goarch: mipsle, mips: softfloat, output: mipsle-softfloat }
- { goos: linux, goarch: mips64, output: mips64 } - { goos: linux, goarch: mips64, output: mips64 }
- { goos: linux, goarch: mips64le, output: mips64le } - { goos: linux, goarch: mips64le, output: mips64le }
- { goos: linux, goarch: loong64, output: loong64-abi1, abi: '1' } - { goos: linux, goarch: loong64, output: loong64-abi1, abi: '1' }
@@ -67,12 +67,6 @@ jobs:
- { goos: android, goarch: arm, ndk: armv7a-linux-androideabi34, output: armv7 } - { goos: android, goarch: arm, ndk: armv7a-linux-androideabi34, output: armv7 }
- { goos: android, goarch: arm64, ndk: aarch64-linux-android34, output: arm64-v8 } - { goos: android, goarch: arm64, ndk: aarch64-linux-android34, output: arm64-v8 }
# Go 1.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 # Go 1.21 can revert commit `9e4385` to work on Windows 7
# https://github.com/golang/go/issues/64622#issuecomment-1847475161 # https://github.com/golang/go/issues/64622#issuecomment-1847475161
# (OR we can just use golang1.21.4 which unneeded any patch) # (OR we can just use golang1.21.4 which unneeded any patch)
@@ -85,11 +79,6 @@ jobs:
- { goos: windows, goarch: amd64, goamd64: v1, output: amd64-compatible-go120, goversion: '1.20' } - { goos: windows, goarch: amd64, goamd64: v1, output: amd64-compatible-go120, goversion: '1.20' }
- { goos: windows, goarch: amd64, goamd64: v3, output: amd64-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. # 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: arm64, output: arm64-go120, goversion: '1.20' }
- { goos: darwin, goarch: amd64, goamd64: v1, output: amd64-compatible-go120, goversion: '1.20' } - { goos: darwin, goarch: amd64, goamd64: v1, output: amd64-compatible-go120, goversion: '1.20' }
@@ -107,7 +96,7 @@ jobs:
if: ${{ matrix.jobs.goversion == '' && matrix.jobs.goarch != 'loong64' }} if: ${{ matrix.jobs.goversion == '' && matrix.jobs.goarch != 'loong64' }}
uses: actions/setup-go@v5 uses: actions/setup-go@v5
with: with:
go-version: '1.23' go-version: '1.22'
- name: Set up Go - name: Set up Go
if: ${{ matrix.jobs.goversion != '' && matrix.jobs.goarch != 'loong64' }} if: ${{ matrix.jobs.goversion != '' && matrix.jobs.goarch != 'loong64' }}
@@ -118,52 +107,31 @@ jobs:
- name: Set up Go1.22 loongarch abi1 - name: Set up Go1.22 loongarch abi1
if: ${{ matrix.jobs.goarch == 'loong64' && matrix.jobs.abi == '1' }} if: ${{ matrix.jobs.goarch == 'loong64' && matrix.jobs.abi == '1' }}
run: | run: |
wget -q https://github.com/MetaCubeX/loongarch64-golang/releases/download/1.22.4/go1.22.4.linux-amd64-abi1.tar.gz wget -q https://github.com/xishang0128/loongarch64-golang/releases/download/1.22.0/go1.22.0.linux-amd64-abi1.tar.gz
sudo tar zxf go1.22.4.linux-amd64-abi1.tar.gz -C /usr/local sudo tar zxf go1.22.0.linux-amd64-abi1.tar.gz -C /usr/local
echo "/usr/local/go/bin" >> $GITHUB_PATH echo "/usr/local/go/bin" >> $GITHUB_PATH
- name: Set up Go1.22 loongarch abi2 - name: Set up Go1.22 loongarch abi2
if: ${{ matrix.jobs.goarch == 'loong64' && matrix.jobs.abi == '2' }} if: ${{ matrix.jobs.goarch == 'loong64' && matrix.jobs.abi == '2' }}
run: | run: |
wget -q https://github.com/MetaCubeX/loongarch64-golang/releases/download/1.22.4/go1.22.4.linux-amd64-abi2.tar.gz wget -q https://github.com/xishang0128/loongarch64-golang/releases/download/1.22.0/go1.22.0.linux-amd64-abi2.tar.gz
sudo tar zxf go1.22.4.linux-amd64-abi2.tar.gz -C /usr/local sudo tar zxf go1.22.0.linux-amd64-abi2.tar.gz -C /usr/local
echo "/usr/local/go/bin" >> $GITHUB_PATH echo "/usr/local/go/bin" >> $GITHUB_PATH
# modify from https://github.com/restic/restic/issues/4636#issuecomment-1896455557
# this patch file only works on golang1.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"
# 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)
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 # modify from https://github.com/restic/restic/issues/4636#issuecomment-1896455557
# this patch file only works on golang1.22.x # this patch file only works on golang1.22.x
# that means after golang1.23 release it must be changed # that means after golang1.23 release it must be changed
# see: https://github.com/MetaCubeX/go/commits/release-branch.go1.22/
# revert: # revert:
# 693def151adff1af707d82d28f55dba81ceb08e1: "crypto/rand,runtime: switch RtlGenRandom for ProcessPrng" # 693def151adff1af707d82d28f55dba81ceb08e1: "crypto/rand,runtime: switch RtlGenRandom for ProcessPrng"
# 7c1157f9544922e96945196b47b95664b1e39108: "net: remove sysSocket fallback for Windows 7" # 7c1157f9544922e96945196b47b95664b1e39108: "net: remove sysSocket fallback for Windows 7"
# 48042aa09c2f878c4faa576948b07fe625c4707a: "syscall: remove Windows 7 console handle workaround" # 48042aa09c2f878c4faa576948b07fe625c4707a: "syscall: remove Windows 7 console handle workaround"
# a17d959debdb04cd550016a3501dd09d50cd62e7: "runtime: always use LoadLibraryEx to load system libraries"
- name: Revert Golang1.22 commit for Windows7/8 - name: Revert Golang1.22 commit for Windows7/8
if: ${{ matrix.jobs.goos == 'windows' && matrix.jobs.goversion == '1.22' }} if: ${{ matrix.jobs.goos == 'windows' && matrix.jobs.goversion == '' }}
run: | run: |
cd $(go env GOROOT) cd $(go env GOROOT)
curl https://github.com/MetaCubeX/go/commit/9779155f18b6556a034f7bb79fb7fb2aad1e26a9.diff | patch --verbose -p 1 patch --verbose -R -p 1 < $GITHUB_WORKSPACE/.github/patch_go122/693def151adff1af707d82d28f55dba81ceb08e1.diff
curl https://github.com/MetaCubeX/go/commit/ef0606261340e608017860b423ffae5c1ce78239.diff | patch --verbose -p 1 patch --verbose -R -p 1 < $GITHUB_WORKSPACE/.github/patch_go122/7c1157f9544922e96945196b47b95664b1e39108.diff
curl https://github.com/MetaCubeX/go/commit/7f83badcb925a7e743188041cb6e561fc9b5b642.diff | patch --verbose -p 1 patch --verbose -R -p 1 < $GITHUB_WORKSPACE/.github/patch_go122/48042aa09c2f878c4faa576948b07fe625c4707a.diff
curl https://github.com/MetaCubeX/go/commit/83ff9782e024cb328b690cbf0da4e7848a327f4f.diff | patch --verbose -p 1
# modify from https://github.com/restic/restic/issues/4636#issuecomment-1896455557 # modify from https://github.com/restic/restic/issues/4636#issuecomment-1896455557
- name: Revert Golang1.21 commit for Windows7/8 - name: Revert Golang1.21 commit for Windows7/8
@@ -187,14 +155,13 @@ jobs:
echo "BUILDTIME=$(date)" >> $GITHUB_ENV echo "BUILDTIME=$(date)" >> $GITHUB_ENV
echo "CGO_ENABLED=0" >> $GITHUB_ENV echo "CGO_ENABLED=0" >> $GITHUB_ENV
echo "BUILDTAG=-extldflags --static" >> $GITHUB_ENV echo "BUILDTAG=-extldflags --static" >> $GITHUB_ENV
echo "GOTOOLCHAIN=local" >> $GITHUB_ENV
- name: Setup NDK - name: Setup NDK
if: ${{ matrix.jobs.goos == 'android' }} if: ${{ matrix.jobs.goos == 'android' }}
uses: nttld/setup-ndk@v1 uses: nttld/setup-ndk@v1
id: setup-ndk id: setup-ndk
with: with:
ndk-version: r27 ndk-version: r26c
- name: Set NDK path - name: Set NDK path
if: ${{ matrix.jobs.goos == 'android' }} if: ${{ matrix.jobs.goos == 'android' }}
@@ -207,8 +174,6 @@ jobs:
if: ${{ matrix.jobs.test == 'test' }} if: ${{ matrix.jobs.test == 'test' }}
run: | run: |
go test ./... go test ./...
echo "---test with_gvisor---"
go test ./... -tags "with_gvisor" -count=1
- name: Update CA - name: Update CA
run: | run: |
@@ -221,10 +186,10 @@ jobs:
GOOS: ${{matrix.jobs.goos}} GOOS: ${{matrix.jobs.goos}}
GOARCH: ${{matrix.jobs.goarch}} GOARCH: ${{matrix.jobs.goarch}}
GOAMD64: ${{matrix.jobs.goamd64}} GOAMD64: ${{matrix.jobs.goamd64}}
GOARM: ${{matrix.jobs.goarm}} GOARM: ${{matrix.jobs.arm}}
GOMIPS: ${{matrix.jobs.gomips}} GOMIPS: ${{matrix.jobs.mips}}
run: | run: |
go env echo $CGO_ENABLED
go build -v -tags "with_gvisor" -trimpath -ldflags "${BUILDTAG} -X 'github.com/metacubex/mihomo/constant.Version=${VERSION}' -X 'github.com/metacubex/mihomo/constant.BuildTime=${BUILDTIME}' -w -s -buildid=" go build -v -tags "with_gvisor" -trimpath -ldflags "${BUILDTAG} -X 'github.com/metacubex/mihomo/constant.Version=${VERSION}' -X 'github.com/metacubex/mihomo/constant.BuildTime=${BUILDTIME}' -w -s -buildid="
if [ "${{matrix.jobs.goos}}" = "windows" ]; then if [ "${{matrix.jobs.goos}}" = "windows" ]; then
cp mihomo.exe mihomo-${{matrix.jobs.goos}}-${{matrix.jobs.output}}.exe cp mihomo.exe mihomo-${{matrix.jobs.goos}}-${{matrix.jobs.output}}.exe
@@ -387,18 +352,18 @@ jobs:
git fetch --tags git fetch --tags
echo "PREVERSION=$(git describe --tags --abbrev=0 HEAD)" >> $GITHUB_ENV echo "PREVERSION=$(git describe --tags --abbrev=0 HEAD)" >> $GITHUB_ENV
- name: Force push Alpha branch to Meta - name: Merge Alpha branch into Meta
run: | run: |
git config --global user.email "github-actions[bot]@users.noreply.github.com" git config --global user.email "github-actions[bot]@users.noreply.github.com"
git config --global user.name "github-actions[bot]" git config --global user.name "github-actions[bot]"
git fetch origin Alpha:Alpha git fetch origin Alpha:Alpha
git push origin Alpha:Meta --force git merge Alpha
git push origin Meta
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Tag the commit on Alpha - name: Tag the commit
run: | run: |
git checkout Alpha
git tag ${{ github.event.inputs.version }} git tag ${{ github.event.inputs.version }}
git push origin ${{ github.event.inputs.version }} git push origin ${{ github.event.inputs.version }}
env: env:

View File

@@ -13,7 +13,7 @@ WORKDIR /mihomo
COPY bin/ bin/ COPY bin/ bin/
RUN FILE_NAME=`sh file-name.sh` && echo $FILE_NAME && \ RUN FILE_NAME=`sh file-name.sh` && echo $FILE_NAME && \
FILE_NAME=`ls bin/ | egrep "$FILE_NAME.gz"|awk NR==1` && echo $FILE_NAME && \ FILE_NAME=`ls bin/ | egrep "$FILE_NAME.gz"|awk NR==1` && echo $FILE_NAME && \
mv bin/$FILE_NAME mihomo.gz && gzip -d mihomo.gz && chmod +x mihomo && echo "$FILE_NAME" > /mihomo-config/test mv bin/$FILE_NAME mihomo.gz && gzip -d mihomo.gz && echo "$FILE_NAME" > /mihomo-config/test
FROM alpine:latest FROM alpine:latest
LABEL org.opencontainers.image.source="https://github.com/MetaCubeX/mihomo" LABEL org.opencontainers.image.source="https://github.com/MetaCubeX/mihomo"
@@ -23,4 +23,5 @@ VOLUME ["/root/.config/mihomo/"]
COPY --from=builder /mihomo-config/ /root/.config/mihomo/ COPY --from=builder /mihomo-config/ /root/.config/mihomo/
COPY --from=builder /mihomo/mihomo /mihomo COPY --from=builder /mihomo/mihomo /mihomo
RUN chmod +x /mihomo
ENTRYPOINT [ "/mihomo" ] ENTRYPOINT [ "/mihomo" ]

View File

@@ -163,3 +163,7 @@ clean:
CLANG ?= clang-14 CLANG ?= clang-14
CFLAGS := -O2 -g -Wall -Werror $(CFLAGS) CFLAGS := -O2 -g -Wall -Werror $(CFLAGS)
ebpf: export BPF_CLANG := $(CLANG)
ebpf: export BPF_CFLAGS := $(CFLAGS)
ebpf:
cd component/ebpf/ && go generate ./...

View File

@@ -10,7 +10,6 @@ import (
"net/netip" "net/netip"
"net/url" "net/url"
"strconv" "strconv"
"strings"
"time" "time"
"github.com/metacubex/mihomo/common/atomic" "github.com/metacubex/mihomo/common/atomic"
@@ -19,7 +18,6 @@ import (
"github.com/metacubex/mihomo/component/ca" "github.com/metacubex/mihomo/component/ca"
"github.com/metacubex/mihomo/component/dialer" "github.com/metacubex/mihomo/component/dialer"
C "github.com/metacubex/mihomo/constant" C "github.com/metacubex/mihomo/constant"
"github.com/metacubex/mihomo/log"
"github.com/puzpuzpuz/xsync/v3" "github.com/puzpuzpuz/xsync/v3"
) )
@@ -41,11 +39,6 @@ type Proxy struct {
extra *xsync.MapOf[string, *internalProxyState] extra *xsync.MapOf[string, *internalProxyState]
} }
// Adapter implements C.Proxy
func (p *Proxy) Adapter() C.ProxyAdapter {
return p.ProxyAdapter
}
// AliveForTestUrl implements C.Proxy // AliveForTestUrl implements C.Proxy
func (p *Proxy) AliveForTestUrl(url string) bool { func (p *Proxy) AliveForTestUrl(url string) bool {
if state, ok := p.extra.Load(url); ok { if state, ok := p.extra.Load(url); ok {
@@ -262,18 +255,10 @@ func (p *Proxy) URLTest(ctx context.Context, url string, expectedStatus utils.In
if unifiedDelay { if unifiedDelay {
second := time.Now() second := time.Now()
var ignoredErr error resp, err = client.Do(req)
var secondResp *http.Response if err == nil {
secondResp, ignoredErr = client.Do(req)
if ignoredErr == nil {
resp = secondResp
_ = resp.Body.Close() _ = resp.Body.Close()
start = second start = second
} else {
if strings.HasPrefix(url, "http://") {
log.Errorln("%s failed to get the second response from %s: %v", p.Name(), url, ignoredErr)
log.Warnln("It is recommended to use HTTPS for provider.health-check.url and group.url to ensure better reliability. Due to some proxy providers hijacking test addresses and not being compatible with repeated HEAD requests, using HTTP may result in failed tests.")
}
} }
} }

View File

@@ -11,8 +11,6 @@ import (
func NewHTTPS(request *http.Request, conn net.Conn, additions ...Addition) (net.Conn, *C.Metadata) { func NewHTTPS(request *http.Request, conn net.Conn, additions ...Addition) (net.Conn, *C.Metadata) {
metadata := parseHTTPAddr(request) metadata := parseHTTPAddr(request)
metadata.Type = C.HTTPS metadata.Type = C.HTTPS
metadata.RawSrcAddr = conn.RemoteAddr()
metadata.RawDstAddr = conn.LocalAddr()
ApplyAdditions(metadata, WithSrcAddr(conn.RemoteAddr()), WithInAddr(conn.LocalAddr())) ApplyAdditions(metadata, WithSrcAddr(conn.RemoteAddr()), WithInAddr(conn.LocalAddr()))
ApplyAdditions(metadata, additions...) ApplyAdditions(metadata, additions...)
return conn, metadata return conn, metadata

View File

@@ -3,58 +3,16 @@ package inbound
import ( import (
"context" "context"
"net" "net"
"sync"
"github.com/metacubex/mihomo/component/keepalive"
"github.com/metacubex/tfo-go"
) )
var (
lc = tfo.ListenConfig{
DisableTFO: true,
}
mutex sync.RWMutex
)
func SetTfo(open bool) {
mutex.Lock()
defer mutex.Unlock()
lc.DisableTFO = !open
}
func Tfo() bool {
mutex.RLock()
defer mutex.RUnlock()
return !lc.DisableTFO
}
func SetMPTCP(open bool) { func SetMPTCP(open bool) {
mutex.Lock() setMultiPathTCP(getListenConfig(), open)
defer mutex.Unlock()
setMultiPathTCP(&lc.ListenConfig, open)
}
func MPTCP() bool {
mutex.RLock()
defer mutex.RUnlock()
return getMultiPathTCP(&lc.ListenConfig)
} }
func ListenContext(ctx context.Context, network, address string) (net.Listener, error) { func ListenContext(ctx context.Context, network, address string) (net.Listener, error) {
mutex.RLock()
defer mutex.RUnlock()
return lc.Listen(ctx, network, address) return lc.Listen(ctx, network, address)
} }
func Listen(network, address string) (net.Listener, error) { func Listen(network, address string) (net.Listener, error) {
return ListenContext(context.Background(), network, address) return ListenContext(context.Background(), network, address)
} }
func init() {
keepalive.SetDisableKeepAliveCallback.Register(func(b bool) {
mutex.Lock()
defer mutex.Unlock()
keepalive.SetNetListenConfig(&lc.ListenConfig)
})
}

View File

@@ -1,14 +0,0 @@
//go:build !windows
package inbound
import (
"net"
"os"
)
const SupportNamedPipe = false
func ListenNamedPipe(path string) (net.Listener, error) {
return nil, os.ErrInvalid
}

View File

@@ -0,0 +1,23 @@
//go:build unix
package inbound
import (
"net"
"github.com/metacubex/tfo-go"
)
var (
lc = tfo.ListenConfig{
DisableTFO: true,
}
)
func SetTfo(open bool) {
lc.DisableTFO = !open
}
func getListenConfig() *net.ListenConfig {
return &lc.ListenConfig
}

View File

@@ -2,31 +2,14 @@ package inbound
import ( import (
"net" "net"
"os"
"github.com/metacubex/wireguard-go/ipc/namedpipe"
"golang.org/x/sys/windows"
) )
const SupportNamedPipe = true var (
lc = net.ListenConfig{}
)
// windowsSDDL is the Security Descriptor set on the namedpipe. func SetTfo(open bool) {}
// It provides read/write access to all users and the local system.
const windowsSDDL = "D:PAI(A;OICI;GWGR;;;BU)(A;OICI;GWGR;;;SY)"
func ListenNamedPipe(path string) (net.Listener, error) { func getListenConfig() *net.ListenConfig {
sddl := os.Getenv("LISTEN_NAMEDPIPE_SDDL") return &lc
if sddl == "" {
sddl = windowsSDDL
}
securityDescriptor, err := windows.SecurityDescriptorFromString(sddl)
if err != nil {
return nil, err
}
namedpipeLC := namedpipe.ListenConfig{
SecurityDescriptor: securityDescriptor,
InputBufferSize: 256 * 1024,
OutputBufferSize: 256 * 1024,
}
return namedpipeLC.Listen(path)
} }

View File

@@ -8,7 +8,3 @@ const multipathTCPAvailable = false
func setMultiPathTCP(listenConfig *net.ListenConfig, open bool) { func setMultiPathTCP(listenConfig *net.ListenConfig, open bool) {
} }
func getMultiPathTCP(listenConfig *net.ListenConfig) bool {
return false
}

View File

@@ -9,7 +9,3 @@ const multipathTCPAvailable = true
func setMultiPathTCP(listenConfig *net.ListenConfig, open bool) { func setMultiPathTCP(listenConfig *net.ListenConfig, open bool) {
listenConfig.SetMultipathTCP(open) listenConfig.SetMultipathTCP(open)
} }
func getMultiPathTCP(listenConfig *net.ListenConfig) bool {
return listenConfig.MultipathTCP()
}

View File

@@ -6,6 +6,7 @@ import (
"os" "os"
"strconv" "strconv"
N "github.com/metacubex/mihomo/common/net"
"github.com/metacubex/mihomo/component/dialer" "github.com/metacubex/mihomo/component/dialer"
"github.com/metacubex/mihomo/component/loopback" "github.com/metacubex/mihomo/component/loopback"
"github.com/metacubex/mihomo/component/resolver" "github.com/metacubex/mihomo/component/resolver"
@@ -32,11 +33,12 @@ func (d *Direct) DialContext(ctx context.Context, metadata *C.Metadata, opts ...
return nil, err return nil, err
} }
} }
opts = append(opts, dialer.WithResolver(resolver.DirectHostResolver)) opts = append(opts, dialer.WithResolver(resolver.DefaultResolver))
c, err := dialer.DialContext(ctx, "tcp", metadata.RemoteAddress(), d.Base.DialOptions(opts...)...) c, err := dialer.DialContext(ctx, "tcp", metadata.RemoteAddress(), d.Base.DialOptions(opts...)...)
if err != nil { if err != nil {
return nil, err return nil, err
} }
N.TCPKeepAlive(c)
return d.loopBack.NewConn(NewConn(c, d)), nil return d.loopBack.NewConn(NewConn(c, d)), nil
} }
@@ -49,7 +51,7 @@ func (d *Direct) ListenPacketContext(ctx context.Context, metadata *C.Metadata,
} }
// net.UDPConn.WriteTo only working with *net.UDPAddr, so we need a net.UDPAddr // net.UDPConn.WriteTo only working with *net.UDPAddr, so we need a net.UDPAddr
if !metadata.Resolved() { if !metadata.Resolved() {
ip, err := resolver.ResolveIPWithResolver(ctx, metadata.Host, resolver.DirectHostResolver) ip, err := resolver.ResolveIPWithResolver(ctx, metadata.Host, resolver.DefaultResolver)
if err != nil { if err != nil {
return nil, errors.New("can't resolve ip") return nil, errors.New("can't resolve ip")
} }

View File

@@ -7,11 +7,13 @@ import (
"encoding/base64" "encoding/base64"
"errors" "errors"
"fmt" "fmt"
"io" "io"
"net" "net"
"net/http" "net/http"
"strconv" "strconv"
N "github.com/metacubex/mihomo/common/net"
"github.com/metacubex/mihomo/component/ca" "github.com/metacubex/mihomo/component/ca"
"github.com/metacubex/mihomo/component/dialer" "github.com/metacubex/mihomo/component/dialer"
"github.com/metacubex/mihomo/component/proxydialer" "github.com/metacubex/mihomo/component/proxydialer"
@@ -74,6 +76,7 @@ func (h *Http) DialContextWithDialer(ctx context.Context, dialer C.Dialer, metad
if err != nil { if err != nil {
return nil, fmt.Errorf("%s connect error: %w", h.addr, err) return nil, fmt.Errorf("%s connect error: %w", h.addr, err)
} }
N.TCPKeepAlive(c)
defer func(c net.Conn) { defer func(c net.Conn) {
safeConnClose(c, err) safeConnClose(c, err)

View File

@@ -7,7 +7,6 @@ import (
"fmt" "fmt"
"net" "net"
"net/netip" "net/netip"
"runtime"
"strconv" "strconv"
"time" "time"
@@ -15,7 +14,6 @@ import (
"github.com/metacubex/quic-go/congestion" "github.com/metacubex/quic-go/congestion"
M "github.com/sagernet/sing/common/metadata" M "github.com/sagernet/sing/common/metadata"
CN "github.com/metacubex/mihomo/common/net"
"github.com/metacubex/mihomo/component/ca" "github.com/metacubex/mihomo/component/ca"
"github.com/metacubex/mihomo/component/dialer" "github.com/metacubex/mihomo/component/dialer"
"github.com/metacubex/mihomo/component/proxydialer" "github.com/metacubex/mihomo/component/proxydialer"
@@ -45,8 +43,6 @@ type Hysteria struct {
option *HysteriaOption option *HysteriaOption
client *core.Client client *core.Client
closeCh chan struct{} // for test
} }
func (h *Hysteria) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.Conn, error) { func (h *Hysteria) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.Conn, error) {
@@ -55,7 +51,7 @@ func (h *Hysteria) DialContext(ctx context.Context, metadata *C.Metadata, opts .
return nil, err return nil, err
} }
return NewConn(CN.NewRefConn(tcpConn, h), h), nil return NewConn(tcpConn, h), nil
} }
func (h *Hysteria) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) { func (h *Hysteria) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) {
@@ -63,13 +59,13 @@ func (h *Hysteria) ListenPacketContext(ctx context.Context, metadata *C.Metadata
if err != nil { if err != nil {
return nil, err return nil, err
} }
return newPacketConn(CN.NewRefPacketConn(&hyPacketConn{udpConn}, h), h), nil return newPacketConn(&hyPacketConn{udpConn}, h), nil
} }
func (h *Hysteria) genHdc(ctx context.Context, opts ...dialer.Option) utils.PacketDialer { func (h *Hysteria) genHdc(ctx context.Context, opts ...dialer.Option) utils.PacketDialer {
return &hyDialerWithContext{ return &hyDialerWithContext{
ctx: context.Background(), ctx: context.Background(),
hyDialer: func(network string, rAddr net.Addr) (net.PacketConn, error) { hyDialer: func(network string) (net.PacketConn, error) {
var err error var err error
var cDialer C.Dialer = dialer.NewDialer(h.Base.DialOptions(opts...)...) var cDialer C.Dialer = dialer.NewDialer(h.Base.DialOptions(opts...)...)
if len(h.option.DialerProxy) > 0 { if len(h.option.DialerProxy) > 0 {
@@ -78,7 +74,7 @@ func (h *Hysteria) genHdc(ctx context.Context, opts ...dialer.Option) utils.Pack
return nil, err return nil, err
} }
} }
rAddrPort, _ := netip.ParseAddrPort(rAddr.String()) rAddrPort, _ := netip.ParseAddrPort(h.Addr())
return cDialer.ListenPacket(ctx, network, "", rAddrPort) return cDialer.ListenPacket(ctx, network, "", rAddrPort)
}, },
remoteAddr: func(addr string) (net.Addr, error) { remoteAddr: func(addr string) (net.Addr, error) {
@@ -131,7 +127,11 @@ func (c *HysteriaOption) Speed() (uint64, uint64, error) {
} }
func NewHysteria(option HysteriaOption) (*Hysteria, error) { func NewHysteria(option HysteriaOption) (*Hysteria, error) {
clientTransport := &transport.ClientTransport{} clientTransport := &transport.ClientTransport{
Dialer: &net.Dialer{
Timeout: 8 * time.Second,
},
}
addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port)) addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port))
ports := option.Ports ports := option.Ports
@@ -218,7 +218,7 @@ func NewHysteria(option HysteriaOption) (*Hysteria, error) {
if err != nil { if err != nil {
return nil, fmt.Errorf("hysteria %s create error: %w", addr, err) return nil, fmt.Errorf("hysteria %s create error: %w", addr, err)
} }
outbound := &Hysteria{ return &Hysteria{
Base: &Base{ Base: &Base{
name: option.Name, name: option.Name,
addr: addr, addr: addr,
@@ -231,19 +231,7 @@ func NewHysteria(option HysteriaOption) (*Hysteria, error) {
}, },
option: &option, option: &option,
client: client, client: client,
} }, nil
runtime.SetFinalizer(outbound, closeHysteria)
return outbound, nil
}
func closeHysteria(h *Hysteria) {
if h.client != nil {
_ = h.client.Close()
}
if h.closeCh != nil {
close(h.closeCh)
}
} }
type hyPacketConn struct { type hyPacketConn struct {
@@ -280,7 +268,7 @@ func (c *hyPacketConn) WriteTo(p []byte, addr net.Addr) (n int, err error) {
} }
type hyDialerWithContext struct { type hyDialerWithContext struct {
hyDialer func(network string, rAddr net.Addr) (net.PacketConn, error) hyDialer func(network string) (net.PacketConn, error)
ctx context.Context ctx context.Context
remoteAddr func(host string) (net.Addr, error) remoteAddr func(host string) (net.Addr, error)
} }
@@ -290,7 +278,7 @@ func (h *hyDialerWithContext) ListenPacket(rAddr net.Addr) (net.PacketConn, erro
if addrPort, err := netip.ParseAddrPort(rAddr.String()); err == nil { if addrPort, err := netip.ParseAddrPort(rAddr.String()); err == nil {
network = dialer.ParseNetwork(network, addrPort.Addr()) network = dialer.ParseNetwork(network, addrPort.Addr())
} }
return h.hyDialer(network, rAddr) return h.hyDialer(network)
} }
func (h *hyDialerWithContext) Context() context.Context { func (h *hyDialerWithContext) Context() context.Context {

View File

@@ -38,8 +38,6 @@ type Hysteria2 struct {
option *Hysteria2Option option *Hysteria2Option
client *hysteria2.Client client *hysteria2.Client
dialer proxydialer.SingDialer dialer proxydialer.SingDialer
closeCh chan struct{} // for test
} }
type Hysteria2Option struct { type Hysteria2Option struct {
@@ -91,9 +89,6 @@ func closeHysteria2(h *Hysteria2) {
if h.client != nil { if h.client != nil {
_ = h.client.CloseWithError(errors.New("proxy removed")) _ = h.client.CloseWithError(errors.New("proxy removed"))
} }
if h.closeCh != nil {
close(h.closeCh)
}
} }
func NewHysteria2(option Hysteria2Option) (*Hysteria2, error) { func NewHysteria2(option Hysteria2Option) (*Hysteria2, error) {

View File

@@ -1,38 +0,0 @@
package outbound
import (
"context"
"runtime"
"testing"
"time"
)
func TestHysteria2GC(t *testing.T) {
option := Hysteria2Option{}
option.Server = "127.0.0.1"
option.Ports = "200,204,401-429,501-503"
option.HopInterval = 30
option.Password = "password"
option.Obfs = "salamander"
option.ObfsPassword = "password"
option.SNI = "example.com"
option.ALPN = []string{"h3"}
hy, err := NewHysteria2(option)
if err != nil {
t.Error(err)
return
}
closeCh := make(chan struct{})
hy.closeCh = closeCh
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
defer cancel()
hy = nil
runtime.GC()
select {
case <-closeCh:
return
case <-ctx.Done():
t.Error("timeout not GC")
}
}

View File

@@ -1,39 +0,0 @@
package outbound
import (
"context"
"runtime"
"testing"
"time"
)
func TestHysteriaGC(t *testing.T) {
option := HysteriaOption{}
option.Server = "127.0.0.1"
option.Ports = "200,204,401-429,501-503"
option.Protocol = "udp"
option.Up = "1Mbps"
option.Down = "1Mbps"
option.HopInterval = 30
option.Obfs = "salamander"
option.SNI = "example.com"
option.ALPN = []string{"h3"}
hy, err := NewHysteria(option)
if err != nil {
t.Error(err)
return
}
closeCh := make(chan struct{})
hy.closeCh = closeCh
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
defer cancel()
hy = nil
runtime.GC()
select {
case <-closeCh:
return
case <-ctx.Done():
t.Error("timeout not GC")
}
}

View File

@@ -37,7 +37,7 @@ func NewRejectWithOption(option RejectOption) *Reject {
return &Reject{ return &Reject{
Base: &Base{ Base: &Base{
name: option.Name, name: option.Name,
tp: C.Reject, tp: C.Direct,
udp: true, udp: true,
}, },
} }

View File

@@ -149,6 +149,7 @@ func (ss *ShadowSocks) DialContextWithDialer(ctx context.Context, dialer C.Diale
if err != nil { if err != nil {
return nil, fmt.Errorf("%s connect error: %w", ss.addr, err) return nil, fmt.Errorf("%s connect error: %w", ss.addr, err)
} }
N.TCPKeepAlive(c)
defer func(c net.Conn) { defer func(c net.Conn) {
safeConnClose(c, err) safeConnClose(c, err)

View File

@@ -80,6 +80,7 @@ func (ssr *ShadowSocksR) DialContextWithDialer(ctx context.Context, dialer C.Dia
if err != nil { if err != nil {
return nil, fmt.Errorf("%s connect error: %w", ssr.addr, err) return nil, fmt.Errorf("%s connect error: %w", ssr.addr, err)
} }
N.TCPKeepAlive(c)
defer func(c net.Conn) { defer func(c net.Conn) {
safeConnClose(c, err) safeConnClose(c, err)

View File

@@ -6,6 +6,7 @@ import (
"net" "net"
"strconv" "strconv"
N "github.com/metacubex/mihomo/common/net"
"github.com/metacubex/mihomo/common/structure" "github.com/metacubex/mihomo/common/structure"
"github.com/metacubex/mihomo/component/dialer" "github.com/metacubex/mihomo/component/dialer"
"github.com/metacubex/mihomo/component/proxydialer" "github.com/metacubex/mihomo/component/proxydialer"
@@ -93,6 +94,7 @@ func (s *Snell) DialContextWithDialer(ctx context.Context, dialer C.Dialer, meta
if err != nil { if err != nil {
return nil, fmt.Errorf("%s connect error: %w", s.addr, err) return nil, fmt.Errorf("%s connect error: %w", s.addr, err)
} }
N.TCPKeepAlive(c)
defer func(c net.Conn) { defer func(c net.Conn) {
safeConnClose(c, err) safeConnClose(c, err)
@@ -120,6 +122,7 @@ func (s *Snell) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, met
if err != nil { if err != nil {
return nil, err return nil, err
} }
N.TCPKeepAlive(c)
c = streamConn(c, streamOption{s.psk, s.version, s.addr, s.obfsOption}) c = streamConn(c, streamOption{s.psk, s.version, s.addr, s.obfsOption})
err = snell.WriteUDPHeader(c, s.version) err = snell.WriteUDPHeader(c, s.version)
@@ -204,7 +207,8 @@ func NewSnell(option SnellOption) (*Snell, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
N.TCPKeepAlive(c)
return streamConn(c, streamOption{psk, option.Version, addr, obfsOption}), nil return streamConn(c, streamOption{psk, option.Version, addr, obfsOption}), nil
}) })
} }

View File

@@ -10,6 +10,7 @@ import (
"net/netip" "net/netip"
"strconv" "strconv"
N "github.com/metacubex/mihomo/common/net"
"github.com/metacubex/mihomo/component/ca" "github.com/metacubex/mihomo/component/ca"
"github.com/metacubex/mihomo/component/dialer" "github.com/metacubex/mihomo/component/dialer"
"github.com/metacubex/mihomo/component/proxydialer" "github.com/metacubex/mihomo/component/proxydialer"
@@ -81,6 +82,7 @@ func (ss *Socks5) DialContextWithDialer(ctx context.Context, dialer C.Dialer, me
if err != nil { if err != nil {
return nil, fmt.Errorf("%s connect error: %w", ss.addr, err) return nil, fmt.Errorf("%s connect error: %w", ss.addr, err)
} }
N.TCPKeepAlive(c)
defer func(c net.Conn) { defer func(c net.Conn) {
safeConnClose(c, err) safeConnClose(c, err)
@@ -126,6 +128,7 @@ func (ss *Socks5) ListenPacketContext(ctx context.Context, metadata *C.Metadata,
safeConnClose(c, err) safeConnClose(c, err)
}(c) }(c)
N.TCPKeepAlive(c)
var user *socks5.User var user *socks5.User
if ss.user != "" { if ss.user != "" {
user = &socks5.User{ user = &socks5.User{

View File

@@ -77,6 +77,7 @@ func (s *sshClient) connect(ctx context.Context, cDialer C.Dialer, addr string)
if err != nil { if err != nil {
return nil, err return nil, err
} }
N.TCPKeepAlive(c)
defer func(c net.Conn) { defer func(c net.Conn) {
safeConnClose(c, err) safeConnClose(c, err)

View File

@@ -9,6 +9,7 @@ import (
"net/http" "net/http"
"strconv" "strconv"
N "github.com/metacubex/mihomo/common/net"
"github.com/metacubex/mihomo/component/ca" "github.com/metacubex/mihomo/component/ca"
"github.com/metacubex/mihomo/component/dialer" "github.com/metacubex/mihomo/component/dialer"
"github.com/metacubex/mihomo/component/proxydialer" "github.com/metacubex/mihomo/component/proxydialer"
@@ -153,6 +154,7 @@ func (t *Trojan) DialContextWithDialer(ctx context.Context, dialer C.Dialer, met
if err != nil { if err != nil {
return nil, fmt.Errorf("%s connect error: %w", t.addr, err) return nil, fmt.Errorf("%s connect error: %w", t.addr, err)
} }
N.TCPKeepAlive(c)
defer func(c net.Conn) { defer func(c net.Conn) {
safeConnClose(c, err) safeConnClose(c, err)
@@ -210,6 +212,7 @@ func (t *Trojan) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, me
defer func(c net.Conn) { defer func(c net.Conn) {
safeConnClose(c, err) safeConnClose(c, err)
}(c) }(c)
N.TCPKeepAlive(c)
c, err = t.plainStream(ctx, c) c, err = t.plainStream(ctx, c)
if err != nil { if err != nil {
return nil, fmt.Errorf("%s connect error: %w", t.addr, err) return nil, fmt.Errorf("%s connect error: %w", t.addr, err)
@@ -311,6 +314,7 @@ func NewTrojan(option TrojanOption) (*Trojan, error) {
if err != nil { if err != nil {
return nil, fmt.Errorf("%s connect error: %s", t.addr, err.Error()) return nil, fmt.Errorf("%s connect error: %s", t.addr, err.Error())
} }
N.TCPKeepAlive(c)
return c, nil return c, nil
} }

View File

@@ -55,7 +55,7 @@ func resolveUDPAddr(ctx context.Context, network, address string) (*net.UDPAddr,
return nil, err return nil, err
} }
ip, err := resolver.ResolveIPWithResolver(ctx, host, resolver.ProxyServerHostResolver) ip, err := resolver.ResolveProxyServerHost(ctx, host)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -71,12 +71,12 @@ func resolveUDPAddrWithPrefer(ctx context.Context, network, address string, pref
var fallback netip.Addr var fallback netip.Addr
switch prefer { switch prefer {
case C.IPv4Only: case C.IPv4Only:
ip, err = resolver.ResolveIPv4WithResolver(ctx, host, resolver.ProxyServerHostResolver) ip, err = resolver.ResolveIPv4ProxyServerHost(ctx, host)
case C.IPv6Only: case C.IPv6Only:
ip, err = resolver.ResolveIPv6WithResolver(ctx, host, resolver.ProxyServerHostResolver) ip, err = resolver.ResolveIPv6ProxyServerHost(ctx, host)
case C.IPv6Prefer: case C.IPv6Prefer:
var ips []netip.Addr var ips []netip.Addr
ips, err = resolver.LookupIPWithResolver(ctx, host, resolver.ProxyServerHostResolver) ips, err = resolver.LookupIPProxyServerHost(ctx, host)
if err == nil { if err == nil {
for _, addr := range ips { for _, addr := range ips {
if addr.Is6() { if addr.Is6() {
@@ -92,7 +92,7 @@ func resolveUDPAddrWithPrefer(ctx context.Context, network, address string, pref
default: default:
// C.IPv4Prefer, C.DualStack and other // C.IPv4Prefer, C.DualStack and other
var ips []netip.Addr var ips []netip.Addr
ips, err = resolver.LookupIPWithResolver(ctx, host, resolver.ProxyServerHostResolver) ips, err = resolver.LookupIPProxyServerHost(ctx, host)
if err == nil { if err == nil {
for _, addr := range ips { for _, addr := range ips {
if addr.Is4() { if addr.Is4() {

View File

@@ -262,6 +262,7 @@ func (v *Vless) DialContextWithDialer(ctx context.Context, dialer C.Dialer, meta
if err != nil { if err != nil {
return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error()) return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error())
} }
N.TCPKeepAlive(c)
defer func(c net.Conn) { defer func(c net.Conn) {
safeConnClose(c, err) safeConnClose(c, err)
}(c) }(c)
@@ -326,6 +327,7 @@ func (v *Vless) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, met
if err != nil { if err != nil {
return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error()) return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error())
} }
N.TCPKeepAlive(c)
defer func(c net.Conn) { defer func(c net.Conn) {
safeConnClose(c, err) safeConnClose(c, err)
}(c) }(c)
@@ -503,14 +505,17 @@ func NewVless(option VlessOption) (*Vless, error) {
var addons *vless.Addons var addons *vless.Addons
if option.Network != "ws" && len(option.Flow) >= 16 { if option.Network != "ws" && len(option.Flow) >= 16 {
option.Flow = option.Flow[:16] option.Flow = option.Flow[:16]
if option.Flow != vless.XRV { switch option.Flow {
case vless.XRV:
log.Warnln("To use %s, ensure your server is upgrade to Xray-core v1.8.0+", vless.XRV)
addons = &vless.Addons{
Flow: option.Flow,
}
case vless.XRO, vless.XRD, vless.XRS:
log.Fatalln("Legacy XTLS protocol %s is deprecated and no longer supported", option.Flow)
default:
return nil, fmt.Errorf("unsupported xtls flow type: %s", option.Flow) return nil, fmt.Errorf("unsupported xtls flow type: %s", option.Flow)
} }
log.Warnln("To use %s, ensure your server is upgrade to Xray-core v1.8.0+", vless.XRV)
addons = &vless.Addons{
Flow: option.Flow,
}
} }
switch option.PacketEncoding { switch option.PacketEncoding {
@@ -572,6 +577,7 @@ func NewVless(option VlessOption) (*Vless, error) {
if err != nil { if err != nil {
return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error()) return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error())
} }
N.TCPKeepAlive(c)
return c, nil return c, nil
} }

View File

@@ -312,6 +312,7 @@ func (v *Vmess) DialContextWithDialer(ctx context.Context, dialer C.Dialer, meta
if err != nil { if err != nil {
return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error()) return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error())
} }
N.TCPKeepAlive(c)
defer func(c net.Conn) { defer func(c net.Conn) {
safeConnClose(c, err) safeConnClose(c, err)
}(c) }(c)
@@ -372,6 +373,7 @@ func (v *Vmess) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, met
if err != nil { if err != nil {
return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error()) return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error())
} }
N.TCPKeepAlive(c)
defer func(c net.Conn) { defer func(c net.Conn) {
safeConnClose(c, err) safeConnClose(c, err)
}(c) }(c)
@@ -471,6 +473,7 @@ func NewVmess(option VmessOption) (*Vmess, error) {
if err != nil { if err != nil {
return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error()) return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error())
} }
N.TCPKeepAlive(c)
return c, nil return c, nil
} }

View File

@@ -24,27 +24,22 @@ import (
"github.com/metacubex/mihomo/dns" "github.com/metacubex/mihomo/dns"
"github.com/metacubex/mihomo/log" "github.com/metacubex/mihomo/log"
amnezia "github.com/metacubex/amneziawg-go/device"
wireguard "github.com/metacubex/sing-wireguard" wireguard "github.com/metacubex/sing-wireguard"
"github.com/metacubex/wireguard-go/device"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/debug" "github.com/sagernet/sing/common/debug"
E "github.com/sagernet/sing/common/exceptions" E "github.com/sagernet/sing/common/exceptions"
M "github.com/sagernet/sing/common/metadata" M "github.com/sagernet/sing/common/metadata"
"github.com/sagernet/wireguard-go/device"
) )
type wireguardGoDevice interface {
Close()
IpcSet(uapiConf string) error
}
type WireGuard struct { type WireGuard struct {
*Base *Base
bind *wireguard.ClientBind bind *wireguard.ClientBind
device wireguardGoDevice device *device.Device
tunDevice wireguard.Device tunDevice wireguard.Device
dialer proxydialer.SingDialer dialer proxydialer.SingDialer
resolver resolver.Resolver resolver *dns.Resolver
refP *refProxyAdapter refP *refProxyAdapter
initOk atomic.Bool initOk atomic.Bool
@@ -73,8 +68,6 @@ type WireGuardOption struct {
UDP bool `proxy:"udp,omitempty"` UDP bool `proxy:"udp,omitempty"`
PersistentKeepalive int `proxy:"persistent-keepalive,omitempty"` PersistentKeepalive int `proxy:"persistent-keepalive,omitempty"`
AmneziaWGOption *AmneziaWGOption `proxy:"amnezia-wg-option,omitempty"`
Peers []WireGuardPeerOption `proxy:"peers,omitempty"` Peers []WireGuardPeerOption `proxy:"peers,omitempty"`
RemoteDnsResolve bool `proxy:"remote-dns-resolve,omitempty"` RemoteDnsResolve bool `proxy:"remote-dns-resolve,omitempty"`
@@ -92,18 +85,6 @@ type WireGuardPeerOption struct {
AllowedIPs []string `proxy:"allowed-ips,omitempty"` AllowedIPs []string `proxy:"allowed-ips,omitempty"`
} }
type AmneziaWGOption struct {
JC int `proxy:"jc"`
JMin int `proxy:"jmin"`
JMax int `proxy:"jmax"`
S1 int `proxy:"s1"`
S2 int `proxy:"s2"`
H1 uint32 `proxy:"h1"`
H2 uint32 `proxy:"h2"`
H3 uint32 `proxy:"h3"`
H4 uint32 `proxy:"h4"`
}
type wgSingErrorHandler struct { type wgSingErrorHandler struct {
name string name string
} }
@@ -263,20 +244,14 @@ func NewWireGuard(option WireGuardOption) (*WireGuard, error) {
if err != nil { if err != nil {
return nil, E.Cause(err, "create WireGuard device") return nil, E.Cause(err, "create WireGuard device")
} }
logger := &device.Logger{ outbound.device = device.NewDevice(context.Background(), outbound.tunDevice, outbound.bind, &device.Logger{
Verbosef: func(format string, args ...interface{}) { Verbosef: func(format string, args ...interface{}) {
log.SingLogger.Debug(fmt.Sprintf("[WG](%s) %s", option.Name, fmt.Sprintf(format, args...))) log.SingLogger.Debug(fmt.Sprintf("[WG](%s) %s", option.Name, fmt.Sprintf(format, args...)))
}, },
Errorf: func(format string, args ...interface{}) { Errorf: func(format string, args ...interface{}) {
log.SingLogger.Error(fmt.Sprintf("[WG](%s) %s", option.Name, fmt.Sprintf(format, args...))) log.SingLogger.Error(fmt.Sprintf("[WG](%s) %s", option.Name, fmt.Sprintf(format, args...)))
}, },
} }, option.Workers)
if option.AmneziaWGOption != nil {
outbound.bind.SetParseReserved(false) // AmneziaWG don't need parse reserved
outbound.device = amnezia.NewDevice(outbound.tunDevice, outbound.bind, logger, option.Workers)
} else {
outbound.device = device.NewDevice(outbound.tunDevice, outbound.bind, logger, option.Workers)
}
var has6 bool var has6 bool
for _, address := range outbound.localPrefixes { for _, address := range outbound.localPrefixes {
@@ -393,17 +368,6 @@ func (w *WireGuard) genIpcConf(ctx context.Context, updateOnly bool) (string, er
ipcConf := "" ipcConf := ""
if !updateOnly { if !updateOnly {
ipcConf += "private_key=" + w.option.PrivateKey + "\n" ipcConf += "private_key=" + w.option.PrivateKey + "\n"
if w.option.AmneziaWGOption != nil {
ipcConf += "jc=" + strconv.Itoa(w.option.AmneziaWGOption.JC) + "\n"
ipcConf += "jmin=" + strconv.Itoa(w.option.AmneziaWGOption.JMin) + "\n"
ipcConf += "jmax=" + strconv.Itoa(w.option.AmneziaWGOption.JMax) + "\n"
ipcConf += "s1=" + strconv.Itoa(w.option.AmneziaWGOption.S1) + "\n"
ipcConf += "s2=" + strconv.Itoa(w.option.AmneziaWGOption.S2) + "\n"
ipcConf += "h1=" + strconv.FormatUint(uint64(w.option.AmneziaWGOption.H1), 10) + "\n"
ipcConf += "h2=" + strconv.FormatUint(uint64(w.option.AmneziaWGOption.H2), 10) + "\n"
ipcConf += "h3=" + strconv.FormatUint(uint64(w.option.AmneziaWGOption.H3), 10) + "\n"
ipcConf += "h4=" + strconv.FormatUint(uint64(w.option.AmneziaWGOption.H4), 10) + "\n"
}
} }
if len(w.option.Peers) > 0 { if len(w.option.Peers) > 0 {
for i, peer := range w.option.Peers { for i, peer := range w.option.Peers {
@@ -492,6 +456,7 @@ func closeWireGuard(w *WireGuard) {
if w.device != nil { if w.device != nil {
w.device.Close() w.device.Close()
} }
_ = common.Close(w.tunDevice)
if w.closeCh != nil { if w.closeCh != nil {
close(w.closeCh) close(w.closeCh)
} }

View File

@@ -29,7 +29,6 @@ func TestWireGuardGC(t *testing.T) {
err = wg.init(ctx) err = wg.init(ctx)
if err != nil { if err != nil {
t.Error(err) t.Error(err)
return
} }
// must do a small sleep before test GC // must do a small sleep before test GC
// because it maybe deadlocks if w.device.Close call too fast after w.device.Start // because it maybe deadlocks if w.device.Close call too fast after w.device.Start

View File

@@ -204,7 +204,7 @@ func strategyStickySessions(url string) strategyFn {
for i := 1; i < maxRetry; i++ { for i := 1; i < maxRetry; i++ {
proxy := proxies[nowIdx] proxy := proxies[nowIdx]
if proxy.AliveForTestUrl(url) { if proxy.AliveForTestUrl(url) {
if !has || nowIdx != idx { if nowIdx != idx {
lruCache.Set(key, nowIdx) lruCache.Set(key, nowIdx)
} }

View File

@@ -88,9 +88,6 @@ func ParseProxyGroup(config map[string]any, proxyMap map[string]C.Proxy, provide
} else { } else {
groupOption.Proxies = append(groupOption.Proxies, AllProxies...) groupOption.Proxies = append(groupOption.Proxies, AllProxies...)
} }
if len(groupOption.Proxies) == 0 && len(groupOption.Use) == 0 {
groupOption.Proxies = []string{"COMPATIBLE"}
}
} }
if len(groupOption.Proxies) == 0 && len(groupOption.Use) == 0 { if len(groupOption.Proxies) == 0 && len(groupOption.Use) == 0 {

View File

@@ -4,7 +4,3 @@ type SelectAble interface {
Set(string) error Set(string) error
ForceSet(name string) ForceSet(name string)
} }
var _ SelectAble = (*Fallback)(nil)
var _ SelectAble = (*URLTest)(nil)
var _ SelectAble = (*Selector)(nil)

View File

@@ -27,8 +27,6 @@ type extraOption struct {
} }
type HealthCheck struct { type HealthCheck struct {
ctx context.Context
ctxCancel context.CancelFunc
url string url string
extra map[string]*extraOption extra map[string]*extraOption
mu sync.Mutex mu sync.Mutex
@@ -38,6 +36,7 @@ type HealthCheck struct {
lazy bool lazy bool
expectedStatus utils.IntRanges[uint16] expectedStatus utils.IntRanges[uint16]
lastTouch atomic.TypedValue[time.Time] lastTouch atomic.TypedValue[time.Time]
done chan struct{}
singleDo *singledo.Single[struct{}] singleDo *singledo.Single[struct{}]
timeout time.Duration timeout time.Duration
} }
@@ -60,7 +59,7 @@ func (hc *HealthCheck) process() {
} else { } else {
log.Debugln("Skip once health check because we are lazy") log.Debugln("Skip once health check because we are lazy")
} }
case <-hc.ctx.Done(): case <-hc.done:
ticker.Stop() ticker.Stop()
hc.stop() hc.stop()
return return
@@ -147,7 +146,7 @@ func (hc *HealthCheck) check() {
_, _, _ = hc.singleDo.Do(func() (struct{}, error) { _, _, _ = hc.singleDo.Do(func() (struct{}, error) {
id := utils.NewUUIDV4().String() id := utils.NewUUIDV4().String()
log.Debugln("Start New Health Checking {%s}", id) log.Debugln("Start New Health Checking {%s}", id)
b, _ := batch.New[bool](hc.ctx, batch.WithConcurrencyNum[bool](10)) b, _ := batch.New[bool](context.Background(), batch.WithConcurrencyNum[bool](10))
// execute default health check // execute default health check
option := &extraOption{filters: nil, expectedStatus: hc.expectedStatus} option := &extraOption{filters: nil, expectedStatus: hc.expectedStatus}
@@ -196,7 +195,7 @@ func (hc *HealthCheck) execute(b *batch.Batch[bool], url, uid string, option *ex
p := proxy p := proxy
b.Go(p.Name(), func() (bool, error) { b.Go(p.Name(), func() (bool, error) {
ctx, cancel := context.WithTimeout(hc.ctx, hc.timeout) ctx, cancel := context.WithTimeout(context.Background(), hc.timeout)
defer cancel() defer cancel()
log.Debugln("Health Checking, proxy: %s, url: %s, id: {%s}", p.Name(), url, uid) log.Debugln("Health Checking, proxy: %s, url: %s, id: {%s}", p.Name(), url, uid)
_, _ = p.URLTest(ctx, url, expectedStatus) _, _ = p.URLTest(ctx, url, expectedStatus)
@@ -207,7 +206,7 @@ func (hc *HealthCheck) execute(b *batch.Batch[bool], url, uid string, option *ex
} }
func (hc *HealthCheck) close() { func (hc *HealthCheck) close() {
hc.ctxCancel() hc.done <- struct{}{}
} }
func NewHealthCheck(proxies []C.Proxy, url string, timeout uint, interval uint, lazy bool, expectedStatus utils.IntRanges[uint16]) *HealthCheck { func NewHealthCheck(proxies []C.Proxy, url string, timeout uint, interval uint, lazy bool, expectedStatus utils.IntRanges[uint16]) *HealthCheck {
@@ -218,11 +217,8 @@ func NewHealthCheck(proxies []C.Proxy, url string, timeout uint, interval uint,
if timeout == 0 { if timeout == 0 {
timeout = 5000 timeout = 5000
} }
ctx, cancel := context.WithCancel(context.Background())
return &HealthCheck{ return &HealthCheck{
ctx: ctx,
ctxCancel: cancel,
proxies: proxies, proxies: proxies,
url: url, url: url,
timeout: time.Duration(timeout) * time.Millisecond, timeout: time.Duration(timeout) * time.Millisecond,
@@ -230,6 +226,7 @@ func NewHealthCheck(proxies []C.Proxy, url string, timeout uint, interval uint,
interval: time.Duration(interval) * time.Second, interval: time.Duration(interval) * time.Second,
lazy: lazy, lazy: lazy,
expectedStatus: expectedStatus, expectedStatus: expectedStatus,
done: make(chan struct{}, 1),
singleDo: singledo.NewSingle[struct{}](time.Second), singleDo: singledo.NewSingle[struct{}](time.Second),
} }
} }

View File

@@ -1,7 +1,6 @@
package provider package provider
import ( import (
"encoding"
"errors" "errors"
"fmt" "fmt"
"time" "time"
@@ -10,9 +9,8 @@ import (
"github.com/metacubex/mihomo/common/utils" "github.com/metacubex/mihomo/common/utils"
"github.com/metacubex/mihomo/component/resource" "github.com/metacubex/mihomo/component/resource"
C "github.com/metacubex/mihomo/constant" C "github.com/metacubex/mihomo/constant"
"github.com/metacubex/mihomo/constant/features"
types "github.com/metacubex/mihomo/constant/provider" types "github.com/metacubex/mihomo/constant/provider"
"github.com/dlclark/regexp2"
) )
var ( var (
@@ -29,15 +27,6 @@ type healthCheckSchema struct {
ExpectedStatus string `provider:"expected-status,omitempty"` ExpectedStatus string `provider:"expected-status,omitempty"`
} }
type OverrideProxyNameSchema struct {
// matching expression for regex replacement
Pattern *regexp2.Regexp `provider:"pattern"`
// the new content after regex matching
Target string `provider:"target"`
}
var _ encoding.TextUnmarshaler = (*regexp2.Regexp)(nil) // ensure *regexp2.Regexp can decode direct by structure package
type OverrideSchema struct { type OverrideSchema struct {
TFO *bool `provider:"tfo,omitempty"` TFO *bool `provider:"tfo,omitempty"`
MPTcp *bool `provider:"mptcp,omitempty"` MPTcp *bool `provider:"mptcp,omitempty"`
@@ -52,8 +41,6 @@ type OverrideSchema struct {
IPVersion *string `provider:"ip-version,omitempty"` IPVersion *string `provider:"ip-version,omitempty"`
AdditionalPrefix *string `provider:"additional-prefix,omitempty"` AdditionalPrefix *string `provider:"additional-prefix,omitempty"`
AdditionalSuffix *string `provider:"additional-suffix,omitempty"` AdditionalSuffix *string `provider:"additional-suffix,omitempty"`
ProxyName []OverrideProxyNameSchema `provider:"proxy-name,omitempty"`
} }
type proxyProviderSchema struct { type proxyProviderSchema struct {
@@ -107,11 +94,11 @@ func ParseProxyProvider(name string, mapping map[string]any) (types.ProxyProvide
path := C.Path.GetPathByHash("proxies", schema.URL) path := C.Path.GetPathByHash("proxies", schema.URL)
if schema.Path != "" { if schema.Path != "" {
path = C.Path.Resolve(schema.Path) path = C.Path.Resolve(schema.Path)
if !C.Path.IsSafePath(path) { if !features.CMFA && !C.Path.IsSafePath(path) {
return nil, fmt.Errorf("%w: %s", errSubPath, path) return nil, fmt.Errorf("%w: %s", errSubPath, path)
} }
} }
vehicle = resource.NewHTTPVehicle(schema.URL, path, schema.Proxy, schema.Header, resource.DefaultHttpTimeout) vehicle = resource.NewHTTPVehicle(schema.URL, path, schema.Proxy, schema.Header)
default: default:
return nil, fmt.Errorf("%w: %s", errVehicleType, schema.Type) return nil, fmt.Errorf("%w: %s", errVehicleType, schema.Type)
} }

View File

@@ -0,0 +1,36 @@
//go:build android && cmfa
package provider
import (
"time"
)
var (
suspended bool
)
type UpdatableProvider interface {
UpdatedAt() time.Time
}
func (pp *proxySetProvider) UpdatedAt() time.Time {
return pp.Fetcher.UpdatedAt
}
func (pp *proxySetProvider) Close() error {
pp.healthCheck.close()
pp.Fetcher.Destroy()
return nil
}
func (cp *compatibleProvider) Close() error {
cp.healthCheck.close()
return nil
}
func Suspend(s bool) {
suspended = s
}

View File

@@ -1,9 +1,11 @@
package provider package provider
import ( import (
"context"
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
"net/http"
"reflect" "reflect"
"runtime" "runtime"
"strings" "strings"
@@ -12,10 +14,11 @@ import (
"github.com/metacubex/mihomo/adapter" "github.com/metacubex/mihomo/adapter"
"github.com/metacubex/mihomo/common/convert" "github.com/metacubex/mihomo/common/convert"
"github.com/metacubex/mihomo/common/utils" "github.com/metacubex/mihomo/common/utils"
"github.com/metacubex/mihomo/component/profile/cachefile" mihomoHttp "github.com/metacubex/mihomo/component/http"
"github.com/metacubex/mihomo/component/resource" "github.com/metacubex/mihomo/component/resource"
C "github.com/metacubex/mihomo/constant" C "github.com/metacubex/mihomo/constant"
types "github.com/metacubex/mihomo/constant/provider" types "github.com/metacubex/mihomo/constant/provider"
"github.com/metacubex/mihomo/log"
"github.com/metacubex/mihomo/tunnel/statistic" "github.com/metacubex/mihomo/tunnel/statistic"
"github.com/dlclark/regexp2" "github.com/dlclark/regexp2"
@@ -51,7 +54,7 @@ func (pp *proxySetProvider) MarshalJSON() ([]byte, error) {
"proxies": pp.Proxies(), "proxies": pp.Proxies(),
"testUrl": pp.healthCheck.url, "testUrl": pp.healthCheck.url,
"expectedStatus": pp.healthCheck.expectedStatus.String(), "expectedStatus": pp.healthCheck.expectedStatus.String(),
"updatedAt": pp.UpdatedAt(), "updatedAt": pp.UpdatedAt,
"subscriptionInfo": pp.subscriptionInfo, "subscriptionInfo": pp.subscriptionInfo,
}) })
} }
@@ -69,18 +72,20 @@ func (pp *proxySetProvider) HealthCheck() {
} }
func (pp *proxySetProvider) Update() error { func (pp *proxySetProvider) Update() error {
_, _, err := pp.Fetcher.Update() elm, same, err := pp.Fetcher.Update()
if err == nil && !same {
pp.OnUpdate(elm)
}
return err return err
} }
func (pp *proxySetProvider) Initial() error { func (pp *proxySetProvider) Initial() error {
_, err := pp.Fetcher.Initial() elm, err := pp.Fetcher.Initial()
if err != nil { if err != nil {
return err return err
} }
if subscriptionInfo := cachefile.Cache().GetSubscriptionInfo(pp.Name()); subscriptionInfo != "" { pp.OnUpdate(elm)
pp.SetSubscriptionInfo(subscriptionInfo) pp.getSubscriptionInfo()
}
pp.closeAllConnections() pp.closeAllConnections()
return nil return nil
} }
@@ -93,10 +98,6 @@ func (pp *proxySetProvider) Proxies() []C.Proxy {
return pp.proxies return pp.proxies
} }
func (pp *proxySetProvider) Count() int {
return len(pp.proxies)
}
func (pp *proxySetProvider) Touch() { func (pp *proxySetProvider) Touch() {
pp.healthCheck.touch() pp.healthCheck.touch()
} }
@@ -117,14 +118,38 @@ func (pp *proxySetProvider) setProxies(proxies []C.Proxy) {
} }
} }
func (pp *proxySetProvider) SetSubscriptionInfo(userInfo string) { func (pp *proxySetProvider) getSubscriptionInfo() {
pp.subscriptionInfo = NewSubscriptionInfo(userInfo) if pp.VehicleType() != types.HTTP {
} return
func (pp *proxySetProvider) SetProvider(provider types.ProxyProvider) {
if httpVehicle, ok := pp.Vehicle().(*resource.HTTPVehicle); ok {
httpVehicle.SetProvider(provider)
} }
go func() {
ctx, cancel := context.WithTimeout(context.Background(), time.Second*90)
defer cancel()
resp, err := mihomoHttp.HttpRequestWithProxy(ctx, pp.Vehicle().(*resource.HTTPVehicle).Url(),
http.MethodGet, http.Header{"User-Agent": {C.UA}}, nil, pp.Vehicle().Proxy())
if err != nil {
return
}
defer resp.Body.Close()
userInfoStr := strings.TrimSpace(resp.Header.Get("subscription-userinfo"))
if userInfoStr == "" {
resp2, err := mihomoHttp.HttpRequestWithProxy(ctx, pp.Vehicle().(*resource.HTTPVehicle).Url(),
http.MethodGet, http.Header{"User-Agent": {"Quantumultx"}}, nil, pp.Vehicle().Proxy())
if err != nil {
return
}
defer resp2.Body.Close()
userInfoStr = strings.TrimSpace(resp2.Header.Get("subscription-userinfo"))
if userInfoStr == "" {
return
}
}
pp.subscriptionInfo, err = NewSubscriptionInfo(userInfoStr)
if err != nil {
log.Warnln("[Provider] get subscription-userinfo: %e", err)
}
}()
} }
func (pp *proxySetProvider) closeAllConnections() { func (pp *proxySetProvider) closeAllConnections() {
@@ -139,9 +164,9 @@ func (pp *proxySetProvider) closeAllConnections() {
}) })
} }
func (pp *proxySetProvider) Close() error { func stopProxyProvider(pd *ProxySetProvider) {
pp.healthCheck.close() pd.healthCheck.close()
return pp.Fetcher.Close() _ = pd.Fetcher.Destroy()
} }
func NewProxySetProvider(name string, interval time.Duration, filter string, excludeFilter string, excludeType string, dialerProxy string, override OverrideSchema, vehicle types.Vehicle, hc *HealthCheck) (*ProxySetProvider, error) { func NewProxySetProvider(name string, interval time.Duration, filter string, excludeFilter string, excludeType string, dialerProxy string, override OverrideSchema, vehicle types.Vehicle, hc *HealthCheck) (*ProxySetProvider, error) {
@@ -175,33 +200,20 @@ func NewProxySetProvider(name string, interval time.Duration, filter string, exc
fetcher := resource.NewFetcher[[]C.Proxy](name, interval, vehicle, proxiesParseAndFilter(filter, excludeFilter, excludeTypeArray, filterRegs, excludeFilterReg, dialerProxy, override), proxiesOnUpdate(pd)) fetcher := resource.NewFetcher[[]C.Proxy](name, interval, vehicle, proxiesParseAndFilter(filter, excludeFilter, excludeTypeArray, filterRegs, excludeFilterReg, dialerProxy, override), proxiesOnUpdate(pd))
pd.Fetcher = fetcher pd.Fetcher = fetcher
wrapper := &ProxySetProvider{pd} wrapper := &ProxySetProvider{pd}
if httpVehicle, ok := vehicle.(*resource.HTTPVehicle); ok { runtime.SetFinalizer(wrapper, stopProxyProvider)
httpVehicle.SetProvider(wrapper)
}
runtime.SetFinalizer(wrapper, (*ProxySetProvider).Close)
return wrapper, nil return wrapper, nil
} }
func (pp *ProxySetProvider) Close() error {
runtime.SetFinalizer(pp, nil)
return pp.proxySetProvider.Close()
}
func (pp *ProxySetProvider) SetProvider(provider types.ProxyProvider) {
pp.proxySetProvider.SetProvider(provider)
}
// CompatibleProvider for auto gc // CompatibleProvider for auto gc
type CompatibleProvider struct { type CompatibleProvider struct {
*compatibleProvider *compatibleProvider
} }
type compatibleProvider struct { type compatibleProvider struct {
name string name string
healthCheck *HealthCheck healthCheck *HealthCheck
subscriptionInfo *SubscriptionInfo proxies []C.Proxy
proxies []C.Proxy version uint32
version uint32
} }
func (cp *compatibleProvider) MarshalJSON() ([]byte, error) { func (cp *compatibleProvider) MarshalJSON() ([]byte, error) {
@@ -250,10 +262,6 @@ func (cp *compatibleProvider) Proxies() []C.Proxy {
return cp.proxies return cp.proxies
} }
func (cp *compatibleProvider) Count() int {
return len(cp.proxies)
}
func (cp *compatibleProvider) Touch() { func (cp *compatibleProvider) Touch() {
cp.healthCheck.touch() cp.healthCheck.touch()
} }
@@ -266,13 +274,8 @@ func (cp *compatibleProvider) RegisterHealthCheckTask(url string, expectedStatus
cp.healthCheck.registerHealthCheckTask(url, expectedStatus, filter, interval) cp.healthCheck.registerHealthCheckTask(url, expectedStatus, filter, interval)
} }
func (cp *compatibleProvider) Close() error { func stopCompatibleProvider(pd *CompatibleProvider) {
cp.healthCheck.close() pd.healthCheck.close()
return nil
}
func (cp *compatibleProvider) SetSubscriptionInfo(userInfo string) {
cp.subscriptionInfo = NewSubscriptionInfo(userInfo)
} }
func NewCompatibleProvider(name string, proxies []C.Proxy, hc *HealthCheck) (*CompatibleProvider, error) { func NewCompatibleProvider(name string, proxies []C.Proxy, hc *HealthCheck) (*CompatibleProvider, error) {
@@ -291,19 +294,15 @@ func NewCompatibleProvider(name string, proxies []C.Proxy, hc *HealthCheck) (*Co
} }
wrapper := &CompatibleProvider{pd} wrapper := &CompatibleProvider{pd}
runtime.SetFinalizer(wrapper, (*CompatibleProvider).Close) runtime.SetFinalizer(wrapper, stopCompatibleProvider)
return wrapper, nil return wrapper, nil
} }
func (cp *CompatibleProvider) Close() error {
runtime.SetFinalizer(cp, nil)
return cp.compatibleProvider.Close()
}
func proxiesOnUpdate(pd *proxySetProvider) func([]C.Proxy) { func proxiesOnUpdate(pd *proxySetProvider) func([]C.Proxy) {
return func(elm []C.Proxy) { return func(elm []C.Proxy) {
pd.setProxies(elm) pd.setProxies(elm)
pd.version += 1 pd.version += 1
pd.getSubscriptionInfo()
} }
} }
@@ -389,16 +388,6 @@ func proxiesParseAndFilter(filter string, excludeFilter string, excludeTypeArray
case "additional-suffix": case "additional-suffix":
name := mapping["name"].(string) name := mapping["name"].(string)
mapping["name"] = name + *field.Interface().(*string) mapping["name"] = name + *field.Interface().(*string)
case "proxy-name":
// Iterate through all naming replacement rules and perform the replacements.
for _, expr := range override.ProxyName {
name := mapping["name"].(string)
newName, err := expr.Pattern.Replace(name, expr.Target, 0, -1)
if err != nil {
return nil, fmt.Errorf("proxy name replace error: %w", err)
}
mapping["name"] = newName
}
default: default:
mapping[fieldName] = field.Elem().Interface() mapping[fieldName] = field.Elem().Interface()
} }

View File

@@ -1,11 +1,8 @@
package provider package provider
import ( import (
"fmt"
"strconv" "strconv"
"strings" "strings"
"github.com/metacubex/mihomo/log"
) )
type SubscriptionInfo struct { type SubscriptionInfo struct {
@@ -15,45 +12,28 @@ type SubscriptionInfo struct {
Expire int64 Expire int64
} }
func NewSubscriptionInfo(userinfo string) (si *SubscriptionInfo) { func NewSubscriptionInfo(userinfo string) (si *SubscriptionInfo, err error) {
userinfo = strings.ReplaceAll(strings.ToLower(userinfo), " ", "") userinfo = strings.ToLower(userinfo)
userinfo = strings.ReplaceAll(userinfo, " ", "")
si = new(SubscriptionInfo) si = new(SubscriptionInfo)
for _, field := range strings.Split(userinfo, ";") { for _, field := range strings.Split(userinfo, ";") {
name, value, ok := strings.Cut(field, "=") switch name, value, _ := strings.Cut(field, "="); name {
if !ok {
continue
}
intValue, err := parseValue(value)
if err != nil {
log.Warnln("[Provider] get subscription-userinfo: %e", err)
continue
}
switch name {
case "upload": case "upload":
si.Upload = intValue si.Upload, err = strconv.ParseInt(value, 10, 64)
case "download": case "download":
si.Download = intValue si.Download, err = strconv.ParseInt(value, 10, 64)
case "total": case "total":
si.Total = intValue si.Total, err = strconv.ParseInt(value, 10, 64)
case "expire": case "expire":
si.Expire = intValue if value == "" {
si.Expire = 0
} else {
si.Expire, err = strconv.ParseInt(value, 10, 64)
}
}
if err != nil {
return
} }
} }
return
return si
}
func parseValue(value string) (int64, error) {
if intValue, err := strconv.ParseInt(value, 10, 64); err == nil {
return intValue, nil
}
if floatValue, err := strconv.ParseFloat(value, 64); err == nil {
return int64(floatValue), nil
}
return 0, fmt.Errorf("failed to parse value '%s'", value)
} }

View File

@@ -33,8 +33,15 @@ type ARC[K comparable, V any] struct {
// New returns a new Adaptive Replacement Cache (ARC). // New returns a new Adaptive Replacement Cache (ARC).
func New[K comparable, V any](options ...Option[K, V]) *ARC[K, V] { func New[K comparable, V any](options ...Option[K, V]) *ARC[K, V] {
arc := &ARC[K, V]{} arc := &ARC[K, V]{
arc.Clear() p: 0,
t1: list.New[*entry[K, V]](),
b1: list.New[*entry[K, V]](),
t2: list.New[*entry[K, V]](),
b2: list.New[*entry[K, V]](),
len: 0,
cache: make(map[K]*entry[K, V]),
}
for _, option := range options { for _, option := range options {
option(arc) option(arc)
@@ -42,19 +49,6 @@ func New[K comparable, V any](options ...Option[K, V]) *ARC[K, V] {
return arc return arc
} }
func (a *ARC[K, V]) Clear() {
a.mutex.Lock()
defer a.mutex.Unlock()
a.p = 0
a.t1 = list.New[*entry[K, V]]()
a.b1 = list.New[*entry[K, V]]()
a.t2 = list.New[*entry[K, V]]()
a.b2 = list.New[*entry[K, V]]()
a.len = 0
a.cache = make(map[K]*entry[K, V])
}
// Set inserts a new key-value pair into the cache. // Set inserts a new key-value pair into the cache.
// This optimizes future access to this entry (side effect). // This optimizes future access to this entry (side effect).
func (a *ARC[K, V]) Set(key K, value V) { func (a *ARC[K, V]) Set(key K, value V) {

View File

@@ -68,8 +68,10 @@ type LruCache[K comparable, V any] struct {
// New creates an LruCache // New creates an LruCache
func New[K comparable, V any](options ...Option[K, V]) *LruCache[K, V] { func New[K comparable, V any](options ...Option[K, V]) *LruCache[K, V] {
lc := &LruCache[K, V]{} lc := &LruCache[K, V]{
lc.Clear() lru: list.New[*entry[K, V]](),
cache: make(map[K]*list.Element[*entry[K, V]]),
}
for _, option := range options { for _, option := range options {
option(lc) option(lc)
@@ -78,14 +80,6 @@ func New[K comparable, V any](options ...Option[K, V]) *LruCache[K, V] {
return lc return lc
} }
func (c *LruCache[K, V]) Clear() {
c.mu.Lock()
defer c.mu.Unlock()
c.lru = list.New[*entry[K, V]]()
c.cache = make(map[K]*list.Element[*entry[K, V]])
}
// Get returns any representation of a cached response and a bool // Get returns any representation of a cached response and a bool
// set to true if the key was found. // set to true if the key was found.
func (c *LruCache[K, V]) Get(key K) (V, bool) { func (c *LruCache[K, V]) Get(key K) (V, bool) {
@@ -256,6 +250,15 @@ func (c *LruCache[K, V]) deleteElement(le *list.Element[*entry[K, V]]) {
} }
} }
func (c *LruCache[K, V]) Clear() error {
c.mu.Lock()
defer c.mu.Unlock()
c.cache = make(map[K]*list.Element[*entry[K, V]])
return nil
}
// Compute either sets the computed new value for the key or deletes // Compute either sets the computed new value for the key or deletes
// the value for the key. When the delete result of the valueFn function // the value for the key. When the delete result of the valueFn function
// is set to true, the value will be deleted, if it exists. When delete // is set to true, the value will be deleted, if it exists. When delete

View File

@@ -4,8 +4,11 @@ import (
"fmt" "fmt"
"net" "net"
"strings" "strings"
"time"
) )
var KeepAliveInterval = 15 * time.Second
func SplitNetworkType(s string) (string, string, error) { func SplitNetworkType(s string) (string, string, error) {
var ( var (
shecme string shecme string
@@ -44,3 +47,10 @@ func SplitHostPort(s string) (host, port string, hasPort bool, err error) {
host, port, err = net.SplitHostPort(temp) host, port, err = net.SplitHostPort(temp)
return return
} }
func TCPKeepAlive(c net.Conn) {
if tcp, ok := c.(*net.TCPConn); ok {
_ = tcp.SetKeepAlive(true)
_ = tcp.SetKeepAlivePeriod(KeepAliveInterval)
}
}

View File

@@ -51,23 +51,3 @@ func UnMasked(p netip.Prefix) netip.Addr {
} }
return addr return addr
} }
// PrefixCompare returns an integer comparing two prefixes.
// The result will be 0 if p == p2, -1 if p < p2, and +1 if p > p2.
// modify from https://github.com/golang/go/issues/61642#issuecomment-1848587909
func PrefixCompare(p, p2 netip.Prefix) int {
// compare by validity, address family and prefix base address
if c := p.Masked().Addr().Compare(p2.Masked().Addr()); c != 0 {
return c
}
// compare by prefix length
f1, f2 := p.Bits(), p2.Bits()
if f1 < f2 {
return -1
}
if f1 > f2 {
return 1
}
// compare by prefix address
return p.Addr().Compare(p2.Addr())
}

View File

@@ -1,224 +0,0 @@
// 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()
}

View File

@@ -3,7 +3,6 @@ package structure
// references: https://github.com/mitchellh/mapstructure // references: https://github.com/mitchellh/mapstructure
import ( import (
"encoding"
"encoding/base64" "encoding/base64"
"fmt" "fmt"
"reflect" "reflect"
@@ -87,41 +86,35 @@ func (d *Decoder) Decode(src map[string]any, dst any) error {
} }
func (d *Decoder) decode(name string, data any, val reflect.Value) error { func (d *Decoder) decode(name string, data any, val reflect.Value) error {
for { kind := val.Kind()
kind := val.Kind() switch {
if kind == reflect.Pointer && val.IsNil() { case isInt(kind):
return d.decodeInt(name, data, val)
case isUint(kind):
return d.decodeUint(name, data, val)
case isFloat(kind):
return d.decodeFloat(name, data, val)
}
switch kind {
case reflect.Pointer:
if val.IsNil() {
val.Set(reflect.New(val.Type().Elem())) val.Set(reflect.New(val.Type().Elem()))
} }
if ok, err := d.decodeTextUnmarshaller(name, data, val); ok { return d.decode(name, data, val.Elem())
return err case reflect.String:
} return d.decodeString(name, data, val)
switch { case reflect.Bool:
case isInt(kind): return d.decodeBool(name, data, val)
return d.decodeInt(name, data, val) case reflect.Slice:
case isUint(kind): return d.decodeSlice(name, data, val)
return d.decodeUint(name, data, val) case reflect.Map:
case isFloat(kind): return d.decodeMap(name, data, val)
return d.decodeFloat(name, data, val) case reflect.Interface:
} return d.setInterface(name, data, val)
switch kind { case reflect.Struct:
case reflect.Pointer: return d.decodeStruct(name, data, val)
val = val.Elem() default:
continue return fmt.Errorf("type %s not support", val.Kind().String())
case reflect.String:
return d.decodeString(name, data, val)
case reflect.Bool:
return d.decodeBool(name, data, val)
case reflect.Slice:
return d.decodeSlice(name, data, val)
case reflect.Map:
return d.decodeMap(name, data, val)
case reflect.Interface:
return d.setInterface(name, data, val)
case reflect.Struct:
return d.decodeStruct(name, data, val)
default:
return fmt.Errorf("type %s not support", val.Kind().String())
}
} }
} }
@@ -560,25 +553,3 @@ func (d *Decoder) setInterface(name string, data any, val reflect.Value) (err er
val.Set(dataVal) val.Set(dataVal)
return nil return nil
} }
func (d *Decoder) decodeTextUnmarshaller(name string, data any, val reflect.Value) (bool, error) {
if !val.CanAddr() {
return false, nil
}
valAddr := val.Addr()
if !valAddr.CanInterface() {
return false, nil
}
unmarshaller, ok := valAddr.Interface().(encoding.TextUnmarshaler)
if !ok {
return false, nil
}
var str string
if err := d.decodeString(name, data, reflect.Indirect(reflect.ValueOf(&str))); err != nil {
return false, err
}
if err := unmarshaller.UnmarshalText([]byte(str)); err != nil {
return true, fmt.Errorf("cannot parse '%s' as %s: %s", name, val.Type(), err)
}
return true, nil
}

View File

@@ -1,7 +1,6 @@
package structure package structure
import ( import (
"strconv"
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@@ -180,90 +179,3 @@ func TestStructure_SliceNilValueComplex(t *testing.T) {
err = decoder.Decode(rawMap, ss) err = decoder.Decode(rawMap, ss)
assert.NotNil(t, err) assert.NotNil(t, err)
} }
func TestStructure_SliceCap(t *testing.T) {
rawMap := map[string]any{
"foo": []string{},
}
s := &struct {
Foo []string `test:"foo,omitempty"`
Bar []string `test:"bar,omitempty"`
}{}
err := decoder.Decode(rawMap, s)
assert.Nil(t, err)
assert.NotNil(t, s.Foo) // structure's Decode will ensure value not nil when input has value even it was set an empty array
assert.Nil(t, s.Bar)
}
func TestStructure_Base64(t *testing.T) {
rawMap := map[string]any{
"foo": "AQID",
}
s := &struct {
Foo []byte `test:"foo"`
}{}
err := decoder.Decode(rawMap, s)
assert.Nil(t, err)
assert.Equal(t, []byte{1, 2, 3}, s.Foo)
}
func TestStructure_Pointer(t *testing.T) {
rawMap := map[string]any{
"foo": "foo",
}
s := &struct {
Foo *string `test:"foo,omitempty"`
Bar *string `test:"bar,omitempty"`
}{}
err := decoder.Decode(rawMap, s)
assert.Nil(t, err)
assert.NotNil(t, s.Foo)
assert.Equal(t, "foo", *s.Foo)
assert.Nil(t, s.Bar)
}
type num struct {
a int
}
func (n *num) UnmarshalText(text []byte) (err error) {
n.a, err = strconv.Atoi(string(text))
return
}
func TestStructure_TextUnmarshaller(t *testing.T) {
rawMap := map[string]any{
"num": "255",
"num_p": "127",
}
s := &struct {
Num num `test:"num"`
NumP *num `test:"num_p"`
}{}
err := decoder.Decode(rawMap, s)
assert.Nil(t, err)
assert.Equal(t, 255, s.Num.a)
assert.NotNil(t, s.NumP)
assert.Equal(t, s.NumP.a, 127)
// test WeaklyTypedInput
rawMap["num"] = 256
err = decoder.Decode(rawMap, s)
assert.NotNilf(t, err, "should throw error: %#v", s)
err = weakTypeDecoder.Decode(rawMap, s)
assert.Nil(t, err)
assert.Equal(t, 256, s.Num.a)
// test invalid input
rawMap["num_p"] = "abc"
err = decoder.Decode(rawMap, s)
assert.NotNilf(t, err, "should throw error: %#v", s)
}

View File

@@ -1,62 +0,0 @@
package utils
import (
"crypto/md5"
"encoding/hex"
"errors"
)
// HashType warps hash array inside struct
// someday can change to other hash algorithm simply
type HashType struct {
md5 [md5.Size]byte // MD5
}
func MakeHash(data []byte) HashType {
return HashType{md5.Sum(data)}
}
func (h HashType) Equal(hash HashType) bool {
return h.md5 == hash.md5
}
func (h HashType) Bytes() []byte {
return h.md5[:]
}
func (h HashType) String() string {
return hex.EncodeToString(h.Bytes())
}
func (h HashType) MarshalText() ([]byte, error) {
return []byte(h.String()), nil
}
func (h *HashType) UnmarshalText(data []byte) error {
if hex.DecodedLen(len(data)) != md5.Size {
return errors.New("invalid hash length")
}
_, err := hex.Decode(h.md5[:], data)
return err
}
func (h HashType) MarshalBinary() ([]byte, error) {
return h.md5[:], nil
}
func (h *HashType) UnmarshalBinary(data []byte) error {
if len(data) != md5.Size {
return errors.New("invalid hash length")
}
copy(h.md5[:], data)
return nil
}
func (h HashType) Len() int {
return len(h.md5)
}
func (h HashType) IsValid() bool {
var zero HashType
return h != zero
}

View File

@@ -5,11 +5,6 @@ type Authenticator interface {
Users() []string Users() []string
} }
type AuthStore interface {
Authenticator() Authenticator
SetAuthenticator(Authenticator)
}
type AuthUser struct { type AuthUser struct {
User string User string
Pass string Pass string

View File

@@ -46,14 +46,6 @@ func (set *IpCidrSet) IsContain(ip netip.Addr) bool {
return set.ToIPSet().Contains(ip.WithZone("")) return set.ToIPSet().Contains(ip.WithZone(""))
} }
// MatchIp implements C.IpMatcher
func (set *IpCidrSet) MatchIp(ip netip.Addr) bool {
if set.IsEmpty() {
return false
}
return set.IsContain(ip)
}
func (set *IpCidrSet) Merge() error { func (set *IpCidrSet) Merge() error {
var b netipx.IPSetBuilder var b netipx.IPSetBuilder
b.AddSet(set.ToIPSet()) b.AddSet(set.ToIPSet())
@@ -65,10 +57,6 @@ func (set *IpCidrSet) Merge() error {
return nil return nil
} }
func (set *IpCidrSet) IsEmpty() bool {
return set == nil || len(set.rr) == 0
}
func (set *IpCidrSet) Foreach(f func(prefix netip.Prefix) bool) { func (set *IpCidrSet) Foreach(f func(prefix netip.Prefix) bool) {
for _, r := range set.rr { for _, r := range set.rr {
for _, prefix := range r.Prefixes() { for _, prefix := range r.Prefixes() {

View File

@@ -12,8 +12,8 @@ import (
"sync" "sync"
"time" "time"
"github.com/metacubex/mihomo/component/keepalive"
"github.com/metacubex/mihomo/component/resolver" "github.com/metacubex/mihomo/component/resolver"
"github.com/metacubex/mihomo/constant/features"
"github.com/metacubex/mihomo/log" "github.com/metacubex/mihomo/log"
) )
@@ -79,29 +79,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) { 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...) cfg := applyOptions(options...)
lc := &net.ListenConfig{} 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 { if cfg.addrReuse {
addrReuseToListenConfig(lc) addrReuseToListenConfig(lc)
} }
if DefaultSocketHook != nil { // ignore interfaceName, routingMark when DefaultSocketHook not null (in CMFA) if cfg.routingMark != 0 {
socketHookToListenConfig(lc) bindMarkToListenConfig(cfg.routingMark, lc, network, address)
} 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) return lc.ListenPacket(ctx, network, address)
@@ -127,6 +127,10 @@ func GetTcpConcurrent() bool {
} }
func dialContext(ctx context.Context, network string, destination netip.Addr, port string, opt *option) (net.Conn, error) { 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 var address string
if IP4PEnable { if IP4PEnable {
destination, port = lookupIP4P(destination, port) destination, port = lookupIP4P(destination, port)
@@ -145,31 +149,24 @@ func dialContext(ctx context.Context, network string, destination netip.Addr, po
} }
dialer := netDialer.(*net.Dialer) dialer := netDialer.(*net.Dialer)
keepalive.SetNetDialer(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 { if opt.mpTcp {
setMultiPathTCP(dialer) setMultiPathTCP(dialer)
} }
if opt.tfo && !DisableTFO {
if DefaultSocketHook != nil { // ignore interfaceName, routingMark and tfo when DefaultSocketHook not null (in CMFA) return dialTFO(ctx, *dialer, network, address)
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) return dialer.DialContext(ctx, network, address)
} }
@@ -340,18 +337,26 @@ func parseAddr(ctx context.Context, network, address string, preferResolver reso
return nil, "-1", err return nil, "-1", err
} }
if preferResolver == nil {
preferResolver = resolver.ProxyServerHostResolver
}
var ips []netip.Addr var ips []netip.Addr
switch network { switch network {
case "tcp4", "udp4": case "tcp4", "udp4":
ips, err = resolver.LookupIPv4WithResolver(ctx, host, preferResolver) if preferResolver == nil {
ips, err = resolver.LookupIPv4ProxyServerHost(ctx, host)
} else {
ips, err = resolver.LookupIPv4WithResolver(ctx, host, preferResolver)
}
case "tcp6", "udp6": case "tcp6", "udp6":
ips, err = resolver.LookupIPv6WithResolver(ctx, host, preferResolver) if preferResolver == nil {
ips, err = resolver.LookupIPv6ProxyServerHost(ctx, host)
} else {
ips, err = resolver.LookupIPv6WithResolver(ctx, host, preferResolver)
}
default: default:
ips, err = resolver.LookupIPWithResolver(ctx, host, preferResolver) if preferResolver == nil {
ips, err = resolver.LookupIPProxyServerHost(ctx, host)
} else {
ips, err = resolver.LookupIPWithResolver(ctx, host, preferResolver)
}
} }
if err != nil { if err != nil {
return nil, "-1", fmt.Errorf("dns resolve failed: %w", err) return nil, "-1", fmt.Errorf("dns resolve failed: %w", err)

View File

@@ -0,0 +1,39 @@
//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)
}

View File

@@ -0,0 +1,22 @@
//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
}

View File

@@ -0,0 +1,29 @@
package dialer
import (
"context"
"net"
"net/netip"
)
func init() {
// We must use this DialContext to query DNS
// when using net default resolver.
net.DefaultResolver.PreferGo = true
net.DefaultResolver.Dial = resolverDialContext
}
func resolverDialContext(ctx context.Context, network, address string) (net.Conn, error) {
d := &net.Dialer{}
interfaceName := DefaultInterface.Load()
if interfaceName != "" {
dstIP, err := netip.ParseAddr(address)
if err == nil {
_ = bindIfaceToDialer(interfaceName, d, network, dstIP)
}
}
return d.DialContext(ctx, network, address)
}

View File

@@ -1,27 +0,0 @@
package dialer
import (
"context"
"net"
"syscall"
)
// SocketControl
// never change type traits because it's used in CMFA
type SocketControl func(network, address string, conn syscall.RawConn) error
// DefaultSocketHook
// never change type traits because it's used in CMFA
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)
})
}

View File

@@ -5,12 +5,8 @@ import (
"io" "io"
"net" "net"
"time" "time"
"github.com/metacubex/tfo-go"
) )
var DisableTFO = false
type tfoConn struct { type tfoConn struct {
net.Conn net.Conn
closed bool closed bool
@@ -124,16 +120,3 @@ func (c *tfoConn) ReaderReplaceable() bool {
func (c *tfoConn) WriterReplaceable() bool { func (c *tfoConn) WriterReplaceable() bool {
return c.Conn != nil return c.Conn != nil
} }
func dialTFO(ctx context.Context, netDialer net.Dialer, network, address string) (net.Conn, error) {
ctx, cancel := context.WithTimeout(context.Background(), DefaultTCPTimeout)
dialer := tfo.Dialer{Dialer: netDialer, DisableTFO: false}
return &tfoConn{
dialed: make(chan bool, 1),
cancel: cancel,
ctx: ctx,
dialFn: func(ctx context.Context, earlyData []byte) (net.Conn, error) {
return dialer.DialContext(ctx, network, address, earlyData)
},
}, nil
}

View File

@@ -0,0 +1,25 @@
//go:build unix
package dialer
import (
"context"
"net"
"github.com/metacubex/tfo-go"
)
const DisableTFO = false
func dialTFO(ctx context.Context, netDialer net.Dialer, network, address string) (net.Conn, error) {
ctx, cancel := context.WithTimeout(context.Background(), DefaultTCPTimeout)
dialer := tfo.Dialer{Dialer: netDialer, DisableTFO: false}
return &tfoConn{
dialed: make(chan bool, 1),
cancel: cancel,
ctx: ctx,
dialFn: func(ctx context.Context, earlyData []byte) (net.Conn, error) {
return dialer.DialContext(ctx, network, address, earlyData)
},
}, nil
}

View File

@@ -1,11 +1,12 @@
package dialer package dialer
import "github.com/metacubex/mihomo/constant/features" import (
"context"
"net"
)
func init() { const DisableTFO = true
// According to MSDN, this option is available since Windows 10, 1607
// https://msdn.microsoft.com/en-us/library/windows/desktop/ms738596(v=vs.85).aspx func dialTFO(ctx context.Context, netDialer net.Dialer, network, address string) (net.Conn, error) {
if features.WindowsMajorVersion < 10 || (features.WindowsMajorVersion == 10 && features.WindowsBuildNumber < 14393) { return netDialer.DialContext(ctx, network, address)
DisableTFO = true
}
} }

View File

@@ -0,0 +1,99 @@
/* 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

View File

@@ -0,0 +1,262 @@
/* 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

342
component/ebpf/bpf/redir.c Normal file
View File

@@ -0,0 +1,342 @@
#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";

103
component/ebpf/bpf/tc.c Normal file
View File

@@ -0,0 +1,103 @@
#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";

View File

@@ -0,0 +1,13 @@
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)
}

View File

@@ -0,0 +1,12 @@
//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 }

View File

@@ -0,0 +1,15 @@
//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) }

33
component/ebpf/ebpf.go Normal file
View File

@@ -0,0 +1,33 @@
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
}

View File

@@ -0,0 +1,137 @@
//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))
}
}

View File

@@ -0,0 +1,21 @@
//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")
}

View File

@@ -0,0 +1,216 @@
//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
}

View File

@@ -0,0 +1,139 @@
// 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.

View File

@@ -0,0 +1,139 @@
// 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.

View File

@@ -0,0 +1,120 @@
// 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.

View File

@@ -0,0 +1,120 @@
//
//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.

View File

@@ -0,0 +1,147 @@
//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")
}

View File

@@ -7,32 +7,46 @@ import (
) )
type cachefileStore struct { type cachefileStore struct {
cache *cachefile.FakeIpStore cache *cachefile.CacheFile
} }
// GetByHost implements store.GetByHost // GetByHost implements store.GetByHost
func (c *cachefileStore) GetByHost(host string) (netip.Addr, bool) { func (c *cachefileStore) GetByHost(host string) (netip.Addr, bool) {
return c.cache.GetByHost(host) elm := c.cache.GetFakeip([]byte(host))
if elm == nil {
return netip.Addr{}, false
}
if len(elm) == 4 {
return netip.AddrFrom4(*(*[4]byte)(elm)), true
} else {
return netip.AddrFrom16(*(*[16]byte)(elm)), true
}
} }
// PutByHost implements store.PutByHost // PutByHost implements store.PutByHost
func (c *cachefileStore) PutByHost(host string, ip netip.Addr) { func (c *cachefileStore) PutByHost(host string, ip netip.Addr) {
c.cache.PutByHost(host, ip) c.cache.PutFakeip([]byte(host), ip.AsSlice())
} }
// GetByIP implements store.GetByIP // GetByIP implements store.GetByIP
func (c *cachefileStore) GetByIP(ip netip.Addr) (string, bool) { func (c *cachefileStore) GetByIP(ip netip.Addr) (string, bool) {
return c.cache.GetByIP(ip) elm := c.cache.GetFakeip(ip.AsSlice())
if elm == nil {
return "", false
}
return string(elm), true
} }
// PutByIP implements store.PutByIP // PutByIP implements store.PutByIP
func (c *cachefileStore) PutByIP(ip netip.Addr, host string) { func (c *cachefileStore) PutByIP(ip netip.Addr, host string) {
c.cache.PutByIP(ip, host) c.cache.PutFakeip(ip.AsSlice(), []byte(host))
} }
// DelByIP implements store.DelByIP // DelByIP implements store.DelByIP
func (c *cachefileStore) DelByIP(ip netip.Addr) { func (c *cachefileStore) DelByIP(ip netip.Addr) {
c.cache.DelByIP(ip) addr := ip.AsSlice()
c.cache.DelFakeipPair(addr, c.cache.GetFakeip(addr))
} }
// Exist implements store.Exist // Exist implements store.Exist
@@ -49,7 +63,3 @@ func (c *cachefileStore) CloneTo(store store) {}
func (c *cachefileStore) FlushFakeIP() error { func (c *cachefileStore) FlushFakeIP() error {
return c.cache.FlushFakeIP() return c.cache.FlushFakeIP()
} }
func newCachefileStore(cache *cachefile.CacheFile) *cachefileStore {
return &cachefileStore{cache.FakeIpStore()}
}

View File

@@ -67,9 +67,8 @@ func (m *memoryStore) CloneTo(store store) {
// FlushFakeIP implements store.FlushFakeIP // FlushFakeIP implements store.FlushFakeIP
func (m *memoryStore) FlushFakeIP() error { func (m *memoryStore) FlushFakeIP() error {
m.cacheIP.Clear() _ = m.cacheIP.Clear()
m.cacheHost.Clear() return m.cacheHost.Clear()
return nil
} }
func newMemoryStore(size int) *memoryStore { func newMemoryStore(size int) *memoryStore {

View File

@@ -8,7 +8,7 @@ import (
"github.com/metacubex/mihomo/common/nnip" "github.com/metacubex/mihomo/common/nnip"
"github.com/metacubex/mihomo/component/profile/cachefile" "github.com/metacubex/mihomo/component/profile/cachefile"
C "github.com/metacubex/mihomo/constant" "github.com/metacubex/mihomo/component/trie"
) )
const ( const (
@@ -35,8 +35,7 @@ type Pool struct {
offset netip.Addr offset netip.Addr
cycle bool cycle bool
mux sync.Mutex mux sync.Mutex
host []C.DomainMatcher host *trie.DomainTrie[struct{}]
mode C.FilterMode
ipnet netip.Prefix ipnet netip.Prefix
store store store store
} }
@@ -67,20 +66,10 @@ func (p *Pool) LookBack(ip netip.Addr) (string, bool) {
// ShouldSkipped return if domain should be skipped // ShouldSkipped return if domain should be skipped
func (p *Pool) ShouldSkipped(domain string) bool { func (p *Pool) ShouldSkipped(domain string) bool {
should := p.shouldSkipped(domain) if p.host == nil {
if p.mode == C.FilterWhiteList { return false
return !should
} }
return should return p.host.Search(domain) != nil
}
func (p *Pool) shouldSkipped(domain string) bool {
for _, matcher := range p.host {
if matcher.MatchDomain(domain) {
return true
}
}
return false
} }
// Exist returns if given ip exists in fake-ip pool // Exist returns if given ip exists in fake-ip pool
@@ -165,8 +154,7 @@ func (p *Pool) restoreState() {
type Options struct { type Options struct {
IPNet netip.Prefix IPNet netip.Prefix
Host []C.DomainMatcher Host *trie.DomainTrie[struct{}]
Mode C.FilterMode
// Size sets the maximum number of entries in memory // Size sets the maximum number of entries in memory
// and does not work if Persistence is true // and does not work if Persistence is true
@@ -197,11 +185,12 @@ func New(options Options) (*Pool, error) {
offset: first.Prev(), offset: first.Prev(),
cycle: false, cycle: false,
host: options.Host, host: options.Host,
mode: options.Mode,
ipnet: options.IPNet, ipnet: options.IPNet,
} }
if options.Persistence { if options.Persistence {
pool.store = newCachefileStore(cachefile.Cache()) pool.store = &cachefileStore{
cache: cachefile.Cache(),
}
} else { } else {
pool.store = newMemoryStore(options.Size) pool.store = newMemoryStore(options.Size)
} }

View File

@@ -9,9 +9,8 @@ import (
"github.com/metacubex/mihomo/component/profile/cachefile" "github.com/metacubex/mihomo/component/profile/cachefile"
"github.com/metacubex/mihomo/component/trie" "github.com/metacubex/mihomo/component/trie"
C "github.com/metacubex/mihomo/constant"
"github.com/metacubex/bbolt" "github.com/sagernet/bbolt"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
@@ -43,7 +42,9 @@ func createCachefileStore(options Options) (*Pool, string, error) {
return nil, "", err return nil, "", err
} }
pool.store = newCachefileStore(&cachefile.CacheFile{DB: db}) pool.store = &cachefileStore{
cache: &cachefile.CacheFile{DB: db},
}
return pool, f.Name(), nil return pool, f.Name(), nil
} }
@@ -61,13 +62,13 @@ func TestPool_Basic(t *testing.T) {
last := pool.Lookup("bar.com") last := pool.Lookup("bar.com")
bar, exist := pool.LookBack(last) bar, exist := pool.LookBack(last)
assert.Equal(t, first, netip.AddrFrom4([4]byte{192, 168, 0, 4})) assert.True(t, first == netip.AddrFrom4([4]byte{192, 168, 0, 4}))
assert.Equal(t, pool.Lookup("foo.com"), netip.AddrFrom4([4]byte{192, 168, 0, 4})) assert.True(t, pool.Lookup("foo.com") == netip.AddrFrom4([4]byte{192, 168, 0, 4}))
assert.Equal(t, last, netip.AddrFrom4([4]byte{192, 168, 0, 5})) assert.True(t, last == netip.AddrFrom4([4]byte{192, 168, 0, 5}))
assert.True(t, exist) assert.True(t, exist)
assert.Equal(t, bar, "bar.com") assert.Equal(t, bar, "bar.com")
assert.Equal(t, pool.Gateway(), netip.AddrFrom4([4]byte{192, 168, 0, 1})) assert.True(t, pool.Gateway() == netip.AddrFrom4([4]byte{192, 168, 0, 1}))
assert.Equal(t, pool.Broadcast(), netip.AddrFrom4([4]byte{192, 168, 0, 15})) assert.True(t, pool.Broadcast() == netip.AddrFrom4([4]byte{192, 168, 0, 15}))
assert.Equal(t, pool.IPNet().String(), ipnet.String()) assert.Equal(t, pool.IPNet().String(), ipnet.String())
assert.True(t, pool.Exist(netip.AddrFrom4([4]byte{192, 168, 0, 5}))) assert.True(t, pool.Exist(netip.AddrFrom4([4]byte{192, 168, 0, 5})))
assert.False(t, pool.Exist(netip.AddrFrom4([4]byte{192, 168, 0, 6}))) assert.False(t, pool.Exist(netip.AddrFrom4([4]byte{192, 168, 0, 6})))
@@ -89,13 +90,13 @@ func TestPool_BasicV6(t *testing.T) {
last := pool.Lookup("bar.com") last := pool.Lookup("bar.com")
bar, exist := pool.LookBack(last) bar, exist := pool.LookBack(last)
assert.Equal(t, first, netip.MustParseAddr("2001:4860:4860:0000:0000:0000:0000:8804")) assert.True(t, first == netip.MustParseAddr("2001:4860:4860:0000:0000:0000:0000:8804"))
assert.Equal(t, pool.Lookup("foo.com"), netip.MustParseAddr("2001:4860:4860:0000:0000:0000:0000:8804")) assert.True(t, pool.Lookup("foo.com") == netip.MustParseAddr("2001:4860:4860:0000:0000:0000:0000:8804"))
assert.Equal(t, last, netip.MustParseAddr("2001:4860:4860:0000:0000:0000:0000:8805")) assert.True(t, last == netip.MustParseAddr("2001:4860:4860:0000:0000:0000:0000:8805"))
assert.True(t, exist) assert.True(t, exist)
assert.Equal(t, bar, "bar.com") assert.Equal(t, bar, "bar.com")
assert.Equal(t, pool.Gateway(), netip.MustParseAddr("2001:4860:4860:0000:0000:0000:0000:8801")) assert.True(t, pool.Gateway() == netip.MustParseAddr("2001:4860:4860:0000:0000:0000:0000:8801"))
assert.Equal(t, pool.Broadcast(), netip.MustParseAddr("2001:4860:4860:0000:0000:0000:0000:8bff")) assert.True(t, pool.Broadcast() == netip.MustParseAddr("2001:4860:4860:0000:0000:0000:0000:8bff"))
assert.Equal(t, pool.IPNet().String(), ipnet.String()) assert.Equal(t, pool.IPNet().String(), ipnet.String())
assert.True(t, pool.Exist(netip.MustParseAddr("2001:4860:4860:0000:0000:0000:0000:8805"))) assert.True(t, pool.Exist(netip.MustParseAddr("2001:4860:4860:0000:0000:0000:0000:8805")))
assert.False(t, pool.Exist(netip.MustParseAddr("2001:4860:4860:0000:0000:0000:0000:8806"))) assert.False(t, pool.Exist(netip.MustParseAddr("2001:4860:4860:0000:0000:0000:0000:8806")))
@@ -141,20 +142,19 @@ func TestPool_CycleUsed(t *testing.T) {
} }
baz := pool.Lookup("baz.com") baz := pool.Lookup("baz.com")
next := pool.Lookup("foo.com") next := pool.Lookup("foo.com")
assert.Equal(t, foo, baz) assert.True(t, foo == baz)
assert.Equal(t, next, bar) assert.True(t, next == bar)
} }
} }
func TestPool_Skip(t *testing.T) { func TestPool_Skip(t *testing.T) {
ipnet := netip.MustParsePrefix("192.168.0.1/29") ipnet := netip.MustParsePrefix("192.168.0.1/29")
tree := trie.New[struct{}]() tree := trie.New[struct{}]()
assert.NoError(t, tree.Insert("example.com", struct{}{})) tree.Insert("example.com", struct{}{})
assert.False(t, tree.IsEmpty())
pools, tempfile, err := createPools(Options{ pools, tempfile, err := createPools(Options{
IPNet: ipnet, IPNet: ipnet,
Size: 10, Size: 10,
Host: []C.DomainMatcher{tree.NewDomainSet()}, Host: tree,
}) })
assert.Nil(t, err) assert.Nil(t, err)
defer os.Remove(tempfile) defer os.Remove(tempfile)
@@ -162,28 +162,6 @@ func TestPool_Skip(t *testing.T) {
for _, pool := range pools { for _, pool := range pools {
assert.True(t, pool.ShouldSkipped("example.com")) assert.True(t, pool.ShouldSkipped("example.com"))
assert.False(t, pool.ShouldSkipped("foo.com")) assert.False(t, pool.ShouldSkipped("foo.com"))
assert.False(t, pool.shouldSkipped("baz.com"))
}
}
func TestPool_SkipWhiteList(t *testing.T) {
ipnet := netip.MustParsePrefix("192.168.0.1/29")
tree := trie.New[struct{}]()
assert.NoError(t, tree.Insert("example.com", struct{}{}))
assert.False(t, tree.IsEmpty())
pools, tempfile, err := createPools(Options{
IPNet: ipnet,
Size: 10,
Host: []C.DomainMatcher{tree.NewDomainSet()},
Mode: C.FilterWhiteList,
})
assert.Nil(t, err)
defer os.Remove(tempfile)
for _, pool := range pools {
assert.False(t, pool.ShouldSkipped("example.com"))
assert.True(t, pool.ShouldSkipped("foo.com"))
assert.True(t, pool.ShouldSkipped("baz.com"))
} }
} }
@@ -199,7 +177,7 @@ func TestPool_MaxCacheSize(t *testing.T) {
pool.Lookup("baz.com") pool.Lookup("baz.com")
next := pool.Lookup("foo.com") next := pool.Lookup("foo.com")
assert.NotEqual(t, first, next) assert.False(t, first == next)
} }
func TestPool_DoubleMapping(t *testing.T) { func TestPool_DoubleMapping(t *testing.T) {
@@ -229,7 +207,7 @@ func TestPool_DoubleMapping(t *testing.T) {
assert.False(t, bazExist) assert.False(t, bazExist)
assert.True(t, barExist) assert.True(t, barExist)
assert.NotEqual(t, bazIP, newBazIP) assert.False(t, bazIP == newBazIP)
} }
func TestPool_Clone(t *testing.T) { func TestPool_Clone(t *testing.T) {
@@ -241,8 +219,8 @@ func TestPool_Clone(t *testing.T) {
first := pool.Lookup("foo.com") first := pool.Lookup("foo.com")
last := pool.Lookup("bar.com") last := pool.Lookup("bar.com")
assert.Equal(t, first, netip.AddrFrom4([4]byte{192, 168, 0, 4})) assert.True(t, first == netip.AddrFrom4([4]byte{192, 168, 0, 4}))
assert.Equal(t, last, netip.AddrFrom4([4]byte{192, 168, 0, 5})) assert.True(t, last == netip.AddrFrom4([4]byte{192, 168, 0, 5}))
newPool, _ := New(Options{ newPool, _ := New(Options{
IPNet: ipnet, IPNet: ipnet,
@@ -287,13 +265,13 @@ func TestPool_FlushFileCache(t *testing.T) {
baz := pool.Lookup("foo.com") baz := pool.Lookup("foo.com")
nero := pool.Lookup("foo.com") nero := pool.Lookup("foo.com")
assert.Equal(t, foo, fox) assert.True(t, foo == fox)
assert.Equal(t, foo, next) assert.True(t, foo == next)
assert.NotEqual(t, foo, baz) assert.False(t, foo == baz)
assert.Equal(t, bar, bax) assert.True(t, bar == bax)
assert.Equal(t, bar, baz) assert.True(t, bar == baz)
assert.NotEqual(t, bar, next) assert.False(t, bar == next)
assert.Equal(t, baz, nero) assert.True(t, baz == nero)
} }
} }
@@ -316,11 +294,11 @@ func TestPool_FlushMemoryCache(t *testing.T) {
baz := pool.Lookup("foo.com") baz := pool.Lookup("foo.com")
nero := pool.Lookup("foo.com") nero := pool.Lookup("foo.com")
assert.Equal(t, foo, fox) assert.True(t, foo == fox)
assert.Equal(t, foo, next) assert.True(t, foo == next)
assert.NotEqual(t, foo, baz) assert.False(t, foo == baz)
assert.Equal(t, bar, bax) assert.True(t, bar == bax)
assert.Equal(t, bar, baz) assert.True(t, bar == baz)
assert.NotEqual(t, bar, next) assert.False(t, bar == next)
assert.Equal(t, baz, nero) assert.True(t, baz == nero)
} }

View File

@@ -7,7 +7,7 @@ import (
) )
type AttributeList struct { type AttributeList struct {
matcher []BooleanMatcher matcher []AttributeMatcher
} }
func (al *AttributeList) Match(domain *router.Domain) bool { func (al *AttributeList) Match(domain *router.Domain) bool {
@@ -23,14 +23,6 @@ func (al *AttributeList) IsEmpty() bool {
return len(al.matcher) == 0 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 { func parseAttrs(attrs []string) *AttributeList {
al := new(AttributeList) al := new(AttributeList)
for _, attr := range attrs { for _, attr := range attrs {

View File

@@ -6,10 +6,8 @@ import (
"io" "io"
"net/http" "net/http"
"os" "os"
"sync"
"time" "time"
"github.com/metacubex/mihomo/common/atomic"
mihomoHttp "github.com/metacubex/mihomo/component/http" mihomoHttp "github.com/metacubex/mihomo/component/http"
"github.com/metacubex/mihomo/component/mmdb" "github.com/metacubex/mihomo/component/mmdb"
C "github.com/metacubex/mihomo/constant" C "github.com/metacubex/mihomo/constant"
@@ -20,57 +18,36 @@ var (
initGeoSite bool initGeoSite bool
initGeoIP int initGeoIP int
initASN bool initASN bool
initGeoSiteMutex sync.Mutex
initGeoIPMutex sync.Mutex
initASNMutex sync.Mutex
geoIpEnable atomic.Bool
geoSiteEnable atomic.Bool
asnEnable atomic.Bool
geoIpUrl string
mmdbUrl string
geoSiteUrl string
asnUrl string
) )
func GeoIpUrl() string { func InitGeoSite() error {
return geoIpUrl if _, err := os.Stat(C.Path.GeoSite()); os.IsNotExist(err) {
log.Infoln("Can't find GeoSite.dat, start download")
if err := downloadGeoSite(C.Path.GeoSite()); err != nil {
return fmt.Errorf("can't download GeoSite.dat: %s", err.Error())
}
log.Infoln("Download GeoSite.dat finish")
initGeoSite = false
}
if !initGeoSite {
if err := Verify(C.GeositeName); err != nil {
log.Warnln("GeoSite.dat invalid, remove and download: %s", err)
if err := os.Remove(C.Path.GeoSite()); err != nil {
return fmt.Errorf("can't remove invalid GeoSite.dat: %s", err.Error())
}
if err := downloadGeoSite(C.Path.GeoSite()); err != nil {
return fmt.Errorf("can't download GeoSite.dat: %s", err.Error())
}
}
initGeoSite = true
}
return nil
} }
func SetGeoIpUrl(url string) { func downloadGeoSite(path string) (err error) {
geoIpUrl = url
}
func MmdbUrl() string {
return mmdbUrl
}
func SetMmdbUrl(url string) {
mmdbUrl = url
}
func GeoSiteUrl() string {
return geoSiteUrl
}
func SetGeoSiteUrl(url string) {
geoSiteUrl = url
}
func ASNUrl() string {
return asnUrl
}
func SetASNUrl(url string) {
asnUrl = url
}
func downloadToPath(url string, path string) (err error) {
ctx, cancel := context.WithTimeout(context.Background(), time.Second*90) ctx, cancel := context.WithTimeout(context.Background(), time.Second*90)
defer cancel() defer cancel()
resp, err := mihomoHttp.HttpRequest(ctx, url, http.MethodGet, nil, nil) resp, err := mihomoHttp.HttpRequest(ctx, C.GeoSiteUrl, http.MethodGet, http.Header{"User-Agent": {C.UA}}, nil)
if err != nil { if err != nil {
return return
} }
@@ -86,41 +63,30 @@ func downloadToPath(url string, path string) (err error) {
return err return err
} }
func InitGeoSite() error { func downloadGeoIP(path string) (err error) {
geoSiteEnable.Store(true) ctx, cancel := context.WithTimeout(context.Background(), time.Second*90)
initGeoSiteMutex.Lock() defer cancel()
defer initGeoSiteMutex.Unlock() resp, err := mihomoHttp.HttpRequest(ctx, C.GeoIpUrl, http.MethodGet, http.Header{"User-Agent": {C.UA}}, nil)
if _, err := os.Stat(C.Path.GeoSite()); os.IsNotExist(err) { if err != nil {
log.Infoln("Can't find GeoSite.dat, start download") return
if err := downloadToPath(GeoSiteUrl(), C.Path.GeoSite()); err != nil {
return fmt.Errorf("can't download GeoSite.dat: %s", err.Error())
}
log.Infoln("Download GeoSite.dat finish")
initGeoSite = false
} }
if !initGeoSite { defer resp.Body.Close()
if err := Verify(C.GeositeName); err != nil {
log.Warnln("GeoSite.dat invalid, remove and download: %s", err) f, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY, 0o644)
if err := os.Remove(C.Path.GeoSite()); err != nil { if err != nil {
return fmt.Errorf("can't remove invalid GeoSite.dat: %s", err.Error()) return err
}
if err := downloadToPath(GeoSiteUrl(), C.Path.GeoSite()); err != nil {
return fmt.Errorf("can't download GeoSite.dat: %s", err.Error())
}
}
initGeoSite = true
} }
return nil defer f.Close()
_, err = io.Copy(f, resp.Body)
return err
} }
func InitGeoIP() error { func InitGeoIP() error {
geoIpEnable.Store(true) if C.GeodataMode {
initGeoIPMutex.Lock()
defer initGeoIPMutex.Unlock()
if GeodataMode() {
if _, err := os.Stat(C.Path.GeoIP()); os.IsNotExist(err) { if _, err := os.Stat(C.Path.GeoIP()); os.IsNotExist(err) {
log.Infoln("Can't find GeoIP.dat, start download") log.Infoln("Can't find GeoIP.dat, start download")
if err := downloadToPath(GeoIpUrl(), C.Path.GeoIP()); err != nil { if err := downloadGeoIP(C.Path.GeoIP()); err != nil {
return fmt.Errorf("can't download GeoIP.dat: %s", err.Error()) return fmt.Errorf("can't download GeoIP.dat: %s", err.Error())
} }
log.Infoln("Download GeoIP.dat finish") log.Infoln("Download GeoIP.dat finish")
@@ -133,7 +99,7 @@ func InitGeoIP() error {
if err := os.Remove(C.Path.GeoIP()); err != nil { if err := os.Remove(C.Path.GeoIP()); err != nil {
return fmt.Errorf("can't remove invalid GeoIP.dat: %s", err.Error()) return fmt.Errorf("can't remove invalid GeoIP.dat: %s", err.Error())
} }
if err := downloadToPath(GeoIpUrl(), C.Path.GeoIP()); err != nil { if err := downloadGeoIP(C.Path.GeoIP()); err != nil {
return fmt.Errorf("can't download GeoIP.dat: %s", err.Error()) return fmt.Errorf("can't download GeoIP.dat: %s", err.Error())
} }
} }
@@ -144,7 +110,7 @@ func InitGeoIP() error {
if _, err := os.Stat(C.Path.MMDB()); os.IsNotExist(err) { if _, err := os.Stat(C.Path.MMDB()); os.IsNotExist(err) {
log.Infoln("Can't find MMDB, start download") log.Infoln("Can't find MMDB, start download")
if err := downloadToPath(MmdbUrl(), C.Path.MMDB()); err != nil { if err := mmdb.DownloadMMDB(C.Path.MMDB()); err != nil {
return fmt.Errorf("can't download MMDB: %s", err.Error()) return fmt.Errorf("can't download MMDB: %s", err.Error())
} }
} }
@@ -155,7 +121,7 @@ func InitGeoIP() error {
if err := os.Remove(C.Path.MMDB()); err != nil { if err := os.Remove(C.Path.MMDB()); err != nil {
return fmt.Errorf("can't remove invalid MMDB: %s", err.Error()) return fmt.Errorf("can't remove invalid MMDB: %s", err.Error())
} }
if err := downloadToPath(MmdbUrl(), C.Path.MMDB()); err != nil { if err := mmdb.DownloadMMDB(C.Path.MMDB()); err != nil {
return fmt.Errorf("can't download MMDB: %s", err.Error()) return fmt.Errorf("can't download MMDB: %s", err.Error())
} }
} }
@@ -165,12 +131,9 @@ func InitGeoIP() error {
} }
func InitASN() error { func InitASN() error {
asnEnable.Store(true)
initASNMutex.Lock()
defer initASNMutex.Unlock()
if _, err := os.Stat(C.Path.ASN()); os.IsNotExist(err) { if _, err := os.Stat(C.Path.ASN()); os.IsNotExist(err) {
log.Infoln("Can't find ASN.mmdb, start download") log.Infoln("Can't find ASN.mmdb, start download")
if err := downloadToPath(ASNUrl(), C.Path.ASN()); err != nil { if err := mmdb.DownloadASN(C.Path.ASN()); err != nil {
return fmt.Errorf("can't download ASN.mmdb: %s", err.Error()) return fmt.Errorf("can't download ASN.mmdb: %s", err.Error())
} }
log.Infoln("Download ASN.mmdb finish") log.Infoln("Download ASN.mmdb finish")
@@ -182,7 +145,7 @@ func InitASN() error {
if err := os.Remove(C.Path.ASN()); err != nil { if err := os.Remove(C.Path.ASN()); err != nil {
return fmt.Errorf("can't remove invalid ASN: %s", err.Error()) return fmt.Errorf("can't remove invalid ASN: %s", err.Error())
} }
if err := downloadToPath(ASNUrl(), C.Path.ASN()); err != nil { if err := mmdb.DownloadASN(C.Path.ASN()); err != nil {
return fmt.Errorf("can't download ASN: %s", err.Error()) return fmt.Errorf("can't download ASN: %s", err.Error())
} }
} }
@@ -190,15 +153,3 @@ func InitASN() error {
} }
return nil return nil
} }
func GeoIpEnable() bool {
return geoIpEnable.Load()
}
func GeoSiteEnable() bool {
return geoSiteEnable.Load()
}
func ASNEnable() bool {
return asnEnable.Load()
}

View File

@@ -33,13 +33,12 @@ func domainToMatcher(domain *Domain) (strmatcher.Matcher, error) {
type DomainMatcher interface { type DomainMatcher interface {
ApplyDomain(string) bool ApplyDomain(string) bool
Count() int
} }
type succinctDomainMatcher struct { type succinctDomainMatcher struct {
set *trie.DomainSet set *trie.DomainSet
otherMatchers []strmatcher.Matcher otherMatchers []strmatcher.Matcher
count int not bool
} }
func (m *succinctDomainMatcher) ApplyDomain(domain string) bool { func (m *succinctDomainMatcher) ApplyDomain(domain string) bool {
@@ -52,17 +51,16 @@ func (m *succinctDomainMatcher) ApplyDomain(domain string) bool {
} }
} }
} }
if m.not {
isMatched = !isMatched
}
return isMatched return isMatched
} }
func (m *succinctDomainMatcher) Count() int { func NewSuccinctMatcherGroup(domains []*Domain, not bool) (DomainMatcher, error) {
return m.count
}
func NewSuccinctMatcherGroup(domains []*Domain) (DomainMatcher, error) {
t := trie.New[struct{}]() t := trie.New[struct{}]()
m := &succinctDomainMatcher{ m := &succinctDomainMatcher{
count: len(domains), not: not,
} }
for _, d := range domains { for _, d := range domains {
switch d.Type { switch d.Type {
@@ -92,10 +90,10 @@ func NewSuccinctMatcherGroup(domains []*Domain) (DomainMatcher, error) {
type v2rayDomainMatcher struct { type v2rayDomainMatcher struct {
matchers strmatcher.IndexMatcher matchers strmatcher.IndexMatcher
count int not bool
} }
func NewMphMatcherGroup(domains []*Domain) (DomainMatcher, error) { func NewMphMatcherGroup(domains []*Domain, not bool) (DomainMatcher, error) {
g := strmatcher.NewMphMatcherGroup() g := strmatcher.NewMphMatcherGroup()
for _, d := range domains { for _, d := range domains {
matcherType, f := matcherTypeMap[d.Type] matcherType, f := matcherTypeMap[d.Type]
@@ -110,80 +108,119 @@ func NewMphMatcherGroup(domains []*Domain) (DomainMatcher, error) {
g.Build() g.Build()
return &v2rayDomainMatcher{ return &v2rayDomainMatcher{
matchers: g, matchers: g,
count: len(domains), not: not,
}, nil }, nil
} }
func (m *v2rayDomainMatcher) ApplyDomain(domain string) bool { func (m *v2rayDomainMatcher) ApplyDomain(domain string) bool {
return len(m.matchers.Match(strings.ToLower(domain))) > 0 isMatched := len(m.matchers.Match(strings.ToLower(domain))) > 0
} if m.not {
isMatched = !isMatched
func (m *v2rayDomainMatcher) Count() int {
return m.count
}
type notDomainMatcher struct {
DomainMatcher
}
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 {
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),
} }
for _, cidr := range cidrList { return isMatched
}
type GeoIPMatcher struct {
countryCode string
reverseMatch bool
cidrSet *cidr.IpCidrSet
}
func (m *GeoIPMatcher) Init(cidrs []*CIDR) error {
for _, cidr := range cidrs {
addr, ok := netip.AddrFromSlice(cidr.Ip) addr, ok := netip.AddrFromSlice(cidr.Ip)
if !ok { if !ok {
return nil, fmt.Errorf("error when loading GeoIP: invalid IP: %s", cidr.Ip) return fmt.Errorf("error when loading GeoIP: invalid IP: %s", cidr.Ip)
} }
err := m.cidrSet.AddIpCidr(netip.PrefixFrom(addr, int(cidr.Prefix))) err := m.cidrSet.AddIpCidr(netip.PrefixFrom(addr, int(cidr.Prefix)))
if err != nil { if err != nil {
return nil, fmt.Errorf("error when loading GeoIP: %w", err) return fmt.Errorf("error when loading GeoIP: %w", err)
} }
} }
err := m.cidrSet.Merge() return m.cidrSet.Merge()
}
func (m *GeoIPMatcher) SetReverseMatch(isReverseMatch bool) {
m.reverseMatch = isReverseMatch
}
// 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
}
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
}
}
}
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)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return m, nil return matcher, nil
} }
type notIPMatcher struct { func (m *MultiGeoIPMatcher) ApplyIp(ip netip.Addr) bool {
IPMatcher for _, matcher := range m.matchers {
if matcher.Match(ip) {
return true
}
}
return false
} }
func (m notIPMatcher) Match(ip netip.Addr) bool { func NewMultiGeoIPMatcher(geoips []*GeoIP) (*MultiGeoIPMatcher, error) {
return !m.IPMatcher.Match(ip) var matchers []*GeoIPMatcher
} for _, geoip := range geoips {
matcher, err := globalGeoIPContainer.Add(geoip)
if err != nil {
return nil, err
}
matchers = append(matchers, matcher)
}
func NewNotIpMatcherGroup(matcher IPMatcher) IPMatcher { matcher := &MultiGeoIPMatcher{
return notIPMatcher{matcher} matchers: matchers,
}
return matcher, nil
} }

View File

@@ -5,7 +5,8 @@ import (
"fmt" "fmt"
"strings" "strings"
"github.com/metacubex/mihomo/common/singleflight" "golang.org/x/sync/singleflight"
"github.com/metacubex/mihomo/component/geodata/router" "github.com/metacubex/mihomo/component/geodata/router"
C "github.com/metacubex/mihomo/constant" C "github.com/metacubex/mihomo/constant"
"github.com/metacubex/mihomo/log" "github.com/metacubex/mihomo/log"
@@ -13,6 +14,8 @@ import (
var ( var (
geoMode bool geoMode bool
AutoUpdate bool
UpdateInterval int
geoLoaderName = "memconservative" geoLoaderName = "memconservative"
geoSiteMatcher = "succinct" geoSiteMatcher = "succinct"
) )
@@ -23,6 +26,14 @@ func GeodataMode() bool {
return geoMode return geoMode
} }
func GeoAutoUpdate() bool {
return AutoUpdate
}
func GeoUpdateInterval() int {
return UpdateInterval
}
func LoaderName() string { func LoaderName() string {
return geoLoaderName return geoLoaderName
} }
@@ -34,6 +45,12 @@ func SiteMatcherName() string {
func SetGeodataMode(newGeodataMode bool) { func SetGeodataMode(newGeodataMode bool) {
geoMode = newGeodataMode geoMode = newGeodataMode
} }
func SetGeoAutoUpdate(newAutoUpdate bool) {
AutoUpdate = newAutoUpdate
}
func SetGeoUpdateInterval(newGeoUpdateInterval int) {
UpdateInterval = newGeoUpdateInterval
}
func SetLoader(newLoader string) { func SetLoader(newLoader string) {
if newLoader == "memc" { if newLoader == "memc" {
@@ -54,22 +71,21 @@ func SetSiteMatcher(newMatcher string) {
func Verify(name string) error { func Verify(name string) error {
switch name { switch name {
case C.GeositeName: case C.GeositeName:
_, err := LoadGeoSiteMatcher("CN") _, _, err := LoadGeoSiteMatcher("CN")
return err return err
case C.GeoipName: case C.GeoipName:
_, err := LoadGeoIPMatcher("CN") _, _, err := LoadGeoIPMatcher("CN")
return err return err
default: default:
return fmt.Errorf("not support name") return fmt.Errorf("not support name")
} }
} }
var loadGeoSiteMatcherListSF = singleflight.Group[[]*router.Domain]{StoreResult: true} var loadGeoSiteMatcherSF = singleflight.Group{}
var loadGeoSiteMatcherSF = singleflight.Group[router.DomainMatcher]{StoreResult: true}
func LoadGeoSiteMatcher(countryCode string) (router.DomainMatcher, error) { func LoadGeoSiteMatcher(countryCode string) (router.DomainMatcher, int, error) {
if countryCode == "" { if countryCode == "" {
return nil, fmt.Errorf("country code could not be empty") return nil, 0, fmt.Errorf("country code could not be empty")
} }
not := false not := false
@@ -81,84 +97,73 @@ func LoadGeoSiteMatcher(countryCode string) (router.DomainMatcher, error) {
parts := strings.Split(countryCode, "@") parts := strings.Split(countryCode, "@")
if len(parts) == 0 { if len(parts) == 0 {
return nil, errors.New("empty rule") return nil, 0, errors.New("empty rule")
} }
listName := strings.TrimSpace(parts[0]) listName := strings.TrimSpace(parts[0])
attrVal := parts[1:] attrVal := parts[1:]
attrs := parseAttrs(attrVal)
if listName == "" { if listName == "" {
return nil, fmt.Errorf("empty listname in rule: %s", countryCode) return nil, 0, fmt.Errorf("empty listname in rule: %s", countryCode)
} }
matcherName := listName v, err, shared := loadGeoSiteMatcherSF.Do(listName, func() (interface{}, error) {
if !attrs.IsEmpty() { geoLoader, err := GetGeoDataLoader(geoLoaderName)
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 err != nil {
if !shared {
loadGeoSiteMatcherListSF.Forget(listName) // don't store the error result
}
return nil, err 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)
mphminimal perfect hash algorithm
*/
if geoSiteMatcher == "mph" {
return router.NewMphMatcherGroup(domains)
} else {
return router.NewSuccinctMatcherGroup(domains)
}
}) })
if err != nil { if err != nil {
if !shared { if !shared {
loadGeoSiteMatcherSF.Forget(matcherName) // don't store the error result loadGeoSiteMatcherSF.Forget(listName) // don't store the error result
} }
return nil, err return nil, 0, err
} }
if not { domains := v.([]*router.Domain)
matcher = router.NewNotDomainMatcherGroup(matcher)
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
} }
return matcher, nil /**
linear: linear algorithm
matcher, err := router.NewDomainMatcher(domains)
mphminimal 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
} }
var loadGeoIPMatcherSF = singleflight.Group[router.IPMatcher]{StoreResult: true} var loadGeoIPMatcherSF = singleflight.Group{}
func LoadGeoIPMatcher(country string) (router.IPMatcher, error) { func LoadGeoIPMatcher(country string) (*router.GeoIPMatcher, int, error) {
if len(country) == 0 { if len(country) == 0 {
return nil, fmt.Errorf("country code could not be empty") return nil, 0, fmt.Errorf("country code could not be empty")
} }
not := false not := false
@@ -168,36 +173,35 @@ func LoadGeoIPMatcher(country string) (router.IPMatcher, error) {
} }
country = strings.ToLower(country) country = strings.ToLower(country)
matcher, err, shared := loadGeoIPMatcherSF.Do(country, func() (router.IPMatcher, error) { v, err, shared := loadGeoIPMatcherSF.Do(country, func() (interface{}, error) {
log.Infoln("Load GeoIP rule: %s", country)
geoLoader, err := GetGeoDataLoader(geoLoaderName) geoLoader, err := GetGeoDataLoader(geoLoaderName)
if err != nil { if err != nil {
return nil, err return nil, err
} }
cidrList, err := geoLoader.LoadGeoIP(country) return geoLoader.LoadGeoIP(country)
if err != nil {
return nil, err
}
return router.NewGeoIPMatcher(cidrList)
}) })
if err != nil { if err != nil {
if !shared { if !shared {
loadGeoIPMatcherSF.Forget(country) // don't store the error result loadGeoIPMatcherSF.Forget(country) // don't store the error result
log.Warnln("Load GeoIP rule: %s", country)
} }
return nil, err return nil, 0, err
} }
if not { records := v.([]*router.CIDR)
matcher = router.NewNotIpMatcherGroup(matcher)
geoIP := &router.GeoIP{
CountryCode: country,
Cidr: records,
ReverseMatch: not,
} }
return matcher, nil
matcher, err := router.NewGeoIPMatcher(geoIP)
if err != nil {
return nil, 0, err
}
return matcher, len(records), nil
} }
func ClearGeoSiteCache() { func ClearCache() {
loadGeoSiteMatcherListSF.Reset() loadGeoSiteMatcherSF = singleflight.Group{}
loadGeoSiteMatcherSF.Reset() loadGeoIPMatcherSF = singleflight.Group{}
}
func ClearGeoIPCache() {
loadGeoIPMatcherSF.Reset()
} }

View File

@@ -12,22 +12,10 @@ import (
"time" "time"
"github.com/metacubex/mihomo/component/ca" "github.com/metacubex/mihomo/component/ca"
"github.com/metacubex/mihomo/component/dialer" C "github.com/metacubex/mihomo/constant"
"github.com/metacubex/mihomo/listener/inner" "github.com/metacubex/mihomo/listener/inner"
) )
var (
ua string
)
func UA() string {
return ua
}
func SetUA(UA string) {
ua = UA
}
func HttpRequest(ctx context.Context, url, method string, header map[string][]string, body io.Reader) (*http.Response, error) { func HttpRequest(ctx context.Context, url, method string, header map[string][]string, body io.Reader) (*http.Response, error) {
return HttpRequestWithProxy(ctx, url, method, header, body, "") return HttpRequestWithProxy(ctx, url, method, header, body, "")
} }
@@ -47,7 +35,7 @@ func HttpRequestWithProxy(ctx context.Context, url, method string, header map[st
} }
if _, ok := header["User-Agent"]; !ok { if _, ok := header["User-Agent"]; !ok {
req.Header.Set("User-Agent", UA()) req.Header.Set("User-Agent", C.UA)
} }
if err != nil { if err != nil {
@@ -72,7 +60,8 @@ func HttpRequestWithProxy(ctx context.Context, url, method string, header map[st
if conn, err := inner.HandleTcp(address, specialProxy); err == nil { if conn, err := inner.HandleTcp(address, specialProxy); err == nil {
return conn, nil return conn, nil
} else { } else {
return dialer.DialContext(ctx, network, address) d := net.Dialer{}
return d.DialContext(ctx, network, address)
} }
}, },
TLSClientConfig: ca.GetGlobalTLSConfig(&tls.Config{}), TLSClientConfig: ca.GetGlobalTLSConfig(&tls.Config{}),

View File

@@ -1,65 +0,0 @@
package keepalive
import (
"net"
"runtime"
"time"
"github.com/metacubex/mihomo/common/atomic"
"github.com/metacubex/mihomo/common/utils"
)
var (
keepAliveIdle = atomic.NewTypedValue[time.Duration](0 * time.Second)
keepAliveInterval = atomic.NewTypedValue[time.Duration](0 * time.Second)
disableKeepAlive = atomic.NewBool(false)
SetDisableKeepAliveCallback = utils.NewCallback[bool]()
)
func SetKeepAliveIdle(t time.Duration) {
keepAliveIdle.Store(t)
}
func SetKeepAliveInterval(t time.Duration) {
keepAliveInterval.Store(t)
}
func KeepAliveIdle() time.Duration {
return keepAliveIdle.Load()
}
func KeepAliveInterval() time.Duration {
return keepAliveInterval.Load()
}
func SetDisableKeepAlive(disable bool) {
if runtime.GOOS == "android" {
setDisableKeepAlive(false)
} else {
setDisableKeepAlive(disable)
}
}
func setDisableKeepAlive(disable bool) {
disableKeepAlive.Store(disable)
SetDisableKeepAliveCallback.Emit(disable)
}
func DisableKeepAlive() bool {
return disableKeepAlive.Load()
}
func SetNetDialer(dialer *net.Dialer) {
setNetDialer(dialer)
}
func SetNetListenConfig(lc *net.ListenConfig) {
setNetListenConfig(lc)
}
func TCPKeepAlive(c net.Conn) {
if tcp, ok := c.(*net.TCPConn); ok && tcp != nil {
tcpKeepAlive(tcp)
}
}

View File

@@ -1,30 +0,0 @@
//go:build !go1.23
package keepalive
import "net"
func tcpKeepAlive(tcp *net.TCPConn) {
if DisableKeepAlive() {
_ = tcp.SetKeepAlive(false)
} else {
_ = tcp.SetKeepAlive(true)
_ = tcp.SetKeepAlivePeriod(KeepAliveInterval())
}
}
func setNetDialer(dialer *net.Dialer) {
if DisableKeepAlive() {
dialer.KeepAlive = -1 // If negative, keep-alive probes are disabled.
} else {
dialer.KeepAlive = KeepAliveInterval()
}
}
func setNetListenConfig(lc *net.ListenConfig) {
if DisableKeepAlive() {
lc.KeepAlive = -1 // If negative, keep-alive probes are disabled.
} else {
lc.KeepAlive = KeepAliveInterval()
}
}

View File

@@ -1,45 +0,0 @@
//go:build go1.23
package keepalive
import "net"
func keepAliveConfig() net.KeepAliveConfig {
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
}
return config
}
func tcpKeepAlive(tcp *net.TCPConn) {
if DisableKeepAlive() {
_ = tcp.SetKeepAlive(false)
} else {
_ = tcp.SetKeepAliveConfig(keepAliveConfig())
}
}
func setNetDialer(dialer *net.Dialer) {
if DisableKeepAlive() {
dialer.KeepAlive = -1 // If negative, keep-alive probes are disabled.
dialer.KeepAliveConfig.Enable = false
} else {
dialer.KeepAliveConfig = keepAliveConfig()
}
}
func setNetListenConfig(lc *net.ListenConfig) {
if DisableKeepAlive() {
lc.KeepAlive = -1 // If negative, keep-alive probes are disabled.
lc.KeepAliveConfig.Enable = false
} else {
lc.KeepAliveConfig = keepAliveConfig()
}
}

View File

@@ -1,15 +0,0 @@
//go:build go1.23 && unix
package keepalive
func SupportTCPKeepAliveIdle() bool {
return true
}
func SupportTCPKeepAliveInterval() bool {
return true
}
func SupportTCPKeepAliveCount() bool {
return true
}

View File

@@ -1,63 +0,0 @@
//go:build go1.23 && windows
// copy and modify from golang1.23's internal/syscall/windows/version_windows.go
package keepalive
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
}

View File

@@ -1,9 +1,15 @@
package mmdb package mmdb
import ( import (
"context"
"io"
"net/http"
"os"
"sync" "sync"
"time"
mihomoOnce "github.com/metacubex/mihomo/common/once" mihomoOnce "github.com/metacubex/mihomo/common/once"
mihomoHttp "github.com/metacubex/mihomo/component/http"
C "github.com/metacubex/mihomo/constant" C "github.com/metacubex/mihomo/constant"
"github.com/metacubex/mihomo/log" "github.com/metacubex/mihomo/log"
@@ -19,26 +25,26 @@ const (
) )
var ( var (
ipReader IPReader IPreader IPReader
asnReader ASNReader ASNreader ASNReader
ipOnce sync.Once IPonce sync.Once
asnOnce sync.Once ASNonce sync.Once
) )
func LoadFromBytes(buffer []byte) { func LoadFromBytes(buffer []byte) {
ipOnce.Do(func() { IPonce.Do(func() {
mmdb, err := maxminddb.FromBytes(buffer) mmdb, err := maxminddb.FromBytes(buffer)
if err != nil { if err != nil {
log.Fatalln("Can't load mmdb: %s", err.Error()) log.Fatalln("Can't load mmdb: %s", err.Error())
} }
ipReader = IPReader{Reader: mmdb} IPreader = IPReader{Reader: mmdb}
switch mmdb.Metadata.DatabaseType { switch mmdb.Metadata.DatabaseType {
case "sing-geoip": case "sing-geoip":
ipReader.databaseType = typeSing IPreader.databaseType = typeSing
case "Meta-geoip0": case "Meta-geoip0":
ipReader.databaseType = typeMetaV0 IPreader.databaseType = typeMetaV0
default: default:
ipReader.databaseType = typeMaxmind IPreader.databaseType = typeMaxmind
} }
}) })
} }
@@ -52,45 +58,83 @@ func Verify(path string) bool {
} }
func IPInstance() IPReader { func IPInstance() IPReader {
ipOnce.Do(func() { IPonce.Do(func() {
mmdbPath := C.Path.MMDB() mmdbPath := C.Path.MMDB()
log.Infoln("Load MMDB file: %s", mmdbPath) log.Infoln("Load MMDB file: %s", mmdbPath)
mmdb, err := maxminddb.Open(mmdbPath) mmdb, err := maxminddb.Open(mmdbPath)
if err != nil { if err != nil {
log.Fatalln("Can't load MMDB: %s", err.Error()) log.Fatalln("Can't load MMDB: %s", err.Error())
} }
ipReader = IPReader{Reader: mmdb} IPreader = IPReader{Reader: mmdb}
switch mmdb.Metadata.DatabaseType { switch mmdb.Metadata.DatabaseType {
case "sing-geoip": case "sing-geoip":
ipReader.databaseType = typeSing IPreader.databaseType = typeSing
case "Meta-geoip0": case "Meta-geoip0":
ipReader.databaseType = typeMetaV0 IPreader.databaseType = typeMetaV0
default: default:
ipReader.databaseType = typeMaxmind IPreader.databaseType = typeMaxmind
} }
}) })
return ipReader return IPreader
}
func DownloadMMDB(path string) (err error) {
ctx, cancel := context.WithTimeout(context.Background(), time.Second*90)
defer cancel()
resp, err := mihomoHttp.HttpRequest(ctx, C.MmdbUrl, http.MethodGet, http.Header{"User-Agent": {C.UA}}, nil)
if err != nil {
return
}
defer resp.Body.Close()
f, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY, 0o644)
if err != nil {
return err
}
defer f.Close()
_, err = io.Copy(f, resp.Body)
return err
} }
func ASNInstance() ASNReader { func ASNInstance() ASNReader {
asnOnce.Do(func() { ASNonce.Do(func() {
ASNPath := C.Path.ASN() ASNPath := C.Path.ASN()
log.Infoln("Load ASN file: %s", ASNPath) log.Infoln("Load ASN file: %s", ASNPath)
asn, err := maxminddb.Open(ASNPath) asn, err := maxminddb.Open(ASNPath)
if err != nil { if err != nil {
log.Fatalln("Can't load ASN: %s", err.Error()) log.Fatalln("Can't load ASN: %s", err.Error())
} }
asnReader = ASNReader{Reader: asn} ASNreader = ASNReader{Reader: asn}
}) })
return asnReader return ASNreader
}
func DownloadASN(path string) (err error) {
ctx, cancel := context.WithTimeout(context.Background(), time.Second*90)
defer cancel()
resp, err := mihomoHttp.HttpRequest(ctx, C.ASNUrl, http.MethodGet, http.Header{"User-Agent": {C.UA}}, nil)
if err != nil {
return
}
defer resp.Body.Close()
f, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY, 0o644)
if err != nil {
return err
}
defer f.Close()
_, err = io.Copy(f, resp.Body)
return err
} }
func ReloadIP() { func ReloadIP() {
mihomoOnce.Reset(&ipOnce) mihomoOnce.Reset(&IPonce)
} }
func ReloadASN() { func ReloadASN() {
mihomoOnce.Reset(&asnOnce) mihomoOnce.Reset(&ASNonce)
} }

View File

@@ -0,0 +1,18 @@
//go:build android && cmfa
package mmdb
import "github.com/oschwald/maxminddb-golang"
func InstallOverride(override *maxminddb.Reader) {
newReader := IPReader{Reader: override}
switch override.Metadata.DatabaseType {
case "sing-geoip":
IPreader.databaseType = typeSing
case "Meta-geoip0":
IPreader.databaseType = typeMetaV0
default:
IPreader.databaseType = typeMaxmind
}
IPreader = newReader
}

View File

@@ -10,30 +10,47 @@ import (
) )
type Table struct { type Table struct {
mapping *xsync.MapOf[string, *entry] mapping *xsync.MapOf[string, *Entry]
lockMap *xsync.MapOf[string, *sync.Cond]
} }
type entry struct { type Entry struct {
PacketSender C.PacketSender PacketConn C.PacketConn
WriteBackProxy C.WriteBackProxy
LocalUDPConnMap *xsync.MapOf[string, *net.UDPConn] LocalUDPConnMap *xsync.MapOf[string, *net.UDPConn]
LocalLockMap *xsync.MapOf[string, *sync.Cond] LocalLockMap *xsync.MapOf[string, *sync.Cond]
} }
func (t *Table) GetOrCreate(key string, maker func() C.PacketSender) (C.PacketSender, bool) { func (t *Table) Set(key string, e C.PacketConn, w C.WriteBackProxy) {
item, loaded := t.mapping.LoadOrCompute(key, func() *entry { t.mapping.Store(key, &Entry{
return &entry{ PacketConn: e,
PacketSender: maker(), WriteBackProxy: w,
LocalUDPConnMap: xsync.NewMapOf[string, *net.UDPConn](), LocalUDPConnMap: xsync.NewMapOf[string, *net.UDPConn](),
LocalLockMap: xsync.NewMapOf[string, *sync.Cond](), LocalLockMap: xsync.NewMapOf[string, *sync.Cond](),
}
}) })
return item.PacketSender, loaded }
func (t *Table) Get(key string) (C.PacketConn, C.WriteBackProxy) {
entry, exist := t.getEntry(key)
if !exist {
return nil, nil
}
return entry.PacketConn, entry.WriteBackProxy
}
func (t *Table) GetOrCreateLock(key string) (*sync.Cond, bool) {
item, loaded := t.lockMap.LoadOrCompute(key, makeLock)
return item, loaded
} }
func (t *Table) Delete(key string) { func (t *Table) Delete(key string) {
t.mapping.Delete(key) t.mapping.Delete(key)
} }
func (t *Table) DeleteLock(lockKey string) {
t.lockMap.Delete(lockKey)
}
func (t *Table) GetForLocalConn(lAddr, rAddr string) *net.UDPConn { func (t *Table) GetForLocalConn(lAddr, rAddr string) *net.UDPConn {
entry, exist := t.getEntry(lAddr) entry, exist := t.getEntry(lAddr)
if !exist { if !exist {
@@ -88,7 +105,7 @@ func (t *Table) DeleteLockForLocalConn(lAddr, key string) {
entry.LocalLockMap.Delete(key) entry.LocalLockMap.Delete(key)
} }
func (t *Table) getEntry(key string) (*entry, bool) { func (t *Table) getEntry(key string) (*Entry, bool) {
return t.mapping.Load(key) return t.mapping.Load(key)
} }
@@ -99,6 +116,7 @@ func makeLock() *sync.Cond {
// New return *Cache // New return *Cache
func New() *Table { func New() *Table {
return &Table{ return &Table{
mapping: xsync.NewMapOf[string, *entry](), mapping: xsync.NewMapOf[string, *Entry](),
lockMap: xsync.NewMapOf[string, *sync.Cond](),
} }
} }

View File

@@ -55,11 +55,6 @@ func NewEventListener(cb func(Type)) (func(), error) {
} }
handle := uintptr(0) handle := uintptr(0)
// DWORD PowerRegisterSuspendResumeNotification(
// [in] DWORD Flags,
// [in] HANDLE Recipient,
// [out] PHPOWERNOTIFY RegistrationHandle
//);
_, _, err := powerRegisterSuspendResumeNotification.Call( _, _, err := powerRegisterSuspendResumeNotification.Call(
_DEVICE_NOTIFY_CALLBACK, _DEVICE_NOTIFY_CALLBACK,
uintptr(unsafe.Pointer(&params)), uintptr(unsafe.Pointer(&params)),
@@ -70,11 +65,8 @@ func NewEventListener(cb func(Type)) (func(), error) {
} }
return func() { return func() {
// DWORD PowerUnregisterSuspendResumeNotification(
// [in, out] HPOWERNOTIFY RegistrationHandle
//);
_, _, _ = powerUnregisterSuspendResumeNotification.Call( _, _, _ = powerUnregisterSuspendResumeNotification.Call(
handle, uintptr(unsafe.Pointer(&handle)),
) )
runtime.KeepAlive(params) runtime.KeepAlive(params)
runtime.KeepAlive(handle) runtime.KeepAlive(handle)

View File

@@ -23,11 +23,11 @@ func FindProcessName(network string, srcIP netip.Addr, srcPort int) (uint32, str
} }
// PackageNameResolver // PackageNameResolver
// never change type traits because it's used in CMFA // never change type traits because it's used in CFMA
type PackageNameResolver func(metadata *C.Metadata) (string, error) type PackageNameResolver func(metadata *C.Metadata) (string, error)
// DefaultPackageNameResolver // DefaultPackageNameResolver
// never change type traits because it's used in CMFA // never change type traits because it's used in CFMA
var DefaultPackageNameResolver PackageNameResolver var DefaultPackageNameResolver PackageNameResolver
func FindPackageName(metadata *C.Metadata) (string, error) { func FindPackageName(metadata *C.Metadata) (string, error) {

View File

@@ -9,7 +9,7 @@ import (
C "github.com/metacubex/mihomo/constant" C "github.com/metacubex/mihomo/constant"
"github.com/metacubex/mihomo/log" "github.com/metacubex/mihomo/log"
"github.com/metacubex/bbolt" "github.com/sagernet/bbolt"
) )
var ( var (
@@ -17,10 +17,8 @@ var (
fileMode os.FileMode = 0o666 fileMode os.FileMode = 0o666
defaultCache *CacheFile defaultCache *CacheFile
bucketSelected = []byte("selected") bucketSelected = []byte("selected")
bucketFakeip = []byte("fakeip") bucketFakeip = []byte("fakeip")
bucketETag = []byte("etag")
bucketSubscriptionInfo = []byte("subscriptioninfo")
) )
// CacheFile store and update the cache file // CacheFile store and update the cache file
@@ -71,6 +69,80 @@ func (c *CacheFile) SelectedMap() map[string]string {
return mapping return mapping
} }
func (c *CacheFile) PutFakeip(key, value []byte) error {
if c.DB == nil {
return nil
}
err := c.DB.Batch(func(t *bbolt.Tx) error {
bucket, err := t.CreateBucketIfNotExists(bucketFakeip)
if err != nil {
return err
}
return bucket.Put(key, value)
})
if err != nil {
log.Warnln("[CacheFile] write cache to %s failed: %s", c.DB.Path(), err.Error())
}
return err
}
func (c *CacheFile) DelFakeipPair(ip, host []byte) error {
if c.DB == nil {
return nil
}
err := c.DB.Batch(func(t *bbolt.Tx) error {
bucket, err := t.CreateBucketIfNotExists(bucketFakeip)
if err != nil {
return err
}
err = bucket.Delete(ip)
if len(host) > 0 {
if err := bucket.Delete(host); err != nil {
return err
}
}
return err
})
if err != nil {
log.Warnln("[CacheFile] write cache to %s failed: %s", c.DB.Path(), err.Error())
}
return err
}
func (c *CacheFile) GetFakeip(key []byte) []byte {
if c.DB == nil {
return nil
}
tx, err := c.DB.Begin(false)
if err != nil {
return nil
}
defer tx.Rollback()
bucket := tx.Bucket(bucketFakeip)
if bucket == nil {
return nil
}
return bucket.Get(key)
}
func (c *CacheFile) FlushFakeIP() error {
err := c.DB.Batch(func(t *bbolt.Tx) error {
bucket := t.Bucket(bucketFakeip)
if bucket == nil {
return nil
}
return t.DeleteBucket(bucketFakeip)
})
return err
}
func (c *CacheFile) Close() error { func (c *CacheFile) Close() error {
return c.DB.Close() return c.DB.Close()
} }

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