github.com/igoogolx/clash@v1.19.8/component/process/process_linux.go (about)

     1  package process
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/binary"
     6  	"fmt"
     7  	"net"
     8  	"net/netip"
     9  	"os"
    10  	"unsafe"
    11  
    12  	"github.com/igoogolx/clash/common/pool"
    13  
    14  	"github.com/mdlayher/netlink"
    15  	"golang.org/x/sys/unix"
    16  )
    17  
    18  type inetDiagRequest struct {
    19  	Family   byte
    20  	Protocol byte
    21  	Ext      byte
    22  	Pad      byte
    23  	States   uint32
    24  
    25  	SrcPort [2]byte
    26  	DstPort [2]byte
    27  	Src     [16]byte
    28  	Dst     [16]byte
    29  	If      uint32
    30  	Cookie  [2]uint32
    31  }
    32  
    33  type inetDiagResponse struct {
    34  	Family  byte
    35  	State   byte
    36  	Timer   byte
    37  	ReTrans byte
    38  
    39  	SrcPort [2]byte
    40  	DstPort [2]byte
    41  	Src     [16]byte
    42  	Dst     [16]byte
    43  	If      uint32
    44  	Cookie  [2]uint32
    45  
    46  	Expires uint32
    47  	RQueue  uint32
    48  	WQueue  uint32
    49  	UID     uint32
    50  	INode   uint32
    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  		// Swap from & to for udp
    82  		// See also https://www.mail-archive.com/netdev@vger.kernel.org/msg248638.html
    83  		from, to = to, from
    84  	}
    85  
    86  	for _, family := range families {
    87  		inode, uid, err = resolveSocketByNetlinkExact(family, protocol, from, to, netlink.Request)
    88  		if err == nil {
    89  			return inode, uid, err
    90  		}
    91  	}
    92  
    93  	return 0, 0, ErrNotFound
    94  }
    95  
    96  func resolveSocketByNetlinkExact(family byte, protocol byte, from netip.AddrPort, to netip.AddrPort, flags netlink.HeaderFlags) (inode uint32, uid uint32, err error) {
    97  	request := &inetDiagRequest{
    98  		Family:   family,
    99  		Protocol: protocol,
   100  		States:   0xffffffff,
   101  		Cookie:   [2]uint32{0xffffffff, 0xffffffff},
   102  	}
   103  
   104  	var (
   105  		fromAddr []byte
   106  		toAddr   []byte
   107  	)
   108  	if family == unix.AF_INET {
   109  		fromAddr = net.IP(from.Addr().AsSlice()).To4()
   110  		toAddr = net.IP(to.Addr().AsSlice()).To4()
   111  	} else {
   112  		fromAddr = net.IP(from.Addr().AsSlice()).To16()
   113  		toAddr = net.IP(to.Addr().AsSlice()).To16()
   114  	}
   115  
   116  	copy(request.Src[:], fromAddr)
   117  	copy(request.Dst[:], toAddr)
   118  
   119  	binary.BigEndian.PutUint16(request.SrcPort[:], from.Port())
   120  	binary.BigEndian.PutUint16(request.DstPort[:], to.Port())
   121  
   122  	conn, err := netlink.Dial(unix.NETLINK_INET_DIAG, nil)
   123  	if err != nil {
   124  		return 0, 0, err
   125  	}
   126  	defer conn.Close()
   127  
   128  	message := netlink.Message{
   129  		Header: netlink.Header{
   130  			Type:  20, // SOCK_DIAG_BY_FAMILY
   131  			Flags: flags,
   132  		},
   133  		Data: (*(*[unsafe.Sizeof(*request)]byte)(unsafe.Pointer(request)))[:],
   134  	}
   135  
   136  	messages, err := conn.Execute(message)
   137  	if err != nil {
   138  		return 0, 0, err
   139  	}
   140  
   141  	for _, msg := range messages {
   142  		if len(msg.Data) < int(unsafe.Sizeof(inetDiagResponse{})) {
   143  			continue
   144  		}
   145  
   146  		response := (*inetDiagResponse)(unsafe.Pointer(&msg.Data[0]))
   147  
   148  		return response.INode, response.UID, nil
   149  	}
   150  
   151  	return 0, 0, ErrNotFound
   152  }
   153  
   154  func resolveProcessPathByProcSearch(inode, uid uint32) (string, error) {
   155  	procDir, err := os.Open("/proc")
   156  	if err != nil {
   157  		return "", err
   158  	}
   159  	defer procDir.Close()
   160  
   161  	pids, err := procDir.Readdirnames(-1)
   162  	if err != nil {
   163  		return "", err
   164  	}
   165  
   166  	expectedSocketName := fmt.Appendf(nil, "socket:[%d]", inode)
   167  
   168  	pathBuffer := pool.Get(64)
   169  	defer pool.Put(pathBuffer)
   170  
   171  	readlinkBuffer := pool.Get(32)
   172  	defer pool.Put(readlinkBuffer)
   173  
   174  	copy(pathBuffer, "/proc/")
   175  
   176  	for _, pid := range pids {
   177  		if !isPid(pid) {
   178  			continue
   179  		}
   180  
   181  		pathBuffer = append(pathBuffer[:len("/proc/")], pid...)
   182  
   183  		stat := &unix.Stat_t{}
   184  		err = unix.Stat(string(pathBuffer), stat)
   185  		if err != nil {
   186  			continue
   187  		} else if stat.Uid != uid {
   188  			continue
   189  		}
   190  
   191  		pathBuffer = append(pathBuffer, "/fd/"...)
   192  		fdsPrefixLength := len(pathBuffer)
   193  
   194  		fdDir, err := os.Open(string(pathBuffer))
   195  		if err != nil {
   196  			continue
   197  		}
   198  
   199  		fds, err := fdDir.Readdirnames(-1)
   200  		fdDir.Close()
   201  		if err != nil {
   202  			continue
   203  		}
   204  
   205  		for _, fd := range fds {
   206  			pathBuffer = pathBuffer[:fdsPrefixLength]
   207  
   208  			pathBuffer = append(pathBuffer, fd...)
   209  
   210  			n, err := unix.Readlink(string(pathBuffer), readlinkBuffer)
   211  			if err != nil {
   212  				continue
   213  			}
   214  
   215  			if bytes.Equal(readlinkBuffer[:n], expectedSocketName) {
   216  				return os.Readlink("/proc/" + pid + "/exe")
   217  			}
   218  		}
   219  	}
   220  
   221  	return "", fmt.Errorf("inode %d of uid %d not found", inode, uid)
   222  }
   223  
   224  func isPid(name string) bool {
   225  	for _, c := range name {
   226  		if c < '0' || c > '9' {
   227  			return false
   228  		}
   229  	}
   230  
   231  	return true
   232  }