From cc1c1340a3653b200d7495f9b46cb18f922276a8 Mon Sep 17 00:00:00 2001 From: adlyq Date: Thu, 19 May 2022 20:43:41 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=AE=89=E5=8D=93=E6=81=A2=E5=A4=8D?= =?UTF-8?q?=E8=BF=9B=E7=A8=8B=E8=A7=84=E5=88=99=EF=BC=8C=E5=8F=AF=E9=80=9A?= =?UTF-8?q?=E8=BF=87enable-process=E5=BC=80=E5=85=B3=EF=BC=8C=E9=BB=98?= =?UTF-8?q?=E8=AE=A4true?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- component/process/process.go | 14 +- component/process/process_android.go | 228 +++++++++++++++++++++ component/process/process_darwin.go | 11 +- component/process/process_freebsd_amd64.go | 13 +- component/process/process_linux.go | 9 +- component/process/process_other.go | 4 +- component/process/process_windows.go | 11 +- config/config.go | 4 + hub/executor/executor.go | 2 + rule/parser.go | 8 +- tunnel/tunnel.go | 5 +- 11 files changed, 280 insertions(+), 29 deletions(-) create mode 100644 component/process/process_android.go diff --git a/component/process/process.go b/component/process/process.go index 3ecc4b1e2a..252541cbab 100644 --- a/component/process/process.go +++ b/component/process/process.go @@ -6,13 +6,14 @@ import ( C "github.com/Dreamacro/clash/constant" "net" "net/netip" - "runtime" ) var ( ErrInvalidNetwork = errors.New("invalid network") ErrPlatformNotSupport = errors.New("not support on this platform") ErrNotFound = errors.New("process not found") + + enableFindProcess = true ) const ( @@ -20,7 +21,11 @@ const ( UDP = "udp" ) -func FindProcessName(network string, srcIP netip.Addr, srcPort int) (string, error) { +func EnableFindProcess(e bool) { + enableFindProcess = e +} + +func FindProcessName(network string, srcIP netip.Addr, srcPort int) (int32, string, error) { return findProcessName(network, srcIP, srcPort) } @@ -33,10 +38,7 @@ func FindUid(network string, srcIP netip.Addr, srcPort int) (int32, error) { } func ShouldFindProcess(metadata *C.Metadata) bool { - if runtime.GOOS == "android" { - return false - } - if metadata.Process != "" || metadata.ProcessPath != "" { + if !enableFindProcess || metadata.Process != "" || metadata.ProcessPath != "" { return false } for _, ip := range localIPs { diff --git a/component/process/process_android.go b/component/process/process_android.go new file mode 100644 index 0000000000..6013ecd343 --- /dev/null +++ b/component/process/process_android.go @@ -0,0 +1,228 @@ +package process + +import ( + "bytes" + "encoding/binary" + "fmt" + "net" + "net/netip" + "os" + "path" + "path/filepath" + "strings" + "syscall" + "unicode" + "unsafe" + + "github.com/Dreamacro/clash/common/pool" +) + +// from https://github.com/vishvananda/netlink/blob/bca67dfc8220b44ef582c9da4e9172bf1c9ec973/nl/nl_linux.go#L52-L62 +var nativeEndian = func() binary.ByteOrder { + var x uint32 = 0x01020304 + if *(*byte)(unsafe.Pointer(&x)) == 0x01 { + return binary.BigEndian + } + + return binary.LittleEndian +}() + +const ( + sizeOfSocketDiagRequest = syscall.SizeofNlMsghdr + 8 + 48 + socketDiagByFamily = 20 + pathProc = "/proc" +) + +func findProcessName(network string, ip netip.Addr, srcPort int) (int32, string, error) { + inode, uid, err := resolveSocketByNetlink(network, ip, srcPort) + if err != nil { + return -1, "", err + } + + pp, err := resolveProcessNameByProcSearch(inode, uid) + return uid, pp, err +} + +func resolveSocketByNetlink(network string, ip netip.Addr, srcPort int) (int32, int32, error) { + var family byte + var protocol byte + + switch network { + case TCP: + protocol = syscall.IPPROTO_TCP + case UDP: + protocol = syscall.IPPROTO_UDP + default: + return 0, 0, ErrInvalidNetwork + } + + if ip.Is4() { + family = syscall.AF_INET + } else { + family = syscall.AF_INET6 + } + + req := packSocketDiagRequest(family, protocol, ip, uint16(srcPort)) + + socket, err := syscall.Socket(syscall.AF_NETLINK, syscall.SOCK_DGRAM, syscall.NETLINK_INET_DIAG) + if err != nil { + return 0, 0, fmt.Errorf("dial netlink: %w", err) + } + defer syscall.Close(socket) + + _ = 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, + Pad: 0, + Pid: 0, + Groups: 0, + }); err != nil { + return 0, 0, err + } + + if _, err := syscall.Write(socket, req); err != nil { + return 0, 0, fmt.Errorf("write request: %w", err) + } + + rb := pool.Get(pool.RelayBufferSize) + defer pool.Put(rb) + + n, err := syscall.Read(socket, rb) + if err != nil { + return 0, 0, fmt.Errorf("read response: %w", err) + } + + messages, err := syscall.ParseNetlinkMessage(rb[:n]) + if err != nil { + return 0, 0, fmt.Errorf("parse netlink message: %w", err) + } else if len(messages) == 0 { + return 0, 0, fmt.Errorf("unexcepted netlink response") + } + + message := messages[0] + if message.Header.Type&syscall.NLMSG_ERROR != 0 { + 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 uid, inode, nil +} + +func packSocketDiagRequest(family, protocol byte, source netip.Addr, sourcePort uint16) []byte { + s := make([]byte, 16) + + copy(s, source.AsSlice()) + + buf := make([]byte, sizeOfSocketDiagRequest) + + nativeEndian.PutUint32(buf[0:4], sizeOfSocketDiagRequest) + nativeEndian.PutUint16(buf[4:6], socketDiagByFamily) + nativeEndian.PutUint16(buf[6:8], syscall.NLM_F_REQUEST|syscall.NLM_F_DUMP) + nativeEndian.PutUint32(buf[8:12], 0) + nativeEndian.PutUint32(buf[12:16], 0) + + buf[16] = family + buf[17] = protocol + buf[18] = 0 + buf[19] = 0 + nativeEndian.PutUint32(buf[20:24], 0xFFFFFFFF) + + binary.BigEndian.PutUint16(buf[24:26], sourcePort) + binary.BigEndian.PutUint16(buf[26:28], 0) + + copy(buf[28:44], s) + copy(buf[44:60], net.IPv6zero) + + nativeEndian.PutUint32(buf[60:64], 0) + nativeEndian.PutUint64(buf[64:72], 0xFFFFFFFFFFFFFFFF) + + return buf +} + +func unpackSocketDiagResponse(msg *syscall.NetlinkMessage) (inode, uid int32) { + if len(msg.Data) < 72 { + return 0, 0 + } + + data := msg.Data + + uid = int32(nativeEndian.Uint32(data[64:68])) + inode = int32(nativeEndian.Uint32(data[68:72])) + + return +} + +func resolveProcessNameByProcSearch(inode, uid int32) (string, error) { + files, err := os.ReadDir(pathProc) + if err != nil { + return "", err + } + + buffer := make([]byte, syscall.PathMax) + socket := []byte(fmt.Sprintf("socket:[%d]", inode)) + + for _, f := range files { + if !f.IsDir() || !isPid(f.Name()) { + continue + } + + info, err := f.Info() + if err != nil { + return "", err + } + if info.Sys().(*syscall.Stat_t).Uid != uint32(uid) { + continue + } + + processPath := path.Join(pathProc, f.Name()) + fdPath := path.Join(processPath, "fd") + + fds, err := os.ReadDir(fdPath) + if err != nil { + continue + } + + for _, fd := range fds { + n, err := syscall.Readlink(path.Join(fdPath, fd.Name()), buffer) + if err != nil { + continue + } + + if bytes.Equal(buffer[:n], socket) { + cmdline, err := os.ReadFile(path.Join(processPath, "cmdline")) + if err != nil { + return "", err + } + + return splitCmdline(cmdline), nil + } + } + } + + return "", fmt.Errorf("process of uid(%d),inode(%d) not found", uid, inode) +} + +func splitCmdline(cmdline []byte) string { + cmdline = bytes.Trim(cmdline, " ") + + idx := bytes.IndexFunc(cmdline, func(r rune) bool { + return unicode.IsControl(r) || unicode.IsSpace(r) + }) + + if idx == -1 { + return filepath.Base(string(cmdline)) + } + return filepath.Base(string(cmdline[:idx])) +} + +func isPid(s string) bool { + return strings.IndexFunc(s, func(r rune) bool { + return !unicode.IsDigit(r) + }) == -1 +} diff --git a/component/process/process_darwin.go b/component/process/process_darwin.go index 77d93e64f9..ef8f6e0e2e 100644 --- a/component/process/process_darwin.go +++ b/component/process/process_darwin.go @@ -21,7 +21,7 @@ func resolveSocketByNetlink(network string, ip netip.Addr, srcPort int) (int32, return 0, 0, ErrPlatformNotSupport } -func findProcessName(network string, ip netip.Addr, port int) (string, error) { +func findProcessName(network string, ip netip.Addr, port int) (int32, string, error) { var spath string switch network { case TCP: @@ -29,14 +29,14 @@ func findProcessName(network string, ip netip.Addr, port int) (string, error) { case UDP: spath = "net.inet.udp.pcblist_n" default: - return "", ErrInvalidNetwork + return -1, "", ErrInvalidNetwork } isIPv4 := ip.Is4() value, err := syscall.Sysctl(spath) if err != nil { - return "", err + return -1, "", err } buf := []byte(value) @@ -81,10 +81,11 @@ func findProcessName(network string, ip netip.Addr, port int) (string, error) { // xsocket_n.so_last_pid pid := readNativeUint32(buf[so+68 : so+72]) - return getExecPathFromPID(pid) + pp, err := getExecPathFromPID(pid) + return -1, pp, err } - return "", ErrNotFound + return -1, "", ErrNotFound } func getExecPathFromPID(pid uint32) (string, error) { diff --git a/component/process/process_freebsd_amd64.go b/component/process/process_freebsd_amd64.go index 28b3608451..6b5f51f74b 100644 --- a/component/process/process_freebsd_amd64.go +++ b/component/process/process_freebsd_amd64.go @@ -25,7 +25,7 @@ func resolveSocketByNetlink(network string, ip netip.Addr, srcPort int) (int32, return 0, 0, ErrPlatformNotSupport } -func findProcessName(network string, ip netip.Addr, srcPort int) (string, error) { +func findProcessName(network string, ip netip.Addr, srcPort int) (int32, string, error) { once.Do(func() { if err := initSearcher(); err != nil { log.Errorln("Initialize PROCESS-NAME failed: %s", err.Error()) @@ -35,7 +35,7 @@ func findProcessName(network string, ip netip.Addr, srcPort int) (string, error) }) if defaultSearcher == nil { - return "", ErrPlatformNotSupport + return -1, "", ErrPlatformNotSupport } var spath string @@ -46,21 +46,22 @@ func findProcessName(network string, ip netip.Addr, srcPort int) (string, error) case UDP: spath = "net.inet.udp.pcblist" default: - return "", ErrInvalidNetwork + return -1, "", ErrInvalidNetwork } value, err := syscall.Sysctl(spath) if err != nil { - return "", err + return -1, "", err } buf := []byte(value) pid, err := defaultSearcher.Search(buf, ip, uint16(srcPort), isTCP) if err != nil { - return "", err + return -1, "", err } - return getExecPathFromPID(pid) + pp, err := getExecPathFromPID(pid) + return -1, pp, err } func getExecPathFromPID(pid uint32) (string, error) { diff --git a/component/process/process_linux.go b/component/process/process_linux.go index 5a98008e11..4f937ba0d7 100644 --- a/component/process/process_linux.go +++ b/component/process/process_linux.go @@ -1,3 +1,5 @@ +//go:build !android + package process import ( @@ -32,12 +34,13 @@ const ( pathProc = "/proc" ) -func findProcessName(network string, ip netip.Addr, srcPort int) (string, error) { +func findProcessName(network string, ip netip.Addr, srcPort int) (int32, string, error) { inode, uid, err := resolveSocketByNetlink(network, ip, srcPort) if err != nil { - return "", err + return -1, "", err } - return resolveProcessNameByProcSearch(inode, uid) + pp, err := resolveProcessNameByProcSearch(inode, uid) + return uid, pp, err } func resolveSocketByNetlink(network string, ip netip.Addr, srcPort int) (int32, int32, error) { diff --git a/component/process/process_other.go b/component/process/process_other.go index 9c5cb47c43..77dad25084 100644 --- a/component/process/process_other.go +++ b/component/process/process_other.go @@ -4,8 +4,8 @@ package process import "net/netip" -func findProcessName(network string, ip netip.Addr, srcPort int) (string, error) { - return "", ErrPlatformNotSupport +func findProcessName(network string, ip netip.Addr, srcPort int) (int32, string, error) { + return -1, "", ErrPlatformNotSupport } func resolveSocketByNetlink(network string, ip netip.Addr, srcPort int) (int32, int32, error) { diff --git a/component/process/process_windows.go b/component/process/process_windows.go index 1915adeee8..03935c37df 100644 --- a/component/process/process_windows.go +++ b/component/process/process_windows.go @@ -62,7 +62,7 @@ func initWin32API() error { return nil } -func findProcessName(network string, ip netip.Addr, srcPort int) (string, error) { +func findProcessName(network string, ip netip.Addr, srcPort int) (int32, string, error) { once.Do(func() { err := initWin32API() if err != nil { @@ -86,21 +86,22 @@ func findProcessName(network string, ip netip.Addr, srcPort int) (string, error) fn = getExUDPTable class = udpTablePid default: - return "", ErrInvalidNetwork + return -1, "", ErrInvalidNetwork } buf, err := getTransportTable(fn, family, class) if err != nil { - return "", err + return -1, "", err } s := newSearcher(family == windows.AF_INET, network == TCP) pid, err := s.Search(buf, ip, uint16(srcPort)) if err != nil { - return "", err + return -1, "", err } - return getExecPathFromPID(pid) + pp, err := getExecPathFromPID(pid) + return -1, pp, err } type searcher struct { diff --git a/config/config.go b/config/config.go index c89c63ca39..5142210b8e 100644 --- a/config/config.go +++ b/config/config.go @@ -52,6 +52,7 @@ type General struct { GeodataMode bool `json:"geodata-mode"` GeodataLoader string `json:"geodata-loader"` TCPConcurrent bool `json:"tcp-concurrent"` + EnableProcess bool `json:"enable-process"` Tun Tun `json:"tun"` } @@ -206,6 +207,7 @@ type RawConfig struct { GeodataMode bool `yaml:"geodata-mode"` GeodataLoader string `yaml:"geodata-loader"` TCPConcurrent bool `yaml:"tcp-concurrent" json:"tcp-concurrent"` + EnableProcess bool `yaml:"enable-process" json:"enable-process"` Sniffer RawSniffer `yaml:"sniffer"` ProxyProvider map[string]map[string]any `yaml:"proxy-providers"` @@ -255,6 +257,7 @@ func UnmarshalRawConfig(buf []byte) (*RawConfig, error) { Proxy: []map[string]any{}, ProxyGroup: []map[string]any{}, TCPConcurrent: false, + EnableProcess: true, Tun: RawTun{ Enable: false, Device: "", @@ -410,6 +413,7 @@ func parseGeneral(cfg *RawConfig) (*General, error) { GeodataMode: cfg.GeodataMode, GeodataLoader: cfg.GeodataLoader, TCPConcurrent: cfg.TCPConcurrent, + EnableProcess: cfg.EnableProcess, }, nil } diff --git a/hub/executor/executor.go b/hub/executor/executor.go index 17d3f8bc50..c05479edb0 100644 --- a/hub/executor/executor.go +++ b/hub/executor/executor.go @@ -2,6 +2,7 @@ package executor import ( "fmt" + "github.com/Dreamacro/clash/component/process" "github.com/Dreamacro/clash/listener/inner" "net/netip" "os" @@ -270,6 +271,7 @@ func updateSniffer(sniffer *config.Sniffer) { func updateGeneral(general *config.General, force bool) { log.SetLevel(general.LogLevel) + process.EnableFindProcess(general.EnableProcess) tunnel.SetMode(general.Mode) dialer.DisableIPv6 = !general.IPv6 if !dialer.DisableIPv6 { diff --git a/rule/parser.go b/rule/parser.go index e1d27371bd..f59f92a293 100644 --- a/rule/parser.go +++ b/rule/parser.go @@ -3,9 +3,11 @@ package rules import ( "fmt" C "github.com/Dreamacro/clash/constant" + "github.com/Dreamacro/clash/log" RC "github.com/Dreamacro/clash/rule/common" "github.com/Dreamacro/clash/rule/logic" RP "github.com/Dreamacro/clash/rule/provider" + "runtime" ) func ParseRule(tp, payload, target string, params []string) (C.Rule, error) { @@ -42,7 +44,11 @@ func ParseRule(tp, payload, target string, params []string) (C.Rule, error) { case "NETWORK": parsed, parseErr = RC.NewNetworkType(payload, target) case "UID": - parsed, parseErr = RC.NewUid(payload, target) + if runtime.GOOS == "linux" || runtime.GOOS == "android" { + parsed, parseErr = RC.NewUid(payload, target) + } else { + log.Warnln("uid rule not support this platform") + } case "AND": parsed, parseErr = logic.NewAND(payload, target) case "OR": diff --git a/tunnel/tunnel.go b/tunnel/tunnel.go index 4cfc399b11..ae4c1fca94 100644 --- a/tunnel/tunnel.go +++ b/tunnel/tunnel.go @@ -170,7 +170,7 @@ func preHandleMetadata(metadata *C.Metadata) error { // pre resolve process name srcPort, err := strconv.Atoi(metadata.SrcPort) if err == nil && P.ShouldFindProcess(metadata) { - path, err := P.FindProcessName(metadata.NetWork.String(), metadata.SrcIP, srcPort) + uid, path, err := P.FindProcessName(metadata.NetWork.String(), metadata.SrcIP, srcPort) if err != nil { if failTotal < 20 { log.Debugln("[Process] find process %s: %v", metadata.String(), err) @@ -179,6 +179,9 @@ func preHandleMetadata(metadata *C.Metadata) error { } else { metadata.Process = filepath.Base(path) metadata.ProcessPath = path + if uid != -1 { + metadata.Uid = &uid + } if procesCache != metadata.Process { log.Debugln("[Process] %s from process %s", metadata.String(), path) }