| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269 |
- //go:build darwin
- package process
- import (
- "encoding/binary"
- "net/netip"
- "os"
- "sync"
- "syscall"
- "time"
- "unsafe"
- "github.com/sagernet/sing-box/adapter"
- N "github.com/sagernet/sing/common/network"
- "golang.org/x/sys/unix"
- )
- const (
- darwinSnapshotTTL = 200 * time.Millisecond
- darwinXinpgenSize = 24
- darwinXsocketOffset = 104
- darwinXinpcbForeignPort = 16
- darwinXinpcbLocalPort = 18
- darwinXinpcbVFlag = 44
- darwinXinpcbForeignAddr = 48
- darwinXinpcbLocalAddr = 64
- darwinXinpcbIPv4Addr = 12
- darwinXsocketUID = 64
- darwinXsocketLastPID = 68
- darwinTCPExtraStructSize = 208
- )
- type darwinConnectionEntry struct {
- localAddr netip.Addr
- remoteAddr netip.Addr
- localPort uint16
- remotePort uint16
- pid uint32
- uid int32
- }
- type darwinConnectionMatchKind uint8
- const (
- darwinConnectionMatchExact darwinConnectionMatchKind = iota
- darwinConnectionMatchLocalFallback
- darwinConnectionMatchWildcardFallback
- )
- type darwinSnapshot struct {
- createdAt time.Time
- entries []darwinConnectionEntry
- }
- type darwinConnectionFinder struct {
- access sync.Mutex
- ttl time.Duration
- snapshots map[string]darwinSnapshot
- builder func(string) (darwinSnapshot, error)
- }
- var sharedDarwinConnectionFinder = newDarwinConnectionFinder(darwinSnapshotTTL)
- func newDarwinConnectionFinder(ttl time.Duration) *darwinConnectionFinder {
- return &darwinConnectionFinder{
- ttl: ttl,
- snapshots: make(map[string]darwinSnapshot),
- builder: buildDarwinSnapshot,
- }
- }
- func FindDarwinConnectionOwner(network string, source netip.AddrPort, destination netip.AddrPort) (*adapter.ConnectionOwner, error) {
- return sharedDarwinConnectionFinder.find(network, source, destination)
- }
- func (f *darwinConnectionFinder) find(network string, source netip.AddrPort, destination netip.AddrPort) (*adapter.ConnectionOwner, error) {
- networkName := N.NetworkName(network)
- source = normalizeDarwinAddrPort(source)
- destination = normalizeDarwinAddrPort(destination)
- var lastOwner *adapter.ConnectionOwner
- for attempt := 0; attempt < 2; attempt++ {
- snapshot, fromCache, err := f.loadSnapshot(networkName, attempt > 0)
- if err != nil {
- return nil, err
- }
- entry, matchKind, err := matchDarwinConnectionEntry(snapshot.entries, networkName, source, destination)
- if err != nil {
- if err == ErrNotFound && fromCache {
- continue
- }
- return nil, err
- }
- if fromCache && matchKind != darwinConnectionMatchExact {
- continue
- }
- owner := &adapter.ConnectionOwner{
- UserId: entry.uid,
- }
- lastOwner = owner
- if entry.pid == 0 {
- return owner, nil
- }
- processPath, err := getExecPathFromPID(entry.pid)
- if err == nil {
- owner.ProcessPath = processPath
- return owner, nil
- }
- if fromCache {
- continue
- }
- return owner, nil
- }
- if lastOwner != nil {
- return lastOwner, nil
- }
- return nil, ErrNotFound
- }
- func (f *darwinConnectionFinder) loadSnapshot(network string, forceRefresh bool) (darwinSnapshot, bool, error) {
- f.access.Lock()
- defer f.access.Unlock()
- if !forceRefresh {
- if snapshot, loaded := f.snapshots[network]; loaded && time.Since(snapshot.createdAt) < f.ttl {
- return snapshot, true, nil
- }
- }
- snapshot, err := f.builder(network)
- if err != nil {
- return darwinSnapshot{}, false, err
- }
- f.snapshots[network] = snapshot
- return snapshot, false, nil
- }
- func buildDarwinSnapshot(network string) (darwinSnapshot, error) {
- spath, itemSize, err := darwinSnapshotSettings(network)
- if err != nil {
- return darwinSnapshot{}, err
- }
- value, err := unix.SysctlRaw(spath)
- if err != nil {
- return darwinSnapshot{}, err
- }
- return darwinSnapshot{
- createdAt: time.Now(),
- entries: parseDarwinSnapshot(value, itemSize),
- }, nil
- }
- func darwinSnapshotSettings(network string) (string, int, error) {
- itemSize := structSize
- switch network {
- case N.NetworkTCP:
- return "net.inet.tcp.pcblist_n", itemSize + darwinTCPExtraStructSize, nil
- case N.NetworkUDP:
- return "net.inet.udp.pcblist_n", itemSize, nil
- default:
- return "", 0, os.ErrInvalid
- }
- }
- func parseDarwinSnapshot(buf []byte, itemSize int) []darwinConnectionEntry {
- entries := make([]darwinConnectionEntry, 0, (len(buf)-darwinXinpgenSize)/itemSize)
- for i := darwinXinpgenSize; i+itemSize <= len(buf); i += itemSize {
- inp := i
- so := i + darwinXsocketOffset
- entry, ok := parseDarwinConnectionEntry(buf[inp:so], buf[so:so+structSize-darwinXsocketOffset])
- if ok {
- entries = append(entries, entry)
- }
- }
- return entries
- }
- func parseDarwinConnectionEntry(inp []byte, so []byte) (darwinConnectionEntry, bool) {
- if len(inp) < darwinXsocketOffset || len(so) < structSize-darwinXsocketOffset {
- return darwinConnectionEntry{}, false
- }
- entry := darwinConnectionEntry{
- remotePort: binary.BigEndian.Uint16(inp[darwinXinpcbForeignPort : darwinXinpcbForeignPort+2]),
- localPort: binary.BigEndian.Uint16(inp[darwinXinpcbLocalPort : darwinXinpcbLocalPort+2]),
- pid: binary.NativeEndian.Uint32(so[darwinXsocketLastPID : darwinXsocketLastPID+4]),
- uid: int32(binary.NativeEndian.Uint32(so[darwinXsocketUID : darwinXsocketUID+4])),
- }
- flag := inp[darwinXinpcbVFlag]
- switch {
- case flag&0x1 != 0:
- entry.remoteAddr = netip.AddrFrom4([4]byte(inp[darwinXinpcbForeignAddr+darwinXinpcbIPv4Addr : darwinXinpcbForeignAddr+darwinXinpcbIPv4Addr+4]))
- entry.localAddr = netip.AddrFrom4([4]byte(inp[darwinXinpcbLocalAddr+darwinXinpcbIPv4Addr : darwinXinpcbLocalAddr+darwinXinpcbIPv4Addr+4]))
- return entry, true
- case flag&0x2 != 0:
- entry.remoteAddr = netip.AddrFrom16([16]byte(inp[darwinXinpcbForeignAddr : darwinXinpcbForeignAddr+16]))
- entry.localAddr = netip.AddrFrom16([16]byte(inp[darwinXinpcbLocalAddr : darwinXinpcbLocalAddr+16]))
- return entry, true
- default:
- return darwinConnectionEntry{}, false
- }
- }
- func matchDarwinConnectionEntry(entries []darwinConnectionEntry, network string, source netip.AddrPort, destination netip.AddrPort) (darwinConnectionEntry, darwinConnectionMatchKind, error) {
- sourceAddr := source.Addr()
- if !sourceAddr.IsValid() {
- return darwinConnectionEntry{}, darwinConnectionMatchExact, os.ErrInvalid
- }
- var localFallback darwinConnectionEntry
- var hasLocalFallback bool
- var wildcardFallback darwinConnectionEntry
- var hasWildcardFallback bool
- for _, entry := range entries {
- if entry.localPort != source.Port() || sourceAddr.BitLen() != entry.localAddr.BitLen() {
- continue
- }
- if entry.localAddr == sourceAddr && destination.IsValid() && entry.remotePort == destination.Port() && entry.remoteAddr == destination.Addr() {
- return entry, darwinConnectionMatchExact, nil
- }
- if !destination.IsValid() && entry.localAddr == sourceAddr {
- return entry, darwinConnectionMatchExact, nil
- }
- if network != N.NetworkUDP {
- continue
- }
- if !hasLocalFallback && entry.localAddr == sourceAddr {
- hasLocalFallback = true
- localFallback = entry
- }
- if !hasWildcardFallback && entry.localAddr.IsUnspecified() {
- hasWildcardFallback = true
- wildcardFallback = entry
- }
- }
- if hasLocalFallback {
- return localFallback, darwinConnectionMatchLocalFallback, nil
- }
- if hasWildcardFallback {
- return wildcardFallback, darwinConnectionMatchWildcardFallback, nil
- }
- return darwinConnectionEntry{}, darwinConnectionMatchExact, ErrNotFound
- }
- func normalizeDarwinAddrPort(addrPort netip.AddrPort) netip.AddrPort {
- if !addrPort.IsValid() {
- return addrPort
- }
- return netip.AddrPortFrom(addrPort.Addr().Unmap(), addrPort.Port())
- }
- func getExecPathFromPID(pid uint32) (string, error) {
- const (
- procpidpathinfo = 0xb
- procpidpathinfosize = 1024
- proccallnumpidinfo = 0x2
- )
- buf := make([]byte, procpidpathinfosize)
- _, _, errno := syscall.Syscall6(
- syscall.SYS_PROC_INFO,
- proccallnumpidinfo,
- uintptr(pid),
- procpidpathinfo,
- 0,
- uintptr(unsafe.Pointer(&buf[0])),
- procpidpathinfosize)
- if errno != 0 {
- return "", errno
- }
- return unix.ByteSliceToString(buf), nil
- }
|