github.com/kelleygo/clashcore@v1.0.2/component/process/process_freebsd_amd64.go (about)

     1  package process
     2  
     3  import (
     4  	"encoding/binary"
     5  	"fmt"
     6  	"net/netip"
     7  	"strconv"
     8  	"strings"
     9  	"sync"
    10  	"syscall"
    11  	"unsafe"
    12  
    13  	"github.com/kelleygo/clashcore/common/nnip"
    14  	"github.com/kelleygo/clashcore/log"
    15  )
    16  
    17  // store process name for when dealing with multiple PROCESS-NAME rules
    18  var (
    19  	defaultSearcher *searcher
    20  
    21  	once sync.Once
    22  )
    23  
    24  func findProcessName(network string, ip netip.Addr, srcPort int) (uint32, string, error) {
    25  	once.Do(func() {
    26  		if err := initSearcher(); err != nil {
    27  			log.Errorln("Initialize PROCESS-NAME failed: %s", err.Error())
    28  			log.Warnln("All PROCESS-NAME rules will be skipped")
    29  			return
    30  		}
    31  	})
    32  
    33  	if defaultSearcher == nil {
    34  		return 0, "", ErrPlatformNotSupport
    35  	}
    36  
    37  	var spath string
    38  	isTCP := network == TCP
    39  	switch network {
    40  	case TCP:
    41  		spath = "net.inet.tcp.pcblist"
    42  	case UDP:
    43  		spath = "net.inet.udp.pcblist"
    44  	default:
    45  		return 0, "", ErrInvalidNetwork
    46  	}
    47  
    48  	value, err := syscall.Sysctl(spath)
    49  	if err != nil {
    50  		return 0, "", err
    51  	}
    52  
    53  	buf := []byte(value)
    54  	pid, err := defaultSearcher.Search(buf, ip, uint16(srcPort), isTCP)
    55  	if err != nil {
    56  		return 0, "", err
    57  	}
    58  
    59  	pp, err := getExecPathFromPID(pid)
    60  	return 0, pp, err
    61  }
    62  
    63  func getExecPathFromPID(pid uint32) (string, error) {
    64  	buf := make([]byte, 2048)
    65  	size := uint64(len(buf))
    66  	// CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, pid
    67  	mib := [4]uint32{1, 14, 12, pid}
    68  
    69  	_, _, errno := syscall.Syscall6(
    70  		syscall.SYS___SYSCTL,
    71  		uintptr(unsafe.Pointer(&mib[0])),
    72  		uintptr(len(mib)),
    73  		uintptr(unsafe.Pointer(&buf[0])),
    74  		uintptr(unsafe.Pointer(&size)),
    75  		0,
    76  		0)
    77  	if errno != 0 || size == 0 {
    78  		return "", errno
    79  	}
    80  
    81  	return string(buf[:size-1]), nil
    82  }
    83  
    84  func readNativeUint32(b []byte) uint32 {
    85  	return *(*uint32)(unsafe.Pointer(&b[0]))
    86  }
    87  
    88  type searcher struct {
    89  	// sizeof(struct xinpgen)
    90  	headSize int
    91  	// sizeof(struct xtcpcb)
    92  	tcpItemSize int
    93  	// sizeof(struct xinpcb)
    94  	udpItemSize  int
    95  	udpInpOffset int
    96  	port         int
    97  	ip           int
    98  	vflag        int
    99  	socket       int
   100  
   101  	// sizeof(struct xfile)
   102  	fileItemSize int
   103  	data         int
   104  	pid          int
   105  }
   106  
   107  func (s *searcher) Search(buf []byte, ip netip.Addr, port uint16, isTCP bool) (uint32, error) {
   108  	var itemSize int
   109  	var inpOffset int
   110  
   111  	if isTCP {
   112  		// struct xtcpcb
   113  		itemSize = s.tcpItemSize
   114  		inpOffset = 8
   115  	} else {
   116  		// struct xinpcb
   117  		itemSize = s.udpItemSize
   118  		inpOffset = s.udpInpOffset
   119  	}
   120  
   121  	isIPv4 := ip.Is4()
   122  	// skip the first xinpgen block
   123  	for i := s.headSize; i+itemSize <= len(buf); i += itemSize {
   124  		inp := i + inpOffset
   125  
   126  		srcPort := binary.BigEndian.Uint16(buf[inp+s.port : inp+s.port+2])
   127  
   128  		if port != srcPort {
   129  			continue
   130  		}
   131  
   132  		// xinpcb.inp_vflag
   133  		flag := buf[inp+s.vflag]
   134  
   135  		var srcIP netip.Addr
   136  		switch {
   137  		case flag&0x1 > 0 && isIPv4:
   138  			// ipv4
   139  			srcIP = nnip.IpToAddr(buf[inp+s.ip : inp+s.ip+4])
   140  		case flag&0x2 > 0 && !isIPv4:
   141  			// ipv6
   142  			srcIP = nnip.IpToAddr(buf[inp+s.ip-12 : inp+s.ip+4])
   143  		default:
   144  			continue
   145  		}
   146  
   147  		if ip != srcIP {
   148  			continue
   149  		}
   150  
   151  		// xsocket.xso_so, interpreted as big endian anyway since it's only used for comparison
   152  		socket := binary.BigEndian.Uint64(buf[inp+s.socket : inp+s.socket+8])
   153  		return s.searchSocketPid(socket)
   154  	}
   155  	return 0, ErrNotFound
   156  }
   157  
   158  func (s *searcher) searchSocketPid(socket uint64) (uint32, error) {
   159  	value, err := syscall.Sysctl("kern.file")
   160  	if err != nil {
   161  		return 0, err
   162  	}
   163  
   164  	buf := []byte(value)
   165  
   166  	// struct xfile
   167  	itemSize := s.fileItemSize
   168  	for i := 0; i+itemSize <= len(buf); i += itemSize {
   169  		// xfile.xf_data
   170  		data := binary.BigEndian.Uint64(buf[i+s.data : i+s.data+8])
   171  		if data == socket {
   172  			// xfile.xf_pid
   173  			pid := readNativeUint32(buf[i+s.pid : i+s.pid+4])
   174  			return pid, nil
   175  		}
   176  	}
   177  	return 0, ErrNotFound
   178  }
   179  
   180  func newSearcher(major int) *searcher {
   181  	var s *searcher
   182  	switch major {
   183  	case 11:
   184  		s = &searcher{
   185  			headSize:     32,
   186  			tcpItemSize:  1304,
   187  			udpItemSize:  632,
   188  			port:         198,
   189  			ip:           228,
   190  			vflag:        116,
   191  			socket:       88,
   192  			fileItemSize: 80,
   193  			data:         56,
   194  			pid:          8,
   195  			udpInpOffset: 8,
   196  		}
   197  	case 12:
   198  		fallthrough
   199  	case 13:
   200  		s = &searcher{
   201  			headSize:     64,
   202  			tcpItemSize:  744,
   203  			udpItemSize:  400,
   204  			port:         254,
   205  			ip:           284,
   206  			vflag:        392,
   207  			socket:       16,
   208  			fileItemSize: 128,
   209  			data:         56,
   210  			pid:          8,
   211  		}
   212  	}
   213  	return s
   214  }
   215  
   216  func initSearcher() error {
   217  	osRelease, err := syscall.Sysctl("kern.osrelease")
   218  	if err != nil {
   219  		return err
   220  	}
   221  
   222  	dot := strings.Index(osRelease, ".")
   223  	if dot != -1 {
   224  		osRelease = osRelease[:dot]
   225  	}
   226  	major, err := strconv.Atoi(osRelease)
   227  	if err != nil {
   228  		return err
   229  	}
   230  	defaultSearcher = newSearcher(major)
   231  	if defaultSearcher == nil {
   232  		return fmt.Errorf("unsupported freebsd version %d", major)
   233  	}
   234  	return nil
   235  }