mirror of
https://github.com/SagerNet/sing-box.git
synced 2026-04-11 17:47:20 +10:00
270 lines
7.7 KiB
Go
270 lines
7.7 KiB
Go
//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
|
|
}
|