github.com/chwjbn/xclash@v0.2.0/component/process/process_freebsd_amd64.go (about)

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