github.com/chwjbn/xclash@v0.2.0/component/process/process_linux.go (about)

     1  package process
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/binary"
     6  	"fmt"
     7  	"io"
     8  	"net"
     9  	"os"
    10  	"path"
    11  	"path/filepath"
    12  	"syscall"
    13  	"unsafe"
    14  
    15  	"github.com/chwjbn/xclash/common/pool"
    16  )
    17  
    18  // from https://github.com/vishvananda/netlink/blob/bca67dfc8220b44ef582c9da4e9172bf1c9ec973/nl/nl_linux.go#L52-L62
    19  var nativeEndian = func() binary.ByteOrder {
    20  	var x uint32 = 0x01020304
    21  	if *(*byte)(unsafe.Pointer(&x)) == 0x01 {
    22  		return binary.BigEndian
    23  	}
    24  
    25  	return binary.LittleEndian
    26  }()
    27  
    28  type (
    29  	SocketResolver      func(network string, ip net.IP, srcPort int) (inode, uid int, err error)
    30  	ProcessNameResolver func(inode, uid int) (name string, err error)
    31  )
    32  
    33  // export for android
    34  var (
    35  	DefaultSocketResolver      SocketResolver      = resolveSocketByNetlink
    36  	DefaultProcessNameResolver ProcessNameResolver = resolveProcessNameByProcSearch
    37  )
    38  
    39  const (
    40  	sizeOfSocketDiagRequest = syscall.SizeofNlMsghdr + 8 + 48
    41  	socketDiagByFamily      = 20
    42  	pathProc                = "/proc"
    43  )
    44  
    45  func findProcessName(network string, ip net.IP, srcPort int) (string, error) {
    46  	inode, uid, err := DefaultSocketResolver(network, ip, srcPort)
    47  	if err != nil {
    48  		return "", err
    49  	}
    50  
    51  	return DefaultProcessNameResolver(inode, uid)
    52  }
    53  
    54  func resolveSocketByNetlink(network string, ip net.IP, srcPort int) (int, int, error) {
    55  	var family byte
    56  	var protocol byte
    57  
    58  	switch network {
    59  	case TCP:
    60  		protocol = syscall.IPPROTO_TCP
    61  	case UDP:
    62  		protocol = syscall.IPPROTO_UDP
    63  	default:
    64  		return 0, 0, ErrInvalidNetwork
    65  	}
    66  
    67  	if ip.To4() != nil {
    68  		family = syscall.AF_INET
    69  	} else {
    70  		family = syscall.AF_INET6
    71  	}
    72  
    73  	req := packSocketDiagRequest(family, protocol, ip, uint16(srcPort))
    74  
    75  	socket, err := syscall.Socket(syscall.AF_NETLINK, syscall.SOCK_DGRAM, syscall.NETLINK_INET_DIAG)
    76  	if err != nil {
    77  		return 0, 0, err
    78  	}
    79  	defer syscall.Close(socket)
    80  
    81  	syscall.SetNonblock(socket, true)
    82  	syscall.SetsockoptTimeval(socket, syscall.SOL_SOCKET, syscall.SO_SNDTIMEO, &syscall.Timeval{Usec: 50})
    83  	syscall.SetsockoptTimeval(socket, syscall.SOL_SOCKET, syscall.SO_RCVTIMEO, &syscall.Timeval{Usec: 50})
    84  
    85  	if err := syscall.Connect(socket, &syscall.SockaddrNetlink{
    86  		Family: syscall.AF_NETLINK,
    87  		Pad:    0,
    88  		Pid:    0,
    89  		Groups: 0,
    90  	}); err != nil {
    91  		return 0, 0, err
    92  	}
    93  
    94  	if _, err := syscall.Write(socket, req); err != nil {
    95  		return 0, 0, err
    96  	}
    97  
    98  	rb := pool.Get(pool.RelayBufferSize)
    99  	defer pool.Put(rb)
   100  
   101  	n, err := syscall.Read(socket, rb)
   102  	if err != nil {
   103  		return 0, 0, err
   104  	}
   105  
   106  	messages, err := syscall.ParseNetlinkMessage(rb[:n])
   107  	if err != nil {
   108  		return 0, 0, err
   109  	} else if len(messages) == 0 {
   110  		return 0, 0, io.ErrUnexpectedEOF
   111  	}
   112  
   113  	message := messages[0]
   114  	if message.Header.Type&syscall.NLMSG_ERROR != 0 {
   115  		return 0, 0, syscall.ESRCH
   116  	}
   117  
   118  	uid, inode := unpackSocketDiagResponse(&messages[0])
   119  
   120  	return int(uid), int(inode), nil
   121  }
   122  
   123  func packSocketDiagRequest(family, protocol byte, source net.IP, sourcePort uint16) []byte {
   124  	s := make([]byte, 16)
   125  
   126  	if v4 := source.To4(); v4 != nil {
   127  		copy(s, v4)
   128  	} else {
   129  		copy(s, source)
   130  	}
   131  
   132  	buf := make([]byte, sizeOfSocketDiagRequest)
   133  
   134  	nativeEndian.PutUint32(buf[0:4], sizeOfSocketDiagRequest)
   135  	nativeEndian.PutUint16(buf[4:6], socketDiagByFamily)
   136  	nativeEndian.PutUint16(buf[6:8], syscall.NLM_F_REQUEST|syscall.NLM_F_DUMP)
   137  	nativeEndian.PutUint32(buf[8:12], 0)
   138  	nativeEndian.PutUint32(buf[12:16], 0)
   139  
   140  	buf[16] = family
   141  	buf[17] = protocol
   142  	buf[18] = 0
   143  	buf[19] = 0
   144  	nativeEndian.PutUint32(buf[20:24], 0xFFFFFFFF)
   145  
   146  	binary.BigEndian.PutUint16(buf[24:26], sourcePort)
   147  	binary.BigEndian.PutUint16(buf[26:28], 0)
   148  
   149  	copy(buf[28:44], s)
   150  	copy(buf[44:60], net.IPv6zero)
   151  
   152  	nativeEndian.PutUint32(buf[60:64], 0)
   153  	nativeEndian.PutUint64(buf[64:72], 0xFFFFFFFFFFFFFFFF)
   154  
   155  	return buf
   156  }
   157  
   158  func unpackSocketDiagResponse(msg *syscall.NetlinkMessage) (inode, uid uint32) {
   159  	if len(msg.Data) < 72 {
   160  		return 0, 0
   161  	}
   162  
   163  	data := msg.Data
   164  
   165  	uid = nativeEndian.Uint32(data[64:68])
   166  	inode = nativeEndian.Uint32(data[68:72])
   167  
   168  	return
   169  }
   170  
   171  func resolveProcessNameByProcSearch(inode, uid int) (string, error) {
   172  	files, err := os.ReadDir(pathProc)
   173  	if err != nil {
   174  		return "", err
   175  	}
   176  
   177  	buffer := make([]byte, syscall.PathMax)
   178  	socket := []byte(fmt.Sprintf("socket:[%d]", inode))
   179  
   180  	for _, f := range files {
   181  		if !f.IsDir() || !isPid(f.Name()) {
   182  			continue
   183  		}
   184  
   185  		info, err := f.Info()
   186  		if err != nil {
   187  			return "", err
   188  		}
   189  		if info.Sys().(*syscall.Stat_t).Uid != uint32(uid) {
   190  			continue
   191  		}
   192  
   193  		processPath := path.Join(pathProc, f.Name())
   194  		fdPath := path.Join(processPath, "fd")
   195  
   196  		fds, err := os.ReadDir(fdPath)
   197  		if err != nil {
   198  			continue
   199  		}
   200  
   201  		for _, fd := range fds {
   202  			n, err := syscall.Readlink(path.Join(fdPath, fd.Name()), buffer)
   203  			if err != nil {
   204  				continue
   205  			}
   206  
   207  			if bytes.Equal(buffer[:n], socket) {
   208  				cmdline, err := os.ReadFile(path.Join(processPath, "cmdline"))
   209  				if err != nil {
   210  					return "", err
   211  				}
   212  
   213  				return splitCmdline(cmdline), nil
   214  			}
   215  		}
   216  	}
   217  
   218  	return "", syscall.ESRCH
   219  }
   220  
   221  func splitCmdline(cmdline []byte) string {
   222  	indexOfEndOfString := len(cmdline)
   223  
   224  	for i, c := range cmdline {
   225  		if c == 0 {
   226  			indexOfEndOfString = i
   227  			break
   228  		}
   229  	}
   230  
   231  	return filepath.Base(string(cmdline[:indexOfEndOfString]))
   232  }
   233  
   234  func isPid(s string) bool {
   235  	for _, s := range s {
   236  		if s < '0' || s > '9' {
   237  			return false
   238  		}
   239  	}
   240  
   241  	return true
   242  }