github.com/kelleygo/clashcore@v1.0.2/component/process/process_linux.go (about)

     1  package process
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/binary"
     6  	"fmt"
     7  	"net/netip"
     8  	"os"
     9  	"path"
    10  	"path/filepath"
    11  	"runtime"
    12  	"strings"
    13  	"syscall"
    14  	"unicode"
    15  	"unsafe"
    16  
    17  	"github.com/mdlayher/netlink"
    18  	"golang.org/x/sys/unix"
    19  )
    20  
    21  const (
    22  	SOCK_DIAG_BY_FAMILY  = 20
    23  	inetDiagRequestSize  = int(unsafe.Sizeof(inetDiagRequest{}))
    24  	inetDiagResponseSize = int(unsafe.Sizeof(inetDiagResponse{}))
    25  )
    26  
    27  type inetDiagRequest struct {
    28  	Family   byte
    29  	Protocol byte
    30  	Ext      byte
    31  	Pad      byte
    32  	States   uint32
    33  
    34  	SrcPort [2]byte
    35  	DstPort [2]byte
    36  	Src     [16]byte
    37  	Dst     [16]byte
    38  	If      uint32
    39  	Cookie  [2]uint32
    40  }
    41  
    42  type inetDiagResponse struct {
    43  	Family  byte
    44  	State   byte
    45  	Timer   byte
    46  	ReTrans byte
    47  
    48  	SrcPort [2]byte
    49  	DstPort [2]byte
    50  	Src     [16]byte
    51  	Dst     [16]byte
    52  	If      uint32
    53  	Cookie  [2]uint32
    54  
    55  	Expires uint32
    56  	RQueue  uint32
    57  	WQueue  uint32
    58  	UID     uint32
    59  	INode   uint32
    60  }
    61  
    62  func findProcessName(network string, ip netip.Addr, srcPort int) (uint32, string, error) {
    63  	uid, inode, err := resolveSocketByNetlink(network, ip, srcPort)
    64  	if err != nil {
    65  		return 0, "", err
    66  	}
    67  
    68  	pp, err := resolveProcessNameByProcSearch(inode, uid)
    69  	return uid, pp, err
    70  }
    71  
    72  func resolveSocketByNetlink(network string, ip netip.Addr, srcPort int) (uint32, uint32, error) {
    73  	request := &inetDiagRequest{
    74  		States: 0xffffffff,
    75  		Cookie: [2]uint32{0xffffffff, 0xffffffff},
    76  	}
    77  
    78  	if ip.Is4() {
    79  		request.Family = unix.AF_INET
    80  	} else {
    81  		request.Family = unix.AF_INET6
    82  	}
    83  
    84  	if strings.HasPrefix(network, "tcp") {
    85  		request.Protocol = unix.IPPROTO_TCP
    86  	} else if strings.HasPrefix(network, "udp") {
    87  		request.Protocol = unix.IPPROTO_UDP
    88  	} else {
    89  		return 0, 0, ErrInvalidNetwork
    90  	}
    91  
    92  	copy(request.Src[:], ip.AsSlice())
    93  
    94  	binary.BigEndian.PutUint16(request.SrcPort[:], uint16(srcPort))
    95  
    96  	conn, err := netlink.Dial(unix.NETLINK_INET_DIAG, nil)
    97  	if err != nil {
    98  		return 0, 0, err
    99  	}
   100  	defer conn.Close()
   101  
   102  	message := netlink.Message{
   103  		Header: netlink.Header{
   104  			Type:  SOCK_DIAG_BY_FAMILY,
   105  			Flags: netlink.Request | netlink.Dump,
   106  		},
   107  		Data: (*(*[inetDiagRequestSize]byte)(unsafe.Pointer(request)))[:],
   108  	}
   109  
   110  	messages, err := conn.Execute(message)
   111  	if err != nil {
   112  		return 0, 0, err
   113  	}
   114  
   115  	for _, msg := range messages {
   116  		if len(msg.Data) < inetDiagResponseSize {
   117  			continue
   118  		}
   119  
   120  		response := (*inetDiagResponse)(unsafe.Pointer(&msg.Data[0]))
   121  
   122  		return response.UID, response.INode, nil
   123  	}
   124  
   125  	return 0, 0, ErrNotFound
   126  }
   127  
   128  func resolveProcessNameByProcSearch(inode, uid uint32) (string, error) {
   129  	files, err := os.ReadDir("/proc")
   130  	if err != nil {
   131  		return "", err
   132  	}
   133  
   134  	buffer := make([]byte, unix.PathMax)
   135  	socket := fmt.Appendf(nil, "socket:[%d]", inode)
   136  
   137  	for _, f := range files {
   138  		if !f.IsDir() || !isPid(f.Name()) {
   139  			continue
   140  		}
   141  
   142  		info, err := f.Info()
   143  		if err != nil {
   144  			return "", err
   145  		}
   146  		if info.Sys().(*syscall.Stat_t).Uid != uid {
   147  			continue
   148  		}
   149  
   150  		processPath := filepath.Join("/proc", f.Name())
   151  		fdPath := filepath.Join(processPath, "fd")
   152  
   153  		fds, err := os.ReadDir(fdPath)
   154  		if err != nil {
   155  			continue
   156  		}
   157  
   158  		for _, fd := range fds {
   159  			n, err := unix.Readlink(filepath.Join(fdPath, fd.Name()), buffer)
   160  			if err != nil {
   161  				continue
   162  			}
   163  			if runtime.GOOS == "android" {
   164  				if bytes.Equal(buffer[:n], socket) {
   165  					cmdline, err := os.ReadFile(path.Join(processPath, "cmdline"))
   166  					if err != nil {
   167  						return "", err
   168  					}
   169  
   170  					return splitCmdline(cmdline), nil
   171  				}
   172  			} else {
   173  				if bytes.Equal(buffer[:n], socket) {
   174  					return os.Readlink(filepath.Join(processPath, "exe"))
   175  				}
   176  			}
   177  
   178  		}
   179  	}
   180  
   181  	return "", fmt.Errorf("process of uid(%d),inode(%d) not found", uid, inode)
   182  }
   183  
   184  func splitCmdline(cmdline []byte) string {
   185  	cmdline = bytes.Trim(cmdline, " ")
   186  
   187  	idx := bytes.IndexFunc(cmdline, func(r rune) bool {
   188  		return unicode.IsControl(r) || unicode.IsSpace(r) || r == ':'
   189  	})
   190  
   191  	if idx == -1 {
   192  		return filepath.Base(string(cmdline))
   193  	}
   194  	return filepath.Base(string(cmdline[:idx]))
   195  }
   196  
   197  func isPid(s string) bool {
   198  	return strings.IndexFunc(s, func(r rune) bool {
   199  		return !unicode.IsDigit(r)
   200  	}) == -1
   201  }