Files
sing-box-for-apple/HelperService/ConnectionOwnerLookup.swift
T

192 lines
6.2 KiB
Swift

import Darwin
import Foundation
import os
private let PROC_PIDPATHINFO_MAXSIZE: Int32 = 4096
private let logger = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "ConnectionOwnerLookup")
enum ConnectionOwnerLookup {
struct Result {
let userId: Int32
let userName: String
let processPath: String
}
static func find(
ipProtocol: Int32,
sourceAddress: String,
sourcePort: Int32,
destinationAddress: String,
destinationPort: Int32
) -> Result? {
let sourceAddr = parseAddress(sourceAddress)
let destAddr = parseAddress(destinationAddress)
guard let sourceAddr, let destAddr else {
logger.error("find: failed to parse addresses")
return nil
}
let pidCount = proc_listpids(UInt32(PROC_ALL_PIDS), 0, nil, 0)
guard pidCount > 0 else {
logger.error("find: no processes found")
return nil
}
let pidBufferSize = Int(pidCount) * MemoryLayout<pid_t>.size
let pids = UnsafeMutablePointer<pid_t>.allocate(capacity: Int(pidCount))
defer { pids.deallocate() }
let actualCount = proc_listpids(UInt32(PROC_ALL_PIDS), 0, pids, Int32(pidBufferSize))
guard actualCount > 0 else {
logger.error("find: failed to list processes")
return nil
}
let numPids = Int(actualCount) / MemoryLayout<pid_t>.size
for i in 0 ..< numPids {
let pid = pids[i]
if pid == 0 { continue }
if let result = checkProcessForConnection(
pid: pid,
ipProtocol: ipProtocol,
sourceAddr: sourceAddr,
sourcePort: UInt16(sourcePort),
destAddr: destAddr,
destPort: UInt16(destinationPort)
) {
return result
}
}
return nil
}
private static func checkProcessForConnection(
pid: pid_t,
ipProtocol: Int32,
sourceAddr: Data,
sourcePort: UInt16,
destAddr: Data,
destPort: UInt16
) -> Result? {
let bufferSize = proc_pidinfo(pid, PROC_PIDLISTFDS, 0, nil, 0)
guard bufferSize > 0 else { return nil }
let fdBuffer = UnsafeMutableRawPointer.allocate(byteCount: Int(bufferSize), alignment: MemoryLayout<proc_fdinfo>.alignment)
defer { fdBuffer.deallocate() }
let actualSize = proc_pidinfo(pid, PROC_PIDLISTFDS, 0, fdBuffer, bufferSize)
guard actualSize > 0 else { return nil }
let fdCount = Int(actualSize) / MemoryLayout<proc_fdinfo>.size
for i in 0 ..< fdCount {
let fd = fdBuffer.load(fromByteOffset: i * MemoryLayout<proc_fdinfo>.size, as: proc_fdinfo.self)
guard fd.proc_fdtype == PROX_FDTYPE_SOCKET else { continue }
var socketInfo = socket_fdinfo()
let socketInfoSize = Int32(MemoryLayout<socket_fdinfo>.size)
let result = proc_pidfdinfo(pid, fd.proc_fd, PROC_PIDFDSOCKETINFO, &socketInfo, socketInfoSize)
guard result == socketInfoSize else { continue }
let soi: in_sockinfo
if ipProtocol == IPPROTO_TCP {
guard socketInfo.psi.soi_kind == SOCKINFO_TCP else { continue }
soi = socketInfo.psi.soi_proto.pri_tcp.tcpsi_ini
} else if ipProtocol == IPPROTO_UDP {
guard socketInfo.psi.soi_kind == SOCKINFO_IN else { continue }
soi = socketInfo.psi.soi_proto.pri_in
} else {
continue
}
if matchesConnection(
socketInfo: soi,
sourceAddr: sourceAddr,
sourcePort: sourcePort,
destAddr: destAddr,
destPort: destPort
) {
return getProcessInfo(pid: pid)
}
}
return nil
}
private static func matchesConnection(
socketInfo: in_sockinfo,
sourceAddr: Data,
sourcePort: UInt16,
destAddr: Data,
destPort: UInt16
) -> Bool {
let localPort = UInt16(bigEndian: UInt16(truncatingIfNeeded: socketInfo.insi_lport))
let remotePort = UInt16(bigEndian: UInt16(truncatingIfNeeded: socketInfo.insi_fport))
guard localPort == sourcePort, remotePort == destPort else {
return false
}
var localAddr = socketInfo.insi_laddr
var remoteAddr = socketInfo.insi_faddr
let localData: Data
let remoteData: Data
if sourceAddr.count == 4 {
localData = Data(bytes: &localAddr.ina_46.i46a_addr4, count: 4)
remoteData = Data(bytes: &remoteAddr.ina_46.i46a_addr4, count: 4)
} else {
localData = Data(bytes: &localAddr.ina_6, count: 16)
remoteData = Data(bytes: &remoteAddr.ina_6, count: 16)
}
return localData == sourceAddr && remoteData == destAddr
}
private static func getProcessInfo(pid: pid_t) -> Result? {
let pathBuffer = UnsafeMutablePointer<CChar>.allocate(capacity: Int(PROC_PIDPATHINFO_MAXSIZE))
defer { pathBuffer.deallocate() }
let pathLength = proc_pidpath(pid, pathBuffer, UInt32(PROC_PIDPATHINFO_MAXSIZE))
let processPath = pathLength > 0 ? String(cString: pathBuffer) : ""
var info = proc_bsdinfo()
let infoSize = Int32(MemoryLayout<proc_bsdinfo>.size)
let result = proc_pidinfo(pid, PROC_PIDTBSDINFO, 0, &info, infoSize)
guard result == infoSize else { return nil }
let uid = Int32(info.pbi_uid)
let userName: String
if let pw = getpwuid(info.pbi_uid) {
userName = String(cString: pw.pointee.pw_name)
} else {
userName = String(uid)
}
return Result(userId: uid, userName: userName, processPath: processPath)
}
private static func parseAddress(_ address: String) -> Data? {
var addr4 = in_addr()
if inet_pton(AF_INET, address, &addr4) == 1 {
return Data(bytes: &addr4, count: 4)
}
var addr6 = in6_addr()
if inet_pton(AF_INET6, address, &addr6) == 1 {
return Data(bytes: &addr6, count: 16)
}
return nil
}
}