Chore: move find process name to a single part

This commit is contained in:
Dreamacro 2020-12-17 22:17:27 +08:00
parent 0d33dc3eb9
commit 4b1b494164
10 changed files with 171 additions and 341 deletions

View file

@ -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)
}

View file

@ -1,109 +1,26 @@
package rules package process
import ( import (
"bytes" "bytes"
"encoding/binary" "encoding/binary"
"errors"
"fmt"
"net" "net"
"path/filepath" "path/filepath"
"strconv"
"strings"
"syscall" "syscall"
"unsafe" "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 ( const (
procpidpathinfo = 0xb procpidpathinfo = 0xb
procpidpathinfosize = 1024 procpidpathinfosize = 1024
proccallnumpidinfo = 0x2 proccallnumpidinfo = 0x2
) )
func getExecPathFromPID(pid uint32) (string, error) { func findProcessName(network string, ip net.IP, port int) (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
}
var spath string var spath string
switch metadata.NetWork { switch network {
case C.TCP: case TCP:
spath = "net.inet.tcp.pcblist_n" spath = "net.inet.tcp.pcblist_n"
case C.UDP: case UDP:
spath = "net.inet.udp.pcblist_n" spath = "net.inet.udp.pcblist_n"
default: default:
return "", ErrInvalidNetwork return "", ErrInvalidNetwork
@ -123,7 +40,7 @@ func getExecPathFromAddress(metadata *C.Metadata) (string, error) {
// rup8(sizeof(xinpcb_n)) + rup8(sizeof(xsocket_n)) + // rup8(sizeof(xinpcb_n)) + rup8(sizeof(xsocket_n)) +
// 2 * rup8(sizeof(xsockbuf_n)) + rup8(sizeof(xsockstat_n)) // 2 * rup8(sizeof(xsockbuf_n)) + rup8(sizeof(xsockstat_n))
itemSize := 384 itemSize := 384
if metadata.NetWork == C.TCP { if network == TCP {
// rup8(sizeof(xtcpcb_n)) // rup8(sizeof(xtcpcb_n))
itemSize += 208 itemSize += 208
} }
@ -161,7 +78,28 @@ func getExecPathFromAddress(metadata *C.Metadata) (string, error) {
return getExecPathFromPID(pid) 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 { func readNativeUint32(b []byte) uint32 {

View file

@ -1,8 +1,7 @@
package rules package process
import ( import (
"encoding/binary" "encoding/binary"
"errors"
"fmt" "fmt"
"net" "net"
"path/filepath" "path/filepath"
@ -12,78 +11,48 @@ import (
"syscall" "syscall"
"unsafe" "unsafe"
"github.com/Dreamacro/clash/common/cache"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/log" "github.com/Dreamacro/clash/log"
) )
// store process name for when dealing with multiple PROCESS-NAME rules // store process name for when dealing with multiple PROCESS-NAME rules
var ( 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 defaultSearcher *searcher
once sync.Once once sync.Once
) )
type Process struct { func findProcessName(network string, ip net.IP, srcPort int) (string, error) {
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) {
once.Do(func() { once.Do(func() {
err := initSearcher() if err := initSearcher(); err != nil {
if err != nil {
log.Errorln("Initialize PROCESS-NAME failed: %s", err.Error()) log.Errorln("Initialize PROCESS-NAME failed: %s", err.Error())
log.Warnln("All PROCESS-NAME rules will be skipped") log.Warnln("All PROCESS-NAME rules will be skipped")
return return
} }
matchMeta = match
}) })
return &Process{
adapter: adapter, var spath string
process: process, isTCP := network == TCP
}, nil 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) { func getExecPathFromPID(pid uint32) (string, error) {
@ -107,41 +76,6 @@ func getExecPathFromPID(pid uint32) (string, error) {
return filepath.Base(string(buf[:size-1])), nil 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 { func readNativeUint32(b []byte) uint32 {
return *(*uint32)(unsafe.Pointer(&b[0])) 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]) socket := binary.BigEndian.Uint64(buf[inp+s.socket : inp+s.socket+8])
return s.searchSocketPid(socket) return s.searchSocketPid(socket)
} }
return 0, errNotFound return 0, ErrNotFound
} }
func (s *searcher) searchSocketPid(socket uint64) (uint32, error) { func (s *searcher) searchSocketPid(socket uint64) (uint32, error) {
@ -235,7 +169,7 @@ func (s *searcher) searchSocketPid(socket uint64) (uint32, error) {
return pid, nil return pid, nil
} }
} }
return 0, errNotFound return 0, ErrNotFound
} }
func newSearcher(major int) *searcher { func newSearcher(major int) *searcher {

View file

@ -1,4 +1,4 @@
package rules package process
import ( import (
"bytes" "bytes"
@ -9,15 +9,10 @@ import (
"net" "net"
"path" "path"
"path/filepath" "path/filepath"
"strconv"
"strings"
"syscall" "syscall"
"unsafe" "unsafe"
"github.com/Dreamacro/clash/common/cache"
"github.com/Dreamacro/clash/common/pool" "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 // 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) type ProcessNameResolver func(inode, uid int) (name string, err error)
// export for android // export for android
@ -39,51 +34,6 @@ var (
DefaultProcessNameResolver ProcessNameResolver = resolveProcessNameByProcSearch 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 ( const (
sizeOfSocketDiagRequest = syscall.SizeofNlMsghdr + 8 + 48 sizeOfSocketDiagRequest = syscall.SizeofNlMsghdr + 8 + 48
socketDiagByFamily = 20 socketDiagByFamily = 20
@ -92,10 +42,8 @@ const (
var nativeEndian binary.ByteOrder = binary.LittleEndian var nativeEndian binary.ByteOrder = binary.LittleEndian
var processCache = cache.NewLRUCache(cache.WithAge(2), cache.WithSize(64)) func findProcessName(network string, ip net.IP, srcPort int) (string, error) {
inode, uid, err := DefaultSocketResolver(network, ip, srcPort)
func resolveProcessName(metadata *C.Metadata) (string, error) {
inode, uid, err := DefaultSocketResolver(metadata)
if err != nil { if err != nil {
return "", err return "", err
} }
@ -103,31 +51,26 @@ func resolveProcessName(metadata *C.Metadata) (string, error) {
return DefaultProcessNameResolver(inode, uid) 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 family byte
var protocol byte var protocol byte
switch metadata.NetWork { switch network {
case C.TCP: case TCP:
protocol = syscall.IPPROTO_TCP protocol = syscall.IPPROTO_TCP
case C.UDP: case UDP:
protocol = syscall.IPPROTO_UDP protocol = syscall.IPPROTO_UDP
default: default:
return 0, 0, ErrInvalidNetwork return 0, 0, ErrInvalidNetwork
} }
if metadata.SrcIP.To4() != nil { if ip.To4() != nil {
family = syscall.AF_INET family = syscall.AF_INET
} else { } else {
family = syscall.AF_INET6 family = syscall.AF_INET6
} }
srcPort, err := strconv.Atoi(metadata.SrcPort) req := packSocketDiagRequest(family, protocol, ip, uint16(srcPort))
if err != nil {
return 0, 0, err
}
req := packSocketDiagRequest(family, protocol, metadata.SrcIP, uint16(srcPort))
socket, err := syscall.Socket(syscall.AF_NETLINK, syscall.SOCK_DGRAM, syscall.NETLINK_INET_DIAG) socket, err := syscall.Socket(syscall.AF_NETLINK, syscall.SOCK_DGRAM, syscall.NETLINK_INET_DIAG)
if err != nil { if err != nil {

View file

@ -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
}

View file

@ -1,18 +1,13 @@
package rules package process
import ( import (
"errors"
"fmt" "fmt"
"net" "net"
"path/filepath" "path/filepath"
"strconv"
"strings"
"sync" "sync"
"syscall" "syscall"
"unsafe" "unsafe"
"github.com/Dreamacro/clash/common/cache"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/log" "github.com/Dreamacro/clash/log"
"golang.org/x/sys/windows" "golang.org/x/sys/windows"
@ -27,10 +22,6 @@ const (
) )
var ( 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 getExTcpTable uintptr
getExUdpTable uintptr getExUdpTable uintptr
queryProcName uintptr queryProcName uintptr
@ -67,47 +58,7 @@ func initWin32API() error {
return nil return nil
} }
type Process struct { func findProcessName(network string, ip net.IP, srcPort int) (string, error) {
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) {
once.Do(func() { once.Do(func() {
err := initWin32API() err := initWin32API()
if err != nil { if err != nil {
@ -115,16 +66,7 @@ func NewProcess(process string, adapter string) (*Process, error) {
log.Warnln("All PROCESS-NAMES rules will be skiped") log.Warnln("All PROCESS-NAMES rules will be skiped")
return return
} }
matchMeta = match
}) })
return &Process{
adapter: adapter,
process: process,
}, nil
}
func resolveProcessName(metadata *C.Metadata) (string, error) {
ip := metadata.SrcIP
family := windows.AF_INET family := windows.AF_INET
if ip.To4() == nil { if ip.To4() == nil {
family = windows.AF_INET6 family = windows.AF_INET6
@ -132,28 +74,23 @@ func resolveProcessName(metadata *C.Metadata) (string, error) {
var class int var class int
var fn uintptr var fn uintptr
switch metadata.NetWork { switch network {
case C.TCP: case TCP:
fn = getExTcpTable fn = getExTcpTable
class = tcpTablePidConn class = tcpTablePidConn
case C.UDP: case UDP:
fn = getExUdpTable fn = getExUdpTable
class = udpTablePid class = udpTablePid
default: default:
return "", ErrInvalidNetwork return "", ErrInvalidNetwork
} }
srcPort, err := strconv.Atoi(metadata.SrcPort)
if err != nil {
return "", err
}
buf, err := getTransportTable(fn, family, class) buf, err := getTransportTable(fn, family, class)
if err != nil { if err != nil {
return "", err 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)) pid, err := s.Search(buf, ip, uint16(srcPort))
if err != nil { 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]) pid := readNativeUint32(row[s.pid : s.pid+4])
return pid, nil return pid, nil
} }
return 0, errNotFound return 0, ErrNotFound
} }
func newSearcher(isV4, isTCP bool) *searcher { func newSearcher(isV4, isTCP bool) *searcher {

View file

@ -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) parsed, parseErr := R.ParseRule(rule[0], payload, target, params)
if parseErr != nil { 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()) return nil, fmt.Errorf("rules[%d] [%s] error: %s", idx, line, parseErr.Error())
} }

View file

@ -6,8 +6,6 @@ import (
var ( var (
errPayload = errors.New("payload error") errPayload = errors.New("payload error")
ErrPlatformNotSupport = errors.New("not support on this platform")
ErrInvalidNetwork = errors.New("invalid network")
noResolve = "no-resolve" noResolve = "no-resolve"
) )

65
rules/process.go Normal file
View file

@ -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
}

View file

@ -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
}