mirror of
https://github.com/XTLS/Xray-core.git
synced 2026-07-13 23:28:46 +00:00
349 lines
9.7 KiB
Go
349 lines
9.7 KiB
Go
//go:build darwin
|
|
|
|
package net
|
|
|
|
import (
|
|
"bytes"
|
|
"net"
|
|
"net/netip"
|
|
"path/filepath"
|
|
"strings"
|
|
"syscall"
|
|
"unsafe"
|
|
|
|
"golang.org/x/sys/unix"
|
|
|
|
"github.com/xtls/xray-core/common/errors"
|
|
)
|
|
|
|
const (
|
|
darwinProcPIDListFDs = 1
|
|
darwinProcPIDFDSocketInfo = 3
|
|
darwinProcFDTypeSocket = 2
|
|
darwinProcFDInfoSize = 8
|
|
darwinSocketFDInfoSize = 792
|
|
darwinSocketFDInfoPSIOff = 24
|
|
darwinSocketInfoProtoOff = darwinSocketFDInfoPSIOff + 156
|
|
darwinSocketInfoFamilyOff = darwinSocketFDInfoPSIOff + 160
|
|
darwinSocketInfoKindOff = darwinSocketFDInfoPSIOff + 232
|
|
darwinSocketInfoInSockOff = darwinSocketFDInfoPSIOff + 240
|
|
darwinInSockInfoFPortOff = darwinSocketInfoInSockOff
|
|
darwinInSockInfoLPortOff = darwinSocketInfoInSockOff + 4
|
|
darwinInSockInfoVFlagOff = darwinSocketInfoInSockOff + 24
|
|
darwinInSockInfoFAddrOff = darwinSocketInfoInSockOff + 32
|
|
darwinInSockInfoLAddrOff = darwinSocketInfoInSockOff + 48
|
|
darwinInSockInfoSize = 80
|
|
darwinInSockInfoIPv4 = 0x1
|
|
darwinInSockInfoIPv6 = 0x2
|
|
darwinSockInfoIN = 1
|
|
darwinSockInfoTCP = 2
|
|
)
|
|
|
|
type darwinSocketMatchLevel int
|
|
|
|
const (
|
|
darwinSocketNoMatch darwinSocketMatchLevel = iota
|
|
darwinSocketPortMatch
|
|
darwinSocketRemoteMatch
|
|
darwinSocketLocalMatch
|
|
darwinSocketExactMatch
|
|
)
|
|
|
|
func FindProcess(network, srcIP string, srcPort uint16, destIP string, destPort uint16) (PID int, Name string, AbsolutePath string, err error) {
|
|
isLocal, err := IsLocal(net.ParseIP(srcIP))
|
|
if err != nil {
|
|
return 0, "", "", errors.New("failed to determine if address is local: ", err)
|
|
}
|
|
if !isLocal {
|
|
return 0, "", "", ErrNotLocal
|
|
}
|
|
if network != "tcp" && network != "udp" {
|
|
panic("Unsupported network type for process lookup.")
|
|
}
|
|
|
|
srcAddr, err := netip.ParseAddr(srcIP)
|
|
if err != nil {
|
|
return 0, "", "", errors.New("invalid source IP address: ", srcIP)
|
|
}
|
|
srcAddr = srcAddr.Unmap()
|
|
|
|
var dstAddr netip.Addr
|
|
hasDstAddr := false
|
|
if destIP != "" && destPort != 0 {
|
|
dstAddr, err = netip.ParseAddr(destIP)
|
|
if err != nil {
|
|
return 0, "", "", errors.New("invalid destination IP address: ", destIP)
|
|
}
|
|
dstAddr = dstAddr.Unmap()
|
|
hasDstAddr = true
|
|
}
|
|
|
|
processes, err := unix.SysctlKinfoProcSlice("kern.proc.all")
|
|
if err != nil {
|
|
return 0, "", "", errors.New("failed to list processes").Base(err)
|
|
}
|
|
|
|
var bestPID int32
|
|
bestLevel := darwinSocketNoMatch
|
|
ambiguousBest := false
|
|
|
|
for _, process := range processes {
|
|
pid := process.Proc.P_pid
|
|
if pid <= 0 {
|
|
continue
|
|
}
|
|
|
|
matchLevel, err := darwinProcessSocketMatchLevel(pid, network, srcAddr, srcPort, dstAddr, destPort, hasDstAddr)
|
|
if err != nil || matchLevel == darwinSocketNoMatch {
|
|
continue
|
|
}
|
|
if matchLevel == darwinSocketExactMatch {
|
|
bestPID = pid
|
|
bestLevel = matchLevel
|
|
ambiguousBest = false
|
|
break
|
|
}
|
|
if matchLevel > bestLevel {
|
|
bestPID = pid
|
|
bestLevel = matchLevel
|
|
ambiguousBest = false
|
|
continue
|
|
}
|
|
if matchLevel == bestLevel {
|
|
ambiguousBest = true
|
|
}
|
|
}
|
|
|
|
if bestLevel == darwinSocketNoMatch {
|
|
return 0, "", "", errors.New("process not found for ", network, " connection from ", srcIP, ":", srcPort, " to ", destIP, ":", destPort)
|
|
}
|
|
if ambiguousBest {
|
|
return 0, "", "", errors.New("ambiguous process match for ", network, " connection from ", srcIP, ":", srcPort, " to ", destIP, ":", destPort)
|
|
}
|
|
|
|
absPath, err := darwinProcessPath(bestPID)
|
|
if err != nil {
|
|
return 0, "", "", errors.New("could not get process path for PID ", bestPID, ": ", err)
|
|
}
|
|
|
|
absPath = filepath.ToSlash(absPath)
|
|
return int(bestPID), filepath.Base(absPath), absPath, nil
|
|
}
|
|
|
|
func darwinProcessSocketMatchLevel(pid int32, network string, srcAddr netip.Addr, srcPort uint16, dstAddr netip.Addr, dstPort uint16, hasDstAddr bool) (darwinSocketMatchLevel, error) {
|
|
fds, err := darwinProcessFDs(pid)
|
|
if err != nil {
|
|
return darwinSocketNoMatch, err
|
|
}
|
|
|
|
bestLevel := darwinSocketNoMatch
|
|
info := make([]byte, darwinSocketFDInfoSize)
|
|
for fd := 0; fd+darwinProcFDInfoSize <= len(fds); fd += darwinProcFDInfoSize {
|
|
fdNumber := int32(darwinReadNativeUint32(fds[fd : fd+4]))
|
|
fdType := darwinReadNativeUint32(fds[fd+4 : fd+8])
|
|
if fdType != darwinProcFDTypeSocket {
|
|
continue
|
|
}
|
|
|
|
n, err := darwinProcPIDFDInfo(pid, fdNumber, darwinProcPIDFDSocketInfo, info)
|
|
if err != nil || n < darwinSocketInfoInSockOff+darwinInSockInfoSize {
|
|
continue
|
|
}
|
|
level := darwinSocketInfoMatchLevel(info[:n], network, srcAddr, srcPort, dstAddr, dstPort, hasDstAddr)
|
|
if level == darwinSocketExactMatch {
|
|
return level, nil
|
|
}
|
|
if level > bestLevel {
|
|
bestLevel = level
|
|
}
|
|
}
|
|
|
|
return bestLevel, nil
|
|
}
|
|
|
|
func darwinProcessFDs(pid int32) ([]byte, error) {
|
|
n, err := darwinProcPIDInfo(pid, darwinProcPIDListFDs, 0, nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if n <= 0 {
|
|
return nil, nil
|
|
}
|
|
|
|
buf := make([]byte, n)
|
|
n, err = darwinProcPIDInfo(pid, darwinProcPIDListFDs, 0, buf)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return buf[:n], nil
|
|
}
|
|
|
|
func darwinSocketInfoMatchLevel(info []byte, network string, srcAddr netip.Addr, srcPort uint16, dstAddr netip.Addr, dstPort uint16, hasDstAddr bool) darwinSocketMatchLevel {
|
|
protocol := int(darwinReadNativeUint32(info[darwinSocketInfoProtoOff : darwinSocketInfoProtoOff+4]))
|
|
family := int(darwinReadNativeUint32(info[darwinSocketInfoFamilyOff : darwinSocketInfoFamilyOff+4]))
|
|
kind := int(darwinReadNativeUint32(info[darwinSocketInfoKindOff : darwinSocketInfoKindOff+4]))
|
|
|
|
switch network {
|
|
case "tcp":
|
|
if protocol != unix.IPPROTO_TCP || kind != darwinSockInfoTCP {
|
|
return darwinSocketNoMatch
|
|
}
|
|
case "udp":
|
|
if protocol != unix.IPPROTO_UDP || kind != darwinSockInfoIN {
|
|
return darwinSocketNoMatch
|
|
}
|
|
default:
|
|
return darwinSocketNoMatch
|
|
}
|
|
|
|
vflag := info[darwinInSockInfoVFlagOff]
|
|
if srcAddr.Is4() {
|
|
if family != unix.AF_INET || vflag&darwinInSockInfoIPv4 == 0 {
|
|
return darwinSocketNoMatch
|
|
}
|
|
} else {
|
|
if family != unix.AF_INET6 || vflag&darwinInSockInfoIPv6 == 0 {
|
|
return darwinSocketNoMatch
|
|
}
|
|
}
|
|
|
|
localPort := int32(darwinReadNativeUint32(info[darwinInSockInfoLPortOff : darwinInSockInfoLPortOff+4]))
|
|
if !darwinPortMatches(localPort, srcPort) {
|
|
return darwinSocketNoMatch
|
|
}
|
|
localAddrMatches := darwinAddrMatchesOrUnspecified(info[darwinInSockInfoLAddrOff:darwinInSockInfoLAddrOff+16], srcAddr)
|
|
|
|
foreignAddrRaw := info[darwinInSockInfoFAddrOff : darwinInSockInfoFAddrOff+16]
|
|
foreignPort := int32(darwinReadNativeUint32(info[darwinInSockInfoFPortOff : darwinInSockInfoFPortOff+4]))
|
|
|
|
if !hasDstAddr {
|
|
if localAddrMatches {
|
|
return darwinSocketExactMatch
|
|
}
|
|
return darwinSocketNoMatch
|
|
}
|
|
|
|
remoteMatches := darwinPortMatches(foreignPort, dstPort) && darwinAddrMatches(foreignAddrRaw, dstAddr)
|
|
if network == "udp" && darwinEndpointIsZero(foreignAddrRaw, foreignPort) && localAddrMatches {
|
|
return darwinSocketExactMatch
|
|
}
|
|
switch {
|
|
case localAddrMatches && remoteMatches:
|
|
return darwinSocketExactMatch
|
|
case localAddrMatches:
|
|
return darwinSocketLocalMatch
|
|
case remoteMatches:
|
|
return darwinSocketRemoteMatch
|
|
default:
|
|
return darwinSocketPortMatch
|
|
}
|
|
}
|
|
|
|
func darwinPortMatches(value int32, port uint16) bool {
|
|
raw := uint16(value)
|
|
return raw == port || darwinNtohs(raw) == port
|
|
}
|
|
|
|
func darwinNtohs(value uint16) uint16 {
|
|
return value<<8 | value>>8
|
|
}
|
|
|
|
func darwinAddrMatches(raw []byte, addr netip.Addr) bool {
|
|
if addr.Is4() {
|
|
ip := addr.As4()
|
|
return bytes.Equal(raw[12:16], ip[:])
|
|
}
|
|
ip := addr.As16()
|
|
return bytes.Equal(raw, ip[:])
|
|
}
|
|
|
|
func darwinAddrMatchesOrUnspecified(raw []byte, addr netip.Addr) bool {
|
|
if darwinAddrMatches(raw, addr) {
|
|
return true
|
|
}
|
|
if addr.Is4() {
|
|
return darwinBytesAreZero(raw[12:16])
|
|
}
|
|
return darwinBytesAreZero(raw)
|
|
}
|
|
|
|
func darwinEndpointIsZero(rawAddr []byte, port int32) bool {
|
|
return uint32(port) == 0 && darwinBytesAreZero(rawAddr)
|
|
}
|
|
|
|
func darwinBytesAreZero(raw []byte) bool {
|
|
for _, value := range raw {
|
|
if value != 0 {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
func darwinReadNativeUint32(b []byte) uint32 {
|
|
return *(*uint32)(unsafe.Pointer(&b[0]))
|
|
}
|
|
|
|
func darwinProcessPath(pid int32) (string, error) {
|
|
buf := make([]byte, unix.PathMax)
|
|
n, err := darwinProcPIDPath(pid, buf)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
if n <= 0 {
|
|
return "", errors.New("empty process path")
|
|
}
|
|
return strings.TrimRight(string(buf[:n]), "\x00"), nil
|
|
}
|
|
|
|
func darwinProcPIDInfo(pid int32, flavor int, arg uint64, buf []byte) (int, error) {
|
|
var ptr unsafe.Pointer
|
|
if len(buf) > 0 {
|
|
ptr = unsafe.Pointer(&buf[0])
|
|
}
|
|
|
|
r0, _, errno := syscall_syscall6(libc_proc_pidinfo_trampoline_addr, uintptr(pid), uintptr(flavor), uintptr(arg), uintptr(ptr), uintptr(len(buf)), 0)
|
|
if errno != 0 {
|
|
return 0, errno
|
|
}
|
|
return int(r0), nil
|
|
}
|
|
|
|
func darwinProcPIDFDInfo(pid int32, fd int32, flavor int, buf []byte) (int, error) {
|
|
var ptr unsafe.Pointer
|
|
if len(buf) > 0 {
|
|
ptr = unsafe.Pointer(&buf[0])
|
|
}
|
|
|
|
r0, _, errno := syscall_syscall6(libc_proc_pidfdinfo_trampoline_addr, uintptr(pid), uintptr(fd), uintptr(flavor), uintptr(ptr), uintptr(len(buf)), 0)
|
|
if errno != 0 {
|
|
return 0, errno
|
|
}
|
|
return int(r0), nil
|
|
}
|
|
|
|
func darwinProcPIDPath(pid int32, buf []byte) (int, error) {
|
|
r0, _, errno := syscall_syscall6(libc_proc_pidpath_trampoline_addr, uintptr(pid), uintptr(unsafe.Pointer(&buf[0])), uintptr(len(buf)), 0, 0, 0)
|
|
if errno != 0 {
|
|
return 0, errno
|
|
}
|
|
return int(r0), nil
|
|
}
|
|
|
|
var libc_proc_pidinfo_trampoline_addr uintptr
|
|
|
|
//go:cgo_import_dynamic libc_proc_pidinfo proc_pidinfo "/usr/lib/libproc.dylib"
|
|
|
|
var libc_proc_pidfdinfo_trampoline_addr uintptr
|
|
|
|
//go:cgo_import_dynamic libc_proc_pidfdinfo proc_pidfdinfo "/usr/lib/libproc.dylib"
|
|
|
|
var libc_proc_pidpath_trampoline_addr uintptr
|
|
|
|
//go:cgo_import_dynamic libc_proc_pidpath proc_pidpath "/usr/lib/libproc.dylib"
|
|
|
|
// Implemented in the runtime package (runtime/sys_darwin.go).
|
|
func syscall_syscall6(fn, a1, a2, a3, a4, a5, a6 uintptr) (r1, r2 uintptr, err syscall.Errno)
|
|
|
|
//go:linkname syscall_syscall6 syscall.syscall6
|