github.com/yaling888/clash@v1.53.0/component/process/process_linux.go (about)

     1  package process
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"net"
     7  	"net/netip"
     8  	"os"
     9  
    10  	"github.com/vishvananda/netlink"
    11  	"github.com/vishvananda/netlink/nl"
    12  	"golang.org/x/sys/unix"
    13  
    14  	"github.com/yaling888/clash/common/pool"
    15  )
    16  
    17  const (
    18  	sizeofSocketID = 0x30
    19  	sizeofSocket   = sizeofSocketID + 0x18
    20  )
    21  
    22  type socketRequest struct {
    23  	Family   uint8
    24  	Protocol uint8
    25  	Ext      uint8
    26  	pad      uint8
    27  	States   uint32
    28  	ID       netlink.SocketID
    29  }
    30  
    31  func (r *socketRequest) serialize(bp *pool.BufferWriter) {
    32  	bp.PutUint8(r.Family)
    33  	bp.PutUint8(r.Protocol)
    34  	bp.PutUint8(r.Ext)
    35  	bp.PutUint8(r.pad)
    36  	bp.PutUint32(r.States)
    37  	bp.PutUint16be(r.ID.SourcePort)
    38  	bp.PutUint16be(r.ID.DestinationPort)
    39  	if r.Family == unix.AF_INET6 {
    40  		bp.PutIPv6(r.ID.Source)
    41  		bp.PutIPv6(r.ID.Destination)
    42  	} else {
    43  		bp.PutIPv4(r.ID.Source)
    44  		bp.Grow(12)
    45  		bp.PutIPv4(r.ID.Destination)
    46  		bp.Grow(12)
    47  	}
    48  	bp.PutUint32(r.ID.Interface)
    49  	bp.PutUint32(r.ID.Cookie[0])
    50  	bp.PutUint32(r.ID.Cookie[1])
    51  }
    52  
    53  func findProcessPath(network string, from netip.AddrPort, to netip.AddrPort) (string, error) {
    54  	inode, uid, err := resolveSocketByNetlink(network, from, to)
    55  	if err != nil {
    56  		return "", err
    57  	}
    58  
    59  	return resolveProcessPathByProcSearch(inode, uid)
    60  }
    61  
    62  func resolveSocketByNetlink(network string, from netip.AddrPort, to netip.AddrPort) (inode uint32, uid uint32, err error) {
    63  	var families []byte
    64  	if from.Addr().Unmap().Is4() {
    65  		families = []byte{unix.AF_INET, unix.AF_INET6}
    66  	} else {
    67  		families = []byte{unix.AF_INET6, unix.AF_INET}
    68  	}
    69  
    70  	var protocol byte
    71  	switch network {
    72  	case TCP:
    73  		protocol = unix.IPPROTO_TCP
    74  	case UDP:
    75  		protocol = unix.IPPROTO_UDP
    76  	default:
    77  		return 0, 0, ErrInvalidNetwork
    78  	}
    79  
    80  	if protocol == unix.IPPROTO_UDP {
    81  		from, to = to, from
    82  	}
    83  
    84  	for _, family := range families {
    85  		inode, uid, err = resolveSocketByNetlinkExact(family, protocol, from, to)
    86  		if err == nil {
    87  			return inode, uid, err
    88  		}
    89  	}
    90  
    91  	return 0, 0, ErrNotFound
    92  }
    93  
    94  func resolveSocketByNetlinkExact(family byte, protocol byte, fromAddr netip.AddrPort, toAddr netip.AddrPort) (uint32, uint32, error) {
    95  	var (
    96  		fromIP net.IP
    97  		toIP   net.IP
    98  	)
    99  	if family == unix.AF_INET {
   100  		fromIP = net.IP(fromAddr.Addr().AsSlice()).To4()
   101  		toIP = net.IP(toAddr.Addr().AsSlice()).To4()
   102  	} else {
   103  		fromIP = net.IP(fromAddr.Addr().AsSlice()).To16()
   104  		toIP = net.IP(toAddr.Addr().AsSlice()).To16()
   105  	}
   106  
   107  	s, err := nl.Subscribe(unix.NETLINK_INET_DIAG)
   108  	if err != nil {
   109  		return 0, 0, err
   110  	}
   111  	defer s.Close()
   112  
   113  	bufP := pool.GetBufferWriter()
   114  	defer pool.PutBufferWriter(bufP)
   115  
   116  	request := &socketRequest{
   117  		Family:   family,
   118  		Protocol: protocol,
   119  		ID: netlink.SocketID{
   120  			SourcePort:      fromAddr.Port(),
   121  			DestinationPort: toAddr.Port(),
   122  			Source:          fromIP,
   123  			Destination:     toIP,
   124  			Cookie:          [2]uint32{nl.TCPDIAG_NOCOOKIE, nl.TCPDIAG_NOCOOKIE},
   125  		},
   126  	}
   127  	request.serialize(bufP)
   128  
   129  	req := nl.NewNetlinkRequest(nl.SOCK_DIAG_BY_FAMILY, 0) // unix.NLM_F_DUMP
   130  	req.AddRawData(bufP.Bytes())
   131  
   132  	err = s.Send(req)
   133  	if err != nil {
   134  		return 0, 0, err
   135  	}
   136  
   137  	msgs, from, err := s.Receive()
   138  	if err != nil {
   139  		return 0, 0, err
   140  	}
   141  
   142  	if from.Pid != nl.PidKernel {
   143  		return 0, 0, fmt.Errorf("wrong sender portid %d, expected %d", from.Pid, nl.PidKernel)
   144  	}
   145  	if len(msgs) == 0 {
   146  		return 0, 0, fmt.Errorf("no message nor error from netlink")
   147  	}
   148  	if len(msgs) > 2 {
   149  		return 0, 0, fmt.Errorf("multiple (%d) matching sockets", len(msgs))
   150  	}
   151  
   152  	inode, uid, err := deserialize(msgs[0].Data)
   153  	if err != nil {
   154  		return 0, 0, ErrNotFound
   155  	}
   156  
   157  	return inode, uid, nil
   158  }
   159  
   160  func resolveProcessPathByProcSearch(inode, uid uint32) (string, error) {
   161  	const (
   162  		path    = "/proc/"
   163  		pathLen = len(path)
   164  	)
   165  	procDir, err := os.Open(path)
   166  	if err != nil {
   167  		return "", err
   168  	}
   169  	defer func(procDir *os.File) {
   170  		_ = procDir.Close()
   171  	}(procDir)
   172  
   173  	pids, err := procDir.Readdirnames(-1)
   174  	if err != nil {
   175  		return "", err
   176  	}
   177  
   178  	expectedSocketName := fmt.Appendf(nil, "socket:[%d]", inode)
   179  
   180  	pathBuffer := pool.GetBufferWriter()
   181  	defer pool.PutBufferWriter(pathBuffer)
   182  
   183  	readlinkBuffer := pool.GetBufferWriter()
   184  	readlinkBuffer.Grow(32)
   185  	defer pool.PutBufferWriter(readlinkBuffer)
   186  
   187  	pathBuffer.PutString(path)
   188  	for _, pid := range pids {
   189  		if !isPid(pid) {
   190  			continue
   191  		}
   192  
   193  		pathBuffer.
   194  			Truncate(pathLen).
   195  			PutString(pid)
   196  
   197  		stat := &unix.Stat_t{}
   198  		err = unix.Stat(pathBuffer.String(), stat)
   199  		if err != nil {
   200  			continue
   201  		} else if stat.Uid != uid {
   202  			continue
   203  		}
   204  
   205  		pathBuffer.PutString("/fd/")
   206  		fdsPrefixLength := pathBuffer.Len()
   207  
   208  		fdDir, err := os.Open(pathBuffer.String())
   209  		if err != nil {
   210  			continue
   211  		}
   212  
   213  		fds, err := fdDir.Readdirnames(-1)
   214  		_ = fdDir.Close()
   215  		if err != nil {
   216  			continue
   217  		}
   218  
   219  		for _, fd := range fds {
   220  			pathBuffer.
   221  				Truncate(fdsPrefixLength).
   222  				PutString(fd)
   223  
   224  			n, err := unix.Readlink(pathBuffer.String(), *readlinkBuffer)
   225  			if err != nil {
   226  				continue
   227  			}
   228  
   229  			if bytes.Equal((*readlinkBuffer)[:n], expectedSocketName) {
   230  				return os.Readlink(path + pid + "/exe")
   231  			}
   232  		}
   233  	}
   234  
   235  	return "", fmt.Errorf("inode %d of uid %d not found", inode, uid)
   236  }
   237  
   238  func isPid(name string) bool {
   239  	for _, c := range name {
   240  		if c < '0' || c > '9' {
   241  			return false
   242  		}
   243  	}
   244  
   245  	return true
   246  }
   247  
   248  func deserialize(b []byte) (inode, uid uint32, err error) {
   249  	if len(b) < sizeofSocket {
   250  		return 0, 0, fmt.Errorf("socket data short read (%d); want %d", len(b), sizeofSocket)
   251  	}
   252  	buf := pool.BufferReader(b)
   253  	buf.Skip(64)
   254  	uid = buf.ReadUint32()
   255  	inode = buf.ReadUint32()
   256  	return
   257  }