github.com/inazumav/sing-box@v0.0.0-20230926072359-ab51429a14f1/common/process/searcher_darwin.go (about)

     1  package process
     2  
     3  import (
     4  	"context"
     5  	"encoding/binary"
     6  	"net/netip"
     7  	"os"
     8  	"strconv"
     9  	"strings"
    10  	"syscall"
    11  	"unsafe"
    12  
    13  	N "github.com/sagernet/sing/common/network"
    14  
    15  	"golang.org/x/sys/unix"
    16  )
    17  
    18  var _ Searcher = (*darwinSearcher)(nil)
    19  
    20  type darwinSearcher struct{}
    21  
    22  func NewSearcher(_ Config) (Searcher, error) {
    23  	return &darwinSearcher{}, nil
    24  }
    25  
    26  func (d *darwinSearcher) FindProcessInfo(ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*Info, error) {
    27  	processName, err := findProcessName(network, source.Addr(), int(source.Port()))
    28  	if err != nil {
    29  		return nil, err
    30  	}
    31  	return &Info{ProcessPath: processName, UserId: -1}, nil
    32  }
    33  
    34  var structSize = func() int {
    35  	value, _ := syscall.Sysctl("kern.osrelease")
    36  	major, _, _ := strings.Cut(value, ".")
    37  	n, _ := strconv.ParseInt(major, 10, 64)
    38  	switch true {
    39  	case n >= 22:
    40  		return 408
    41  	default:
    42  		// from darwin-xnu/bsd/netinet/in_pcblist.c:get_pcblist_n
    43  		// size/offset are round up (aligned) to 8 bytes in darwin
    44  		// rup8(sizeof(xinpcb_n)) + rup8(sizeof(xsocket_n)) +
    45  		// 2 * rup8(sizeof(xsockbuf_n)) + rup8(sizeof(xsockstat_n))
    46  		return 384
    47  	}
    48  }()
    49  
    50  func findProcessName(network string, ip netip.Addr, port int) (string, error) {
    51  	var spath string
    52  	switch network {
    53  	case N.NetworkTCP:
    54  		spath = "net.inet.tcp.pcblist_n"
    55  	case N.NetworkUDP:
    56  		spath = "net.inet.udp.pcblist_n"
    57  	default:
    58  		return "", os.ErrInvalid
    59  	}
    60  
    61  	isIPv4 := ip.Is4()
    62  
    63  	value, err := syscall.Sysctl(spath)
    64  	if err != nil {
    65  		return "", err
    66  	}
    67  
    68  	buf := []byte(value)
    69  
    70  	// from darwin-xnu/bsd/netinet/in_pcblist.c:get_pcblist_n
    71  	// size/offset are round up (aligned) to 8 bytes in darwin
    72  	// rup8(sizeof(xinpcb_n)) + rup8(sizeof(xsocket_n)) +
    73  	// 2 * rup8(sizeof(xsockbuf_n)) + rup8(sizeof(xsockstat_n))
    74  	itemSize := structSize
    75  	if network == N.NetworkTCP {
    76  		// rup8(sizeof(xtcpcb_n))
    77  		itemSize += 208
    78  	}
    79  	// skip the first xinpgen(24 bytes) block
    80  	for i := 24; i+itemSize <= len(buf); i += itemSize {
    81  		// offset of xinpcb_n and xsocket_n
    82  		inp, so := i, i+104
    83  
    84  		srcPort := binary.BigEndian.Uint16(buf[inp+18 : inp+20])
    85  		if uint16(port) != srcPort {
    86  			continue
    87  		}
    88  
    89  		// xinpcb_n.inp_vflag
    90  		flag := buf[inp+44]
    91  
    92  		var srcIP netip.Addr
    93  		switch {
    94  		case flag&0x1 > 0 && isIPv4:
    95  			// ipv4
    96  			srcIP = netip.AddrFrom4(*(*[4]byte)(buf[inp+76 : inp+80]))
    97  		case flag&0x2 > 0 && !isIPv4:
    98  			// ipv6
    99  			srcIP = netip.AddrFrom16(*(*[16]byte)(buf[inp+64 : inp+80]))
   100  		default:
   101  			continue
   102  		}
   103  
   104  		if ip != srcIP {
   105  			continue
   106  		}
   107  
   108  		// xsocket_n.so_last_pid
   109  		pid := readNativeUint32(buf[so+68 : so+72])
   110  		return getExecPathFromPID(pid)
   111  	}
   112  
   113  	return "", ErrNotFound
   114  }
   115  
   116  func getExecPathFromPID(pid uint32) (string, error) {
   117  	const (
   118  		procpidpathinfo     = 0xb
   119  		procpidpathinfosize = 1024
   120  		proccallnumpidinfo  = 0x2
   121  	)
   122  	buf := make([]byte, procpidpathinfosize)
   123  	_, _, errno := syscall.Syscall6(
   124  		syscall.SYS_PROC_INFO,
   125  		proccallnumpidinfo,
   126  		uintptr(pid),
   127  		procpidpathinfo,
   128  		0,
   129  		uintptr(unsafe.Pointer(&buf[0])),
   130  		procpidpathinfosize)
   131  	if errno != 0 {
   132  		return "", errno
   133  	}
   134  
   135  	return unix.ByteSliceToString(buf), nil
   136  }
   137  
   138  func readNativeUint32(b []byte) uint32 {
   139  	return *(*uint32)(unsafe.Pointer(&b[0]))
   140  }