github.com/sagernet/sing-box@v1.2.7/common/process/searcher_linux_shared.go (about)

     1  //go:build linux
     2  
     3  package process
     4  
     5  import (
     6  	"bytes"
     7  	"encoding/binary"
     8  	"fmt"
     9  	"net"
    10  	"net/netip"
    11  	"os"
    12  	"path"
    13  	"strings"
    14  	"syscall"
    15  	"unicode"
    16  	"unsafe"
    17  
    18  	"github.com/sagernet/sing/common"
    19  	"github.com/sagernet/sing/common/buf"
    20  	E "github.com/sagernet/sing/common/exceptions"
    21  	N "github.com/sagernet/sing/common/network"
    22  )
    23  
    24  // from https://github.com/vishvananda/netlink/blob/bca67dfc8220b44ef582c9da4e9172bf1c9ec973/nl/nl_linux.go#L52-L62
    25  var nativeEndian = func() binary.ByteOrder {
    26  	var x uint32 = 0x01020304
    27  	if *(*byte)(unsafe.Pointer(&x)) == 0x01 {
    28  		return binary.BigEndian
    29  	}
    30  
    31  	return binary.LittleEndian
    32  }()
    33  
    34  const (
    35  	sizeOfSocketDiagRequest = syscall.SizeofNlMsghdr + 8 + 48
    36  	socketDiagByFamily      = 20
    37  	pathProc                = "/proc"
    38  )
    39  
    40  func resolveSocketByNetlink(network string, source netip.AddrPort, destination netip.AddrPort) (inode, uid uint32, err error) {
    41  	var family uint8
    42  	var protocol uint8
    43  
    44  	switch network {
    45  	case N.NetworkTCP:
    46  		protocol = syscall.IPPROTO_TCP
    47  	case N.NetworkUDP:
    48  		protocol = syscall.IPPROTO_UDP
    49  	default:
    50  		return 0, 0, os.ErrInvalid
    51  	}
    52  
    53  	if source.Addr().Is4() {
    54  		family = syscall.AF_INET
    55  	} else {
    56  		family = syscall.AF_INET6
    57  	}
    58  
    59  	req := packSocketDiagRequest(family, protocol, source)
    60  
    61  	socket, err := syscall.Socket(syscall.AF_NETLINK, syscall.SOCK_DGRAM, syscall.NETLINK_INET_DIAG)
    62  	if err != nil {
    63  		return 0, 0, E.Cause(err, "dial netlink")
    64  	}
    65  	defer syscall.Close(socket)
    66  
    67  	syscall.SetsockoptTimeval(socket, syscall.SOL_SOCKET, syscall.SO_SNDTIMEO, &syscall.Timeval{Usec: 100})
    68  	syscall.SetsockoptTimeval(socket, syscall.SOL_SOCKET, syscall.SO_RCVTIMEO, &syscall.Timeval{Usec: 100})
    69  
    70  	err = syscall.Connect(socket, &syscall.SockaddrNetlink{
    71  		Family: syscall.AF_NETLINK,
    72  		Pad:    0,
    73  		Pid:    0,
    74  		Groups: 0,
    75  	})
    76  	if err != nil {
    77  		return
    78  	}
    79  
    80  	_, err = syscall.Write(socket, req)
    81  	if err != nil {
    82  		return 0, 0, E.Cause(err, "write netlink request")
    83  	}
    84  
    85  	_buffer := buf.StackNew()
    86  	defer common.KeepAlive(_buffer)
    87  	buffer := common.Dup(_buffer)
    88  	defer buffer.Release()
    89  
    90  	n, err := syscall.Read(socket, buffer.FreeBytes())
    91  	if err != nil {
    92  		return 0, 0, E.Cause(err, "read netlink response")
    93  	}
    94  
    95  	buffer.Truncate(n)
    96  
    97  	messages, err := syscall.ParseNetlinkMessage(buffer.Bytes())
    98  	if err != nil {
    99  		return 0, 0, E.Cause(err, "parse netlink message")
   100  	} else if len(messages) == 0 {
   101  		return 0, 0, E.New("unexcepted netlink response")
   102  	}
   103  
   104  	message := messages[0]
   105  	if message.Header.Type&syscall.NLMSG_ERROR != 0 {
   106  		return 0, 0, E.New("netlink message: NLMSG_ERROR")
   107  	}
   108  
   109  	inode, uid = unpackSocketDiagResponse(&messages[0])
   110  	return
   111  }
   112  
   113  func packSocketDiagRequest(family, protocol byte, source netip.AddrPort) []byte {
   114  	s := make([]byte, 16)
   115  	copy(s, source.Addr().AsSlice())
   116  
   117  	buf := make([]byte, sizeOfSocketDiagRequest)
   118  
   119  	nativeEndian.PutUint32(buf[0:4], sizeOfSocketDiagRequest)
   120  	nativeEndian.PutUint16(buf[4:6], socketDiagByFamily)
   121  	nativeEndian.PutUint16(buf[6:8], syscall.NLM_F_REQUEST|syscall.NLM_F_DUMP)
   122  	nativeEndian.PutUint32(buf[8:12], 0)
   123  	nativeEndian.PutUint32(buf[12:16], 0)
   124  
   125  	buf[16] = family
   126  	buf[17] = protocol
   127  	buf[18] = 0
   128  	buf[19] = 0
   129  	nativeEndian.PutUint32(buf[20:24], 0xFFFFFFFF)
   130  
   131  	binary.BigEndian.PutUint16(buf[24:26], source.Port())
   132  	binary.BigEndian.PutUint16(buf[26:28], 0)
   133  
   134  	copy(buf[28:44], s)
   135  	copy(buf[44:60], net.IPv6zero)
   136  
   137  	nativeEndian.PutUint32(buf[60:64], 0)
   138  	nativeEndian.PutUint64(buf[64:72], 0xFFFFFFFFFFFFFFFF)
   139  
   140  	return buf
   141  }
   142  
   143  func unpackSocketDiagResponse(msg *syscall.NetlinkMessage) (inode, uid uint32) {
   144  	if len(msg.Data) < 72 {
   145  		return 0, 0
   146  	}
   147  
   148  	data := msg.Data
   149  
   150  	uid = nativeEndian.Uint32(data[64:68])
   151  	inode = nativeEndian.Uint32(data[68:72])
   152  
   153  	return
   154  }
   155  
   156  func resolveProcessNameByProcSearch(inode, uid uint32) (string, error) {
   157  	files, err := os.ReadDir(pathProc)
   158  	if err != nil {
   159  		return "", err
   160  	}
   161  
   162  	buffer := make([]byte, syscall.PathMax)
   163  	socket := []byte(fmt.Sprintf("socket:[%d]", inode))
   164  
   165  	for _, f := range files {
   166  		if !f.IsDir() || !isPid(f.Name()) {
   167  			continue
   168  		}
   169  
   170  		info, err := f.Info()
   171  		if err != nil {
   172  			return "", err
   173  		}
   174  		if info.Sys().(*syscall.Stat_t).Uid != uid {
   175  			continue
   176  		}
   177  
   178  		processPath := path.Join(pathProc, f.Name())
   179  		fdPath := path.Join(processPath, "fd")
   180  
   181  		fds, err := os.ReadDir(fdPath)
   182  		if err != nil {
   183  			continue
   184  		}
   185  
   186  		for _, fd := range fds {
   187  			n, err := syscall.Readlink(path.Join(fdPath, fd.Name()), buffer)
   188  			if err != nil {
   189  				continue
   190  			}
   191  
   192  			if bytes.Equal(buffer[:n], socket) {
   193  				return os.Readlink(path.Join(processPath, "exe"))
   194  			}
   195  		}
   196  	}
   197  
   198  	return "", fmt.Errorf("process of uid(%d),inode(%d) not found", uid, inode)
   199  }
   200  
   201  func isPid(s string) bool {
   202  	return strings.IndexFunc(s, func(r rune) bool {
   203  		return !unicode.IsDigit(r)
   204  	}) == -1
   205  }