diff --git a/component/process/process.go b/component/process/process.go new file mode 100644 index 00000000..67a5df66 --- /dev/null +++ b/component/process/process.go @@ -0,0 +1,21 @@ +package process + +import ( + "errors" + "net" +) + +var ( + ErrInvalidNetwork = errors.New("invalid network") + ErrPlatformNotSupport = errors.New("not support on this platform") + ErrNotFound = errors.New("process not found") +) + +const ( + TCP = "tcp" + UDP = "udp" +) + +func FindProcessName(network string, srcIP net.IP, srcPort int) (string, error) { + return findProcessName(network, srcIP, srcPort) +} diff --git a/rules/process_darwin.go b/component/process/process_darwin.go similarity index 56% rename from rules/process_darwin.go rename to component/process/process_darwin.go index 1af828a9..5157b209 100644 --- a/rules/process_darwin.go +++ b/component/process/process_darwin.go @@ -1,109 +1,26 @@ -package rules +package process import ( "bytes" "encoding/binary" - "errors" - "fmt" "net" "path/filepath" - "strconv" - "strings" "syscall" "unsafe" - - "github.com/Dreamacro/clash/common/cache" - C "github.com/Dreamacro/clash/constant" - "github.com/Dreamacro/clash/log" ) -// store process name for when dealing with multiple PROCESS-NAME rules -var processCache = cache.NewLRUCache(cache.WithAge(2), cache.WithSize(64)) - -type Process struct { - adapter string - process string -} - -func (ps *Process) RuleType() C.RuleType { - return C.Process -} - -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 { - name, err := getExecPathFromAddress(metadata) - if err != nil { - log.Debugln("[%s] getExecPathFromAddress error: %s", C.Process.String(), err.Error()) - } - - processCache.Set(key, name) - - cached = name - } - - return strings.EqualFold(cached.(string), ps.process) -} - -func (p *Process) Adapter() string { - return p.adapter -} - -func (p *Process) Payload() string { - return p.process -} - -func (p *Process) ShouldResolveIP() bool { - return false -} - -func NewProcess(process string, adapter string) (*Process, error) { - return &Process{ - adapter: adapter, - process: process, - }, nil -} - const ( procpidpathinfo = 0xb procpidpathinfosize = 1024 proccallnumpidinfo = 0x2 ) -func getExecPathFromPID(pid uint32) (string, error) { - buf := make([]byte, procpidpathinfosize) - _, _, errno := syscall.Syscall6( - syscall.SYS_PROC_INFO, - proccallnumpidinfo, - uintptr(pid), - procpidpathinfo, - 0, - uintptr(unsafe.Pointer(&buf[0])), - procpidpathinfosize) - if errno != 0 { - return "", errno - } - firstZero := bytes.IndexByte(buf, 0) - if firstZero <= 0 { - return "", nil - } - - return filepath.Base(string(buf[:firstZero])), nil -} - -func getExecPathFromAddress(metadata *C.Metadata) (string, error) { - ip := metadata.SrcIP - port, err := strconv.Atoi(metadata.SrcPort) - if err != nil { - return "", err - } - +func findProcessName(network string, ip net.IP, port int) (string, error) { var spath string - switch metadata.NetWork { - case C.TCP: + switch network { + case TCP: spath = "net.inet.tcp.pcblist_n" - case C.UDP: + case UDP: spath = "net.inet.udp.pcblist_n" default: return "", ErrInvalidNetwork @@ -123,7 +40,7 @@ func getExecPathFromAddress(metadata *C.Metadata) (string, error) { // rup8(sizeof(xinpcb_n)) + rup8(sizeof(xsocket_n)) + // 2 * rup8(sizeof(xsockbuf_n)) + rup8(sizeof(xsockstat_n)) itemSize := 384 - if metadata.NetWork == C.TCP { + if network == TCP { // rup8(sizeof(xtcpcb_n)) itemSize += 208 } @@ -161,7 +78,28 @@ func getExecPathFromAddress(metadata *C.Metadata) (string, error) { return getExecPathFromPID(pid) } - return "", errors.New("process not found") + return "", ErrNotFound +} + +func getExecPathFromPID(pid uint32) (string, error) { + buf := make([]byte, procpidpathinfosize) + _, _, errno := syscall.Syscall6( + syscall.SYS_PROC_INFO, + proccallnumpidinfo, + uintptr(pid), + procpidpathinfo, + 0, + uintptr(unsafe.Pointer(&buf[0])), + procpidpathinfosize) + if errno != 0 { + return "", errno + } + firstZero := bytes.IndexByte(buf, 0) + if firstZero <= 0 { + return "", nil + } + + return filepath.Base(string(buf[:firstZero])), nil } func readNativeUint32(b []byte) uint32 { diff --git a/rules/process_freebsd_amd64.go b/component/process/process_freebsd_amd64.go similarity index 70% rename from rules/process_freebsd_amd64.go rename to component/process/process_freebsd_amd64.go index 24c416a2..5a80670e 100644 --- a/rules/process_freebsd_amd64.go +++ b/component/process/process_freebsd_amd64.go @@ -1,8 +1,7 @@ -package rules +package process import ( "encoding/binary" - "errors" "fmt" "net" "path/filepath" @@ -12,78 +11,48 @@ import ( "syscall" "unsafe" - "github.com/Dreamacro/clash/common/cache" - C "github.com/Dreamacro/clash/constant" "github.com/Dreamacro/clash/log" ) // store process name for when dealing with multiple PROCESS-NAME rules var ( - processCache = cache.NewLRUCache(cache.WithAge(2), cache.WithSize(64)) - errNotFound = errors.New("process not found") - matchMeta = func(p *Process, m *C.Metadata) bool { return false } - defaultSearcher *searcher once sync.Once ) -type Process struct { - adapter string - process string -} - -func (ps *Process) RuleType() C.RuleType { - return C.Process -} - -func match(ps *Process, 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 { - name, err := getExecPathFromAddress(metadata) - if err != nil { - log.Debugln("[%s] getExecPathFromAddress error: %s", C.Process.String(), err.Error()) - } - - processCache.Set(key, name) - - cached = name - } - - return strings.EqualFold(cached.(string), ps.process) -} - -func (ps *Process) Match(metadata *C.Metadata) bool { - return matchMeta(ps, metadata) -} - -func (p *Process) Adapter() string { - return p.adapter -} - -func (p *Process) Payload() string { - return p.process -} - -func (p *Process) ShouldResolveIP() bool { - return false -} - -func NewProcess(process string, adapter string) (*Process, error) { +func findProcessName(network string, ip net.IP, srcPort int) (string, error) { once.Do(func() { - err := initSearcher() - if err != nil { + if err := initSearcher(); err != nil { log.Errorln("Initialize PROCESS-NAME failed: %s", err.Error()) log.Warnln("All PROCESS-NAME rules will be skipped") return } - matchMeta = match }) - return &Process{ - adapter: adapter, - process: process, - }, nil + + var spath string + isTCP := network == TCP + switch network { + case TCP: + spath = "net.inet.tcp.pcblist" + case UDP: + spath = "net.inet.udp.pcblist" + default: + return "", ErrInvalidNetwork + } + + value, err := syscall.Sysctl(spath) + if err != nil { + return "", err + } + + buf := []byte(value) + pid, err := defaultSearcher.Search(buf, ip, uint16(srcPort), isTCP) + if err != nil { + return "", err + } + + return getExecPathFromPID(pid) } func getExecPathFromPID(pid uint32) (string, error) { @@ -107,41 +76,6 @@ func getExecPathFromPID(pid uint32) (string, error) { return filepath.Base(string(buf[:size-1])), nil } -func getExecPathFromAddress(metadata *C.Metadata) (string, error) { - ip := metadata.SrcIP - port, err := strconv.Atoi(metadata.SrcPort) - if err != nil { - return "", err - } - - var spath string - var isTCP bool - switch metadata.NetWork { - case C.TCP: - spath = "net.inet.tcp.pcblist" - isTCP = true - case C.UDP: - spath = "net.inet.udp.pcblist" - isTCP = false - default: - return "", ErrInvalidNetwork - } - - value, err := syscall.Sysctl(spath) - if err != nil { - return "", err - } - - buf := []byte(value) - - pid, err := defaultSearcher.Search(buf, ip, uint16(port), isTCP) - if err != nil { - return "", err - } - - return getExecPathFromPID(pid) -} - func readNativeUint32(b []byte) uint32 { return *(*uint32)(unsafe.Pointer(&b[0])) } @@ -213,7 +147,7 @@ func (s *searcher) Search(buf []byte, ip net.IP, port uint16, isTCP bool) (uint3 socket := binary.BigEndian.Uint64(buf[inp+s.socket : inp+s.socket+8]) return s.searchSocketPid(socket) } - return 0, errNotFound + return 0, ErrNotFound } func (s *searcher) searchSocketPid(socket uint64) (uint32, error) { @@ -235,7 +169,7 @@ func (s *searcher) searchSocketPid(socket uint64) (uint32, error) { return pid, nil } } - return 0, errNotFound + return 0, ErrNotFound } func newSearcher(major int) *searcher { diff --git a/rules/process_linux.go b/component/process/process_linux.go similarity index 73% rename from rules/process_linux.go rename to component/process/process_linux.go index d883b295..1f651cd4 100644 --- a/rules/process_linux.go +++ b/component/process/process_linux.go @@ -1,4 +1,4 @@ -package rules +package process import ( "bytes" @@ -9,15 +9,10 @@ import ( "net" "path" "path/filepath" - "strconv" - "strings" "syscall" "unsafe" - "github.com/Dreamacro/clash/common/cache" "github.com/Dreamacro/clash/common/pool" - C "github.com/Dreamacro/clash/constant" - "github.com/Dreamacro/clash/log" ) // from https://github.com/vishvananda/netlink/blob/bca67dfc8220b44ef582c9da4e9172bf1c9ec973/nl/nl_linux.go#L52-L62 @@ -30,7 +25,7 @@ func init() { } } -type SocketResolver func(metadata *C.Metadata) (inode, uid int, err error) +type SocketResolver func(network string, ip net.IP, srcPort int) (inode, uid int, err error) type ProcessNameResolver func(inode, uid int) (name string, err error) // export for android @@ -39,51 +34,6 @@ var ( DefaultProcessNameResolver ProcessNameResolver = resolveProcessNameByProcSearch ) -type Process struct { - adapter string - process string -} - -func (p *Process) RuleType() C.RuleType { - return C.Process -} - -func (p *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 { - processName, err := resolveProcessName(metadata) - if err != nil { - log.Debugln("[%s] Resolve process of %s failure: %s", C.Process.String(), key, err.Error()) - } - - processCache.Set(key, processName) - - cached = processName - } - - return strings.EqualFold(cached.(string), p.process) -} - -func (p *Process) Adapter() string { - return p.adapter -} - -func (p *Process) Payload() string { - return p.process -} - -func (p *Process) ShouldResolveIP() bool { - return false -} - -func NewProcess(process string, adapter string) (*Process, error) { - return &Process{ - adapter: adapter, - process: process, - }, nil -} - const ( sizeOfSocketDiagRequest = syscall.SizeofNlMsghdr + 8 + 48 socketDiagByFamily = 20 @@ -92,10 +42,8 @@ const ( var nativeEndian binary.ByteOrder = binary.LittleEndian -var processCache = cache.NewLRUCache(cache.WithAge(2), cache.WithSize(64)) - -func resolveProcessName(metadata *C.Metadata) (string, error) { - inode, uid, err := DefaultSocketResolver(metadata) +func findProcessName(network string, ip net.IP, srcPort int) (string, error) { + inode, uid, err := DefaultSocketResolver(network, ip, srcPort) if err != nil { return "", err } @@ -103,31 +51,26 @@ func resolveProcessName(metadata *C.Metadata) (string, error) { return DefaultProcessNameResolver(inode, uid) } -func resolveSocketByNetlink(metadata *C.Metadata) (int, int, error) { +func resolveSocketByNetlink(network string, ip net.IP, srcPort int) (int, int, error) { var family byte var protocol byte - switch metadata.NetWork { - case C.TCP: + switch network { + case TCP: protocol = syscall.IPPROTO_TCP - case C.UDP: + case UDP: protocol = syscall.IPPROTO_UDP default: return 0, 0, ErrInvalidNetwork } - if metadata.SrcIP.To4() != nil { + if ip.To4() != nil { family = syscall.AF_INET } else { family = syscall.AF_INET6 } - srcPort, err := strconv.Atoi(metadata.SrcPort) - if err != nil { - return 0, 0, err - } - - req := packSocketDiagRequest(family, protocol, metadata.SrcIP, uint16(srcPort)) + req := packSocketDiagRequest(family, protocol, ip, uint16(srcPort)) socket, err := syscall.Socket(syscall.AF_NETLINK, syscall.SOCK_DGRAM, syscall.NETLINK_INET_DIAG) if err != nil { diff --git a/component/process/process_other.go b/component/process/process_other.go new file mode 100644 index 00000000..1e0bd447 --- /dev/null +++ b/component/process/process_other.go @@ -0,0 +1,10 @@ +// +build !darwin,!linux,!windows +// +build !freebsd !amd64 + +package process + +import "net" + +func findProcessName(network string, ip net.IP, srcPort int) (string, error) { + return "", ErrPlatformNotSupport +} diff --git a/rules/process_windows.go b/component/process/process_windows.go similarity index 75% rename from rules/process_windows.go rename to component/process/process_windows.go index b1e4b931..39953111 100644 --- a/rules/process_windows.go +++ b/component/process/process_windows.go @@ -1,18 +1,13 @@ -package rules +package process import ( - "errors" "fmt" "net" "path/filepath" - "strconv" - "strings" "sync" "syscall" "unsafe" - "github.com/Dreamacro/clash/common/cache" - C "github.com/Dreamacro/clash/constant" "github.com/Dreamacro/clash/log" "golang.org/x/sys/windows" @@ -27,10 +22,6 @@ const ( ) var ( - processCache = cache.NewLRUCache(cache.WithAge(2), cache.WithSize(64)) - errNotFound = errors.New("process not found") - matchMeta = func(p *Process, m *C.Metadata) bool { return false } - getExTcpTable uintptr getExUdpTable uintptr queryProcName uintptr @@ -67,47 +58,7 @@ func initWin32API() error { return nil } -type Process struct { - adapter string - process string -} - -func (p *Process) RuleType() C.RuleType { - return C.Process -} - -func (p *Process) Adapter() string { - return p.adapter -} - -func (p *Process) Payload() string { - return p.process -} - -func (p *Process) ShouldResolveIP() bool { - return false -} - -func match(p *Process, 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 { - processName, err := resolveProcessName(metadata) - if err != nil { - log.Debugln("[%s] Resolve process of %s failed: %s", C.Process.String(), key, err.Error()) - } - - processCache.Set(key, processName) - cached = processName - } - return strings.EqualFold(cached.(string), p.process) -} - -func (p *Process) Match(metadata *C.Metadata) bool { - return matchMeta(p, metadata) -} - -func NewProcess(process string, adapter string) (*Process, error) { +func findProcessName(network string, ip net.IP, srcPort int) (string, error) { once.Do(func() { err := initWin32API() if err != nil { @@ -115,16 +66,7 @@ func NewProcess(process string, adapter string) (*Process, error) { log.Warnln("All PROCESS-NAMES rules will be skiped") return } - matchMeta = match }) - return &Process{ - adapter: adapter, - process: process, - }, nil -} - -func resolveProcessName(metadata *C.Metadata) (string, error) { - ip := metadata.SrcIP family := windows.AF_INET if ip.To4() == nil { family = windows.AF_INET6 @@ -132,28 +74,23 @@ func resolveProcessName(metadata *C.Metadata) (string, error) { var class int var fn uintptr - switch metadata.NetWork { - case C.TCP: + switch network { + case TCP: fn = getExTcpTable class = tcpTablePidConn - case C.UDP: + case UDP: fn = getExUdpTable class = udpTablePid default: return "", ErrInvalidNetwork } - srcPort, err := strconv.Atoi(metadata.SrcPort) - if err != nil { - return "", err - } - buf, err := getTransportTable(fn, family, class) if err != nil { return "", err } - s := newSearcher(family == windows.AF_INET, metadata.NetWork == C.TCP) + s := newSearcher(family == windows.AF_INET, network == TCP) pid, err := s.Search(buf, ip, uint16(srcPort)) if err != nil { @@ -203,7 +140,7 @@ func (s *searcher) Search(b []byte, ip net.IP, port uint16) (uint32, error) { pid := readNativeUint32(row[s.pid : s.pid+4]) return pid, nil } - return 0, errNotFound + return 0, ErrNotFound } func newSearcher(isV4, isTCP bool) *searcher { diff --git a/config/config.go b/config/config.go index 38a55915..f89fe433 100644 --- a/config/config.go +++ b/config/config.go @@ -395,10 +395,6 @@ func parseRules(cfg *RawConfig, proxies map[string]C.Proxy) ([]C.Rule, error) { parsed, parseErr := R.ParseRule(rule[0], payload, target, params) if parseErr != nil { - if parseErr == R.ErrPlatformNotSupport { - log.Warnln("Rules[%d] [%s] don't support current OS, skip", idx, line) - continue - } return nil, fmt.Errorf("rules[%d] [%s] error: %s", idx, line, parseErr.Error()) } diff --git a/rules/base.go b/rules/base.go index 2db3558c..0f2d9f29 100644 --- a/rules/base.go +++ b/rules/base.go @@ -5,9 +5,7 @@ import ( ) var ( - errPayload = errors.New("payload error") - ErrPlatformNotSupport = errors.New("not support on this platform") - ErrInvalidNetwork = errors.New("invalid network") + errPayload = errors.New("payload error") noResolve = "no-resolve" ) diff --git a/rules/process.go b/rules/process.go new file mode 100644 index 00000000..3f502e7f --- /dev/null +++ b/rules/process.go @@ -0,0 +1,65 @@ +package rules + +import ( + "fmt" + "strconv" + "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 +} + +func (ps *Process) RuleType() C.RuleType { + return C.Process +} + +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 + } + + return strings.EqualFold(cached.(string), ps.process) +} + +func (p *Process) Adapter() string { + return p.adapter +} + +func (p *Process) Payload() string { + return p.process +} + +func (p *Process) ShouldResolveIP() bool { + return false +} + +func NewProcess(process string, adapter string) (*Process, error) { + return &Process{ + adapter: adapter, + process: process, + }, nil +} diff --git a/rules/process_other.go b/rules/process_other.go deleted file mode 100644 index a392250b..00000000 --- a/rules/process_other.go +++ /dev/null @@ -1,12 +0,0 @@ -// +build !darwin,!linux,!windows -// +build !freebsd !amd64 - -package rules - -import ( - C "github.com/Dreamacro/clash/constant" -) - -func NewProcess(process string, adapter string) (C.Rule, error) { - return nil, ErrPlatformNotSupport -}