From 827f50cd41dbf3a7f1cdc7ff0d0d6ccb9309a4e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Fri, 4 Sep 2026 21:32:13 +0800 Subject: [PATCH] Improve process search --- adapter/platform.go | 10 ++-- common/dialer/default.go | 4 +- common/process/searcher_darwin_shared.go | 55 +++++++++++-------- common/process/searcher_linux.go | 4 +- common/process/searcher_windows.go | 39 +++++++++++-- daemon/started_service.go | 6 +- experimental/clashapi/connections.go | 8 +-- .../libbox/connection_owner_darwin.go | 7 ++- experimental/libbox/oom_report.go | 8 +-- experimental/libbox/platform.go | 9 +++ experimental/libbox/service.go | 12 ++-- route/process_cache.go | 13 +++-- route/rule/rule_item_package_name.go | 4 +- route/rule/rule_item_package_name_regex.go | 4 +- route/rule/rule_item_process_name.go | 7 ++- route/rule/rule_item_process_path.go | 9 +-- route/rule/rule_item_process_path_regex.go | 5 +- service/powerreport/record.go | 2 +- service/resolved/resolve1.go | 12 ++-- 19 files changed, 140 insertions(+), 78 deletions(-) diff --git a/adapter/platform.go b/adapter/platform.go index 29f8bb644..4ec165d39 100644 --- a/adapter/platform.go +++ b/adapter/platform.go @@ -112,11 +112,11 @@ type FindConnectionOwnerRequest struct { } type ConnectionOwner struct { - ProcessID uint32 - UserId int32 - UserName string - ProcessPath string - AndroidPackageNames []string + ProcessID uint32 + UserId int32 + UserName string + ProcessPaths []string + PackageNames []string } type Notification struct { diff --git a/common/dialer/default.go b/common/dialer/default.go index f202283c5..efc099c9e 100644 --- a/common/dialer/default.go +++ b/common/dialer/default.go @@ -519,8 +519,8 @@ func (d *DefaultDialer) dialAttribution(ctx context.Context, destination M.Socks ProcessID: metadata.ProcessInfo.ProcessID, UserID: metadata.ProcessInfo.UserId, UserName: metadata.ProcessInfo.UserName, - ProcessPath: metadata.ProcessInfo.ProcessPath, - PackageNames: metadata.ProcessInfo.AndroidPackageNames, + ProcessPaths: metadata.ProcessInfo.ProcessPaths, + PackageNames: metadata.ProcessInfo.PackageNames, } } attribution.Rule = metadata.RouteRule diff --git a/common/process/searcher_darwin_shared.go b/common/process/searcher_darwin_shared.go index 4cb3dde14..e720f695e 100644 --- a/common/process/searcher_darwin_shared.go +++ b/common/process/searcher_darwin_shared.go @@ -20,26 +20,28 @@ import ( const ( darwinSnapshotTTL = 200 * time.Millisecond - darwinXinpgenSize = 24 - darwinXsocketOffset = 104 - darwinXinpcbForeignPort = 16 - darwinXinpcbLocalPort = 18 - darwinXinpcbVFlag = 44 - darwinXinpcbForeignAddr = 48 - darwinXinpcbLocalAddr = 64 - darwinXinpcbIPv4Addr = 12 - darwinXsocketUID = 64 - darwinXsocketLastPID = 68 - darwinTCPExtraStructSize = 208 + darwinXinpgenSize = 24 + darwinXsocketOffset = 104 + darwinXinpcbForeignPort = 16 + darwinXinpcbLocalPort = 18 + darwinXinpcbVFlag = 44 + darwinXinpcbForeignAddr = 48 + darwinXinpcbLocalAddr = 64 + darwinXinpcbIPv4Addr = 12 + darwinXsocketUID = 64 + darwinXsocketLastPID = 68 + darwinXsocketEffectivePID = 72 + darwinTCPExtraStructSize = 208 ) type darwinConnectionEntry struct { - localAddr netip.Addr - remoteAddr netip.Addr - localPort uint16 - remotePort uint16 - pid uint32 - uid int32 + localAddr netip.Addr + remoteAddr netip.Addr + localPort uint16 + remotePort uint16 + pid uint32 + effectivePid uint32 + uid int32 } type darwinConnectionMatchKind uint8 @@ -105,7 +107,15 @@ func (f *darwinConnectionFinder) find(network string, source netip.AddrPort, des } processPath, err := getExecPathFromPID(entry.pid) if err == nil { - owner.ProcessPath = processPath + owner.ProcessID = entry.pid + owner.ProcessPaths = []string{processPath} + if entry.effectivePid != 0 && entry.effectivePid != entry.pid { + effectivePath, effectiveErr := getExecPathFromPID(entry.effectivePid) + if effectiveErr == nil { + owner.ProcessID = entry.effectivePid + owner.ProcessPaths = []string{effectivePath, processPath} + } + } return owner, nil } if fromCache { @@ -186,10 +196,11 @@ func parseDarwinConnectionEntry(inp []byte, so []byte) (darwinConnectionEntry, b return darwinConnectionEntry{}, false } entry := darwinConnectionEntry{ - remotePort: binary.BigEndian.Uint16(inp[darwinXinpcbForeignPort : darwinXinpcbForeignPort+2]), - localPort: binary.BigEndian.Uint16(inp[darwinXinpcbLocalPort : darwinXinpcbLocalPort+2]), - pid: binary.NativeEndian.Uint32(so[darwinXsocketLastPID : darwinXsocketLastPID+4]), - uid: int32(binary.NativeEndian.Uint32(so[darwinXsocketUID : darwinXsocketUID+4])), + remotePort: binary.BigEndian.Uint16(inp[darwinXinpcbForeignPort : darwinXinpcbForeignPort+2]), + localPort: binary.BigEndian.Uint16(inp[darwinXinpcbLocalPort : darwinXinpcbLocalPort+2]), + pid: binary.NativeEndian.Uint32(so[darwinXsocketLastPID : darwinXsocketLastPID+4]), + effectivePid: binary.NativeEndian.Uint32(so[darwinXsocketEffectivePID : darwinXsocketEffectivePID+4]), + uid: int32(binary.NativeEndian.Uint32(so[darwinXsocketUID : darwinXsocketUID+4])), } flag := inp[darwinXinpcbVFlag] switch { diff --git a/common/process/searcher_linux.go b/common/process/searcher_linux.go index 570e23d3b..d19ba3d17 100644 --- a/common/process/searcher_linux.go +++ b/common/process/searcher_linux.go @@ -88,7 +88,7 @@ func (s *linuxSearcher) FindProcessInfo(ctx context.Context, network string, sou if err != nil { s.logger.DebugContext(ctx, "find process path: ", err) } else { - processInfo.ProcessPath = processPath + processInfo.ProcessPaths = []string{processPath} } if s.packageManager != nil { appID := uid % 100000 @@ -99,7 +99,7 @@ func (s *linuxSearcher) FindProcessInfo(ctx context.Context, network string, sou if packages, loaded := s.packageManager.PackagesByID(appID); loaded { packageNames = append(packageNames, packages...) } - processInfo.AndroidPackageNames = common.Uniq(packageNames) + processInfo.PackageNames = common.Uniq(packageNames) } return processInfo, nil } diff --git a/common/process/searcher_windows.go b/common/process/searcher_windows.go index f01176570..fe37ff1a8 100644 --- a/common/process/searcher_windows.go +++ b/common/process/searcher_windows.go @@ -10,6 +10,7 @@ import ( "github.com/sagernet/sing/common/winiphlpapi" "golang.org/x/sys/windows" + "golang.org/x/sys/windows/registry" ) var _ Searcher = (*windowsSearcher)(nil) @@ -36,15 +37,45 @@ func (s *windowsSearcher) Close() error { } func (s *windowsSearcher) FindProcessInfo(ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*adapter.ConnectionOwner, error) { - pid, err := winiphlpapi.FindPid(network, source) + owner, err := winiphlpapi.FindSocketOwner(network, source) if err != nil { return nil, err } - path, err := getProcessPath(pid) + path, err := getProcessPath(owner.Pid) if err != nil { - return &adapter.ConnectionOwner{ProcessID: pid, UserId: -1}, err + return &adapter.ConnectionOwner{ProcessID: owner.Pid, UserId: -1}, err } - return &adapter.ConnectionOwner{ProcessID: pid, ProcessPath: path, UserId: -1}, nil + processPaths := []string{path} + if owner.ServiceName != "" { + servicePath, serviceErr := getServiceDLLPath(owner.ServiceName) + if serviceErr == nil { + processPaths = []string{servicePath, path} + } + } + return &adapter.ConnectionOwner{ProcessID: owner.Pid, ProcessPaths: processPaths, UserId: -1}, nil +} + +func getServiceDLLPath(serviceName string) (string, error) { + serviceKeyPath := `SYSTEM\CurrentControlSet\Services\` + serviceName + var lastErr error + for _, keyPath := range []string{serviceKeyPath + `\Parameters`, serviceKeyPath} { + key, err := registry.OpenKey(registry.LOCAL_MACHINE, keyPath, registry.QUERY_VALUE) + if err != nil { + lastErr = err + continue + } + value, valueType, err := key.GetStringValue("ServiceDll") + key.Close() + if err != nil { + lastErr = err + continue + } + if valueType == registry.EXPAND_SZ { + return registry.ExpandString(value) + } + return value, nil + } + return "", lastErr } func getProcessPath(pid uint32) (string, error) { diff --git a/daemon/started_service.go b/daemon/started_service.go index f1fb0f0d8..4039c0343 100644 --- a/daemon/started_service.go +++ b/daemon/started_service.go @@ -1055,8 +1055,10 @@ func buildConnectionProto(metadata *trafficcontrol.TrackerMetadata) *Connection ProcessId: metadata.Metadata.ProcessInfo.ProcessID, UserId: metadata.Metadata.ProcessInfo.UserId, UserName: metadata.Metadata.ProcessInfo.UserName, - ProcessPath: metadata.Metadata.ProcessInfo.ProcessPath, - PackageNames: metadata.Metadata.ProcessInfo.AndroidPackageNames, + PackageNames: metadata.Metadata.ProcessInfo.PackageNames, + } + if len(metadata.Metadata.ProcessInfo.ProcessPaths) > 0 { + processInfo.ProcessPath = metadata.Metadata.ProcessInfo.ProcessPaths[0] } } return &Connection{ diff --git a/experimental/clashapi/connections.go b/experimental/clashapi/connections.go index 2cf0a714b..3da9dc6e6 100644 --- a/experimental/clashapi/connections.go +++ b/experimental/clashapi/connections.go @@ -61,10 +61,10 @@ func (c connectionObject) MarshalJSON() ([]byte, error) { } var processPath string if c.Metadata.ProcessInfo != nil { - if c.Metadata.ProcessInfo.ProcessPath != "" { - processPath = c.Metadata.ProcessInfo.ProcessPath - } else if len(c.Metadata.ProcessInfo.AndroidPackageNames) > 0 { - processPath = c.Metadata.ProcessInfo.AndroidPackageNames[0] + if len(c.Metadata.ProcessInfo.ProcessPaths) > 0 { + processPath = c.Metadata.ProcessInfo.ProcessPaths[0] + } else if len(c.Metadata.ProcessInfo.PackageNames) > 0 { + processPath = c.Metadata.ProcessInfo.PackageNames[0] } if processPath == "" { if c.Metadata.ProcessInfo.UserId != -1 { diff --git a/experimental/libbox/connection_owner_darwin.go b/experimental/libbox/connection_owner_darwin.go index 202201067..ffb4bc38e 100644 --- a/experimental/libbox/connection_owner_darwin.go +++ b/experimental/libbox/connection_owner_darwin.go @@ -33,8 +33,11 @@ func FindConnectionOwner(ipProtocol int32, sourceAddress string, sourcePort int3 return nil, err } result := &ConnectionOwner{ - UserId: owner.UserId, - ProcessPath: owner.ProcessPath, + UserId: owner.UserId, + processPaths: owner.ProcessPaths, + } + if len(owner.ProcessPaths) > 0 { + result.ProcessPath = owner.ProcessPaths[0] } if owner.UserId != -1 && owner.UserName == "" { osUser, _ := user.LookupId(F.ToString(owner.UserId)) diff --git a/experimental/libbox/oom_report.go b/experimental/libbox/oom_report.go index d7bfcf581..cba1e4718 100644 --- a/experimental/libbox/oom_report.go +++ b/experimental/libbox/oom_report.go @@ -160,10 +160,10 @@ func buildOOMConnection(connection *trafficcontrol.TrackerMetadata) oomConnectio } var process string if processInfo := metadata.ProcessInfo; processInfo != nil { - if processInfo.ProcessPath != "" { - process = processInfo.ProcessPath - } else if len(processInfo.AndroidPackageNames) > 0 { - process = processInfo.AndroidPackageNames[0] + if len(processInfo.ProcessPaths) > 0 { + process = processInfo.ProcessPaths[0] + } else if len(processInfo.PackageNames) > 0 { + process = processInfo.PackageNames[0] } if process == "" { if processInfo.UserId != -1 { diff --git a/experimental/libbox/platform.go b/experimental/libbox/platform.go index b04f8a991..19759b3c6 100644 --- a/experimental/libbox/platform.go +++ b/experimental/libbox/platform.go @@ -90,9 +90,18 @@ type ConnectionOwner struct { UserId int32 UserName string ProcessPath string + processPaths []string androidPackageNames []string } +func (c *ConnectionOwner) SetProcessPaths(paths StringIterator) { + c.processPaths = iteratorToArray[string](paths) +} + +func (c *ConnectionOwner) ProcessPaths() StringIterator { + return newIterator(c.processPaths) +} + func (c *ConnectionOwner) SetAndroidPackageNames(names StringIterator) { c.androidPackageNames = iteratorToArray[string](names) } diff --git a/experimental/libbox/service.go b/experimental/libbox/service.go index bbca35525..628ab8da1 100644 --- a/experimental/libbox/service.go +++ b/experimental/libbox/service.go @@ -223,11 +223,15 @@ func (w *platformInterfaceWrapper) FindConnectionOwner(request *adapter.FindConn if err != nil { return nil, err } + processPaths := result.processPaths + if len(processPaths) == 0 && result.ProcessPath != "" { + processPaths = []string{result.ProcessPath} + } return &adapter.ConnectionOwner{ - UserId: result.UserId, - UserName: result.UserName, - ProcessPath: result.ProcessPath, - AndroidPackageNames: result.androidPackageNames, + UserId: result.UserId, + UserName: result.UserName, + ProcessPaths: processPaths, + PackageNames: result.androidPackageNames, }, nil } diff --git a/route/process_cache.go b/route/process_cache.go index f99cebadb..2fa41a80e 100644 --- a/route/process_cache.go +++ b/route/process_cache.go @@ -51,18 +51,19 @@ func (r *Router) searchProcessInfo(ctx context.Context, metadata *adapter.Inboun return } metadata.ProcessInfo = processInfo - if processInfo.ProcessPath != "" { + if len(processInfo.ProcessPaths) > 0 { + processPath := strings.Join(processInfo.ProcessPaths, ", ") if processInfo.UserName != "" { - r.logger.InfoContext(ctx, "found process path: ", processInfo.ProcessPath, ", user: ", processInfo.UserName) + r.logger.InfoContext(ctx, "found process path: ", processPath, ", user: ", processInfo.UserName) } else if processInfo.UserId != -1 { - r.logger.InfoContext(ctx, "found process path: ", processInfo.ProcessPath, ", user id: ", processInfo.UserId) + r.logger.InfoContext(ctx, "found process path: ", processPath, ", user id: ", processInfo.UserId) } else { - r.logger.InfoContext(ctx, "found process path: ", processInfo.ProcessPath) + r.logger.InfoContext(ctx, "found process path: ", processPath) } return } - if len(processInfo.AndroidPackageNames) > 0 { - r.logger.InfoContext(ctx, "found package name: ", strings.Join(processInfo.AndroidPackageNames, ", ")) + if len(processInfo.PackageNames) > 0 { + r.logger.InfoContext(ctx, "found package name: ", strings.Join(processInfo.PackageNames, ", ")) return } if processInfo.UserId != -1 { diff --git a/route/rule/rule_item_package_name.go b/route/rule/rule_item_package_name.go index 514768de7..bed686245 100644 --- a/route/rule/rule_item_package_name.go +++ b/route/rule/rule_item_package_name.go @@ -25,10 +25,10 @@ func NewPackageNameItem(packageNameList []string) *PackageNameItem { } func (r *PackageNameItem) Match(metadata *adapter.InboundContext) bool { - if metadata.ProcessInfo == nil || len(metadata.ProcessInfo.AndroidPackageNames) == 0 { + if metadata.ProcessInfo == nil || len(metadata.ProcessInfo.PackageNames) == 0 { return false } - for _, packageName := range metadata.ProcessInfo.AndroidPackageNames { + for _, packageName := range metadata.ProcessInfo.PackageNames { if r.packageMap[packageName] { return true } diff --git a/route/rule/rule_item_package_name_regex.go b/route/rule/rule_item_package_name_regex.go index e329b6278..d2b4afc75 100644 --- a/route/rule/rule_item_package_name_regex.go +++ b/route/rule/rule_item_package_name_regex.go @@ -39,11 +39,11 @@ func NewPackageNameRegexItem(expressions []string) (*PackageNameRegexItem, error } func (r *PackageNameRegexItem) Match(metadata *adapter.InboundContext) bool { - if metadata.ProcessInfo == nil || len(metadata.ProcessInfo.AndroidPackageNames) == 0 { + if metadata.ProcessInfo == nil || len(metadata.ProcessInfo.PackageNames) == 0 { return false } for _, matcher := range r.matchers { - if slices.ContainsFunc(metadata.ProcessInfo.AndroidPackageNames, matcher.MatchString) { + if slices.ContainsFunc(metadata.ProcessInfo.PackageNames, matcher.MatchString) { return true } } diff --git a/route/rule/rule_item_process_name.go b/route/rule/rule_item_process_name.go index fa0f71651..3e58c6473 100644 --- a/route/rule/rule_item_process_name.go +++ b/route/rule/rule_item_process_name.go @@ -2,6 +2,7 @@ package rule import ( "path/filepath" + "slices" "strings" "github.com/sagernet/sing-box/adapter" @@ -26,10 +27,12 @@ func NewProcessItem(processNameList []string) *ProcessItem { } func (r *ProcessItem) Match(metadata *adapter.InboundContext) bool { - if metadata.ProcessInfo == nil || metadata.ProcessInfo.ProcessPath == "" { + if metadata.ProcessInfo == nil { return false } - return r.processMap[filepath.Base(metadata.ProcessInfo.ProcessPath)] + return slices.ContainsFunc(metadata.ProcessInfo.ProcessPaths, func(processPath string) bool { + return r.processMap[filepath.Base(processPath)] + }) } func (r *ProcessItem) String() string { diff --git a/route/rule/rule_item_process_path.go b/route/rule/rule_item_process_path.go index ac5c6a180..9367c439f 100644 --- a/route/rule/rule_item_process_path.go +++ b/route/rule/rule_item_process_path.go @@ -1,6 +1,7 @@ package rule import ( + "slices" "strings" "github.com/sagernet/sing-box/adapter" @@ -29,15 +30,11 @@ func (r *ProcessPathItem) Match(metadata *adapter.InboundContext) bool { if metadata.ProcessInfo == nil { return false } - if metadata.ProcessInfo.ProcessPath != "" && r.processMap[metadata.ProcessInfo.ProcessPath] { + if slices.ContainsFunc(metadata.ProcessInfo.ProcessPaths, func(processPath string) bool { return r.processMap[processPath] }) { return true } if C.IsAndroid { - for _, packageName := range metadata.ProcessInfo.AndroidPackageNames { - if r.processMap[packageName] { - return true - } - } + return slices.ContainsFunc(metadata.ProcessInfo.PackageNames, func(packageName string) bool { return r.processMap[packageName] }) } return false } diff --git a/route/rule/rule_item_process_path_regex.go b/route/rule/rule_item_process_path_regex.go index 76cf67b9f..c31693638 100644 --- a/route/rule/rule_item_process_path_regex.go +++ b/route/rule/rule_item_process_path_regex.go @@ -2,6 +2,7 @@ package rule import ( "regexp" + "slices" "strings" "github.com/sagernet/sing-box/adapter" @@ -38,11 +39,11 @@ func NewProcessPathRegexItem(expressions []string) (*ProcessPathRegexItem, error } func (r *ProcessPathRegexItem) Match(metadata *adapter.InboundContext) bool { - if metadata.ProcessInfo == nil || metadata.ProcessInfo.ProcessPath == "" { + if metadata.ProcessInfo == nil { return false } for _, matcher := range r.matchers { - if matcher.MatchString(metadata.ProcessInfo.ProcessPath) { + if slices.ContainsFunc(metadata.ProcessInfo.ProcessPaths, matcher.MatchString) { return true } } diff --git a/service/powerreport/record.go b/service/powerreport/record.go index 06c4e0857..064718951 100644 --- a/service/powerreport/record.go +++ b/service/powerreport/record.go @@ -38,7 +38,7 @@ type ProcessAttribution struct { ProcessID uint32 `json:"processId,omitempty"` UserID int32 `json:"userId,omitempty"` UserName string `json:"userName,omitempty"` - ProcessPath string `json:"processPath,omitempty"` + ProcessPaths []string `json:"processPaths,omitempty"` PackageNames []string `json:"packageNames,omitempty"` } diff --git a/service/resolved/resolve1.go b/service/resolved/resolve1.go index a02d03439..c60ec723c 100644 --- a/service/resolved/resolve1.go +++ b/service/resolved/resolve1.go @@ -125,11 +125,11 @@ func (t *resolve1Manager) createMetadata(sender dbus.Sender) adapter.InboundCont processPath, err := os.Readlink(F.ToString("/proc/", senderPid, "/exe")) if err == nil { - processInfo.ProcessPath = processPath + processInfo.ProcessPaths = []string{processPath} } else { processName, readErr := os.ReadFile(F.ToString("/proc/", senderPid, "/comm")) if readErr == nil { - processInfo.ProcessPath = strings.TrimSpace(string(processName)) + processInfo.ProcessPaths = []string{strings.TrimSpace(string(processName))} } } @@ -165,8 +165,8 @@ func (t *resolve1Manager) log(sender dbus.Sender, message ...any) { metadata := t.createMetadata(sender) if metadata.ProcessInfo != nil { var prefix string - if metadata.ProcessInfo.ProcessPath != "" { - prefix = filepath.Base(metadata.ProcessInfo.ProcessPath) + if len(metadata.ProcessInfo.ProcessPaths) > 0 { + prefix = filepath.Base(metadata.ProcessInfo.ProcessPaths[0]) } else if metadata.ProcessInfo.UserName != "" { prefix = F.ToString("user:", metadata.ProcessInfo.UserName) } else if metadata.ProcessInfo.UserId != 0 { @@ -183,8 +183,8 @@ func (t *resolve1Manager) logRequest(sender dbus.Sender, message ...any) context metadata := t.createMetadata(sender) if metadata.ProcessInfo != nil { var prefix string - if metadata.ProcessInfo.ProcessPath != "" { - prefix = filepath.Base(metadata.ProcessInfo.ProcessPath) + if len(metadata.ProcessInfo.ProcessPaths) > 0 { + prefix = filepath.Base(metadata.ProcessInfo.ProcessPaths[0]) } else if metadata.ProcessInfo.UserName != "" { prefix = F.ToString("user:", metadata.ProcessInfo.UserName) } else if metadata.ProcessInfo.UserId != 0 {