From 9683c297a74c07cf63f84a41f6ffa138a5adefca Mon Sep 17 00:00:00 2001 From: Kr328 Date: Wed, 9 Mar 2022 13:41:50 +0800 Subject: [PATCH 1/2] Chore: add more details to process resolving (#2017) --- component/process/process_linux.go | 45 ++++++++++++------------------ 1 file changed, 18 insertions(+), 27 deletions(-) diff --git a/component/process/process_linux.go b/component/process/process_linux.go index 0ca427163..c718dc2f2 100644 --- a/component/process/process_linux.go +++ b/component/process/process_linux.go @@ -4,7 +4,6 @@ import ( "bytes" "encoding/binary" "fmt" - "io" "net" "os" "path" @@ -27,17 +26,6 @@ var nativeEndian = func() binary.ByteOrder { return binary.LittleEndian }() -type ( - SocketResolver func(network string, ip net.IP, srcPort int) (inode, uid int, err error) - ProcessNameResolver func(inode, uid int) (name string, err error) -) - -// export for android -var ( - DefaultSocketResolver SocketResolver = resolveSocketByNetlink - DefaultProcessNameResolver ProcessNameResolver = resolveProcessNameByProcSearch -) - const ( sizeOfSocketDiagRequest = syscall.SizeofNlMsghdr + 8 + 48 socketDiagByFamily = 20 @@ -45,15 +33,15 @@ const ( ) func findProcessName(network string, ip net.IP, srcPort int) (string, error) { - inode, uid, err := DefaultSocketResolver(network, ip, srcPort) + inode, uid, err := resolveSocketByNetlink(network, ip, srcPort) if err != nil { return "", err } - return DefaultProcessNameResolver(inode, uid) + return resolveProcessNameByProcSearch(inode, uid) } -func resolveSocketByNetlink(network string, ip net.IP, srcPort int) (int, int, error) { +func resolveSocketByNetlink(network string, ip net.IP, srcPort int) (int32, int32, error) { var family byte var protocol byte @@ -76,7 +64,7 @@ func resolveSocketByNetlink(network string, ip net.IP, srcPort int) (int, int, e socket, err := syscall.Socket(syscall.AF_NETLINK, syscall.SOCK_DGRAM, syscall.NETLINK_INET_DIAG) if err != nil { - return 0, 0, err + return 0, 0, fmt.Errorf("dial netlink: %w", err) } defer syscall.Close(socket) @@ -94,7 +82,7 @@ func resolveSocketByNetlink(network string, ip net.IP, srcPort int) (int, int, e } if _, err := syscall.Write(socket, req); err != nil { - return 0, 0, err + return 0, 0, fmt.Errorf("write request: %w", err) } rb := pool.Get(pool.RelayBufferSize) @@ -102,24 +90,27 @@ func resolveSocketByNetlink(network string, ip net.IP, srcPort int) (int, int, e n, err := syscall.Read(socket, rb) if err != nil { - return 0, 0, err + return 0, 0, fmt.Errorf("read response: %w", err) } messages, err := syscall.ParseNetlinkMessage(rb[:n]) if err != nil { - return 0, 0, err + return 0, 0, fmt.Errorf("parse netlink message: %w", err) } else if len(messages) == 0 { - return 0, 0, io.ErrUnexpectedEOF + return 0, 0, fmt.Errorf("unexcepted netlink response") } message := messages[0] if message.Header.Type&syscall.NLMSG_ERROR != 0 { - return 0, 0, syscall.ESRCH + return 0, 0, fmt.Errorf("netlink message: NLMSG_ERROR") } uid, inode := unpackSocketDiagResponse(&messages[0]) + if uid < 0 || inode < 0 { + return 0, 0, fmt.Errorf("invalid uid(%d) or inode(%d)", uid, inode) + } - return int(uid), int(inode), nil + return uid, inode, nil } func packSocketDiagRequest(family, protocol byte, source net.IP, sourcePort uint16) []byte { @@ -157,20 +148,20 @@ func packSocketDiagRequest(family, protocol byte, source net.IP, sourcePort uint return buf } -func unpackSocketDiagResponse(msg *syscall.NetlinkMessage) (inode, uid uint32) { +func unpackSocketDiagResponse(msg *syscall.NetlinkMessage) (inode, uid int32) { if len(msg.Data) < 72 { return 0, 0 } data := msg.Data - uid = nativeEndian.Uint32(data[64:68]) - inode = nativeEndian.Uint32(data[68:72]) + uid = int32(nativeEndian.Uint32(data[64:68])) + inode = int32(nativeEndian.Uint32(data[68:72])) return } -func resolveProcessNameByProcSearch(inode, uid int) (string, error) { +func resolveProcessNameByProcSearch(inode, uid int32) (string, error) { files, err := os.ReadDir(pathProc) if err != nil { return "", err @@ -217,7 +208,7 @@ func resolveProcessNameByProcSearch(inode, uid int) (string, error) { } } - return "", syscall.ESRCH + return "", fmt.Errorf("process of uid(%d),inode(%d) not found", uid, inode) } func splitCmdline(cmdline []byte) string { From b866f06414585c2c73ce2aef7a2f7b3a0dda2abe Mon Sep 17 00:00:00 2001 From: Kr328 Date: Sat, 12 Mar 2022 19:07:53 +0800 Subject: [PATCH 2/2] Chore: move find connection process to tunnel (#2016) --- component/process/process_darwin.go | 3 +- component/process/process_freebsd_amd64.go | 3 +- component/process/process_linux.go | 21 ++-------- component/process/process_windows.go | 3 +- constant/metadata.go | 19 ++++----- constant/rule.go | 4 ++ rule/domain.go | 4 ++ rule/domain_keyword.go | 4 ++ rule/domain_suffix.go | 4 ++ rule/final.go | 4 ++ rule/geoip.go | 4 ++ rule/ipcidr.go | 4 ++ rule/parser.go | 4 +- rule/port.go | 4 ++ rule/process.go | 45 ++++++++-------------- tunnel/tunnel.go | 18 +++++++++ 16 files changed, 84 insertions(+), 64 deletions(-) diff --git a/component/process/process_darwin.go b/component/process/process_darwin.go index 7e4baf74c..7870b2272 100644 --- a/component/process/process_darwin.go +++ b/component/process/process_darwin.go @@ -3,7 +3,6 @@ package process import ( "encoding/binary" "net" - "path/filepath" "syscall" "unsafe" @@ -96,7 +95,7 @@ func getExecPathFromPID(pid uint32) (string, error) { return "", errno } - return filepath.Base(unix.ByteSliceToString(buf)), nil + return unix.ByteSliceToString(buf), nil } func readNativeUint32(b []byte) uint32 { diff --git a/component/process/process_freebsd_amd64.go b/component/process/process_freebsd_amd64.go index bf84ce5b8..f3e646463 100644 --- a/component/process/process_freebsd_amd64.go +++ b/component/process/process_freebsd_amd64.go @@ -4,7 +4,6 @@ import ( "encoding/binary" "fmt" "net" - "path/filepath" "strconv" "strings" "sync" @@ -77,7 +76,7 @@ func getExecPathFromPID(pid uint32) (string, error) { return "", errno } - return filepath.Base(string(buf[:size-1])), nil + return string(buf[:size-1]), nil } func readNativeUint32(b []byte) uint32 { diff --git a/component/process/process_linux.go b/component/process/process_linux.go index c718dc2f2..d70a29228 100644 --- a/component/process/process_linux.go +++ b/component/process/process_linux.go @@ -7,7 +7,6 @@ import ( "net" "os" "path" - "path/filepath" "strings" "syscall" "unicode" @@ -68,9 +67,8 @@ func resolveSocketByNetlink(network string, ip net.IP, srcPort int) (int32, int3 } defer syscall.Close(socket) - syscall.SetNonblock(socket, true) - syscall.SetsockoptTimeval(socket, syscall.SOL_SOCKET, syscall.SO_SNDTIMEO, &syscall.Timeval{Usec: 50}) - syscall.SetsockoptTimeval(socket, syscall.SOL_SOCKET, syscall.SO_RCVTIMEO, &syscall.Timeval{Usec: 50}) + syscall.SetsockoptTimeval(socket, syscall.SOL_SOCKET, syscall.SO_SNDTIMEO, &syscall.Timeval{Usec: 100}) + syscall.SetsockoptTimeval(socket, syscall.SOL_SOCKET, syscall.SO_RCVTIMEO, &syscall.Timeval{Usec: 100}) if err := syscall.Connect(socket, &syscall.SockaddrNetlink{ Family: syscall.AF_NETLINK, @@ -198,12 +196,7 @@ func resolveProcessNameByProcSearch(inode, uid int32) (string, error) { } if bytes.Equal(buffer[:n], socket) { - cmdline, err := os.ReadFile(path.Join(processPath, "cmdline")) - if err != nil { - return "", err - } - - return splitCmdline(cmdline), nil + return os.Readlink(path.Join(processPath, "exe")) } } } @@ -211,14 +204,6 @@ func resolveProcessNameByProcSearch(inode, uid int32) (string, error) { return "", fmt.Errorf("process of uid(%d),inode(%d) not found", uid, inode) } -func splitCmdline(cmdline []byte) string { - idx := bytes.IndexFunc(cmdline, func(r rune) bool { - return unicode.IsControl(r) || unicode.IsSpace(r) - }) - - return filepath.Base(string(cmdline[:idx])) -} - func isPid(s string) bool { return strings.IndexFunc(s, func(r rune) bool { return !unicode.IsDigit(r) diff --git a/component/process/process_windows.go b/component/process/process_windows.go index 834bc8241..e2fb96cab 100644 --- a/component/process/process_windows.go +++ b/component/process/process_windows.go @@ -3,7 +3,6 @@ package process import ( "fmt" "net" - "path/filepath" "sync" "syscall" "unsafe" @@ -220,5 +219,5 @@ func getExecPathFromPID(pid uint32) (string, error) { if r1 == 0 { return "", err } - return filepath.Base(syscall.UTF16ToString(buf[:size])), nil + return syscall.UTF16ToString(buf[:size]), nil } diff --git a/constant/metadata.go b/constant/metadata.go index a911b5e8e..6789c913e 100644 --- a/constant/metadata.go +++ b/constant/metadata.go @@ -63,15 +63,16 @@ func (t Type) MarshalJSON() ([]byte, error) { // Metadata is used to store connection address type Metadata struct { - NetWork NetWork `json:"network"` - Type Type `json:"type"` - SrcIP net.IP `json:"sourceIP"` - DstIP net.IP `json:"destinationIP"` - SrcPort string `json:"sourcePort"` - DstPort string `json:"destinationPort"` - AddrType int `json:"-"` - Host string `json:"host"` - DNSMode DNSMode `json:"dnsMode"` + NetWork NetWork `json:"network"` + Type Type `json:"type"` + SrcIP net.IP `json:"sourceIP"` + DstIP net.IP `json:"destinationIP"` + SrcPort string `json:"sourcePort"` + DstPort string `json:"destinationPort"` + AddrType int `json:"-"` + Host string `json:"host"` + DNSMode DNSMode `json:"dnsMode"` + ProcessPath string `json:"processPath"` } func (m *Metadata) RemoteAddress() string { diff --git a/constant/rule.go b/constant/rule.go index d7c43416d..9e1908f22 100644 --- a/constant/rule.go +++ b/constant/rule.go @@ -11,6 +11,7 @@ const ( SrcPort DstPort Process + ProcessPath MATCH ) @@ -36,6 +37,8 @@ func (rt RuleType) String() string { return "DstPort" case Process: return "Process" + case ProcessPath: + return "ProcessPath" case MATCH: return "Match" default: @@ -49,4 +52,5 @@ type Rule interface { Adapter() string Payload() string ShouldResolveIP() bool + ShouldFindProcess() bool } diff --git a/rule/domain.go b/rule/domain.go index f23ab18de..2b2a076bc 100644 --- a/rule/domain.go +++ b/rule/domain.go @@ -34,6 +34,10 @@ func (d *Domain) ShouldResolveIP() bool { return false } +func (d *Domain) ShouldFindProcess() bool { + return false +} + func NewDomain(domain string, adapter string) *Domain { return &Domain{ domain: strings.ToLower(domain), diff --git a/rule/domain_keyword.go b/rule/domain_keyword.go index b3d6fbaad..046eafe48 100644 --- a/rule/domain_keyword.go +++ b/rule/domain_keyword.go @@ -35,6 +35,10 @@ func (dk *DomainKeyword) ShouldResolveIP() bool { return false } +func (dk *DomainKeyword) ShouldFindProcess() bool { + return false +} + func NewDomainKeyword(keyword string, adapter string) *DomainKeyword { return &DomainKeyword{ keyword: strings.ToLower(keyword), diff --git a/rule/domain_suffix.go b/rule/domain_suffix.go index a1f9f28f1..092193386 100644 --- a/rule/domain_suffix.go +++ b/rule/domain_suffix.go @@ -35,6 +35,10 @@ func (ds *DomainSuffix) ShouldResolveIP() bool { return false } +func (ds *DomainSuffix) ShouldFindProcess() bool { + return false +} + func NewDomainSuffix(suffix string, adapter string) *DomainSuffix { return &DomainSuffix{ suffix: strings.ToLower(suffix), diff --git a/rule/final.go b/rule/final.go index 91c0b9d97..80a38f036 100644 --- a/rule/final.go +++ b/rule/final.go @@ -28,6 +28,10 @@ func (f *Match) ShouldResolveIP() bool { return false } +func (f *Match) ShouldFindProcess() bool { + return false +} + func NewMatch(adapter string) *Match { return &Match{ adapter: adapter, diff --git a/rule/geoip.go b/rule/geoip.go index 3b6fbe6b8..152d4e631 100644 --- a/rule/geoip.go +++ b/rule/geoip.go @@ -42,6 +42,10 @@ func (g *GEOIP) ShouldResolveIP() bool { return !g.noResolveIP } +func (g *GEOIP) ShouldFindProcess() bool { + return false +} + func NewGEOIP(country string, adapter string, noResolveIP bool) *GEOIP { geoip := &GEOIP{ country: country, diff --git a/rule/ipcidr.go b/rule/ipcidr.go index ff3b83c44..42c0c9fb7 100644 --- a/rule/ipcidr.go +++ b/rule/ipcidr.go @@ -54,6 +54,10 @@ func (i *IPCIDR) ShouldResolveIP() bool { return !i.noResolveIP } +func (i *IPCIDR) ShouldFindProcess() bool { + return false +} + func NewIPCIDR(s string, adapter string, opts ...IPCIDROption) (*IPCIDR, error) { _, ipnet, err := net.ParseCIDR(s) if err != nil { diff --git a/rule/parser.go b/rule/parser.go index 6e78b8fae..212029bf2 100644 --- a/rule/parser.go +++ b/rule/parser.go @@ -32,7 +32,9 @@ func ParseRule(tp, payload, target string, params []string) (C.Rule, error) { case "DST-PORT": parsed, parseErr = NewPort(payload, target, false) case "PROCESS-NAME": - parsed, parseErr = NewProcess(payload, target) + parsed, parseErr = NewProcess(payload, target, true) + case "PROCESS-PATH": + parsed, parseErr = NewProcess(payload, target, false) case "MATCH": parsed = NewMatch(target) default: diff --git a/rule/port.go b/rule/port.go index b6e8abb1c..2cc7a7a22 100644 --- a/rule/port.go +++ b/rule/port.go @@ -38,6 +38,10 @@ func (p *Port) ShouldResolveIP() bool { return false } +func (p *Port) ShouldFindProcess() bool { + return false +} + func NewPort(port string, adapter string, isSource bool) (*Port, error) { _, err := strconv.ParseUint(port, 10, 16) if err != nil { diff --git a/rule/process.go b/rule/process.go index 638ec7239..52f0cec2b 100644 --- a/rule/process.go +++ b/rule/process.go @@ -1,21 +1,16 @@ package rules import ( - "fmt" - "strconv" + "path/filepath" "strings" - "github.com/Dreamacro/clash/common/cache" - "github.com/Dreamacro/clash/component/process" C "github.com/Dreamacro/clash/constant" - "github.com/Dreamacro/clash/log" ) -var processCache = cache.NewLRUCache(cache.WithAge(2), cache.WithSize(64)) - type Process struct { - adapter string - process string + adapter string + process string + nameOnly bool } func (ps *Process) RuleType() C.RuleType { @@ -23,26 +18,11 @@ func (ps *Process) RuleType() C.RuleType { } func (ps *Process) Match(metadata *C.Metadata) bool { - key := fmt.Sprintf("%s:%s:%s", metadata.NetWork.String(), metadata.SrcIP.String(), metadata.SrcPort) - cached, hit := processCache.Get(key) - if !hit { - srcPort, err := strconv.Atoi(metadata.SrcPort) - if err != nil { - processCache.Set(key, "") - return false - } - - name, err := process.FindProcessName(metadata.NetWork.String(), metadata.SrcIP, srcPort) - if err != nil { - log.Debugln("[Rule] find process name %s error: %s", C.Process.String(), err.Error()) - } - - processCache.Set(key, name) - - cached = name + if ps.nameOnly { + return strings.EqualFold(filepath.Base(metadata.ProcessPath), ps.process) } - return strings.EqualFold(cached.(string), ps.process) + return strings.EqualFold(metadata.ProcessPath, ps.process) } func (ps *Process) Adapter() string { @@ -57,9 +37,14 @@ func (ps *Process) ShouldResolveIP() bool { return false } -func NewProcess(process string, adapter string) (*Process, error) { +func (ps *Process) ShouldFindProcess() bool { + return true +} + +func NewProcess(process string, adapter string, nameOnly bool) (*Process, error) { return &Process{ - adapter: adapter, - process: process, + adapter: adapter, + process: process, + nameOnly: nameOnly, }, nil } diff --git a/tunnel/tunnel.go b/tunnel/tunnel.go index f00a7bd02..072848800 100644 --- a/tunnel/tunnel.go +++ b/tunnel/tunnel.go @@ -5,11 +5,13 @@ import ( "fmt" "net" "runtime" + "strconv" "sync" "time" "github.com/Dreamacro/clash/adapter/inbound" "github.com/Dreamacro/clash/component/nat" + P "github.com/Dreamacro/clash/component/process" "github.com/Dreamacro/clash/component/resolver" C "github.com/Dreamacro/clash/constant" "github.com/Dreamacro/clash/constant/provider" @@ -308,6 +310,7 @@ func match(metadata *C.Metadata) (C.Proxy, C.Rule, error) { defer configMux.RUnlock() var resolved bool + var processFound bool if node := resolver.DefaultHosts.Search(metadata.Host); node != nil { ip := node.Data.(net.IP) @@ -327,6 +330,21 @@ func match(metadata *C.Metadata) (C.Proxy, C.Rule, error) { resolved = true } + if !processFound && rule.ShouldFindProcess() { + processFound = true + + srcPort, err := strconv.Atoi(metadata.SrcPort) + if err == nil { + path, err := P.FindProcessName(metadata.NetWork.String(), metadata.SrcIP, srcPort) + if err != nil { + log.Debugln("[Process] find process %s: %v", metadata.String(), err) + } else { + log.Debugln("[Process] %s from process %s", metadata.String(), path) + metadata.ProcessPath = path + } + } + } + if rule.Match(metadata) { adapter, ok := proxies[rule.Adapter()] if !ok {